diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8364cc4f..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**Relevant Requirements** -Indicate any enhancement issues (i.e., requirements) that are impacted by this issue. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Python Version [e.g. 3.7] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/documentation-update.md b/.github/ISSUE_TEMPLATE/documentation-update.md deleted file mode 100644 index 6eb7c869..00000000 --- a/.github/ISSUE_TEMPLATE/documentation-update.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Documentation update -about: Update to the documentation -title: '' -labels: documentation -assignees: '' - ---- - -** Location where change is recommended ** - -** Recommended change ** - -** Reason ** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b786a387..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Requirement Text** -What must this feature do, specifically - this is the thing to test to. E.g., Ability to simulate model until a specified event has been met. - -**Background Information** -Optional - -**Suggested Solution** -Optional, Solution that is proposed. Requirement can be met with other solutions - -**DoD** -What need to be completed for this feature to be complete. E.g., - - [ ] Implement feature - - [ ] Add to example - - [ ] Add to tutorial - - [ ] Add tests - - [ ] Add to change notes for next release diff --git a/.github/ISSUE_TEMPLATE/testing-update.md b/.github/ISSUE_TEMPLATE/testing-update.md deleted file mode 100644 index 6b57a323..00000000 --- a/.github/ISSUE_TEMPLATE/testing-update.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Testing update -about: Update to tests or CI/CD pipeline -title: '' -labels: CI/CD -assignees: '' - ---- - -** Test to Update ** - -** Change Recommended ** - -** Reason ** diff --git a/.github/workflows/pr-messages.yml b/.github/workflows/pr-messages.yml deleted file mode 100644 index 3a839832..00000000 --- a/.github/workflows/pr-messages.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Print PR Message - Non Release - -on: - pull_request: - branches: - - 'dev' - types: [opened] - -jobs: - benchmark_branch: - runs-on: ubuntu-latest - steps: - - name: Auto Comment - uses: wow-actions/auto-comment@v1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - pullRequestOpened: > - Thank you for opening this PR. Each PR into dev requires a code review. For the code review, look at the following: - - - [ ] Reviewer should look for bugs, efficiency, readability, testing, and coverage in examples (if relevant). - - - [ ] Ensure that each PR adding a new feature should include a test verifying that feature. - - - [ ] All tests must be passing. - - - [ ] All errors from static analysis must be resolved. - - - [ ] Review the test coverage reports (if there is a change) - will be added as comment on PR if there is a change - - - [ ] Review the software benchmarking results (if there is a change) - will be added as comment on PR - - - [ ] Any added dependencies are included in requirements.txt, setup.py, and dev_guide.rst (this document) - - - [ ] All warnings from static analysis must be reviewed and resolved - if deemed appropriate. diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml deleted file mode 100644 index e6d2b476..00000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,96 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python package - -on: - push: - pull_request: - paths: - - prog_algs - -jobs: - test-prog_models-dev: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.7'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install prog_models - run: | - python -m pip install git+https://github.com/nasa/prog_models.git@dev - - name: Install - run: | - python -m pip install --upgrade pip - python -m pip install -e . - python -m pip install notebook - python -m pip install testbook - - name: Run tests - run: python -m tests - # test-prog_models-released: - # runs-on: ubuntu-latest - # strategy: - # matrix: - # python-version: ['3.7'] - # steps: - # - uses: actions/checkout@v2 - # - name: Set up Python ${{ matrix.python-version }} - # uses: actions/setup-python@v2 - # with: - # python-version: ${{ matrix.python-version }} - # - name: Install - # run: | - # python -m pip install --upgrade pip - # python -m pip install -e . - # python -m pip install notebook - # python -m pip install testbook - # - name: Run tests - # run: python -m tests - copyright: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.10'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Run copyright check - run: | - python scripts/test_copyright.py - coverage: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.10'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install prog_models from dev - run: | - python -m pip install git+https://github.com/nasa/prog_models.git@dev - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install -e . - pip install coverage - pip install notebook - pip install testbook - - name: Run coverage - run: | - coverage run -m tests - coverage xml - - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v2 - with: - fail_ci_if_error: true diff --git a/.github/workflows/release-messages.yml b/.github/workflows/release-messages.yml deleted file mode 100644 index 2842dc5a..00000000 --- a/.github/workflows/release-messages.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Print PR Message - Release - -on: - pull_request: - branches: - - 'release/**' - types: [opened] - -jobs: - benchmark_branch: - runs-on: ubuntu-latest - steps: - - name: Auto Comment - uses: wow-actions/auto-comment@v1 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - pullRequestOpened: > - Thank you for opening this PR. Since this is a release branch, the PR must complete the release checklist, below: - - - [ ] Check that each new feature has corresponding tests - - - [ ] Confirm all dependencies are in the following: requirements.txt, setup.py, the bottom of dev_guide.rst - - - [ ] Confirm that all issues associated with the release have been closed (i.e., requirements have been met) or assigned to another release - - - [ ] Run unit tests `python -m tests` - - - [ ] If present, run manual tests `python -m tests.test_manual` - - - [ ] Review the template(s) - - - [ ] Review static-analysis/linter results - - - [ ] Review the tutorial - - - [ ] Run and review the examples - - - [ ] Check that all examples are tested - - - [ ] Check new files in PR for any accidentally added - - - [ ] Check documents - - - [ ] Check that all desired examples are in docs - - - [ ] General review: see if any updates are required - - - [ ] Rebuild sphinx documents: `sphinx-build sphinx_config/ docs/` - - - [ ] Write release notes - - - [ ] Update version number in src/\*/__init__.py and setup.py - - - [ ] For releases adding new features- ensure that NASA release process has been followed. - - - [ ] Confirm that on GitHub Releases page, the next release has been started and that a schedule is present including at least Release Date, Release Review Date, and Release Branch Opening Date.` diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml deleted file mode 100644 index 51bd60e6..00000000 --- a/.github/workflows/release-tests.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Python package - -on: - push: - branches: - - 'release/**' - pull_request: - branches: - - 'release/**' - paths: - - prog_algs - -jobs: - test-prog_models-dev: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.8', '3.9', '3.10'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install prog_models - run: | - python -m pip install git+https://github.com/nasa/prog_models.git@dev - - name: Install - run: | - python -m pip install --upgrade pip - python -m pip install -e . - python -m pip install notebook - python -m pip install testbook - - name: Run tests - run: python -m tests - test-prog_models-released: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.8', '3.9', '3.10'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install - run: | - python -m pip install --upgrade pip - python -m pip install -e . - python -m pip install notebook - python -m pip install testbook - - name: Run tests - run: python -m tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 381fd315..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Release - -on: - push: - branches: - - 'release/**' - - 'master' - -jobs: - analysis: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.9'] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install prog_models - run: | - python -m pip install git+https://github.com/nasa/prog_models.git@dev - - name: Lint with flake8 - run: | - python -m pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 src/prog_algs --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 src/prog_algs --count --benchmark --exit-zero --show-source --max-complexity=10 --max-line-length=127 --statistics --tee --output-file=lint_results_${{ matrix.python-version }}.txt - - name: Upload Lint Results - uses: actions/upload-artifact@v2 - with: - name: lint_results_${{matrix.python-version}} - path: lint_results_${{matrix.python-version}}.txt - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: 'python' - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 688f5636..00000000 --- a/.gitignore +++ /dev/null @@ -1,106 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# VS Code -.vscode/ -.pypirc -.DS_Store -data_test.pkl -predictor_test.pkl - -.VSCodeCounter/ diff --git a/README.md b/README.md index 196c0530..eea70b43 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,3 @@ -# Prognostics Algorithm Python Package -[![CodeFactor](https://www.codefactor.io/repository/github/nasa/prog_algs/badge)](https://www.codefactor.io/repository/github/nasa/prog_algs) -[![GitHub License](https://img.shields.io/badge/License-NOSA-green)](https://github.com/nasa/prog_algs/blob/master/license.pdf) -[![GitHub Releases](https://img.shields.io/github/release/nasa/prog_algs.svg)](https://github.com/nasa/prog_algs/releases) -[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/nasa/prog_algs/master?tutorial.ipynb) +# ProgPy Packages -The Prognostic Algorithm Package is a python framework for model-based prognostics (computation of remaining useful life) of engineering systems, and provides a set of algorithms for state estimation and prediction, including uncertainty propagation. The algorithms take as inputs prognostic models (from NASA's Prognostics Model Package), and perform estimation and prediction functions. The library allows the rapid development of prognostics solutions for given models of components and systems. Different algorithms can be easily swapped to do comparative studies and evaluations of different algorithms to select the best for the application at hand. - -This is designed to be used with the [Prognostics Models Package](https://github.com/nasa/prog_models). - -## Installation -`pip3 install prog_algs` - -## Documentation -See documentation [here](https://nasa.github.io/progpy/prog_algs_guide.html) - -## Repository Directory Structure - -`src/prog_algs/` - The prognostics algorithm python package
-`examples/` - Example Python scripts using prog_algs
-`tests/` - Tests for prog_models
-`README.md` - The readme (this file)
- -## Citing this repository -Use the following to cite this repository: - -``` -@misc{2023_nasa_prog_algs, - author = {Christopher Teubert and Matteo Corbetta and Chetan Kulkarni}, - title = {Prognostics Algorithm Python Package}, - month = May, - year = 2023, - version = {1.5}, - url = {https://github.com/nasa/prog\_algs} - } -``` - -The corresponding reference should look like this: - -C. Teubert, M. Corbetta, C. Kulkarni, Prognostics Algorithm Python Package, v1.5, May 2023. URL https://github.com/nasa/prog_algs. - -Alternatively, if using both prog_models and prog_algs, you can cite the combined package as - -C. Teubert, C. Kulkarni, M. Corbetta, K. Jarvis, M. Daigle, ProgPy Prognostics Python Packages, v1.5, May 2023. URL https://nasa.github.io/progpy. - -## Acknowledgements -The structure and algorithms of this package are strongly inspired by the [MATLAB Prognostics Algorithm Library](https://github.com/nasa/PrognosticsAlgorithmLibrary) and the [MATLAB Prognostics Metrics Library](https://github.com/nasa/PrognosticsMetricsLibrary). We would like to recognize Matthew Daigle, Shankar Sankararaman and the rest of the team that contributed to the Prognostics Model Library for the contributions their work on the MATLAB library made to the design of prog_algs - -## Notices -Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -## Disclaimers -No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS." - -Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT. +The prog_algs package has been combined with the prog_models package to create the new progpy python package, here: https://github.com/nasa/progpy. diff --git a/docs/.buildinfo b/docs/.buildinfo deleted file mode 100644 index e944f836..00000000 --- a/docs/.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: f13fb5c9a3612612d281a460bc957c40 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.doctrees/dev_guide.doctree b/docs/.doctrees/dev_guide.doctree deleted file mode 100644 index 63151ae9..00000000 Binary files a/docs/.doctrees/dev_guide.doctree and /dev/null differ diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle deleted file mode 100644 index 32eefb4a..00000000 Binary files a/docs/.doctrees/environment.pickle and /dev/null differ diff --git a/docs/.doctrees/exceptions.doctree b/docs/.doctrees/exceptions.doctree deleted file mode 100644 index 8969685b..00000000 Binary files a/docs/.doctrees/exceptions.doctree and /dev/null differ diff --git a/docs/.doctrees/getting_started.doctree b/docs/.doctrees/getting_started.doctree deleted file mode 100644 index b0263828..00000000 Binary files a/docs/.doctrees/getting_started.doctree and /dev/null differ diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree deleted file mode 100644 index 8338acdd..00000000 Binary files a/docs/.doctrees/index.doctree and /dev/null differ diff --git a/docs/.doctrees/metrics.doctree b/docs/.doctrees/metrics.doctree deleted file mode 100644 index 46078437..00000000 Binary files a/docs/.doctrees/metrics.doctree and /dev/null differ diff --git a/docs/.doctrees/prediction.doctree b/docs/.doctrees/prediction.doctree deleted file mode 100644 index bce59c04..00000000 Binary files a/docs/.doctrees/prediction.doctree and /dev/null differ diff --git a/docs/.doctrees/predictors.doctree b/docs/.doctrees/predictors.doctree deleted file mode 100644 index db4182cc..00000000 Binary files a/docs/.doctrees/predictors.doctree and /dev/null differ diff --git a/docs/.doctrees/release.doctree b/docs/.doctrees/release.doctree deleted file mode 100644 index 49369de1..00000000 Binary files a/docs/.doctrees/release.doctree and /dev/null differ diff --git a/docs/.doctrees/state_estimators.doctree b/docs/.doctrees/state_estimators.doctree deleted file mode 100644 index 314d941d..00000000 Binary files a/docs/.doctrees/state_estimators.doctree and /dev/null differ diff --git a/docs/.doctrees/uncertain_data.doctree b/docs/.doctrees/uncertain_data.doctree deleted file mode 100644 index cfc938f1..00000000 Binary files a/docs/.doctrees/uncertain_data.doctree and /dev/null differ diff --git a/docs/.doctrees/visualize.doctree b/docs/.doctrees/visualize.doctree deleted file mode 100644 index 469e5a46..00000000 Binary files a/docs/.doctrees/visualize.doctree and /dev/null differ diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/7150.md b/docs/7150.md deleted file mode 100644 index df233501..00000000 --- a/docs/7150.md +++ /dev/null @@ -1,30 +0,0 @@ -# Software Classification Documents -## Software Classification: Class-E - -## Compliance Notation Legend -FC: Fully Compliant - -T: Tailored (Specific tailoring described in mitigation)[SWE-121](https://swehb.nasa.gov/display/7150/SWE-121+-+Document+Alternate+Requirements) - -PC: Partially Compliant - -NC: Not Compliant - -NA: Not Applicable - -## Class-E Compliance Matrix -| NPR SWE #| Requirement Text |Comply | Evidence | -|:---|:--|:--|:--| -| [033](https://swehb.nasa.gov/display/SWEHBVC/SWE-033+-+Acquisition+vs.+Development+Assessment) | The project manager shall assess options for software acquisition versus development | FC | Assessed, there are some existing prognostics tools, but no general python package that can support model-based prognostics like we need. | -| [013](https://swehb.nasa.gov/display/SWEHBVC/SWE-013+-+Software+Plans) | The project manager shall develop, maintain, and execute software plans that cover the entire software life cycle and, as a minimum, address the requirements of this directive with approved tailoring | FC | See docs/ folder | -| [042](https://swehb.nasa.gov/display/SWEHBVC/SWE-042+-+Source+Code+Electronic+Access) | The project manager shall require the software developer(s) to provide NASA with electronic access to the source code developed for the project in a modifiable format.| FC | See git repository| -| [139](https://swehb.nasa.gov/display/SWEHBVC/SWE-139+-+Shall+Statements) | The project manager shall comply with the requirements in this NPR that are marked with an ”X” in Appendix C consistent with their software classification. | FC | This document describes compliance with NPR requirements | -| [121](https://swehb.nasa.gov/display/SWEHBVC/SWE-121+-+Document+Tailored+Requirements) | Where approved, the project manager shall document and reflect the tailored requirement in the plans or procedures controlling the development, acquisition, and deployment of the affected software. | FC | This Document | -| [125](https://swehb.nasa.gov/display/SWEHBVC/SWE-125+-+Requirements+Compliance+Matrix) | Each project manager with software components shall maintain a requirements mapping matrix or multiple requirements mapping matrices against requirements in this NPR, including those delegated to other parties or accomplished by contract vehicles or Space Act Agreements. | FC | This document | -| [029](https://swehb.nasa.gov/display/SWEHBVC/SWE-020+-+Software+Classification) | The project manager shall classify each system and subsystem containing software in accordance with the highest applicable software classification definitions for Classes A, B, C, D, E, and F software in Appendix D. | FC | This document | -| [022](https://swehb.nasa.gov/display/SWEHBVC/SWE-022+-+Software+Assurance) | The project manager shall plan and implement software assurance per [NASA-STD-8739.8](https://standards.nasa.gov/file/2640/download?token=GE2uRzcJ) | FC | See softwareplan.md | -| [205](https://swehb.nasa.gov/display/SWEHBVC/SWE-205+-+Determination+of+Safety-Critical+Software) | The project manager, in conjunction with the SMA organization, shall determine if each software component is considered to be safety-critical per the criteria defined in [NASA-STD-8739.8](https://standards.nasa.gov/file/2640/download?token=GE2uRzcJ). | NA | Not safety Critical | -| [023](https://swehb.nasa.gov/display/SWEHBVC/SWE-023+-+Software+Safety-Critical+Requirements) | If a project has safety-critical software, the project manager shall implement the safety-critical software requirements contained in [NASA-STD-8739.8](https://standards.nasa.gov/file/2640/download?token=GE2uRzcJ). | NA | Not Safety Critical| -| [206](https://swehb.nasa.gov/display/SWEHBVC/SWE-206+-+Auto-Generation+Software+Inputs) | The project manager shall require the software developers and suppliers to provide NASA with electronic access to the models, simulations, and associated data used as inputs for auto-generation of software. | FC | No auto generation| -| [148](https://swehb.nasa.gov/display/SWEHBVC/SWE-148+-+Contribute+to+Agency+Software+Catalog) | The project manager shall evaluate software for potential reuse by other projects across NASA and contribute reuse candidates to the NASA Internal Sharing and Reuse Software systems. However, if the project manager is a contractor, then a civil servant must pre-approve all such software contributions; all software contributions should include, at a minimum, the following information:
1. Software Title.
2. Software Description.
3. The Civil Servant Software Technical Point of Contact for the software product.
4. The language or languages used to develop the software.
5. Any third party code contained therein and the record of the requisite license or permission received from the third party permitting the Government’s use, if applicable. | FC | Will contribute | -| [156](https://swehb.nasa.gov/display/SWEHBVC/SWE-156+-+Evaluate+Systems+for+Security+Risks) | The project manager shall perform a software cybersecurity assessment on the software components per the Agency security policies and the project requirements, including risks posed by the use of COTS, GOTS, MOTS, OSS, or reused software components. | FC | Was evaluated in bitbucket code review| diff --git a/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py b/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py deleted file mode 100644 index cc409ce0..00000000 --- a/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -An example illustrating the creation of a new state estimator. - -In this example a basic state estimator is constructed by subclassing the StateEstimator class. This StateEstimator is then demonstrated with a ThrownObject model -""" - -from prog_algs.state_estimators import StateEstimator -from prog_algs.uncertain_data import ScalarData -import random - -class BlindlyStumbleEstimator(StateEstimator): - """ - A new state estimator. This is not a very effective state estimator, but one that technically works. It blindly stumbles towards the correct state by randomly generating a new state each timestep and selecting the state that's most consistant with the measurements. - - I do not in any universe recommend using this state estimator for anything other then demonstrating a bad state estimator. It's intended as an example of creating a new state estimation algorithm. - - This state estimator was created by copying the state estimator template and filling out each function with the logic for this algorithm - """ - def __init__(self, model, x0, measurement = None): - """ - Initialize the state estimator - - Args: - model (PrognosticsModel): Model to be used in state estimation - x0 (dict): Initial State - """ - self.m = model - self.state = x0 - - def estimate(self, t, u, z): - """ - Update the state estimate - - Args: - t (Number): Time - u (dict): Inputs (load) for time t - z (dict): Measured output at time t - """ - # Generate new candidate state - x2 = {key : float(value) + 10*(random.random()-0.5) for (key,value) in self.state.items()} - - # Calculate outputs - z_est = self.m.output(t, self.state) - z_est2 = self.m.output(t, x2) - - # Now score them each by how close they are to the measured z - z_est_score = sum([abs(z_est[key] - z[key]) for key in self.m.outputs]) - z_est2_score = sum([abs(z_est2[key] - z[key]) for key in self.m.outputs]) - - # Now choose the closer one - if z_est2_score < z_est_score: - self.state = x2 - - @property - def x(self): - """ - Measured state - """ - return ScalarData(self.state) - -# Model used in example -class ThrownObject(): - """ - Model that similates an object thrown into the air without air resistance - """ - - inputs = [] # no inputs, no way to control - states = [ - 'x', # Position (m) - 'v' # Velocity (m/s) - ] - outputs = [ # Anything we can measure - 'x' # Position (m) - ] - events = [ - 'falling', # Event- object is falling - 'impact' # Event- object has impacted ground - ] - - # The Default parameters. Overwritten by passing parameters dictionary into constructor - parameters = { - 'thrower_height': 1.83, # m - 'throwing_speed': 40, # m/s - 'g': -9.81, # Acceleration due to gravity in m/s^2 - 'process_noise': 0.0 # amount of noise in each step - } - - def initialize(self, u = None, z = None): - self.max_x = 0.0 - return { - 'x': self.parameters['thrower_height'], # Thrown, so initial altitude is height of thrower - 'v': self.parameters['throwing_speed'] # Velocity at which the ball is thrown - this guy is an professional baseball pitcher - } - - def dx(self, t, x, u = None): - # apply_process_noise is used to add process noise to each step - return { - 'x': x['v'], - 'v': self.parameters['g'] # Acceleration of gravity - } - - def output(self, t, x): - return { - 'x': x['x'] - } - - def event_state(self, t, x): - self.max_x = max(self.max_x, x['x']) # Maximum altitude - return { - 'falling': max(x['v']/self.parameters['throwing_speed'],0), # Throwing speed is max speed - 'impact': max(x['x']/self.max_x,0) # 1 until falling begins, then it's fraction of height - } - -def run_example(): - # This example creates a new state estimator, instead of using the included algorihtms. - # The new state estimator was defined above and can now be used like the UKF or PF - - # First we define the model to be used with the state estimator - m = ThrownObject() - - # Lets pretend we have no idea what the state is, we'll provide an estimate of 0 - x0 = {key : 0 for key in m.states} - filt = BlindlyStumbleEstimator(m, x0) - - # Now lets simulate it forward and see what it looks like - dt = 0.1 - x = m.initialize() - print('t: {}. State: {} (Ground truth: {})'.format(0, filt.x.mean, x)) - for i in range(1, int(8.4/dt)): - # Update ground truth state - x = {key : x[key] + m.dx(i*dt, x)[key] * dt for key in m.states} - - # Run estimation step - filt.estimate(i*dt, None, m.output(i*dt, x)) - - # Print result - print('t: {}. State: {} (Ground truth: {})'.format(i*dt, filt.x.mean, x)) - - # The results probably should show that it is estimating the state with a significant delay - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb b/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb deleted file mode 100644 index 1eab11d1..00000000 --- a/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb +++ /dev/null @@ -1,620 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Welcome to the Prognostics Algorithms Package Tutorial" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The goal of this notebook is to instruct the user on how to use and extend the NASA Python Prognostics Algorithms Package. \n", - "\n", - "First some background. The Prognostics Algorithms Package (`prog_algs`) contains tools for performing prognostics (event prediction) using the Prognostics Models Package. `prog_algs` also includes tools for analyzing the performance of prognostics algorithms. \n", - "\n", - "A few definitions:\n", - "* state estimation: The process of estimating the (possibly hidden) state of a system given sensor information on observable states\n", - "* prediction: The process of predicting the evolution of a system state with time and the occurrence of events. \n", - "\n", - "The `prog_algs` package has the following primary subpackages\n", - "* `prog_algs.state_estimators` - Tools for performing state estimation\n", - "* `prog_algs.predictors` - Tools for performing prediction\n", - "* `prog_algs.uncertain_data` - Tools for representing data with uncertainty\n", - "\n", - "In addition to the `prog_algs` package, this repo includes examples showing how to use the package (see `examples/`), a template for implementing a new state estimator (`state_estimator_template`), a template for implementing a new predictor (`predictor_template`), documentation (), and this tutorial (`tutorial.ipynb`).\n", - "\n", - "Before you start, install `prog_algs` using pip:\n", - "\n", - " `pip install prog_algs`\n", - "\n", - "or, to use the pre-release, close from GitHub and checkout the dev branch. Then run the following command:\n", - " `pip install -e .`\n", - "\n", - "Now let's get started with some examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## UncertainData - Representing a Distribution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Uncertainty is sometimes present in data used for performing state estimations or making predictions.\n", - "\n", - "In `prog_algs`, data with uncertainty is represented using classes inheriting from `UncertainData`:\n", - "* `prog_algs.uncertain_data.MultivariateNormalDist` - Data represented by a multivariate normal distribution with mean and covariance matrix\n", - "* `prog_algs.uncertain_data.ScalarData` - Data without uncertainty, a single value\n", - "* `prog_algs.uncertain_data.UnweightedSamples` - Data represented by a set of unweighted samples. Objects of this class can be treated like a list where samples[n] returns the nth sample (Dict)\n", - "\n", - "To begin using `UncertainData`, import the type that best portrays the data. In this simple demonstration, we import the `UnweightedSamples` data type. See for full details on the available `UncertainData` types." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.uncertain_data import UnweightedSamples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With `UnweightedSamples` imported, construct an object with samples. This object can be initialized using either a dictionary, list, or model.*Container type from prog_models (e.g., StateContainer). Let's try creating an object using a dictionary. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "samples = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])\n", - "print(samples)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Given an integer value, addition and subtraction can be performed on the `UncertainData` classes to adjust the distribution by a scalar amount." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "samples = samples + 5\n", - "print(samples)\n", - "samples -= 3\n", - "print(samples)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also sample from any `UncertainData` distribution using the `sample` method. In this case it resamples from the existing samples" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(samples.sample()) # A single sample\n", - "print(samples.sample(10)) # 10 samples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see the keys present using the `.keys()` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(samples.keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and the data corresponding to a specific key can be retrieved using `.key()`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(samples.key('x'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Various properties are available to quantify the `UncertainData` distribution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('mean', samples.mean)\n", - "print('median', samples.median)\n", - "print('covariance', samples.cov)\n", - "print('size', samples.size)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These `UncertainData` classes are used throughout the prog_algs package to represent data with uncertainty, as described in the following sections." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## State Estimation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "State estimation is the process of estimating the system state given sensor data and a model. Typically, this is done repeatedly as new sensor data is available.\n", - "\n", - "In `prog_algs` a State Estimator is used to estimate the system state. \n", - "\n", - "To start, import the needed packages. Here we will import the `BatteryCircuit` model and the `UnscentedKalmanFilter` state estimator. See for more details on the available state estimators.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_models.models import BatteryCircuit\n", - "from prog_algs.state_estimators import UnscentedKalmanFilter" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we construct and initialize the model. \n", - "\n", - "We use the resulting model and initial state to construct the state estimator. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = BatteryCircuit()\n", - "x0 = m.initialize()\n", - "\n", - "# Turn into a distribution - this represents uncertainty in the initial state\n", - "from prog_algs.uncertain_data import MultivariateNormalDist\n", - "from numpy import diag\n", - "INITIAL_UNCERT = 0.05 # Uncertainty in initial state (%)\n", - "# Construct covariance matrix (making sure each value is positive)\n", - "cov = diag([max(abs(INITIAL_UNCERT * value), 1e-9) for value in x0.values()])\n", - "x0 = MultivariateNormalDist(x0.keys(), x0.values(), cov)\n", - "\n", - "# Construct State estimator\n", - "est = UnscentedKalmanFilter(m, x0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can use the estimator to estimate the system state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Prior State:\", est.x.mean)\n", - "print('\\tSOC: ', m.event_state(est.x.mean)['EOD'])\n", - "fig = est.x.plot_scatter(label='prior')\n", - "\n", - "t = 0.1\n", - "u = {'i': 2}\n", - "example_measurements = {'t': 32.2, 'v': 3.915}\n", - "est.estimate(t, u, example_measurements)\n", - "\n", - "print(\"Posterior State:\", est.x.mean)\n", - "print('\\tSOC: ', m.event_state(est.x.mean)['EOD'])\n", - "est.x.plot_scatter(fig= fig, label='posterior')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As mentioned previously, this step is typically repeated when there's new data. filt.x may not be accessed every time the estimate is updated, only when it's needed." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prediction Example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Prediction is the practice of using a state estimation, a model, and estimates of future loading to predict future states and when an event will occur.\n", - "\n", - "First we will import a predictor. In this case, we will use the MonteCarlo Predictor, but see documentation for a full list of predictors and their configuration parameters." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.predictors import MonteCarlo" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we initialize it using the model from the above example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mc = MonteCarlo(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's define future loading and the first state. The first state is the output of the state estimator, and the future loading scheme is a simple piecewise function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = est.x # The state estimate\n", - "\n", - "def future_loading(t, x={}):\n", - " # Variable (piece-wise) future loading scheme \n", - " if (t < 600):\n", - " i = 2\n", - " elif (t < 900):\n", - " i = 1\n", - " elif (t < 1800):\n", - " i = 4\n", - " elif (t < 3000):\n", - " i = 2\n", - " else:\n", - " i = 3\n", - " return m.InputContainer({'i': i})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's use the constructed mc predictor to perform a single prediction. Here we're setting dt to 0.25. Note this may take up to a minute" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mc_results = mc.predict(x, future_loading, dt=0.25, n_samples=20)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format `[sample #][time]` so that `mc_results.states[m][n]` corresponds to the state for sample `m` at time `mc_results.times[m][n]`. Alternately, use the method `snapshot` to get a single point in time. e.g., \n", - "\n", - " `state = mc_results.states.snapshot(3)`\n", - "\n", - "In this case the state snapshot corresponds to time `mc_results.times[3]`. The snapshot method returns type UncertainData. \n", - "\n", - "The `predict` method also returns Time of Event (ToE) as a type UncertainData, representing the predicted time of event (for each event predicted), with uncertainty.\n", - "\n", - "Next, let's use the metrics package to analyze the ToE" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"\\nEOD Predictions (s):\")\n", - "print('\\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))\n", - "print('\\tAssuming ground truth 3005.25: ', mc_results.time_of_event.metrics(ground_truth = 3005.25))\n", - "from prog_algs.metrics import prob_success \n", - "print('\\tP(Success) if mission ends at 3005.25: ', prob_success(mc_results.time_of_event, 3005.25))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These analysis methods applied to ToE can also be applied to anything of type UncertainData (e.g., state snapshot). \n", - "\n", - "You can also visualize the results in a variety of different ways. For example, state transition" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = mc_results.states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(mc_results.times[0])))\n", - "for i in range(1, 4):\n", - " index = int(len(mc_results.times)/4*i)\n", - " mc_results.states.snapshot(index).plot_scatter(fig=fig, label = \"t={:.0f}\".format(mc_results.times[index]))\n", - "mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = \"t={:.0f}\".format(int(mc_results.times[-1])))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or time of event (ToE)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = mc_results.time_of_event.plot_hist()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note, for this event, there is only one event (End of Discharge). Many models have multiple events that can be predicted. For these models, ToE for each event is returned and can be analyzed.\n", - "\n", - "Alternately, a specific event (or events) can be specified for prediction. See `examples.predict_specific_event` for more details." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Frequently the prediction step is run periodically, less often than the state estimator step" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extending - Adding a new state estimator" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "New state estimators can be created by extending the state_estimator interface. As an example lets use a really dumb state estimator that adds random noise each step - and accepts the state that is closest. \n", - "\n", - "First thing we need to do is import the StateEstimator parent class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.state_estimators.state_estimator import StateEstimator" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we select how state will be represented. In this case there's no uncertainty- it's just one state, so we represent it as a scaler. Import the appropriate class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.uncertain_data import ScalarData" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we construct the class, implementing the functions of the state estimator template (`state_estimator_template.py`)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import random \n", - "\n", - "class BlindlyStumbleEstimator(StateEstimator):\n", - " def __init__(self, model, x0):\n", - " self.m = model\n", - " self.state = x0\n", - "\n", - " def estimate(self, t, u, z):\n", - " # Generate new candidate state\n", - " x2 = {key : float(value) + 10*(random.random()-0.5) for (key,value) in self.state.items()}\n", - "\n", - " # Calculate outputs\n", - " z_est = self.m.output(self.state)\n", - " z_est2 = self.m.output(x2)\n", - "\n", - " # Now score them each by how close they are to the measured z\n", - " z_est_score = sum([abs(z_est[key] - z[key]) for key in self.m.outputs])\n", - " z_est2_score = sum([abs(z_est2[key] - z[key]) for key in self.m.outputs])\n", - "\n", - " # Now choose the closer one\n", - " if z_est2_score < z_est_score: \n", - " self.state = x2\n", - "\n", - " @property\n", - " def x(self):\n", - " return ScalarData(self.state)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Great, now let's try it out using the model from earlier. with an initial state of all 0s. It should slowly converge towards the correct state" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "x0 = {key: 0 for key in m.states}\n", - "se = BlindlyStumbleEstimator(m, x0)\n", - "\n", - "for i in range(25):\n", - " u = {'i': 0}\n", - " z = {'t': 18.95, 'v': 4.183}\n", - " se.estimate(i, u, z)\n", - " print(se.x.mean)\n", - " print(\"\\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extending - Adding a new Predictor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Like the example above with StateEstimators, Predictors can be extended by subclassing the Predictor class. Copy `predictor_template.py` as a starting point." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion\n", - "This is just the basics, there's much more to learn. Please see the documentation at and the examples in the `examples/` folder for more details on how to use the package, including:\n", - "* `examples.basic_example` : A basic Example using prog_algs for Prognostics \n", - "* `examples.benchmarking_example` : An example benchmarking the performance of prognostics algorithms\n", - "* `examples.eol_event` : An example where a model has multiple events, but the user is only interested in predicting the time when the first event occurs (whatever it is).\n", - "* `examples.measurement_eqn_example` : An example where not every output is measured or measurements are not in the same format as outputs, so a measurement equation is defined to translate between outputs and what's measured. \n", - "* `examples.new_state_estimator_example` : An example of extending StateEstimator to create a new state estimator class\n", - "* `examples.playback` : A full example performing prognostics using playback data.\n", - "* `examples.predict_specific_event` : An example where the model has multiple events, but the user is only interested in predicting a specific event (or events).\n", - "* `examples.thrown_object_example` : An example performing prognostics with the simplified ThrownObject model\n", - "* `examples.utpredictor` : An example using the Unscented Transform Predictor for prediction.\n", - "\n", - "Thank you for trying out this tutorial. Open an issue on github () or email Chris Teubert (christopher.a.teubert@nasa.gov) with any questions or issues." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved." - ] - } - ], - "metadata": { - "interpreter": { - "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2" - }, - "kernelspec": { - "display_name": "Python 3.8.13 64-bit", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.13" - }, - "metadata": { - "interpreter": { - "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2" - } - }, - "orig_nbformat": 2 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py b/docs/_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py deleted file mode 100644 index eab72e2d..00000000 --- a/docs/_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example demonstrates use of the Kalman Filter State Estimator with a LinearModel. - -First, a linear model is defined. Then the KF State estimator is used with fake data to estimate state. -""" - -import numpy as np - -from prog_models import LinearModel -from prog_algs.state_estimators import KalmanFilter - -# Linear Model for an object thrown into the air -class ThrownObject(LinearModel): - """ - Model that similates an object thrown into the air without air resistance - - Events (2) - | falling: The object is falling - | impact: The object has hit the ground - - Inputs/Loading: (0) - - States: (2) - | x: Position in space (m) - | v: Velocity in space (m/s) - - Outputs/Measurements: (1) - | x: Position in space (m) - - Keyword Args - ------------ - process_noise : Optional, float or Dict[Srt, float] - Process noise (applied at dx/next_state). - Can be number (e.g., .2) applied to every state, a dictionary of values for each - state (e.g., {'x1': 0.2, 'x2': 0.3}), or a function (x) -> x - process_noise_dist : Optional, String - distribution for process noise (e.g., normal, uniform, triangular) - measurement_noise : Optional, float or Dict[Srt, float] - Measurement noise (applied in output eqn). - Can be number (e.g., .2) applied to every output, a dictionary of values for each - output (e.g., {'z1': 0.2, 'z2': 0.3}), or a function (z) -> z - measurement_noise_dist : Optional, String - distribution for measurement noise (e.g., normal, uniform, triangular) - g : Optional, float - Acceleration due to gravity (m/s^2). Default is 9.81 m/s^2 (standard gravity) - thrower_height : Optional, float - Height of the thrower (m). Default is 1.83 m - throwing_speed : Optional, float - Speed at which the ball is thrown (m/s). Default is 40 m/s - """ - - inputs = [] # no inputs, no way to control - states = [ - 'x', # Position (m) - 'v' # Velocity (m/s) - ] - outputs = [ - 'x' # Position (m) - ] - events = [ - 'impact' # Event- object has impacted ground - ] - - A = np.array([[0, 1], [0, 0]]) - E = np.array([[0], [-9.81]]) - C = np.array([[1, 0]]) - F = None # Will override method - - # The Default parameters. Overwritten by passing parameters dictionary into constructor - default_parameters = { - 'thrower_height': 1.83, # m - 'throwing_speed': 40, # m/s - 'g': -9.81 # Acceleration due to gravity in m/s^2 - } - - def initialize(self, u=None, z=None): - return self.StateContainer({ - 'x': self.parameters['thrower_height'], # Thrown, so initial altitude is height of thrower - 'v': self.parameters['throwing_speed'] # Velocity at which the ball is thrown - this guy is a professional baseball pitcher - }) - - # This is actually optional. Leaving thresholds_met empty will use the event state to define thresholds. - # Threshold = Event State == 0. However, this implementation is more efficient, so we included it - def threshold_met(self, x): - return { - 'falling': x['v'] < 0, - 'impact': x['x'] <= 0 - } - - def event_state(self, x): - x_max = x['x'] + np.square(x['v'])/(-self.parameters['g']*2) # Use speed and position to estimate maximum height - return { - 'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0), # Throwing speed is max speed - 'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1 # 1 until falling begins, then it's fraction of height - } - -def run_example(): - # Step 1: Instantiate the model - m = ThrownObject(process_noise = 0, measurement_noise = 0) - - # Step 2: Instantiate the Kalman Filter State Estimator - # Define the initial state to be slightly off of actual - x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40} - kf = KalmanFilter(m, x_guess) - - # Step 3: Run the Kalman Filter State Estimator - # Here we're using simulated data from the thrown_object. In a real application you would be using sensor data from the system - dt = 0.01 # Time step (s) - print_freq = 50 # Print every print_freq'th iteration - x = m.initialize() - u = m.InputContainer({}) # No input for this model - - for i in range(500): - # Get simulated output (would be measured in a real application) - z = m.output(x) - - # Estimate New State - kf.estimate(i*dt, u, z) - x_est = kf.x.mean - - # Print Results - if i%print_freq == 0: # Print every print_freq'th iteration - print(f"t: {i*dt:.2f}\n\tEstimate: {x_est}\n\tTruth: {x}") - diff = {key: x_est[key] - x[key] for key in x.keys()} - print(f"\t Diff: {diff}") - - # Update Real state for next step - x = m.next_state(x, u, dt) - -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py b/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py deleted file mode 100644 index 3ec1544a..00000000 --- a/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -An example using the UnscentedTransformPredictor class to predict the degredation of a battery. -""" - -from prog_models.models import BatteryCircuit -from prog_algs import * -# from prog_algs.visualize import plot_hist -# import matplotlib.pyplot as plt - -def run_example(): - ## Setup - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - - batt = BatteryCircuit() - - ## State Estimation - perform a single ukf state estimate step - filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0']) - - import matplotlib.pyplot as plt # For plotting - print("Prior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - example_measurements = {'t': 32.2, 'v': 3.915} - t = 0.1 - filt.estimate(t, future_loading(t), example_measurements) - print("Posterior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - ## Prediction - Predict EOD given current state - # Setup prediction - mc = predictors.UnscentedTransformPredictor(batt) - - # Predict with a step size of 0.1 - mc_results = mc.predict(filt.x, future_loading, dt=0.1, save_freq= 100) - - # Print Results - for i, time in enumerate(mc_results.times): - print('\nt = {}'.format(time)) - print('\tu = {}'.format(mc_results.inputs.snapshot(i).mean)) - print('\tx = {}'.format(mc_results.states.snapshot(i).mean)) - print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean)) - print('\tevent state = {}'.format(mc_results.event_states.snapshot(i).mean)) - - print('\nToE:', mc_results.time_of_event.mean) - - # You can also access the final state (of type UncertainData), like so: - final_state = mc_results.time_of_event.final_state - print('Final state @EOD: ', final_state['EOD'].mean) - # toe.plot_hist() - # plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py b/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py deleted file mode 100644 index 54c8ac22..00000000 --- a/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation and prediction with uncertainty given a Prognostics Model. Unlike basic_example, this example uses a model with multiple events (ThrownObject). Prediction only ends when all events are met - -Method: An instance of the Thrown Object model in prog_models is created, and the prediction process is achieved in three steps: - 1) State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate - 2) Prediction of future states (with uncertainty) and the times at which the event thresholds will be reached -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) -""" - -from prog_models.models.thrown_object import ThrownObject -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs import * - -from pprint import pprint - -def run_example(): - # Step 1: Setup model & future loading - def future_loading(t, x = None): - return {} - m = ThrownObject(process_noise = 0.25, measurement_noise = 0.2) - initial_state = m.initialize({}, {}) - - # Step 2: Demonstrating state estimator - print("\nPerforming State Estimation Step...") - - # Step 2a: Setup - NUM_SAMPLES = 1000 - filt = state_estimators.ParticleFilter(m, initial_state, num_particles = NUM_SAMPLES) - # VVV Uncomment this to use UKF State Estimator VVV - # filt = state_estimators.UnscentedKalmanFilter(batt, initial_state) - - # Step 2b: Print & Plot Prior State - filt.estimate(0.1, {}, m.output(initial_state)) - - # Note: in a prognostic application the above state estimation step would be repeated each time - # there is new data. Here we're doing one step to demonstrate how the state estimator is used - - # Step 3: Demonstrating Predictor - print("\nPerforming Prediction Step...") - - # Step 3a: Setup Predictor - mc = predictors.MonteCarlo(m) - - # Step 3b: Perform a prediction - samples = filt.x # Since we're using a particle filter, which is also sample-based, we can directly use the samples, without changes - STEP_SIZE = 0.01 - mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon=8) - print("\nPredicted Time of Event:") - pprint(mc_results.time_of_event.metrics()) # Note this takes some time - mc_results.time_of_event.plot_hist(keys = 'impact') - mc_results.time_of_event.plot_hist(keys = 'falling') - - # Step 4: Show all plots - import matplotlib.pyplot as plt # For plotting - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py b/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py deleted file mode 100644 index 1442a22c..00000000 --- a/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation and prediction with uncertainty given a Prognostics Model with a specific prediction horizon. This prediction horizon marks the end of the "time of interest" for the prediction. Often this represents the end of a mission or sufficiently in the future where the user is unconcerned with the events - -Method: An instance of the Thrown Object model in prog_models is created, and the prediction process is achieved in three steps: - 1) State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate - 2) Prediction of future states (with uncertainty) and the times at which the event thresholds will be reached, within the prediction horizon. All events outside the horizon come back as None and are ignored in metrics -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) -""" - -from prog_models.models.thrown_object import ThrownObject -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs import * - -from pprint import pprint - -def run_example(): - # Step 1: Setup model & future loading - def future_loading(t, x = None): - return {} - m = ThrownObject(process_noise = 0.25, measurement_noise = 0.2) - initial_state = m.initialize({}, {}) - - # Step 2: Demonstrating state estimator - print("\nPerforming State Estimation Step...") - - # Step 2a: Setup - NUM_SAMPLES = 1000 - filt = state_estimators.ParticleFilter(m, initial_state, num_particles = NUM_SAMPLES) - # VVV Uncomment this to use UKF State Estimator VVV - # filt = state_estimators.UnscentedKalmanFilter(batt, initial_state) - - # Step 2b: Print & Plot Prior State - filt.estimate(0.1, {}, m.output(initial_state)) - - # Note: in a prognostic application the above state estimation step would be repeated each time - # there is new data. Here we're doing one step to demonstrate how the state estimator is used - - # Step 3: Demonstrating Predictor - print("\nPerforming Prediction Step...") - - # Step 3a: Setup Predictor - mc = predictors.MonteCarlo(m) - - # Step 3b: Perform a prediction - # THIS IS WHERE WE DIVERGE FROM THE THROWN_OBJECT_EXAMPLE - # Here we set a prediction horizon - # We're saying we are not interested in any events that occur after this time - PREDICTION_HORIZON = 7.75 - samples = filt.x # Since we're using a particle filter, which is also sample-based, we can directly use the samples, without changes - STEP_SIZE = 0.01 - mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon = PREDICTION_HORIZON) - print("\nPredicted Time of Event:") - metrics = mc_results.time_of_event.metrics() - pprint(metrics) # Note this takes some time - mc_results.time_of_event.plot_hist(keys = 'impact') - mc_results.time_of_event.plot_hist(keys = 'falling') - - print("\nSamples where impact occurs before horizon: {:.2f}%".format(metrics['impact']['number of samples']/NUM_SAMPLES*100)) - - # Step 4: Show all plots - import matplotlib.pyplot as plt # For plotting - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py b/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py deleted file mode 100644 index bc982487..00000000 --- a/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs state estimation and prediction using playback data. - -Method: An instance of the BatteryCircuit model in prog_models is created, the state estimation is set up by defining a state_estimator, and the prediction method is set up by defining a predictor. - Prediction is then performed using playback data. For each data point: - 1) The necessary data is extracted (time, current load, output values) and corresponding values defined (t, i, and z) - 2) The current state estimate is performed and samples are drawn from this distribution - 3) Prediction performed to get future states (with uncertainty) and the times at which the event threshold will be reached - -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics - iv) Figures illustrating results -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator -# VVV Uncomment this to use UnscentedKalmanFilter instead VVV -# from prog_algs.state_estimators import ParticleFilter as StateEstimator - -from prog_algs.predictors import ToEPredictionProfile - -from prog_algs.predictors import UnscentedTransformPredictor as Predictor -# VVV Uncomment this to use MonteCarloPredictor instead -# from prog_algs.predictors import MonteCarlo as Predictor - -from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist - -import csv -import matplotlib.pyplot as plt -import numpy as np - -# Constants -NUM_SAMPLES = 20 -NUM_PARTICLES = 1000 # For state estimator (if using ParticleFilter) -TIME_STEP = 1 -PREDICTION_UPDATE_FREQ = 50 # Number of steps between prediction update -PLOT = True -PROCESS_NOISE = 1e-4 # Percentage process noise -MEASUREMENT_NOISE = 1e-4 # Percentage measurement noise -X0_COV = 1 # Covariance percentage with initial state -GROUND_TRUTH = {'EOD':2780} -ALPHA = 0.05 -BETA = 0.90 -LAMBDA_VALUE = 1500 - -def run_example(): - # Setup Model - batt = Battery() - - # Initial state - x0 = batt.initialize() - batt.parameters['process_noise'] = {key: PROCESS_NOISE * value for key, value in x0.items()} - z0 = batt.output(x0) - batt.parameters['measurement_noise'] = {key: MEASUREMENT_NOISE * value for key, value in z0.items()} - x0 = MultivariateNormalDist(x0.keys(), list(x0.values()), np.diag([max(1e-9, X0_COV * abs(x)) for x in x0.values()])) - - # Setup State Estimation - filt = StateEstimator(batt, x0, num_particles = NUM_PARTICLES) - - # Setup Prediction - def future_loading(t, x=None): - return {'i': 2.35} - Q = np.diag([batt.parameters['process_noise'][key] for key in batt.states]) - R = np.diag([batt.parameters['measurement_noise'][key] for key in batt.outputs]) - mc = Predictor(batt, Q = Q, R = R) - - # Run Playback - step = 0 - profile = ToEPredictionProfile() - - with open('examples/data_const_load.csv', 'r') as f: - reader = csv.reader(f) - next(reader) # Skip header - for row in reader: - step += 1 - print("{} s: {} W, {} C, {} V".format(*row)) - t = float(row[0]) - i = {'i': float(row[1])/float(row[3])} - z = {'t': float(row[2]), 'v': float(row[3])} - - # State Estimation Step - filt.estimate(t, i, z) - eod = batt.event_state(filt.x.mean)['EOD'] - print(" - Event State: ", eod) - - # Prediction Step (every PREDICTION_UPDATE_FREQ steps) - if (step%PREDICTION_UPDATE_FREQ == 0): - mc_results = mc.predict(filt.x, future_loading, t0 = t, n_samples=NUM_SAMPLES, dt=TIME_STEP) - metrics = mc_results.time_of_event.metrics() - print(' - ToE: {} (sigma: {})'.format(metrics['EOD']['mean'], metrics['EOD']['std'])) - profile.add_prediction(t, mc_results.time_of_event) - - # Calculating Prognostic Horizon once the loop completes - from prog_algs.uncertain_data.uncertain_data import UncertainData - from prog_algs.metrics import samples as metrics - - def criteria_eqn(tte : UncertainData, ground_truth_tte : dict) -> dict: - """ - Sample criteria equation for playback. - # UPDATE THIS CRITERIA EQN AND WHAT IS CALCULATED - - Args: - tte : UncertainData - Time to event in UncertainData format. - ground_truth_tte : dict - Dictionary of ground truth of time to event. - """ - - # Set an alpha value - bounds = {} - for key, value in ground_truth_tte.items(): - # Set bounds for precentage_in_bounds by adding/subtracting to the ground_truth - alpha_calc = value * ALPHA - bounds[key] = [value - alpha_calc, value + alpha_calc] # Construct bounds for all events - percentage_in_bounds = tte.percentage_in_bounds(bounds) - - # Verify if percentage in bounds for this ground truth meets beta distribution percentage limit - return {key: percentage_in_bounds[key] > BETA for key in percentage_in_bounds.keys()} - - # Generate plots for playback example - playback_plots = profile.plot(GROUND_TRUTH, ALPHA, True) - - # Calculate prognostic horizon with ground truth, and print - ph = profile.prognostic_horizon(criteria_eqn, GROUND_TRUTH) - print(f"Prognostic Horizon for 'EOD': {ph['EOD']}") - - # Calculate alpha lambda with ground truth, lambda, alpha, and beta, and print - al = profile.alpha_lambda(GROUND_TRUTH, LAMBDA_VALUE, ALPHA, BETA) - print(f"Alpha Lambda for 'EOD': {al['EOD']}") - - # Calculate cumulative relative accuracy with ground truth, and print - cra = profile.cumulative_relative_accuracy(GROUND_TRUTH) - print(f"Cumulative Relative Accuracy for 'EOD': {cra['EOD']}") - - input('Press any key to exit') - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py b/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py deleted file mode 100644 index 4b9a9856..00000000 --- a/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -In this example the BatteryElectroChemEOD model is used with a particle filter to estimate the state of the battery -""" - -from prog_models.models import BatteryElectroChemEOD -from prog_algs import * -import numpy as np -import matplotlib.pyplot as plt - -def run_example(): - ## Setup - def future_loading(t, x=None): - load = 1 - return {"i": load} - # Save battery model - # Time increment - dt = 1 - # Process noise - Q_vars = { - 'tb': 1, - 'Vo': 0.01, - 'Vsn': 0.01, - 'Vsp': 0.01, - 'qnB': 1, - 'qnS': 1, - 'qpB': 1, - 'qpS': 1 - } - # Measurement noise - R_vars = { - 't': 2, - 'v': 0.02 - } - battery = BatteryElectroChemEOD(process_noise= Q_vars, - measurement_noise = R_vars, - dt = dt) - - # Simulate data until EOD - start_u = future_loading(0) - start_x = battery.initialize(start_u) - start_y = battery.output(start_x) - sim_results = battery.simulate_to_threshold(future_loading, start_y, save_freq = 1) - - # Run particle filter - all_particles = [] - n_times = int(np.round(np.random.uniform(len(sim_results.times)*.25,len(sim_results.times)*.45,1)))# Random current time - - for i in range(n_times): - if i == 0: - batt_pf = state_estimators.ParticleFilter(model = battery, x0 = sim_results.states[i], num_particles = 250) - else: - batt_pf.estimate(t = sim_results.times[i], u = sim_results.inputs[i], z = sim_results.outputs[i]) - all_particles.append(batt_pf.particles) - - # Mean of the particles - alpha = 0.05 - states_vsn = [s['tb'] for s in sim_results.states] - pf_mean = [{key: np.mean(ps[key]) for key in battery.states} for ps in all_particles] - pf_low = [{key: np.quantile(ps[key], alpha / 2.0) for key in battery.states} for ps in all_particles] - pf_upp = [{key: np.quantile(ps[key], 1.0 - alpha / 2.0) for key in battery.states} for ps in all_particles] - print("First State:", pf_mean[0]) - print("Current State:", pf_mean[-1]) - plt.plot(sim_results.times[:n_times],[p['tb'] for p in pf_mean],linewidth=0.7,color="blue") - plt.plot(sim_results.times[:n_times], states_vsn[:n_times],"--",linewidth=0.7,color="red") - plt.fill_between(sim_results.times[:n_times],[p['tb'] for p in pf_low],[p['tb'] for p in pf_upp],alpha=0.5,color="blue") - plt.show() - - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() \ No newline at end of file diff --git a/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py b/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py deleted file mode 100644 index 0cbe2fcd..00000000 --- a/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation and prediction with uncertainty given a Prognostics Model. - -Method: An instance of the BatteryCircuit model in prog_models is created, and the prediction process is achieved in three steps: - 1) State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate - 2) Prediction of future states (with uncertainty) and the times at which the event threshold will be reached - 3) Metrics tools are used to further investigate the results of prediction -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics - iv) Figures illustrating results -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -from prog_models.models import BatteryElectroChemEOD as Battery - -from prog_algs import * - -def run_example(): - # Step 1: Setup model & future loading - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - # Measurement noise - R_vars = { - 't': 2, - 'v': 0.02 - } - batt = Battery(measurement_noise = R_vars) - initial_state = batt.parameters['x0'] - - # Step 2: Demonstrating state estimator - print("\nPerforming State Estimation Step") - - # Step 2a: Setup - filt = state_estimators.ParticleFilter(batt, initial_state) - # VVV Uncomment this to use UKF State Estimator VVV - # filt = state_estimators.UnscentedKalmanFilter(batt, initial_state) - - # Step 2b: Print & Plot Prior State - print("Prior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - fig = filt.x.plot_scatter(label='prior') - - # Step 2c: Perform state estimation step - example_measurements = {'t': 32.2, 'v': 3.915} - t = 0.1 - filt.estimate(t, future_loading(t), example_measurements) - - # Step 2d: Print & Plot Resulting Posterior State - print("\nPosterior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - filt.x.plot_scatter(fig=fig, label='posterior') # Add posterior state to figure from prior state - - # Note: in a prognostic application the above state estimation step would be repeated each time - # there is new data. Here we're doing one step to demonstrate how the state estimator is used - - # Step 3: Demonstrating Predictor - print("\n\nPerforming Prediction Step") - - # Step 3a: Setup Predictor - mc = predictors.MonteCarlo(batt) - - # Step 3b: Perform a prediction - NUM_SAMPLES = 5 - STEP_SIZE = 0.1 - mc_results = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE) - print('ToE', mc_results.time_of_event.mean) - - # Step 3c: Analyze the results - - # Note: The results of a sample-based prediction can be accessed by sample, e.g., - states_sample_1 = mc_results.states[1] - # now states_sample_1[n] corresponds to times[n] for the first sample - - # You can also access a state distribution at a specific time using the .snapshot function - states_time_1 = mc_results.states.snapshot(1) - # now you have all the samples corresponding to times[1] - - # You can also access the final state (of type UncertainData), like so: - final_state = mc_results.time_of_event.final_state - print('Final state @EOD: ', final_state['EOD'].mean) - - # You can also use the metrics package to generate some useful metrics on the result of a prediction - print("\nEOD Prediction Metrics") - - from prog_algs.metrics import prob_success - print('\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6])) - print('\tAssuming ground truth 3002.25: ', mc_results.time_of_event.metrics(ground_truth=3005.25)) - print('\tP(Success) if mission ends at 3002.25: ', prob_success(mc_results.time_of_event, 3005.25)) - - # Plot state transition - # Here we will plot the states at t0, 25% to ToE, 50% to ToE, 75% to ToE, and ToE - fig = mc_results.states.snapshot(0).plot_scatter(label = "t={} s".format(int(mc_results.times[0]))) # 0 - quarter_index = int(len(mc_results.times)/4) - mc_results.states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index]))) # 25% - mc_results.states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*2]))) # 50% - mc_results.states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*3]))) # 75% - mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[-1]))) # 100% - - mc_results.time_of_event.plot_hist() - - # Step 4: Show all plots - import matplotlib.pyplot as plt # For plotting - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py b/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py deleted file mode 100644 index 82b1eaf6..00000000 --- a/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -""" -This example demonstrates a use case where someone wants to predict the first event (i.e., End Of Life (EOL)) of a system. Many system models have multiple events that can occur. In some prognostics applications, users are not interested in predicting a specific event, and are instead interested in when the first event occurs, regardless of the event. This example demonstrates how to predict the first event of a system. - -Method: An instance of ThrownObject is used for this example. In this case it is trivial because the event 'falling' will always occur before 'impact', but for some other models that might not be true. The ThrownObject class is subclassed to add a new event 'EOL' which occurs if any other event occurs. The new model is then instantiated and used for prognostics like in basic_example. Prediction specifically specifies EOL as the event to be predicted. -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time the event 'EOL' is predicted to occur (with uncertainty) - iii) Histogram of the event 'EOL' -""" - -from prog_models.models import ThrownObject -from prog_algs.predictors import MonteCarlo -from prog_algs.uncertain_data import ScalarData - -def run_example(): - # Step 1: Define subclass with EOL event - # Similar to the prog_models 'events' example, but with an EOL event - class ThrownObjectWithEOL(ThrownObject): - events = ThrownObject.events + ['EOL'] - - def event_state(self, x): - es = super().event_state(x) - # Add EOL Event (minimum event state) - es['EOL'] = min(list(es.values())) - return es - - def threshold_met(self, x): - t_met = super().threshold_met(x) - # Add EOL Event (if any events have occured) - t_met['EOL'] = any(list(t_met.values())) - return t_met - - # Step 2: Create instance of subclass - m = ThrownObjectWithEOL(process_noise=0.05) - - # Step 3: Setup for prediction - pred = MonteCarlo(m) - def future_loading(t=None, x=None): - return {} # No future loading for ThrownObject - state = ScalarData(m.initialize()) - - # Step 4: Predict to EOL event - pred_results = pred.predict(state, future_loading, events=['EOL'], dt=0.025) - - # Step 5: Plot results - pred_results.time_of_event.plot_hist() - - import matplotlib.pyplot as plt - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py b/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py deleted file mode 100644 index cd2fdca4..00000000 --- a/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation with uncertainty given a Prognostics Model for a system in which not all output values are measured. - -Method: An instance of the BatteryCircuit model in prog_models is created. We assume that we are only measuring one of the output values, and we define a subclass to remove the other output value. - Estimation of the current state is performed at various time steps, using the defined state_estimator. - -Results: - i) Estimate of the current state given various times - ii) Display of results, such as prior and posterior state estimate values and SOC -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs import * - -def run_example(): - # Step 1: Subclass model with measurement equation - # In this case we're only measuring 'v' (i.e., removing temperature) - # To do this we're creating a new class that's subclassed from the complete model. - # To change the outputs we just have to override outputs (the list of keys) - class MyBattery(Battery): - outputs = ['v'] - - # Step 2: Setup model & future loading - def future_loading(t, x={}): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - - batt = MyBattery() - x0 = batt.parameters['x0'] - - # Step 3: Use the updated model - filt = state_estimators.ParticleFilter(batt, x0) - - # Step 4: Run step and print results - print('Running state estimation step with only one of 2 outputs measured') - - # Print Prior - print("\nPrior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - # Estimate Step - # Note, only voltage was needed in the measurement step, since that is the only output we're measuring - t = 0.1 - load = future_loading(t) - filt.estimate(t, load, {'v': 3.915}) - - # Print Posterior - print("\nPosterior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - # Another Estimate Step - t = 0.2 - load = future_loading(t) - filt.estimate(t, load, {'v': 3.91}) - - # Print Posterior Again - print("\nPosterior State (t={}):".format(t), filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - # Note that the particle filter was still able to perform state estimation. - # The updated outputs can be used for any case where the measurement doesn't match the model outputs - # For example, when units are different, or when the measurement is some combination of the outputs - # These are a little more complicated, since they require an instance of the parent class. For example: - - parent = Battery() - - - class MyBattery(Battery): - outputs = ['tv'] # output is temperature * voltage (for some reason) - - def output(self, x): - parent.parameters = self.parameters # only needed if you expect to change parameters - z = parent.output(x) - return self.OutputContainer({'tv': z['v'] * z['t']}) - - batt = MyBattery() - filt = state_estimators.ParticleFilter(batt, x0) - - print('-----------------\n\nExample 2') - print("\nPrior State:", filt.x.mean) - print("\toutput: ", batt.output(filt.x.mean)) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - t = 0.1 - load = future_loading(t) - filt.estimate(t, load, {'tv': 80}) - print("\nPosterior State:", filt.x.mean) - print("\toutput: ", batt.output(filt.x.mean)) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py b/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py deleted file mode 100644 index 053a179f..00000000 --- a/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -In this example we are using the UTPredictor to predict a specific event, in this case impact. This will then ignore the other events which are not of interest. -""" - -from prog_models.models.thrown_object import ThrownObject -from prog_algs import * - -def run_example(): - ## Setup - def future_loading(t, x = None): - return {} - - m = ThrownObject() - initial_state = m.initialize({}, {}) - - ## State Estimation - perform a single ukf state estimate step - filt = state_estimators.UnscentedKalmanFilter(m, initial_state) - filt.estimate(0.1, {}, m.output(initial_state)) - - ## Prediction - Predict EOD given current state - # Setup prediction - pred = predictors.UnscentedTransformPredictor(m) - - # Predict with a step size of 0.1 - mc_results = pred.predict(filt.x, future_loading, dt=0.1, save_freq= 1, events=['impact']) - - # Print Results - for i, time in enumerate(mc_results.times): - print('\nt = {}'.format(time)) - print('\tu = {}'.format(mc_results.inputs.snapshot(i).mean)) - print('\tx = {}'.format(mc_results.states.snapshot(i).mean)) - print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean)) - print('\tevent state = {}'.format(mc_results.states.snapshot(i).mean)) - - # Note only impact event is shown here - print('\nToE:', mc_results.time_of_event.mean) - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py b/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py deleted file mode 100644 index 1f1679fb..00000000 --- a/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs benchmarking for a state estimation and prediction with uncertainty given a Prognostics Model. The process and benchmarking analysis are run for various sample sizes. - -Method: An instance of the BatteryCircuit model in prog_models is created, state estimation is set up with a chosen state_estimator, and prediction is set up with a chosen predictor. - Prediction of future states (with uncertainty) is then performed for various sample sizes. - Metrics are calculated and displayed for each run. - -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction for each distinct sample size - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics, including alpha-lambda metric -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs import * - -def run_example(): - # Step 1: Setup Model and Future Loading - def future_loading(t, x={}): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - - batt = Battery() - - # Step 2: Setup Predictor - pred = predictors.MonteCarlo(batt, dt= 0.05) - - # Step 3: Estimate State - x0 = batt.initialize() - state_estimator = state_estimators.ParticleFilter(batt, x0) - # Send in some data to estimate state - state_estimator.estimate(0.1, future_loading(0.1), {'t': 32.2, 'v': 3.915}) - state_estimator.estimate(0.2, future_loading(0.2), {'t': 32.3, 'v': 3.91}) - - # Step 4: Benchmark Predictions - # Here we're comparing the results given different numbers of samples - print('Benchmarking...') - import time # For timing prediction - from prog_algs.metrics import samples as metrics - - # Perform benchmarking for each number of samples - sample_counts = [1, 2, 5, 10] - for sample_count in sample_counts: - print('\nRun 1 ({} samples)'.format(sample_count)) - start = time.perf_counter() - pred_results = pred.predict(state_estimator.x, future_loading, n_samples = sample_count) - toe = pred_results.time_of_event.key("EOD") # Looking at EOD event - end = time.perf_counter() - print('\tMSE: {:4.2f}s'.format(metrics.mean_square_error(toe, 3005.4))) - print('\tRMSE: {:4.2f}s'.format(metrics.root_mean_square_error(toe, 3005.4))) - print('\tRuntime: {:4.2f}s'.format(end - start)) - - # This same approach can be applied for benchmarking and comparing other changes - # For example: different sampling methods, prediction algorithms, step sizes, models - -# This allows the module to be executed directly -if __name__=='__main__': - run_example() diff --git a/docs/_images/alpha_lambda_plt_ex.png b/docs/_images/alpha_lambda_plt_ex.png deleted file mode 100644 index c489c48f..00000000 Binary files a/docs/_images/alpha_lambda_plt_ex.png and /dev/null differ diff --git a/docs/_images/package_structure.png b/docs/_images/package_structure.png deleted file mode 100644 index e5b56af1..00000000 Binary files a/docs/_images/package_structure.png and /dev/null differ diff --git a/docs/_images/scatter_hist_ex.png b/docs/_images/scatter_hist_ex.png deleted file mode 100644 index b4e9907a..00000000 Binary files a/docs/_images/scatter_hist_ex.png and /dev/null differ diff --git a/docs/_images/scatter_plt_ex.png b/docs/_images/scatter_plt_ex.png deleted file mode 100644 index 3352babc..00000000 Binary files a/docs/_images/scatter_plt_ex.png and /dev/null differ diff --git a/docs/_sources/dev_guide.rst.txt b/docs/_sources/dev_guide.rst.txt deleted file mode 100644 index 0e1e3a34..00000000 --- a/docs/_sources/dev_guide.rst.txt +++ /dev/null @@ -1,102 +0,0 @@ -Developers Guide -================ - -This document includes some details relevant for developers. - -.. contents:: - :backlinks: top - -Guidance for Contributors -------------------------- - -Below are a few design decision made by the authors, documented for the reference of new contributors: - -* When supplied by or to the user, values with names (e.g., inputs, states, outputs, event_states, event occurance, etc.) should be supplied as dictionaries (or dict-like objects) where they can be referred to by name. -* Visualize and metrics subpackages shall be independent (i.e., not have any dependencies with the wider package or other subpackages) -* Limit introduction of new external dependencies, when possible. -* This is a research tool, so when making a design decision between operational efficiency and usability, generally choose the more usable option -* Except in the most extreme cases, maintain backwards compatibility for the convenience of existing users - - * If a feature is to be removed, mark it as depreciated for at least 2 releases before removing - -* Whenever possible, design so a new feature can be used for any model, interchangably. -* Whenever possible, UncertainData types, state estimators, and predictors should be interchangable -* Demonstrate common use cases as an example. -* Every feature should be demonstrated in an example - - * The most commonly used features should be demonstrated in the tutorial - -Branching Strategy ------------------- -Our project is following the git strategy described `here `_. Details specific to each branch are described below. - -`master`: Every merge into the master branch is done using a pull request (never commiting directly), is assigned a release number, and must comply with the release checklist. The release checklist is a software assurance tool. - -`dev`: Every merge into the dev branch that contains a functional change is done using a pull request (not commiting directly). Every commit should be functional. All unit tests must function before commiting to dev or merging another branch. - -`Feature Branches`: These branches include changes specific to a new feature. Before merging into dev unit tests should all run, tests should be added for the feature, and documentation should be updated, as appropriate. - -Release Checklist -***************** -* Code review - all software must be checked by someone other than the author -* Check that each new feature has a corresponding tests -* Run unit tests `python -m tests` -* Check documents- see if any updates are required -* Rebuild sphinx documents: `sphinx-build sphinx-config/ docs/` -* Write release notes -* For releases adding new features- ensure that NASA release process has been followed - -NPR 7150 --------- -This section describes this project's compliance with the NASA's NPR 7150 requirements, documented `here `_. - -* Software Classification: Class-E (Research Software) -* Safety Criticality: Not Safety Critical - -Compliance Notation Legend -************************** -* FC: Fully Compliant -* T: Tailored (Specific tailoring described in mitigation) `SWE-121 `_ -* PC: Partially Compliant -* NC: Not Compliant -* NA: Not Applicable - -Compliance Matrix -***************** -+-------+----------------------------------+------------+---------------------+ -| SWE # | Description | Compliance | Evidence | -+=======+==================================+============+=====================+ -| 033 | Assess aquisiton Options | FC | See section below | -+-------+----------------------------------+------------+---------------------+ -| 013 | Maintain Software Plans | FC | This document | -+-------+----------------------------------+------------+---------------------+ -| 042 | Electronic Accesss to Source | FC | This repo | -+-------+----------------------------------+------------+---------------------+ -| 139 | Comply with 7150 | FC | This document | -+-------+----------------------------------+------------+---------------------+ -| 121 | Tailored Reqs | NA | No tailoring | -+-------+----------------------------------+------------+---------------------+ -| 125 | Compliance Matrix | FC | This document | -+-------+----------------------------------+------------+---------------------+ -| 029 | Software Classification | FC | This document | -+-------+----------------------------------+------------+---------------------+ -| 022 | Software Assurance | FC | This document | -+-------+----------------------------------+------------+---------------------+ -| 205 | Safety Cricial Software | FC | See above | -+-------+----------------------------------+------------+---------------------+ -| 023 | Safety Critical Reqs | NA | Not safety critical | -+-------+----------------------------------+------------+---------------------+ -| 206 | Autogen Software | NA | No autogen | -+-------+----------------------------------+------------+---------------------+ -| 148 | Software Catolog | FC | Will be added | -+-------+----------------------------------+------------+---------------------+ -| 156 | Perform CyberSecurity Assessment | FC | See section below | -+-------+----------------------------------+------------+---------------------+ - -Aquisition Options -****************** -Assessed, there are some existing prognostics tools, but no general python package that can support model-based prognostics like we need. - -Cybersecurity Assessment -************************ -Assessed, no significant Cybersecurity concerns were identified- research software. \ No newline at end of file diff --git a/docs/_sources/exceptions.rst.txt b/docs/_sources/exceptions.rst.txt deleted file mode 100644 index f9612fab..00000000 --- a/docs/_sources/exceptions.rst.txt +++ /dev/null @@ -1,18 +0,0 @@ -Exceptions -====================== -Exceptions returned from prog_model fall into a few custom exception types, defined below. Catching any Prognostics Model Exception can be acomplished using `try-except ProgModelException`. Alternatively, a user can catch a specific exception type. - -Prognostics Model Exceptions ----------------------------- - -.. autoexception:: prog_algs.exceptions.ProgAlgException - :members: - :inherited-members: - -.. autoexception:: prog_algs.exceptions.ProgAlgInputException - :members: - :inherited-members: - -.. autoexception:: prog_algs.exceptions.ProgAlgTypeError - :members: - :inherited-members: diff --git a/docs/_sources/getting_started.rst.txt b/docs/_sources/getting_started.rst.txt deleted file mode 100644 index ef2cb76c..00000000 --- a/docs/_sources/getting_started.rst.txt +++ /dev/null @@ -1,112 +0,0 @@ -Getting Started -=============== -.. image:: https://mybinder.org/badge_logo.svg - :target: https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb - -The NASA Prognostics Algorithms Package is a Python framework for defining, building, using, and testing Algorithms for prognostics (computation of remaining useful life) of engineering systems, and provides a set of prognostics algorithms developed within this framework, suitable for use in prognostics applications. It can be used in conjunction with the Prognostics Models Package (`prog_models`) to perform research in prognostics with prognostics systems. - -Installing ------------------------ - -Installing from pip (recommended) -******************************************** -The latest stable release of `prog_algs` is hosted on PyPi. For most users (unless you want to contribute to the development of `prog_algs`), this version will be adequate. To install from the command line, use the following command: - -.. code-block:: console - - $ pip install prog_algs - -Installing Pre-Release Versions with GitHub -******************************************** -For users who would like to contribute to `prog_algs` or would like to use pre-release features can do so using the 'dev' branch (or a feature branch) on the `prog_algs GitHub repo `__. This isn't recommended for most users as this version may be unstable. To use this version, use the following commands: - -.. code-block:: console - - $ git clone https://github.com/nasa/prog_algs - $ cd prog_algs - $ git checkout dev - $ pip install -e . - -Summary ---------- -A few definitions to get started: - -* **events**: something that can be predicted (e.g., system failure). An event has either occurred or not. - -* **event state**: progress towards event occurring. Defined as a number where an event state of 0 indicates the event has occurred and 1 indicates no progress towards the event (i.e., fully healthy operation for a failure event). For gradually occurring events (e.g., discharge) the number will progress from 1 to 0 as the event nears. In prognostics, event state is frequently called "State of Health". - -* **inputs**: control applied to the system being modeled (e.g., current drawn from a battery). - -* **outputs**: measured sensor values from a system (e.g., voltage and temperature of a battery). - -* **performance metrics**: performance characteristics of a system that are a function of system state, but are not directly measured. - -* **states**: Internal parameters (typically hidden states) used to represent the state of the system- can be same as inputs/outputs but do not have to be. - -* **process noise**: representing uncertainty in the model transition (e.g., model uncertainty). - -* **measurement noise**: representing uncertainty in the measurement process (e.g., sensor sensitivity, sensor misalignements, environmental effects). - -The structure of the packages is illustrated below: - -.. image:: images/package_structure.png - -Prognostics is performed using `State Estimators `__ and `Predictors `__. State Estimators are resposible for estimating the current state of the modeled system using sensor data and a prognostics model (see: `prog_models package `__). The state estimator then produces an estimate of the system state with uncertainty in the form of an `uncertain data object `__. This state estimate is used by the predictor to predict when events will occur (Time of Event, ToE - returned as an `uncertain data object `__), and future system states (returned as a `Prediction object `__). - -Data Structures -*************** - -A few custom data structures are available for storing and manipulating prognostics data of various forms. These structures are listed below and desribed on their respective pages: - * `SimResult (from prog_models) `__ : The result of a single simulation (without uncertainty). Can be used to store inputs, outputs, states, event_states, observables, etc. Is returned by the model.simulate_to* methods. - * `UncertainData `__ : Used throughout the package to represent data with uncertainty. There are a variety of subclasses of UncertainData to represent data with uncertainty in different forms (e.g., ScalarData, MultivariateNormalDist, UnweightedSamples). Notibly, this is used to represent the output of a StateEstimator's `estimate` method, individual snapshots of a prediction, and the time of event estimate from a predictor's `predict` method. - * `Prediction `__ : Prediction of future values (with uncertainty) of some variable (e.g., input, state, output, event_states, etc.). The `predict` method of predictors return this. - * `ToEPredictionProfile `__ : The result of multiple predictions, including time of prediction. This data structure can be treated as a dictionary of time of prediction to toe prediction. - -Use ----- -The best way to learn how to use `prog_algs` is through the `tutorial `__. There are also a number of examples which show different aspects of the package, summarized and linked below: - -* :download:`examples.basic_example <../examples/basic_example.py>` - .. automodule:: examples.basic_example - | -* :download:`examples.thrown_object_example <../examples/thrown_object_example.py>` - .. automodule:: examples.thrown_object_example - | -* :download:`examples.utpredictor <../examples/utpredictor.py>` - .. automodule:: examples.utpredictor - | -* :download:`examples.benchmarking_example <../examples/benchmarking_example.py>` - .. automodule:: examples.benchmarking_example - | -* :download:`examples.eol_event <../examples/eol_event.py>` - .. automodule:: examples.eol_event - | -* :download:`examples.horizon <../examples/horizon.py>` - .. automodule:: examples.horizon - | -* :download:`examples.kalman_filter <../examples/kalman_filter.py>` - .. automodule:: examples.kalman_filter - | -* :download:`examples.measurement_eqn_example <../examples/measurement_eqn_example.py>` - .. automodule:: examples.measurement_eqn_example - | -* :download:`examples.new_state_estimator_example <../examples/new_state_estimator_example.py>` - .. automodule:: examples.new_state_estimator_example - | -* :download:`examples.playback <../examples/playback.py>` - .. automodule:: examples.playback - | -* :download:`examples.predict_specific_event <../examples/predict_specific_event.py>` - .. automodule:: examples.predict_specific_event - | -* :download:`examples.particle_filter_battery_example <../examples/particle_filter_battery_example.py>` - .. automodule:: examples.particle_filter_battery_example - | -* :download:`tutorial <../tutorial.ipynb>` - | - -Extending ---------- -New State Estimators and Predictors are created by extending the :class:`prog_algs.state_estimators.StateEstimator` and :class:`prog_algs.predictors.Predictor` class, respectively. - -See :download:`examples.new_state_estimator_example <../examples/new_state_estimator_example.py>` for an example of this approach. diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt deleted file mode 100644 index 5ad95bd4..00000000 --- a/docs/_sources/index.rst.txt +++ /dev/null @@ -1,57 +0,0 @@ -Prognostics Algorithms Python Package -============================================================= -.. image:: https://mybinder.org/badge_logo.svg - :target: https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb - -The NASA PCoE Prognostic Algorithms Package is a python framework for model-based prognostics (computation of remaining useful life) of engineering systems. The package provides an extendable set of algorithms for state estimation and prediction, including uncertainty propagation. The package also include metrics, visualization, and analysis tools needed to measure the prognostic performance. The algorithms use prognostic models (from NASA's Prognostics Model Package) to perform estimation and prediction functions. The package enables the rapid development of prognostics solutions for given models of components and systems. Different algorithms can be easily swapped to do comparative studies and evaluations of different algorithms to select the best for the application at hand. - -The Prognostics Algorithms Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE) and `Diagnostics & Prognostics Group `__. - -If you are new to this package, see `getting started `__. - -.. toctree:: - :maxdepth: 2 - - Tutorial - getting_started - state_estimators - predictors - uncertain_data - prediction - metrics - ProgModels - ProgServer - dev_guide - GitHub - release - -Citing this repository ------------------------ -Use the following to cite this repository: - -@misc{2021_nasa_prog_algs, - | author = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta}, - | title = {Prognostics Algorithms Python Package}, - | month = May, - | year = 2022, - | version = {1.3.0}, - | url = {https://github.com/nasa/prog_algs} - | } - -The corresponding reference should look like this: - -C. Teubert, C. Kulkarni, M. Corbetta. Prognostics Algorithms Python Package, v1.3.0, May 2022. URL https://github.com/nasa/prog_algs. - -Indices and tables ------------------------ - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -Disclaimers ----------------------- - -No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS." - -Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT. \ No newline at end of file diff --git a/docs/_sources/metrics.rst.txt b/docs/_sources/metrics.rst.txt deleted file mode 100644 index f0441858..00000000 --- a/docs/_sources/metrics.rst.txt +++ /dev/null @@ -1,98 +0,0 @@ -Metrics and Figures -==================== - -Metrics and Figures are the primary tools prognostics researchers use to understand data, measure performance, and compare methods and models. There are several metrics and figures included in `prog_algs`, most of which are implemented as methods in the various prognostic data structures: `UncertainData`, `Prediction`, and `ToEPredictionProfile`. The supported metrics and figures are described below, divided by structure that they act on. - -.. contents:: - :backlinks: top - -.. role:: raw-html(raw) - :format: html - -UncertainData Metrics and Figures ------------------------------------------------- -The following metrics and figures act on any UncertainData type (e.g., `MultivariateNormalDist`, `UnweightedSamples`, or `ScalarData`). They are meant to provide useful information about a distribution (e.g., predicted state at a future point, Time of Event). - -Simple Statistics -^^^^^^^^^^^^^^^^^^^^^ - -.. automethod:: prog_algs.uncertain_data.UncertainData.mean - -:raw-html:`
` - -.. automethod:: prog_algs.uncertain_data.UncertainData.median - -:raw-html:`
` - -.. automethod:: prog_algs.uncertain_data.UncertainData.cov - -:raw-html:`
` - -.. automethod:: prog_algs.uncertain_data.UncertainData.percentage_in_bounds - -:raw-html:`
` - -.. automethod:: prog_algs.uncertain_data.UncertainData.metrics - -:raw-html:`
` - -.. automethod:: prog_algs.uncertain_data.UncertainData.describe - -Relative Accuracy -^^^^^^^^^^^^^^^^^^^^ - -.. automethod:: prog_algs.uncertain_data.UncertainData.relative_accuracy - -Histogram -^^^^^^^^^^^^^^^^^^^^ -.. automethod:: prog_algs.uncertain_data.UncertainData.plot_hist -.. image:: images/scatter_hist_ex.png - -Scatter Plot -^^^^^^^^^^^^^^^^^^^^ -.. automethod:: prog_algs.uncertain_data.UncertainData.plot_scatter -.. image:: images/scatter_plt_ex.png - -Time of Event Metrics -^^^^^^^^^^^^^^^^^^^^^^^ -These metrics operate on a UncertainData object that represents the distribution of time of events - -.. autofunction:: prog_algs.metrics.prob_success - -Prediction Metrics and Figures ------------------------------------------------- -The following metrics and figures act on a Prediction (i.e., data describing future states). They are meant to provide useful information about the prediction. - -Simple Statistics -^^^^^^^^^^^^^^^^^^^^^ - -.. automethod:: prog_algs.predictors.prediction.Prediction.mean - -:raw-html:`
` - -.. automethod:: prog_algs.predictors.prediction.Prediction.monotonicity - -Toe Prediction Profile Metrics and Figures ------------------------------------------------- -The following metrics and figures act on a ToePrediction Profile (i.e., ToE estimates made at multiple points). They are meant to provide useful information about the ToEPredictionProfile. - -Alpha-Lambda -^^^^^^^^^^^^^^^ -.. automethod:: prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile.alpha_lambda - -Cumulative Relative Accuracy -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile.cumulative_relative_accuracy - -Monotonicity -^^^^^^^^^^^^^ -.. automethod:: prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile.monotonicity - -Prognostics Horizon -^^^^^^^^^^^^^^^^^^^^^ -.. automethod:: prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile.prognostic_horizon - -Alpha-Beta Plot -^^^^^^^^^^^^^^^^^ -.. automethod:: prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile.plot -.. image:: images/alpha_lambda_plt_ex.png diff --git a/docs/_sources/prediction.rst.txt b/docs/_sources/prediction.rst.txt deleted file mode 100644 index 07817c38..00000000 --- a/docs/_sources/prediction.rst.txt +++ /dev/null @@ -1,27 +0,0 @@ -Prediction -======================= - -Prediction ----------------------- -Predictions store the result of a prediction (i.e., returned by the predict method of a predictor). They store values (with uncertainty) at different future times. These are used to store states, inputs, outputs, perfomance metrics, and event states with uncertainty at savepoints. - -Two types of predictions are distributed with this package: `Prediction` and `UnweightedSamplesPrediction`, described below. `UnweightedSamplesPrediction` extends `Prediction` to allow some operations specific to cases where each prediction is represented by an UnweightedSamples object (e.g., accessing SimResult for a single sample). - -Base Prediction -********************** -.. autoclass:: prog_algs.predictors.Prediction - :members: - :inherited-members: - -UnweightedSamplesPrediction -******************************** -.. autoclass:: prog_algs.predictors.UnweightedSamplesPrediction - :members: - :inherited-members: - :exclude-members: append, extend, clear, pop, remove, reverse, insert - -ToE Prediction Profile ----------------------- -.. autoclass:: prog_algs.predictors.ToEPredictionProfile - :members: - :inherited-members: diff --git a/docs/_sources/predictors.rst.txt b/docs/_sources/predictors.rst.txt deleted file mode 100644 index 3e09496a..00000000 --- a/docs/_sources/predictors.rst.txt +++ /dev/null @@ -1,41 +0,0 @@ -Predictors -======================= - -The Predictor uses a state estimate (type UncertainData subclass, output of a state estimator), information about expected future loading, and a Prognostics Model (see: `prog_models package `__) to predict both future states (also outputs, performance metrics, event_states) at predefined points and the time that an event will occur (Time of Event, ToE) with uncertainty. - -Here's an example of its use. In this example we use the ThrownObject model and the MonteCarlo predictor, and we save the state every 1s. We also use a scalar first state (i.e., no uncertainty). - -.. code-block:: python - - >>> from prog_models.models import ThrownObject - >>> from prog_algs.predictors import MonteCarlo - >>> from prog_algs.uncertain_data import ScalarData - >>> - >>> m = ThrownObject() - >>> pred = MonteCarlo(m) - >>> first_state = ScalarData({'x': 1.7, 'v': 20}) # Initial state for prediction - >>> def future_loading(t, x): - >>> return {} # ThrownObject doesn't have a way of loading it - >>> - >>> pred_results = pred.predict(first_state, future_loading, save_freq=1) - >>> pred_results.time_of_event.plot_hist(events='impact') # Plot a histogram of when the impact event occurred - -See tutorial and examples for more information and additional features. - -Included Predictors ------------------------ -The following predictors are included with this package. A new predictor can be created by subclassing `prog_algs.predictors.Predictor`. See also: `predictor_template.py` - -Monte Carlo Predictor -********************** -.. autoclass:: prog_algs.predictors.MonteCarlo - -Unscented Transform Predictor -***************************** -.. autoclass:: prog_algs.predictors.UnscentedTransformPredictor - -Predictor Interface ------------------------ -.. autoclass:: prog_algs.predictors.Predictor - :members: - :inherited-members: diff --git a/docs/_sources/release.rst.txt b/docs/_sources/release.rst.txt deleted file mode 100644 index 05538bd6..00000000 --- a/docs/_sources/release.rst.txt +++ /dev/null @@ -1,48 +0,0 @@ -Release Notes -======================= - -.. contents:: - :backlinks: top - -Updates in V1.3 ------------------------ -* **New State Estimator Added** :class:`prog_algs.state_estimators.KalmanFilter`. Works with models derived from :class:`prog_models.LinearModel`. See :download:`examples.kalman_filter <../examples/kalman_filter.py>` -* **New Predictor Added** :class:`prog_algs.predictors.UnscentedTransformPredictor`. See :download:`examples.utpredictor <../examples/utpredictor.py>` -* Initial state estimate (x0) can now be passed as `UncertainData` to represent initial state uncertainty. See :download:`examples.playback <../examples/playback.py>` -* Added new metrics for :class:`prog_algs.predictors.ToEPredictionProfile`: Prognostics horizon, Cumulative Relative Accuracy (CRA). See :download:`examples.playback <../examples/playback.py>` -* Added ability to plot :class:`prog_algs.predictors.ToEPredictionProfile`: profile.plot(). See :download:`examples.playback <../examples/playback.py>` -* Added new metric for :class:`prog_algs.predictors.Prediction`: Monotonicity, Relative Accuracy (RA) -* Added new metric for :class:`prog_algs.uncertain_data.UncertainData` (and subclasses): Root Mean Square Error (RMSE) -* Added new describe method for :class:`prog_algs.uncertain_data.UncertainData` (and subclasses) -* Add support for python 3.10 -* Various performance improvements and bugfixes - - -Updates in v1.2 ---------------- - -Note for Existing Users -*********************** -This release includes changes to the return format of the MonteCarlo Predictor's `predict` method. These changes were necessary to support non-sample based predictors. The non backwards-compatible changes are listed below: -* times: - * previous ```List[List[float]]``` where times[n][m] corresponds to timepoint m of sample n. - * new ```List[float]``` where times[m] corresponds to timepoint m for all samples. -* End of Life (EOL)/ Time of Event (ToE) estimates: - * previous ```List[float]``` where the times correspond to the time that the first event occurs. - * new ```UnweightedSamples``` where keys correspond to the inidividualevents predicted. -* State at time of event (ToE). - * previous: element in states. - * new: member of ToE structure (e.g., ToE.final_state['event1']). - -General Updates -*************** -* New Feature: Histogram and Scatter Plot of UncertainData. -* New Feature: Vectorized particle filter. - * Particle Filter State Estimator is now vectorized for vectorized models - this significantly improves performance. -* New Feature: Unscented Transform Predictor. - * New predictor that propogates sigma points forward to estimate time of event and future states. -* New Feature: `Prediction` class to represent predicted future values. -* New Feature: `ToEPredictionProfile` class to represent and operate on the result of multiple predictions generated at different prediction times. -* Added metrics `percentage_in_bounds` and `metrics` and plots to UncertainData . -* Add support for Python3.9. -* General Bugfixes. diff --git a/docs/_sources/state_estimators.rst.txt b/docs/_sources/state_estimators.rst.txt deleted file mode 100644 index ee41ad5e..00000000 --- a/docs/_sources/state_estimators.rst.txt +++ /dev/null @@ -1,44 +0,0 @@ -State Estimators -======================= -The State Estimator uses sensor information and a Prognostics Model (see: `prog_models package `__) to produce an estimate of system state (which can be used to estimate outputs, event_states, and performance metrics). This state estimate can either be used by itself or as input to a `Predictor `__. A state estimator is typically run each time new information is available. - -Here's an example of its use. In this example we use the unscented kalman filter state estimator and the ThrownObject model. - -.. code-block:: python - - >>> from prog_models.models import ThrownObject - >>> from prog_algs.state_estimators import UnscentedKalmanFilter - >>> - >>> m = ThrownObject() - >>> initial_state = m.initialize() - >>> filt = UnscentedKalmanFilter(m, initial_state) - >>> - >>> load = {} # No load for ThrownObject - >>> new_data = {'x': 1.8} # Observed state - >>> print('Prior: ', filt.x.mean) - >>> filt.estimate(0.1, load, new_data) - >>> print('Posterior: ', filt.x.mean) - -See tutorial and examples for more information and additional features. - -Included State Estimators -------------------------- -The following state estimators are included with this package. A new state estimator can be created by subclassing `prog_algs.state_estimators.StateEstimator`. See also: `state_estimator_template.py` - -Kalman Filter -************************* -.. autoclass:: prog_algs.state_estimators.KalmanFilter - -Unscented Kalman Filter -************************* -.. autoclass:: prog_algs.state_estimators.UnscentedKalmanFilter - -Particle Filter -************************* -.. autoclass:: prog_algs.state_estimators.ParticleFilter - -State Estimator Interface -------------------------- -.. autoclass:: prog_algs.state_estimators.StateEstimator - :members: - :inherited-members: diff --git a/docs/_sources/uncertain_data.rst.txt b/docs/_sources/uncertain_data.rst.txt deleted file mode 100644 index 049b987c..00000000 --- a/docs/_sources/uncertain_data.rst.txt +++ /dev/null @@ -1,26 +0,0 @@ -Uncertain Data -======================= - -The `prog_algs.uncertain_data` package includes classes for representing data with uncertainty. All types of UncertainData can be operated on using `the interface <#interface>`__. Inidividual classes for representing uncertain data of different kinds are described below, in `Implemented UncertainData Types <#implemented-uncertaindata-types>`__. - -Interface ------------------------- -.. autoclass:: prog_algs.uncertain_data.UncertainData - :members: - :inherited-members: - -Implemented UncertainData Types --------------------------------- - -Unweighted Samples -****************** -.. autoclass:: prog_algs.uncertain_data.UnweightedSamples - :members: key - -Multivariate Normal Distribution -******************************** -.. autoclass:: prog_algs.uncertain_data.MultivariateNormalDist - -Scalar Data (i.e., no uncertainty) -********************************** -.. autoclass:: prog_algs.uncertain_data.ScalarData diff --git a/docs/_sources/visualize.rst.txt b/docs/_sources/visualize.rst.txt deleted file mode 100644 index 47393412..00000000 --- a/docs/_sources/visualize.rst.txt +++ /dev/null @@ -1,8 +0,0 @@ -Visualization -============= - -Visualization tools are included in `prog_algs.visualize`, described below: - -.. automodule:: prog_algs.visualize - :members: - :undoc-members: \ No newline at end of file diff --git a/docs/_static/alabaster.css b/docs/_static/alabaster.css deleted file mode 100644 index 0eddaeb0..00000000 --- a/docs/_static/alabaster.css +++ /dev/null @@ -1,701 +0,0 @@ -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Georgia, serif; - font-size: 17px; - background-color: #fff; - color: #000; - margin: 0; - padding: 0; -} - - -div.document { - width: 940px; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 220px; -} - -div.sphinxsidebar { - width: 220px; - font-size: 14px; - line-height: 1.5; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #fff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -div.body > .section { - text-align: left; -} - -div.footer { - width: 940px; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -p.caption { - font-family: inherit; - font-size: inherit; -} - - -div.relations { - display: none; -} - - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0; - margin: -10px 0 0 0px; - text-align: center; -} - -div.sphinxsidebarwrapper h1.logo { - margin-top: -10px; - text-align: center; - margin-bottom: 5px; - text-align: left; -} - -div.sphinxsidebarwrapper h1.logo-name { - margin-top: 0px; -} - -div.sphinxsidebarwrapper p.blurb { - margin-top: 0; - font-style: normal; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Georgia, serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar ul li.toctree-l1 > a { - font-size: 120%; -} - -div.sphinxsidebar ul li.toctree-l2 > a { - font-size: 110%; -} - -div.sphinxsidebar input { - border: 1px solid #CCC; - font-family: Georgia, serif; - font-size: 1em; -} - -div.sphinxsidebar hr { - border: none; - height: 1px; - color: #AAA; - background: #AAA; - - text-align: left; - margin-left: 0; - width: 50%; -} - -div.sphinxsidebar .badge { - border-bottom: none; -} - -div.sphinxsidebar .badge:hover { - border-bottom: none; -} - -/* To address an issue with donation coming after search */ -div.sphinxsidebar h3.donation { - margin-top: 10px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Georgia, serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #DDD; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #EAEAEA; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - margin: 20px 0px; - padding: 10px 30px; - background-color: #EEE; - border: 1px solid #CCC; -} - -div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fafafa; -} - -div.admonition p.admonition-title { - font-family: Georgia, serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: #fff; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.warning { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.danger { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.error { - background-color: #FCC; - border: 1px solid #FAA; - -moz-box-shadow: 2px 2px 4px #D52C2C; - -webkit-box-shadow: 2px 2px 4px #D52C2C; - box-shadow: 2px 2px 4px #D52C2C; -} - -div.caution { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.attention { - background-color: #FCC; - border: 1px solid #FAA; -} - -div.important { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.note { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.tip { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.hint { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.seealso { - background-color: #EEE; - border: 1px solid #CCC; -} - -div.topic { - background-color: #EEE; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt, code { - font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -.hll { - background-color: #FFC; - margin: 0 -12px; - padding: 0 12px; - display: block; -} - -img.screenshot { -} - -tt.descname, tt.descclassname, code.descname, code.descclassname { - font-size: 0.95em; -} - -tt.descname, code.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #EEE; - -webkit-box-shadow: 2px 2px 4px #EEE; - box-shadow: 2px 2px 4px #EEE; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #EEE; - background: #FDFDFD; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.field-list p { - margin-bottom: 0.8em; -} - -/* Cloned from - * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 - */ -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -table.footnote td.label { - width: .1px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - /* Matches the 30px from the narrow-screen "li > ul" selector below */ - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #EEE; - padding: 7px 30px; - margin: 15px 0px; - line-height: 1.3em; -} - -div.viewcode-block:target { - background: #ffd; -} - -dl pre, blockquote pre, li pre { - margin-left: 0; - padding-left: 30px; -} - -tt, code { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, code.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid #fff; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -/* Don't put an underline on images */ -a.image-reference, a.image-reference:hover { - border-bottom: none; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt, a:hover code { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - li > ul { - /* Matches the 30px from the "ul, ol" selector above */ - margin-left: 30px; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: #fff; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: #FFF; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: #fff; - } - - div.sphinxsidebar a { - color: #AAA; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - - -/* misc. */ - -.revsys-inline { - display: none!important; -} - -/* Make nested-list/multi-paragraph items look better in Releases changelog - * pages. Without this, docutils' magical list fuckery causes inconsistent - * formatting between different release sub-lists. - */ -div#changelog > div.section > ul > li > p:only-child { - margin-bottom: 0; -} - -/* Hide fugly table cell borders in ..bibliography:: directive output */ -table.docutils.citation, table.docutils.citation td, table.docutils.citation th { - border: none; - /* Below needed in some edge cases; if not applied, bottom shadows appear */ - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - - -/* relbar */ - -.related { - line-height: 30px; - width: 100%; - font-size: 0.9rem; -} - -.related.top { - border-bottom: 1px solid #EEE; - margin-bottom: 20px; -} - -.related.bottom { - border-top: 1px solid #EEE; -} - -.related ul { - padding: 0; - margin: 0; - list-style: none; -} - -.related li { - display: inline; -} - -nav#rellinks { - float: right; -} - -nav#rellinks li+li:before { - content: "|"; -} - -nav#breadcrumbs li+li:before { - content: "\00BB"; -} - -/* Hide certain items when printing */ -@media print { - div.related { - display: none; - } -} \ No newline at end of file diff --git a/docs/_static/basic.css b/docs/_static/basic.css deleted file mode 100644 index bf18350b..00000000 --- a/docs/_static/basic.css +++ /dev/null @@ -1,906 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2022 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: 450px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a.brackets:before, -span.brackets > a:before{ - content: "["; -} - -a.brackets:after, -span.brackets > a:after { - content: "]"; -} - -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; -} - -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -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, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -aside.sidebar::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; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -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; -} - -dl.footnote > dt, -dl.citation > dt { - float: left; - margin-right: 0.5em; -} - -dl.footnote > dd, -dl.citation > dd { - margin-bottom: 0em; -} - -dl.footnote > dd:after, -dl.citation > dd: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 > dt:after { - content: ":"; -} - -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; -} - -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; -} - -/* -- 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/_static/custom.css b/docs/_static/custom.css deleted file mode 100644 index 2a924f1d..00000000 --- a/docs/_static/custom.css +++ /dev/null @@ -1 +0,0 @@ -/* This file intentionally left blank. */ diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js deleted file mode 100644 index e1bfd708..00000000 --- a/docs/_static/doctools.js +++ /dev/null @@ -1,358 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * 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; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - this.initOnKeyListeners(); - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return 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 : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated === 'undefined') - return string; - return (typeof translated === 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated === 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) === 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - var url = new URL(window.location); - url.searchParams.delete('highlight'); - window.history.replaceState({}, '', url); - }, - - /** - * helper function to focus on search bar - */ - focusSearchBar : function() { - $('input[name=q]').first().focus(); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this === '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - // only install a listener if it is really needed - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && - !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) - return; - - $(document).keydown(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box, textarea, dropdown or button - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' - && activeElementType !== 'BUTTON') { - if (event.altKey || event.ctrlKey || event.metaKey) - return; - - if (!event.shiftKey) { - switch (event.key) { - case 'ArrowLeft': - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) - break; - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - break; - case 'ArrowRight': - if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) - break; - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - break; - case 'Escape': - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) - break; - Documentation.hideSearchWords(); - return false; - } - } - - // some keyboard layouts may need Shift to get / - switch (event.key) { - case '/': - if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) - break; - Documentation.focusSearchBar(); - return false; - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js deleted file mode 100644 index aa189541..00000000 --- a/docs/_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.3.1', - LANGUAGE: 'None', - 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/_static/file.png b/docs/_static/file.png deleted file mode 100644 index a858a410..00000000 Binary files a/docs/_static/file.png and /dev/null differ diff --git a/docs/_static/jquery-3.5.1.js b/docs/_static/jquery-3.5.1.js deleted file mode 100644 index 50937333..00000000 --- a/docs/_static/jquery-3.5.1.js +++ /dev/null @@ -1,10872 +0,0 @@ -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Developers Guide

-

This document includes some details relevant for developers.

- -
-

Guidance for Contributors

-

Below are a few design decision made by the authors, documented for the reference of new contributors:

-
    -
  • When supplied by or to the user, values with names (e.g., inputs, states, outputs, event_states, event occurance, etc.) should be supplied as dictionaries (or dict-like objects) where they can be referred to by name.

  • -
  • Visualize and metrics subpackages shall be independent (i.e., not have any dependencies with the wider package or other subpackages)

  • -
  • Limit introduction of new external dependencies, when possible.

  • -
  • This is a research tool, so when making a design decision between operational efficiency and usability, generally choose the more usable option

  • -
  • Except in the most extreme cases, maintain backwards compatibility for the convenience of existing users

    -
      -
    • If a feature is to be removed, mark it as depreciated for at least 2 releases before removing

    • -
    -
  • -
  • Whenever possible, design so a new feature can be used for any model, interchangably.

  • -
  • Whenever possible, UncertainData types, state estimators, and predictors should be interchangable

  • -
  • Demonstrate common use cases as an example.

  • -
  • Every feature should be demonstrated in an example

    -
      -
    • The most commonly used features should be demonstrated in the tutorial

    • -
    -
  • -
-
-
-

Branching Strategy

-

Our project is following the git strategy described here. Details specific to each branch are described below.

-

master: Every merge into the master branch is done using a pull request (never commiting directly), is assigned a release number, and must comply with the release checklist. The release checklist is a software assurance tool.

-

dev: Every merge into the dev branch that contains a functional change is done using a pull request (not commiting directly). Every commit should be functional. All unit tests must function before commiting to dev or merging another branch.

-

Feature Branches: These branches include changes specific to a new feature. Before merging into dev unit tests should all run, tests should be added for the feature, and documentation should be updated, as appropriate.

-
-

Release Checklist

-
    -
  • Code review - all software must be checked by someone other than the author

  • -
  • Check that each new feature has a corresponding tests

  • -
  • Run unit tests python -m tests

  • -
  • Check documents- see if any updates are required

  • -
  • Rebuild sphinx documents: sphinx-build sphinx-config/ docs/

  • -
  • Write release notes

  • -
  • For releases adding new features- ensure that NASA release process has been followed

  • -
-
-
-
-

NPR 7150

-

This section describes this project’s compliance with the NASA’s NPR 7150 requirements, documented here.

-
    -
  • Software Classification: Class-E (Research Software)

  • -
  • Safety Criticality: Not Safety Critical

  • -
-
-

Compliance Notation Legend

-
    -
  • FC: Fully Compliant

  • -
  • T: Tailored (Specific tailoring described in mitigation) SWE-121

  • -
  • PC: Partially Compliant

  • -
  • NC: Not Compliant

  • -
  • NA: Not Applicable

  • -
-
-
-

Compliance Matrix

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

SWE #

Description

Compliance

Evidence

033

Assess aquisiton Options

FC

See section below

013

Maintain Software Plans

FC

This document

042

Electronic Accesss to Source

FC

This repo

139

Comply with 7150

FC

This document

121

Tailored Reqs

NA

No tailoring

125

Compliance Matrix

FC

This document

029

Software Classification

FC

This document

022

Software Assurance

FC

This document

205

Safety Cricial Software

FC

See above

023

Safety Critical Reqs

NA

Not safety critical

206

Autogen Software

NA

No autogen

148

Software Catolog

FC

Will be added

156

Perform CyberSecurity Assessment

FC

See section below

-
-
-

Aquisition Options

-

Assessed, there are some existing prognostics tools, but no general python package that can support model-based prognostics like we need.

-
-
-

Cybersecurity Assessment

-

Assessed, no significant Cybersecurity concerns were identified- research software.

-
-
-
- - -
- -
-
- -
-
- -Copyright © 2020 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - - - \ No newline at end of file diff --git a/docs/exceptions.html b/docs/exceptions.html deleted file mode 100644 index d038fcd2..00000000 --- a/docs/exceptions.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - Exceptions — Prognostics Algorithms Python Package 1.3.0 documentation - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Exceptions

-

Exceptions returned from prog_model fall into a few custom exception types, defined below. Catching any Prognostics Model Exception can be acomplished using try-except ProgModelException. Alternatively, a user can catch a specific exception type.

-
-

Prognostics Model Exceptions

-
-
-exception prog_algs.exceptions.ProgAlgException
-

Base Prognostics Model Exception

-
-
-with_traceback()
-

Exception.with_traceback(tb) – -set self.__traceback__ to tb and return self.

-
- -
- -
-
-exception prog_algs.exceptions.ProgAlgInputException
-

Prognostics Input Exception - indicates the method input parameters were incorrect

-
-
-with_traceback()
-

Exception.with_traceback(tb) – -set self.__traceback__ to tb and return self.

-
- -
- -
-
-exception prog_algs.exceptions.ProgAlgTypeError
-

Prognostics Type Error - indicates the model could not be constructed

-
-
-with_traceback()
-

Exception.with_traceback(tb) – -set self.__traceback__ to tb and return self.

-
- -
- -
-
- - -
- -
-
- -
-
- -Copyright © 2020 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - - - \ No newline at end of file diff --git a/docs/genindex.html b/docs/genindex.html deleted file mode 100644 index f3c0235e..00000000 --- a/docs/genindex.html +++ /dev/null @@ -1,485 +0,0 @@ - - - - - - - - Index — Prognostics Algorithms Python Package 1.3.1 documentation - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Index

- -
- A - | C - | D - | E - | G - | I - | K - | M - | P - | R - | S - | T - | U - | V - | X - -
-

A

- - - -
- -

C

- - - -
- -

D

- - -
- -

E

- - - -
    -
  • - examples.new_state_estimator_example - -
  • -
  • - examples.particle_filter_battery_example - -
  • -
  • - examples.playback - -
  • -
  • - examples.predict_specific_event - -
  • -
  • - examples.thrown_object_example - -
  • -
  • - examples.utpredictor - -
  • -
- -

G

- - -
- -

I

- - - -
- -

K

- - - -
- -

M

- - - -
- -

P

- - - -
- -

R

- - -
- -

S

- - - -
- -

T

- - -
- -

U

- - - -
- -

V

- - -
- -

X

- - -
- - - -
- -
-
- -
-
- -Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - - - \ No newline at end of file diff --git a/docs/getting_started.html b/docs/getting_started.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/getting_started.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/index.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/metrics.html b/docs/metrics.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/metrics.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv deleted file mode 100644 index 631bcc22..00000000 Binary files a/docs/objects.inv and /dev/null differ diff --git a/docs/prediction.html b/docs/prediction.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/prediction.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/predictors.html b/docs/predictors.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/predictors.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/py-modindex.html b/docs/py-modindex.html deleted file mode 100644 index abbcea71..00000000 --- a/docs/py-modindex.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - Python Module Index — Prognostics Algorithms Python Package 1.3.1 documentation - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Python Module Index

- -
- e -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
- e
- examples -
    - examples.basic_example -
    - examples.benchmarking_example -
    - examples.eol_event -
    - examples.horizon -
    - examples.kalman_filter -
    - examples.measurement_eqn_example -
    - examples.new_state_estimator_example -
    - examples.particle_filter_battery_example -
    - examples.playback -
    - examples.predict_specific_event -
    - examples.thrown_object_example -
    - examples.utpredictor -
- - -
- -
-
- -
-
- -Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - - - \ No newline at end of file diff --git a/docs/release.html b/docs/release.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/release.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/search.html b/docs/search.html deleted file mode 100644 index e532c6a4..00000000 --- a/docs/search.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - Search — Prognostics Algorithms Python Package 1.3.1 documentation - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -

Search

- - - - -

- Searching for multiple words only shows matches that contain - all words. -

- - -
- - - -
- - - -
- -
- - -
- -
-
- -
-
- -Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - - - \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js deleted file mode 100644 index 5ba07006..00000000 --- a/docs/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["getting_started","index","metrics","prediction","predictors","release","state_estimators","uncertain_data"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":5,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["getting_started.rst","index.rst","metrics.rst","prediction.rst","predictors.rst","release.rst","state_estimators.rst","uncertain_data.rst"],objects:{"prog_algs.metrics":[[2,1,1,"","prob_success"]],"prog_algs.predictors":[[4,2,1,"","MonteCarlo"],[3,2,1,"","Prediction"],[4,2,1,"","Predictor"],[3,2,1,"","ToEPredictionProfile"],[4,2,1,"","UnscentedTransformPredictor"],[3,2,1,"","UnweightedSamplesPrediction"]],"prog_algs.predictors.Prediction":[[3,3,1,"","mean"],[3,4,1,"","monotonicity"],[3,4,1,"","snapshot"]],"prog_algs.predictors.Predictor":[[4,4,1,"","predict"]],"prog_algs.predictors.ToEPredictionProfile":[[3,4,1,"","add_prediction"],[3,4,1,"","alpha_lambda"],[3,4,1,"","clear"],[3,4,1,"","cumulative_relative_accuracy"],[3,4,1,"","get"],[3,4,1,"","items"],[3,4,1,"","keys"],[3,4,1,"","monotonicity"],[3,4,1,"","plot"],[3,4,1,"","pop"],[3,4,1,"","popitem"],[3,4,1,"","prognostic_horizon"],[3,4,1,"","setdefault"],[3,4,1,"","update"],[3,4,1,"","values"]],"prog_algs.predictors.UnweightedSamplesPrediction":[[3,4,1,"","count"],[3,4,1,"","index"],[3,3,1,"","mean"],[3,4,1,"","monotonicity"],[3,4,1,"","snapshot"]],"prog_algs.predictors.prediction.Prediction":[[2,4,1,"","mean"],[2,4,1,"","monotonicity"]],"prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile":[[2,4,1,"","alpha_lambda"],[2,4,1,"","cumulative_relative_accuracy"],[2,4,1,"","monotonicity"],[2,4,1,"","plot"],[2,4,1,"","prognostic_horizon"]],"prog_algs.state_estimators":[[6,2,1,"","KalmanFilter"],[6,2,1,"","ParticleFilter"],[6,2,1,"","StateEstimator"],[6,2,1,"","UnscentedKalmanFilter"]],"prog_algs.state_estimators.StateEstimator":[[6,4,1,"","estimate"],[6,3,1,"","x"]],"prog_algs.uncertain_data":[[7,2,1,"","MultivariateNormalDist"],[7,2,1,"","ScalarData"],[7,2,1,"","UncertainData"],[7,2,1,"","UnweightedSamples"]],"prog_algs.uncertain_data.UncertainData":[[7,3,1,"","cov"],[7,4,1,"","describe"],[7,4,1,"","keys"],[7,3,1,"","mean"],[7,3,1,"","median"],[7,4,1,"","metrics"],[7,4,1,"","percentage_in_bounds"],[7,4,1,"","plot_hist"],[7,4,1,"","plot_scatter"],[7,4,1,"","relative_accuracy"],[7,4,1,"","sample"]],"prog_algs.uncertain_data.UnweightedSamples":[[7,4,1,"","key"]],examples:[[0,0,0,"-","basic_example"],[0,0,0,"-","benchmarking_example"],[0,0,0,"-","eol_event"],[0,0,0,"-","horizon"],[0,0,0,"-","kalman_filter"],[0,0,0,"-","measurement_eqn_example"],[0,0,0,"-","new_state_estimator_example"],[0,0,0,"-","particle_filter_battery_example"],[0,0,0,"-","playback"],[0,0,0,"-","predict_specific_event"],[0,0,0,"-","thrown_object_example"],[0,0,0,"-","utpredictor"]]},objnames:{"0":["py","module","Python module"],"1":["py","function","Python function"],"2":["py","class","Python class"],"3":["py","property","Python property"],"4":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:function","2":"py:class","3":"py:property","4":"py:method"},terms:{"0":[0,1,2,3,4,6,7],"000":2,"003":6,"1":[0,1,2,3,4,6,7],"10":[2,4,5,7],"100":[2,7],"1000":[2,7],"12":[2,3,6],"1404":[2,3],"15":[2,3],"175":[2,3],"1e":[2,7],"1s":4,"2":[0,1,2,3,6,7],"20":[2,3,4,7],"2021":[2,3],"2021_nasa_prog_alg":1,"2022":[1,2,3],"22":4,"221":6,"2233":[2,3],"23":[2,7],"239":[2,7],"3":[0,1,2,3,6,7],"306":[2,3],"332":6,"3442":[2,3],"4":6,"46":[2,3],"47":[2,3],"5":[2,4,7],"50":[2,7],"52":[2,3],"6":[2,7],"7":[2,4,7],"748":[2,3],"75":[2,7],"8":6,"9":[2,5,7],"\u03c3":[2,3],"\u03c3sign":[2,3],"abstract":[2,4,6,7],"boolean":[2,3],"case":[0,3],"class":[0,3,4,5,6,7],"default":[2,3,4,7],"do":[0,1],"float":[2,3,4,5,6,7],"function":[0,1,2,3,4,6,7],"import":[2,4,6],"int":[2,3,4,6,7],"new":[0,1,2,4,5,6,7],"return":[0,2,3,4,5,6,7],"true":[0,2,3,7],A:[0,1,2,3,4,6],AND:1,AS:1,At:6,BE:1,BUT:1,BY:1,By:4,FOR:1,For:0,IF:1,IN:1,IS:1,IT:1,ITS:1,If:[1,2,3,4,7],In:[0,3,4,6],Is:[0,3],It:0,NOT:1,No:[1,6],OF:1,ON:1,OR:1,Of:[0,4],SUCH:1,THAT:1,THE:1,TO:1,The:[0,1,2,3,4,5,6,7],Then:0,There:[0,2],These:[0,2,3,5],To:0,WILL:1,With:[2,7],_type:7,abc:6,abil:5,about:[2,4,7],accept:[2,3],access:3,accord:6,accuraci:[3,5,7],achiev:0,act:2,ad:[2,5,7],add:[0,3,5],add_predict:3,addit:[2,4,6,7],adequ:0,after:[2,3],against:1,agenc:1,agre:1,agreement:1,al:[2,3,7],algorithm:[0,2,3,4,6],all:[0,1,2,3,4,5,7],allow:[2,3],along:4,alpha:[0,3,4,6],alpha_lambda:[2,3],also:[0,1,2,3,4,6],alwai:0,an:[0,1,2,3,4,6],analysi:[0,1],ani:[0,1,2,4,6],annual:[2,3],appli:0,applic:[0,1],approach:[0,2,3],ar:[0,1,2,3,4,5,6,7],arg:[2,3,6],argument:[2,3,4,6,7],aris:1,aritif:[2,3],around:[2,3],arrai:[2,3,4,7],articl:[2,3],aspect:0,associ:[2,3],assum:0,assumpt:4,ata:[2,3],author:1,avail:[0,6],b:[2,7],back:0,backward:5,baptistia:[2,3],base:[1,2,4,5,6,7],basic:[0,2,7],basic_exampl:0,batteri:0,batterycircuit:0,batteryelectrochemeod:0,becaus:0,been:[2,3],befor:[0,2,7],begin:4,being:0,below:[0,2,3,5,6,7],benchmark:0,benchmarking_exampl:0,best:[0,1],beta:[3,4,6],between:[2,3,7],bool:[2,3,7],both:4,bound:[2,3,7],branch:0,bugfix:5,build:0,c:[1,2,7],calcul:[0,2,3,7],call:[0,6],callabl:[2,3,4,6],can:[0,1,2,3,4,5,6,7],cannot:4,cautiou:4,cd:0,center:1,chang:5,characterist:0,checkout:0,chetan:1,chosen:0,christoph:1,claim:1,clear:3,clone:0,cobl:[2,3],collect:[2,3,7],com:[0,1,2,3],combin:[2,3],come:0,command:0,compar:[1,2,3],compat:5,compon:1,comput:[0,1,2,3,4],confer:[2,3],configur:[2,3,4,6],conform:1,conjunct:0,consid:[2,7],constitut:1,construct:[0,6],constructor:4,contain:[2,3,7],contractor:1,contribut:0,control:0,corbetta:1,correspond:[0,1,2,3,5],count:[2,3],cov:[2,7],covar:7,covari:[2,4,6,7],cra:[2,3,5],creat:[0,2,4,6,7],creation:0,criteria:[2,3],criteria_eqn:[2,3],cumul:[3,5],cumulative_relative_accuraci:[2,3],current:[0,2,4,6],curv:4,custom:[0,6],d:[2,3,7],damag:1,data:[1,2,3,6],def:[4,6],defaultdict:[2,7],defin:[0,2,3,4,6],definit:0,degred:0,demand:1,demonstr:0,depict:[2,3],deriv:5,describ:[2,3,5,6,7],design:1,desrib:0,dev:0,dev_guid:1,develop:[0,1],deviat:[2,7],diagnost:1,dict:[2,3,6,7],dictionari:[0,2,3,6,7],differ:[0,1,2,3,5,7],dimens:4,directli:[0,4],discharg:0,displai:[0,2,3],dist:[2,7],distinct:0,distribut:[0,1,2,3,4],divid:2,document:[1,6],doe:[1,3,6],doesn:[4,6],domain:4,dont:[2,3],drawn:0,dt:[4,6],e:[0,2,3,4,5,6],each:[0,2,3,4,6,7],easili:1,effect:0,either:[0,1,3,4,6],element:5,els:3,empti:[3,7],en:4,enabl:1,end:[0,4,5],endors:1,engin:[0,1],environment:0,eol:[0,4,5],eol_ev:0,equat:4,equivil:[4,6],error:[1,2,3,5],estim:[0,1,2,3,4,5],et:[2,3,7],etc:0,evalu:[1,2,3],event1:[2,3,4,5],event2:[2,3,4],event3:[2,3],event:[0,3,4,5,7],event_st:[0,4,6],everi:4,exampl:[0,2,3,4,5,6,7],excel:1,exist:[2,3,6,7],expect:4,expens:1,express:1,extend:[1,3],extent:1,extract:0,f:3,fade:6,failur:0,fake:0,fall:0,fals:[2,3],featur:[0,4,5,6],few:0,fig:[2,3,7],figur:[0,1,3,7],filt:6,filter:[0,5],filterpi:6,final_st:5,first:[0,3,4,5],first_stat:4,fit:1,follow:[0,1,2,3,4,6],form:[0,7],format:5,forward:[5,6],found:3,framework:[0,1],free:1,freedom:1,frequenc:4,frequent:0,from:[1,2,3,4,5,6,7],fulli:0,further:[0,1],futur:[0,2,3,4,5],future_load:4,future_loading_eqn:4,g:[0,2,3,4,5,6,7],gaussian:4,gener:[2,3,4,6,7],genet:[2,3],get:[1,3,6,7],git:0,github:1,given:[0,1,2,3,4,6,7],go:[2,3],goebel:[2,7],govern:1,gradual:0,ground:[2,3,7],ground_truth:[2,3,7],group:1,gt:[2,3],ha:[0,2,3],hand:1,hardwar:1,harmless:1,have:[0,4],health:[0,2,3],healthi:0,here:[4,6],hidden:0,histogram:[0,4,5,7],hold:1,horizon:[0,3,4,5],host:0,how:[0,6],http:[0,1,2,3,4],hypothesi:4,i:[0,2,3,4,6],identifi:[2,3],ignor:0,illustr:0,immedi:1,immut:3,impact:[0,4],implement:[1,2,4,6],impli:1,improv:5,includ:[0,1,2,3,5,7],increas:3,indemn:1,indemnifi:1,index:[1,2,3,6],indic:[0,2,3],individu:0,inform:[2,4,6,7],infring:1,inidividu:7,inidividualev:5,initi:[4,5,6],initial_st:6,input:[0,3,4,6],inputcontain:7,instal:1,instanc:[0,2,3],instanti:0,instead:0,integ:3,intellig:[2,3],interest:0,interfac:1,intern:0,interpret:[2,3],investig:0,isn:0,item:3,iter:3,its:[2,3,4,6],itself:6,j:[2,3],journal:[2,3],k:3,kalman:0,kalman_filt:[0,4,5],kalmanfilt:[5,6],kappa:[4,6],kei:[2,3,5,6,7],key1:[2,7],key2:[2,7],keyerror:3,keyword:[2,3,4,6,7],kf:[0,6],kind:[1,7],kulkarni:1,kwarg:[2,3,4,6,7],label:7,lack:3,lambda:[0,3],lambda_valu:[2,3],latest:0,law:1,learn:0,liabil:1,librari:[4,6],life:[0,1,4,5],like:[0,1,3,4,7],limit:1,line:[0,2,3],linear:[0,4],linearmodel:[0,5,6],link:0,list:[0,2,3,4,5,6,7],load:[0,4,6],local:[2,3],logic:[4,6],look:1,loss:1,lower:[2,7],m:[1,2,3,4,5,6,7],made:[2,3],mai:[0,1],make:[2,7],manag:[2,3],mani:0,manipul:0,manner:1,map:[2,3],mark:0,match:6,matlab:[4,6],matplotlib:[2,3,7],matrix:[2,4,6,7],matteo:1,matter:1,mean:[2,3,4,5,6,7],mean_valu:[2,3,7],meant:2,measur:[0,1,2,3,6],measurement_eqn:6,measurement_eqn_exampl:0,median:[2,7],median_valu:[2,7],meet:[2,3],member:5,memori:6,merchant:1,met:[0,2,3,4],method:[0,2,3,4,5,6],metric:[0,1,3,4,5,6,7],might:0,misalign:0,misc:1,mission:0,model:[0,1,2,4,5,6,7],modul:1,monoton:[3,5],monotonicitii:[2,3],monotonicti:[2,3],monotono:[2,3],monte_carlo:6,montecarlo:[3,4,5],month:1,more:[4,6],most:[0,2],multipl:[0,2,3,5],multivariatenormaldist:[0,2,4,7],must:[2,3,4,6,7],n:[2,3,5,7],n_sampl:[2,4,7],name:[2,3],namedtupl:4,nasa:[0,1,4,6],nears:0,necessari:[0,5],need:1,new_data:6,new_state_estimator_exampl:0,next:[2,3],nois:[0,4,6],non:[4,5],none:[0,2,3,6,7],nonlinear:4,normal:[2,3],note:1,notibl:0,now:[2,5],np:4,nsampl:7,nstate:4,nth:7,num_particl:6,num_sampl:[2,7],number:[0,2,3,4,6,7],numpi:7,object:[0,2,3,7],observ:[0,4,6],obtain:4,occur:[0,2,3,4,5],occurr:3,often:0,one:[0,2,3,6],onli:[0,2,7],oper:[0,2,3,5,7],optim:[2,3],option:[2,3,4,6,7],order:[2,3,7],org:[2,3,4],origin:1,other:[0,1],otherwis:3,output:[0,2,3,4,6],outputcontain:7,outsid:0,overritten:[2,7],p:[2,7],p_success:2,packag:[0,3,4,6,7],page:[0,1],pair:[2,3],paper:[2,3],paramet:[0,2,3,4,6,7],parti:1,particl:[0,5],particle_filter_battery_exampl:0,particlefilt:6,particular:1,pass:[2,3,5,7],pcoe:1,percentag:[2,3,7],percentage_in_bound:[2,5,7],perfom:3,perform:[0,1,2,3,4,5,6],permit:1,pf:6,ph:[2,3],phm:[2,3],phmconf:[2,3],phmsocieti:[2,3],php:[2,3],pii:[2,3],place:4,playback:[0,5],plot:[3,4,5,7],plot_hist:[2,4,7],plot_scatt:[2,7],point:[0,2,3,4,5],pop:3,popitem:3,portion:[2,3],posterior:[0,6],pred:4,pred_result:4,predefin:4,predict:[0,1,4,5,6,7],predict_specific_ev:0,predictionresult:4,predictor:[0,1,2,3,5,6],predictor_templ:4,present:[1,3],previou:5,previous:[2,7],primari:2,print:[2,3,6,7],prior:[0,1,6],prob_success:2,probabl:2,procedur:4,process:[0,4,6],produc:[0,2,3,6,7],product:1,profil:[1,5],prog_alg:[0,1,2,3,4,5,6,7],prog_model:[0,4,5,6],progmodel:[1,6],prognost:[0,3,4,5,6,7],prognostic_horizon:[2,3],prognostics_horizon:[2,3],prognosticsmodel:4,progress:0,progserv:1,propag:[1,4],properti:[3,6,7],propog:5,provid:[0,1,2,3,4],purpos:1,py:[4,6],pypi:0,python3:5,python:[0,5],q:[4,6],r:[2,6,7],ra:[2,3,5,7],rais:3,rapid:1,reach:[0,4],real:4,recipi:1,recommend:3,refer:1,regard:1,regardless:0,rel:[3,5,7],relat:[2,3],relative_accuraci:[2,7],releas:1,remain:[0,1],remedi:1,remov:[0,3],repeat:4,replac:6,repo:0,repres:[0,2,3,4,5,7],resampl:6,resample_fcn:6,research:[0,1,2],residual_resampl:6,respect:[0,2,3,7],respos:0,result:[0,1,2,3,4,5],rmse:5,root:5,row:[2,7],run:[0,6],s0004370222000078:[2,3],s:[0,1,2,4,5,6],same:[0,7],sampl:[0,2,3,4,5],satisfactori:4,satisfi:4,save:4,save_freq:4,save_pt:4,savepoint:[3,4],saxena:[2,3],scalar:4,scalardata:[0,2,4,6,7],scale:[4,6],scatter:[5,7],scienc:[2,3,7],sciencedirect:[2,3],search:1,second:6,see:[0,1,4,5,6],select:1,sensit:0,sensor:[0,6],set:[0,1,3,7],setdefault:3,sever:2,shall:1,shap:[2,3],should:[1,2,3,4],show:[0,2,3],sigma:[4,5],sign:[2,3],significantli:5,simresult:[0,3],simul:[0,4],simulate_to:0,singl:[0,2,3,4,7],size:[0,4],snapshot:[0,3,4],so:0,soc:0,societi:[2,3],softwar:1,sole:1,solut:1,some:[0,2,3,4],someon:0,someth:0,space:4,specif:[0,1,2,3,6],specifi:[0,2,3,4,7],squar:5,stabl:0,standard:[2,7],start:[1,3,6],state1:[2,3],state2:[2,3],state:[0,1,2,3,4,5,7],state_estim:[0,5,6],state_estimator_templ:6,statecontain:[6,7],stateestim:[0,6],statist:7,statutori:1,step:[0,4,6],stop:3,store:[0,3,6],str:[2,3,7],string:[2,3,4,7],strong:4,structur:[2,3,5],studi:1,subclass:[0,4,5,6],subcontractor:1,subdictionari:[2,3],subject:1,subset:4,success:2,suffici:0,suitabl:0,sum:[2,3],summar:0,summari:1,summat:[2,3],support:[2,3,4,5,6],swap:1,system:[0,1,4,6],t0:[4,6],t:[0,4,6],tabl:[2,7],temperatur:0,termin:1,test:0,teubert:1,text:[2,7],th:4,thei:[2,3],therefor:4,thi:[0,2,3,4,5,6,7],third:1,those:[2,3,7],three:0,threshold:[0,4],through:[0,2,3],throughout:0,thrown:0,thrown_object_exampl:0,thrownobject:[0,4,6],ti:[2,3],time:[0,3,4,5,6],time_index:3,time_of_ev:[2,4],time_of_predict:3,timepoint:5,timestamp:6,timestep:3,titl:[1,2,7],toe:[0,1,4,5],toe_predict:3,toe_profil:[2,3],toepredict:2,toepredictionprofil:[0,2,3,5],tool:[0,1,2],total:[2,3],toward:0,transform:5,transit:0,treat:[0,3,7],trivial:0,truth:[2,3,7],tte:[2,3],tupl:[2,3,7],turn:6,tutori:[0,1,4,6],two:3,type:[1,2,3,4],typic:[0,6],u:6,ukf:[4,6],uncertain:[0,1],uncertain_data:[2,3,4,5,7],uncertaindata:[0,1,3,4,5,6],uncertaintdata:4,uncertainti:[0,1,3,4,5],uncertiantydata:[2,7],unconcern:0,under:4,understand:2,unilater:1,unit:1,unless:0,unlik:0,unscent:5,unscented_kalman_filt:4,unscentedkalmanfilt:6,unscentedtransformpredictor:[0,4,5],unstabl:0,until:4,unweight:4,unweighted_sampl:3,unweightedsampl:[0,2,3,4,5,7],up:0,updat:[1,3,6],upper:[2,7],url:1,us:[1,2,3,4,6,7],user:[0,4],utpredictor:[0,5],v1:1,v:[3,4,6],valu:[0,2,3,4,5,6,7],valueerror:3,variabl:0,varieti:0,variou:[0,2,5],vector:5,verifi:4,version:1,view:[2,3,7],visual:1,voltag:0,volum:[2,3],wa:[1,2,3],wai:[0,4],waiv:1,waiver:1,want:0,warranti:1,we:[0,4,6],weight:6,well:[1,4,6],were:5,when:[0,2,3,4,7],where:[0,2,3,5,7],whether:[2,3,7],which:[0,2,3,4,6],who:0,wiki:4,wikipedia:4,within:[0,2,3,7],without:[0,1,7],work:5,would:[0,4],www:[2,3],x0:[5,6],x:[4,6],year:1,you:[0,1],z:[0,4,6]},titles:["Getting Started","Prognostics Algorithms Python Package","Metrics and Figures","Prediction","Predictors","Release Notes","State Estimators","Uncertain Data"],titleterms:{"2":5,"3":5,accuraci:2,algorithm:1,alpha:2,base:3,beta:2,carlo:4,cite:1,content:[2,5],cumul:2,data:[0,7],disclaim:1,distribut:7,e:7,estim:6,event:2,exist:5,extend:0,figur:2,filter:6,from:0,gener:5,get:0,github:0,histogram:2,horizon:2,i:7,implement:7,includ:[4,6],indic:1,instal:0,interfac:[4,6,7],kalman:6,lambda:2,metric:2,monoton:2,mont:4,multivari:7,normal:7,note:5,packag:1,particl:6,pip:0,plot:2,pre:0,predict:[2,3],predictor:4,profil:[2,3],prognost:[1,2],python:1,recommend:0,rel:2,releas:[0,5],repositori:1,sampl:7,scalar:7,scatter:2,simpl:2,start:0,state:6,statist:2,structur:0,summari:0,tabl:1,thi:1,time:2,toe:[2,3],transform:4,type:7,uncertain:7,uncertaindata:[2,7],uncertainti:7,unscent:[4,6],unweight:7,unweightedsamplespredict:3,updat:5,us:0,user:5,v1:5,version:0}}) \ No newline at end of file diff --git a/docs/softwareplan.md b/docs/softwareplan.md deleted file mode 100644 index 27f2987e..00000000 --- a/docs/softwareplan.md +++ /dev/null @@ -1,12 +0,0 @@ -# Prognostics Algorithm Library Software Plan - -## Software Goals -- Mirror Prognostics algorithm Matlab Library -- Ability to perform health state estimation and prognostics (prediction of failure) - -## Development Plan -- Oct-Nov: First Implementation -- Dec-Jan: Testing and verification - -## Release & Maintenance Plan -Release open source on Github.com, after which use github ticketing system for bug-tracking and maintenance. \ No newline at end of file diff --git a/docs/state_estimators.html b/docs/state_estimators.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/state_estimators.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/uncertain_data.html b/docs/uncertain_data.html deleted file mode 100644 index 678c677b..00000000 --- a/docs/uncertain_data.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/visualize.html b/docs/visualize.html deleted file mode 100644 index 3494401d..00000000 --- a/docs/visualize.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - Visualization — Prognostics Algorithms Python Package 1.1.0 documentation - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- -
-

Visualization

-

Visualization tools are included in prog_algs.visualize, described below:

-
-
-prog_algs.visualize.plot_scatter(samples, fig=None, keys=None, legend='auto', **kwargs)
-

Produce a scatter plot for a given list of states

-
-
Parameters
-
    -
  • samples ([dict]) – Non-empty list of states where each element is a dictionary containing a single sample

  • -
  • fig (Figure, optional) – Existing figure previously used to plot states. If passed a figure argument additional data will be added to the plot. Defaults to creating new figure

  • -
  • keys (list of strings, optional) – Keys to plot. Defaults to all keys.

  • -
  • legend (optional) – When the legend should be shown, options: -False: Dont show legend -“auto”: Show legend automatically if more than one data set -True: Always show legend

  • -
  • **kwargs (optional) – Additional keyword arguments passed to scatter function. Includes those supported by scatter

  • -
-
-
Returns
-

Figure

-
-
-

Example

-

states = UnweightedSamples([1, 2, 3, 4, 5]) -plot_scatter(states.sample(100)) # With 100 samples -plot_scatter(states.sample(100), keys=[‘state1’, ‘state2’]) # only plot those keys

-
- -
- - -
- -
-
- -
-
- -Copyright © 2020 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - - - \ No newline at end of file diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index 010b98c3..00000000 --- a/examples/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -__all__ = ['basic_example', 'basic_example_battery', 'benchmarking_example', 'eol_event', 'horizon', 'kalman_filter', 'new_state_estimator_example', 'measurement_eqn_example', 'playback', 'predict_specific_event'] diff --git a/examples/basic_example.py b/examples/basic_example.py deleted file mode 100644 index 09a19b9c..00000000 --- a/examples/basic_example.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation and prediction with uncertainty given a Prognostics Model. - -Method: An instance of the ThrownObject model in prog_models is created, and the prediction process is achieved in three steps: - 1) State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate - 2) Prediction of future states (with uncertainty) and the times at which the event threshold will be reached - 3) Metrics tools are used to further investigate the results of prediction -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics - iv) Figures illustrating results -""" - -from prog_models.models import ThrownObject -from prog_algs import * - -def run_example(): - # Step 1: Setup model & future loading - m = ThrownObject(process_noise = 1) - def future_loading(t, x = None): - # No load for a thrown object - return m.InputContainer({}) - initial_state = m.initialize() - - # Step 2: Demonstrating state estimator - # The state estimator is used to estimate the system state given sensor data. - print("\nPerforming State Estimation Step") - - # Step 2a: Setup - filt = state_estimators.ParticleFilter(m, initial_state) - # VVV Uncomment this to use UKF State Estimator VVV - # filt = state_estimators.UnscentedKalmanFilter(m, initial_state) - - # Step 2b: Print & Plot Prior State - print("Prior State:", filt.x.mean) - print('\nevent state: ', m.event_state(filt.x.mean)) - fig = filt.x.plot_scatter(label='prior') - - # Step 2c: Perform state estimation step, given some measurement, above what's expected - example_measurements = m.OutputContainer({'x': 7.5}) - t = 0.1 - u = future_loading(t) - filt.estimate(t, u, example_measurements) # Update state, given (example) sensor data - - # Step 2d: Print & Plot Resulting Posterior State - # Note the posterior state is greater than the predicted state of 5.95 - # This is because of the high measurement - print("\nPosterior State:", filt.x.mean) - # Event state for 'falling' is less, because velocity has decreased - print('\nEvent State: ', m.event_state(filt.x.mean)) - filt.x.plot_scatter(fig=fig, label='posterior') # Add posterior state to figure from prior state - - # Note: in a prognostic application the above state estimation step would be repeated each time - # there is new data. Here we're doing one step to demonstrate how the state estimator is used - - # Step 3: Demonstrating Prediction - print("\n\nPerforming Prediction Step") - - # Step 3a: Setup Predictor - mc = predictors.MonteCarlo(m) - - # Step 3b: Perform a prediction - NUM_SAMPLES = 50 - STEP_SIZE = 0.01 - mc_results = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE, save_freq=STEP_SIZE) - print('Predicted time of event (ToE): ', mc_results.time_of_event.mean) - # Here there are 2 events predicted, when the object starts falling, and when it impacts the ground. - - # Step 3c: Analyze the results - - # Note: The results of a sample-based prediction can be accessed by sample, e.g., - states_sample_1 = mc_results.states[1] - # now states_sample_1[n] corresponds to times[n] for the first sample - - # You can also access a state distribution at a specific time using the .snapshot function - states_time_1 = mc_results.states.snapshot(1) - # now you have all the samples corresponding to times[1] - - # You can also access the final state (of type UncertainData), like so: - # Note: to get a more accurate final state, you can decrease the step size. - final_state = mc_results.time_of_event.final_state - print('State when object starts falling: ', final_state['falling'].mean) - - # You can also use the metrics package to generate some useful metrics on the result of a prediction - print("\nEOD Prediction Metrics") - - from prog_algs.metrics import prob_success - print('\tPortion between 3.65 and 3.8: ', mc_results.time_of_event.percentage_in_bounds([3.65, 3.8], keys='falling')) - print('\tAssuming ground truth 3.7: ', mc_results.time_of_event.metrics(ground_truth=3.7, keys='falling')) - print('\tP(Success) if mission ends at 7.6: ', prob_success(mc_results.time_of_event, 7.6, keys='impact')) - - # Plot state transition - # Here we will plot the states at t0, 25% to ToE, 50% to ToE, 75% to ToE, and ToE - # You should see the states move together (i.e., velocity is lowest and highest when closest to the ground (before impact, and at beginning, respectively)) - fig = mc_results.states.snapshot(0).plot_scatter(label = "t={} s".format(int(mc_results.times[0]))) # 0 - quarter_index = int(len(mc_results.times)/4) - mc_results.states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index]))) # 25% - mc_results.states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*2]))) # 50% - mc_results.states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*3]))) # 75% - mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[-1]))) # 100% - - # Plot time of event for each event - # If you dont see many bins here, this is because there is not much variety in the estimate. - # You can increase the number of bins, decrease step size, or increase the number of samples to see more of a distribution - mc_results.time_of_event.plot_hist(keys='impact') - mc_results.time_of_event.plot_hist(keys='falling') - - # Step 4: Show all plots - import matplotlib.pyplot as plt # For plotting - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/basic_example_battery.py b/examples/basic_example_battery.py deleted file mode 100644 index 4599d553..00000000 --- a/examples/basic_example_battery.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example extends the "basic example" to perform a state estimation and prediction with uncertainty given a more complicated model. Models, state estimators, and predictors can be switched out. See documentation nasa.github.io/progpy for description of options - -Method: An instance of the BatteryCircuit model in prog_models is created, and the prediction process is achieved in three steps: - 1) State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate - 2) Prediction of future states (with uncertainty) and the times at which the event threshold will be reached - 3) Metrics tools are used to further investigate the results of prediction - -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics - iv) Figures illustrating results -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChemEOD as Battery - -from prog_algs.state_estimators import ParticleFilter as StateEstimator -# VVV Uncomment this to use UKF State Estimator VVV -# from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator - -from prog_algs.predictors import MonteCarlo as Predictor -# VVV Uncomment this to use UnscentedTransform Predictor VVV -# from prog_algs.predictors import UnscentedTransformPredictor as Predictor - -def run_example(): - # Step 1: Setup model & future loading - # Measurement noise - R_vars = { - 't': 2, - 'v': 0.02 - } - batt = Battery(process_noise = 0.25, measurement_noise = R_vars) - # Creating the input containers outside of the function accelerates prediction - loads = [ - batt.InputContainer({'i': 2}), - batt.InputContainer({'i': 1}), - batt.InputContainer({'i': 4}), - batt.InputContainer({'i': 2}), - batt.InputContainer({'i': 3}) - ] - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - return loads[0] - elif (t < 900): - return loads[1] - elif (t < 1800): - return loads[2] - elif (t < 3000): - return loads[3] - return loads[-1] - - initial_state = batt.initialize() - - # Step 2: Demonstrating state estimator - print("\nPerforming State Estimation Step") - - # Step 2a: Setup - filt = StateEstimator(batt, initial_state) - - # Step 2b: Print & Plot Prior State - print("Prior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - fig = filt.x.plot_scatter(label='prior') - - # Step 2c: Perform state estimation step - example_measurements = batt.OutputContainer({'t': 32.2, 'v': 3.915}) - t = 0.1 - u = future_loading(t) - filt.estimate(t, u, example_measurements) - - # Step 2d: Print & Plot Resulting Posterior State - print("\nPosterior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - filt.x.plot_scatter(fig=fig, label='posterior') # Add posterior state to figure from prior state - - # Note: in a prognostic application the above state estimation step would be repeated each time - # there is new data. Here we're doing one step to demonstrate how the state estimator is used - - # Step 3: Demonstrating Predictor - print("\n\nPerforming Prediction Step") - - # Step 3a: Setup Predictor - mc = Predictor(batt) - - # Step 3b: Perform a prediction - NUM_SAMPLES = 25 - STEP_SIZE = 0.1 - SAVE_FREQ = 100 # How often to save results - mc_results = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE, save_freq = SAVE_FREQ) - print('ToE', mc_results.time_of_event.mean) - - # Step 3c: Analyze the results - - # Note: The results of a sample-based prediction can be accessed by sample, e.g., - from prog_algs.predictors import UnweightedSamplesPrediction - if isinstance(mc_results, UnweightedSamplesPrediction): - states_sample_1 = mc_results.states[1] - # now states_sample_1[n] corresponds to times[n] for the first sample - - # You can also access a state distribution at a specific time using the .snapshot function - states_time_1 = mc_results.states.snapshot(1) - # now you have all the samples corresponding to times[1] - - # Print Results - print('Results: ') - for i, time in enumerate(mc_results.times): - print('\nt = {}'.format(time)) - print('\tu = {}'.format(mc_results.inputs.snapshot(i).mean)) - print('\tx = {}'.format(mc_results.states.snapshot(i).mean)) - print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean)) - print('\tevent state = {}'.format(mc_results.event_states.snapshot(i).mean)) - - # You can also access the final state (of type UncertainData), like so: - final_state = mc_results.time_of_event.final_state - print('Final state @EOD: ', final_state['EOD'].mean) - - # You can also use the metrics package to generate some useful metrics on the result of a prediction - print("\nEOD Prediction Metrics") - - from prog_algs.metrics import prob_success - print('\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6])) - print('\tAssuming ground truth 3002.25: ', mc_results.time_of_event.metrics(ground_truth=3005.25)) - print('\tP(Success) if mission ends at 3002.25: ', prob_success(mc_results.time_of_event, 3005.25)) - - # Plot state transition - # Here we will plot the states at t0, 25% to ToE, 50% to ToE, 75% to ToE, and ToE - fig = mc_results.states.snapshot(0).plot_scatter(label = "t={} s".format(int(mc_results.times[0]))) # 0 - quarter_index = int(len(mc_results.times)/4) - mc_results.states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index]))) # 25% - mc_results.states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*2]))) # 50% - mc_results.states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*3]))) # 75% - mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[-1]))) # 100% - - mc_results.time_of_event.plot_hist() - - # Step 4: Show all plots - import matplotlib.pyplot as plt # For plotting - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/benchmarking_example.py b/examples/benchmarking_example.py deleted file mode 100644 index 9831f931..00000000 --- a/examples/benchmarking_example.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs benchmarking for a state estimation and prediction with uncertainty given a Prognostics Model. The process and benchmarking analysis are run for various sample sizes. - -Method: An instance of the BatteryCircuit model in prog_models is created, state estimation is set up with a chosen state_estimator, and prediction is set up with a chosen predictor. - Prediction of future states (with uncertainty) is then performed for various sample sizes. - Metrics are calculated and displayed for each run. - -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction for each distinct sample size - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics, including alpha-lambda metric -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs import state_estimators, predictors -from prog_algs.metrics import samples as metrics -import time # For timing prediction - -def run_example(): - # Step 1: Setup Model and Future Loading - batt = Battery() - - # Creating the input containers outside of the function accelerates prediction - loads = [ - batt.InputContainer({'i': 2}), - batt.InputContainer({'i': 1}), - batt.InputContainer({'i': 4}), - batt.InputContainer({'i': 2}), - batt.InputContainer({'i': 3}) - ] - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - return loads[0] - elif (t < 900): - return loads[1] - elif (t < 1800): - return loads[2] - elif (t < 3000): - return loads[3] - return loads[-1] - - # Step 2: Setup Predictor - pred = predictors.MonteCarlo(batt, dt= 0.05) - - # Step 3: Estimate State - x0 = batt.initialize() - state_estimator = state_estimators.ParticleFilter(batt, x0) - # Send in some data to estimate state - z1 = batt.OutputContainer({'t': 32.2, 'v': 3.915}) - z2 = batt.OutputContainer({'t': 32.3, 'v': 3.91}) - state_estimator.estimate(0.1, future_loading(0.1), z1) - state_estimator.estimate(0.2, future_loading(0.2), z2) - - # Step 4: Benchmark Predictions - # Here we're comparing the results given different numbers of samples - print('Benchmarking...') - - # Perform benchmarking for each number of samples - sample_counts = [1, 2, 5, 10] - for sample_count in sample_counts: - print('\nRun 1 ({} samples)'.format(sample_count)) - start = time.perf_counter() - pred_results = pred.predict(state_estimator.x, future_loading, n_samples = sample_count) - toe = pred_results.time_of_event.key("EOD") # Looking at EOD event - end = time.perf_counter() - print('\tMSE: {:4.2f}s'.format(metrics.mean_square_error(toe, 3005.4))) - print('\tRMSE: {:4.2f}s'.format(metrics.root_mean_square_error(toe, 3005.4))) - print('\tRuntime: {:4.2f}s'.format(end - start)) - - # This same approach can be applied for benchmarking and comparing other changes - # For example: different sampling methods, prediction algorithms, step sizes, models - -# This allows the module to be executed directly -if __name__=='__main__': - run_example() diff --git a/examples/data_const_load.csv b/examples/data_const_load.csv deleted file mode 100644 index 6cf6fd5c..00000000 --- a/examples/data_const_load.csv +++ /dev/null @@ -1,2740 +0,0 @@ -Timestamp, power, temperature, voltage -0.00, 0.00, 20.00, 4.10 -1.00, 8.00, 18.74, 4.05 -2.00, 8.00, 18.68, 4.03 -3.00, 8.00, 19.40, 4.00 -4.00, 8.00, 19.24, 3.97 -5.00, 8.00, 19.75, 3.95 -6.00, 8.00, 19.73, 3.94 -7.00, 8.00, 17.46, 3.92 -8.00, 8.00, 21.91, 3.90 -9.00, 8.00, 20.03, 3.89 -10.00, 8.00, 20.61, 3.88 -11.00, 8.00, 20.87, 3.88 -12.00, 8.00, 18.64, 3.87 -13.00, 8.00, 19.05, 3.86 -14.00, 8.00, 19.60, 3.85 -15.00, 8.00, 21.98, 3.85 -16.00, 8.00, 18.91, 3.84 -17.00, 8.00, 18.52, 3.83 -18.00, 8.00, 19.31, 3.86 -19.00, 8.00, 19.60, 3.84 -20.00, 8.00, 21.01, 3.82 -21.00, 8.00, 19.64, 3.81 -22.00, 8.00, 18.94, 3.83 -23.00, 8.00, 20.69, 3.82 -24.00, 8.00, 19.23, 3.82 -25.00, 8.00, 20.16, 3.82 -26.00, 8.00, 20.81, 3.81 -27.00, 8.00, 19.80, 3.82 -28.00, 8.00, 22.40, 3.83 -29.00, 8.00, 20.00, 3.81 -30.00, 8.00, 18.87, 3.82 -31.00, 8.00, 19.11, 3.81 -32.00, 8.00, 17.10, 3.82 -33.00, 8.00, 19.91, 3.81 -34.00, 8.00, 19.74, 3.83 -35.00, 8.00, 19.87, 3.82 -36.00, 8.00, 20.16, 3.81 -37.00, 8.00, 20.32, 3.80 -38.00, 8.00, 21.14, 3.79 -39.00, 8.00, 20.30, 3.81 -40.00, 8.00, 20.36, 3.81 -41.00, 8.00, 19.64, 3.79 -42.00, 8.00, 19.64, 3.81 -43.00, 8.00, 18.61, 3.80 -44.00, 8.00, 20.08, 3.81 -45.00, 8.00, 19.90, 3.80 -46.00, 8.00, 19.54, 3.82 -47.00, 8.00, 18.75, 3.81 -48.00, 8.00, 19.48, 3.81 -49.00, 8.00, 20.13, 3.80 -50.00, 8.00, 19.28, 3.81 -51.00, 8.00, 19.12, 3.79 -52.00, 8.00, 20.34, 3.79 -53.00, 8.00, 20.79, 3.79 -54.00, 8.00, 19.73, 3.79 -55.00, 8.00, 20.03, 3.79 -56.00, 8.00, 19.77, 3.82 -57.00, 8.00, 19.42, 3.79 -58.00, 8.00, 23.23, 3.81 -59.00, 8.00, 18.83, 3.77 -60.00, 8.00, 19.62, 3.81 -61.00, 8.00, 20.03, 3.80 -62.00, 8.00, 20.68, 3.79 -63.00, 8.00, 20.78, 3.80 -64.00, 8.00, 20.72, 3.80 -65.00, 8.00, 19.07, 3.80 -66.00, 8.00, 19.50, 3.78 -67.00, 8.00, 21.93, 3.80 -68.00, 8.00, 19.94, 3.79 -69.00, 8.00, 19.42, 3.80 -70.00, 8.00, 19.28, 3.79 -71.00, 8.00, 20.86, 3.79 -72.00, 8.00, 18.43, 3.79 -73.00, 8.00, 19.46, 3.80 -74.00, 8.00, 20.45, 3.78 -75.00, 8.00, 21.44, 3.80 -76.00, 8.00, 18.65, 3.80 -77.00, 8.00, 19.57, 3.80 -78.00, 8.00, 19.33, 3.78 -79.00, 8.00, 21.21, 3.78 -80.00, 8.00, 19.84, 3.79 -81.00, 8.00, 20.02, 3.76 -82.00, 8.00, 18.24, 3.80 -83.00, 8.00, 20.20, 3.79 -84.00, 8.00, 18.44, 3.80 -85.00, 8.00, 18.01, 3.78 -86.00, 8.00, 20.06, 3.79 -87.00, 8.00, 19.85, 3.79 -88.00, 8.00, 21.04, 3.77 -89.00, 8.00, 19.04, 3.80 -90.00, 8.00, 20.18, 3.78 -91.00, 8.00, 20.07, 3.77 -92.00, 8.00, 20.81, 3.79 -93.00, 8.00, 19.31, 3.79 -94.00, 8.00, 21.60, 3.78 -95.00, 8.00, 20.36, 3.79 -96.00, 8.00, 20.91, 3.80 -97.00, 8.00, 21.12, 3.80 -98.00, 8.00, 20.74, 3.79 -99.00, 8.00, 19.58, 3.79 -100.00, 8.00, 18.05, 3.79 -101.00, 8.00, 18.13, 3.78 -102.00, 8.00, 19.93, 3.77 -103.00, 8.00, 18.15, 3.79 -104.00, 8.00, 21.26, 3.77 -105.00, 8.00, 20.80, 3.81 -106.00, 8.00, 21.36, 3.77 -107.00, 8.00, 20.90, 3.79 -108.00, 8.00, 20.40, 3.78 -109.00, 8.00, 18.02, 3.77 -110.00, 8.00, 19.98, 3.76 -111.00, 8.00, 20.80, 3.79 -112.00, 8.00, 19.99, 3.78 -113.00, 8.00, 20.46, 3.77 -114.00, 8.00, 20.83, 3.79 -115.00, 8.00, 19.08, 3.79 -116.00, 8.00, 19.71, 3.77 -117.00, 8.00, 19.62, 3.77 -118.00, 8.00, 19.89, 3.76 -119.00, 8.00, 19.45, 3.78 -120.00, 8.00, 21.58, 3.77 -121.00, 8.00, 19.03, 3.77 -122.00, 8.00, 18.25, 3.77 -123.00, 8.00, 19.25, 3.78 -124.00, 8.00, 20.67, 3.77 -125.00, 8.00, 21.59, 3.75 -126.00, 8.00, 18.29, 3.78 -127.00, 8.00, 20.90, 3.78 -128.00, 8.00, 21.51, 3.78 -129.00, 8.00, 19.11, 3.76 -130.00, 8.00, 19.97, 3.78 -131.00, 8.00, 19.84, 3.75 -132.00, 8.00, 20.14, 3.79 -133.00, 8.00, 17.76, 3.76 -134.00, 8.00, 21.24, 3.75 -135.00, 8.00, 20.58, 3.77 -136.00, 8.00, 20.27, 3.79 -137.00, 8.00, 20.78, 3.77 -138.00, 8.00, 20.42, 3.77 -139.00, 8.00, 19.93, 3.77 -140.00, 8.00, 19.48, 3.77 -141.00, 8.00, 21.01, 3.78 -142.00, 8.00, 19.52, 3.76 -143.00, 8.00, 19.51, 3.76 -144.00, 8.00, 19.14, 3.78 -145.00, 8.00, 20.08, 3.75 -146.00, 8.00, 18.16, 3.78 -147.00, 8.00, 20.23, 3.77 -148.00, 8.00, 21.65, 3.76 -149.00, 8.00, 19.74, 3.76 -150.00, 8.00, 18.69, 3.76 -151.00, 8.00, 20.43, 3.78 -152.00, 8.00, 19.47, 3.75 -153.00, 8.00, 20.70, 3.74 -154.00, 8.00, 19.31, 3.75 -155.00, 8.00, 19.23, 3.76 -156.00, 8.00, 21.70, 3.77 -157.00, 8.00, 19.55, 3.76 -158.00, 8.00, 18.52, 3.76 -159.00, 8.00, 20.13, 3.76 -160.00, 8.00, 19.65, 3.77 -161.00, 8.00, 18.59, 3.77 -162.00, 8.00, 21.83, 3.76 -163.00, 8.00, 20.25, 3.76 -164.00, 8.00, 19.81, 3.75 -165.00, 8.00, 19.56, 3.77 -166.00, 8.00, 20.75, 3.75 -167.00, 8.00, 20.13, 3.77 -168.00, 8.00, 21.35, 3.77 -169.00, 8.00, 17.96, 3.75 -170.00, 8.00, 19.29, 3.76 -171.00, 8.00, 19.55, 3.77 -172.00, 8.00, 20.08, 3.78 -173.00, 8.00, 20.42, 3.76 -174.00, 8.00, 20.81, 3.75 -175.00, 8.00, 19.48, 3.76 -176.00, 8.00, 18.67, 3.75 -177.00, 8.00, 19.03, 3.75 -178.00, 8.00, 19.47, 3.74 -179.00, 8.00, 19.48, 3.76 -180.00, 8.00, 21.55, 3.77 -181.00, 8.00, 19.74, 3.75 -182.00, 8.00, 21.21, 3.76 -183.00, 8.00, 18.85, 3.75 -184.00, 8.00, 19.67, 3.74 -185.00, 8.00, 19.74, 3.77 -186.00, 8.00, 20.81, 3.77 -187.00, 8.00, 20.09, 3.75 -188.00, 8.00, 21.11, 3.75 -189.00, 8.00, 20.32, 3.75 -190.00, 8.00, 18.07, 3.75 -191.00, 8.00, 20.18, 3.76 -192.00, 8.00, 20.14, 3.74 -193.00, 8.00, 20.79, 3.75 -194.00, 8.00, 19.34, 3.75 -195.00, 8.00, 18.76, 3.75 -196.00, 8.00, 20.64, 3.75 -197.00, 8.00, 20.69, 3.75 -198.00, 8.00, 20.08, 3.75 -199.00, 8.00, 21.65, 3.74 -200.00, 8.00, 20.49, 3.76 -201.00, 8.00, 20.81, 3.76 -202.00, 8.00, 19.94, 3.74 -203.00, 8.00, 19.96, 3.76 -204.00, 8.00, 20.70, 3.74 -205.00, 8.00, 17.87, 3.75 -206.00, 8.00, 21.89, 3.76 -207.00, 8.00, 21.78, 3.76 -208.00, 8.00, 18.41, 3.74 -209.00, 8.00, 20.30, 3.74 -210.00, 8.00, 20.06, 3.73 -211.00, 8.00, 20.43, 3.76 -212.00, 8.00, 18.87, 3.75 -213.00, 8.00, 20.58, 3.75 -214.00, 8.00, 19.77, 3.74 -215.00, 8.00, 19.83, 3.74 -216.00, 8.00, 20.37, 3.73 -217.00, 8.00, 18.14, 3.77 -218.00, 8.00, 19.45, 3.74 -219.00, 8.00, 19.75, 3.73 -220.00, 8.00, 21.69, 3.75 -221.00, 8.00, 19.56, 3.75 -222.00, 8.00, 20.77, 3.73 -223.00, 8.00, 21.53, 3.75 -224.00, 8.00, 21.04, 3.74 -225.00, 8.00, 20.59, 3.73 -226.00, 8.00, 21.40, 3.72 -227.00, 8.00, 18.40, 3.74 -228.00, 8.00, 19.10, 3.74 -229.00, 8.00, 20.89, 3.75 -230.00, 8.00, 20.44, 3.75 -231.00, 8.00, 20.83, 3.75 -232.00, 8.00, 20.78, 3.75 -233.00, 8.00, 20.67, 3.75 -234.00, 8.00, 20.17, 3.74 -235.00, 8.00, 20.07, 3.74 -236.00, 8.00, 22.36, 3.76 -237.00, 8.00, 19.32, 3.75 -238.00, 8.00, 19.48, 3.74 -239.00, 8.00, 20.74, 3.75 -240.00, 8.00, 21.31, 3.72 -241.00, 8.00, 18.17, 3.75 -242.00, 8.00, 19.81, 3.74 -243.00, 8.00, 21.61, 3.73 -244.00, 8.00, 19.49, 3.74 -245.00, 8.00, 19.64, 3.75 -246.00, 8.00, 19.01, 3.72 -247.00, 8.00, 20.23, 3.74 -248.00, 8.00, 19.35, 3.73 -249.00, 8.00, 21.53, 3.73 -250.00, 8.00, 21.70, 3.73 -251.00, 8.00, 21.49, 3.73 -252.00, 8.00, 21.46, 3.74 -253.00, 8.00, 21.69, 3.73 -254.00, 8.00, 20.55, 3.74 -255.00, 8.00, 20.54, 3.72 -256.00, 8.00, 19.23, 3.74 -257.00, 8.00, 18.56, 3.70 -258.00, 8.00, 19.13, 3.74 -259.00, 8.00, 18.89, 3.74 -260.00, 8.00, 20.04, 3.74 -261.00, 8.00, 20.18, 3.71 -262.00, 8.00, 19.09, 3.72 -263.00, 8.00, 20.24, 3.74 -264.00, 8.00, 20.52, 3.72 -265.00, 8.00, 21.17, 3.73 -266.00, 8.00, 21.16, 3.73 -267.00, 8.00, 19.26, 3.75 -268.00, 8.00, 22.89, 3.71 -269.00, 8.00, 20.43, 3.73 -270.00, 8.00, 20.23, 3.73 -271.00, 8.00, 18.69, 3.73 -272.00, 8.00, 19.86, 3.72 -273.00, 8.00, 18.39, 3.73 -274.00, 8.00, 19.86, 3.72 -275.00, 8.00, 20.42, 3.74 -276.00, 8.00, 19.60, 3.73 -277.00, 8.00, 18.84, 3.73 -278.00, 8.00, 20.47, 3.73 -279.00, 8.00, 20.82, 3.73 -280.00, 8.00, 20.96, 3.74 -281.00, 8.00, 21.63, 3.72 -282.00, 8.00, 19.09, 3.71 -283.00, 8.00, 19.59, 3.73 -284.00, 8.00, 20.28, 3.71 -285.00, 8.00, 21.40, 3.70 -286.00, 8.00, 20.34, 3.75 -287.00, 8.00, 20.03, 3.74 -288.00, 8.00, 20.18, 3.72 -289.00, 8.00, 19.61, 3.72 -290.00, 8.00, 21.23, 3.71 -291.00, 8.00, 19.02, 3.73 -292.00, 8.00, 20.56, 3.73 -293.00, 8.00, 18.85, 3.72 -294.00, 8.00, 18.37, 3.72 -295.00, 8.00, 21.23, 3.72 -296.00, 8.00, 19.10, 3.72 -297.00, 8.00, 19.69, 3.71 -298.00, 8.00, 21.29, 3.72 -299.00, 8.00, 18.37, 3.74 -300.00, 8.00, 19.35, 3.72 -301.00, 8.00, 19.84, 3.71 -302.00, 8.00, 20.97, 3.74 -303.00, 8.00, 19.32, 3.73 -304.00, 8.00, 20.51, 3.71 -305.00, 8.00, 20.62, 3.70 -306.00, 8.00, 19.60, 3.72 -307.00, 8.00, 18.66, 3.72 -308.00, 8.00, 19.72, 3.71 -309.00, 8.00, 19.38, 3.71 -310.00, 8.00, 20.63, 3.73 -311.00, 8.00, 20.75, 3.73 -312.00, 8.00, 21.16, 3.71 -313.00, 8.00, 18.88, 3.71 -314.00, 8.00, 22.84, 3.73 -315.00, 8.00, 20.51, 3.73 -316.00, 8.00, 20.06, 3.71 -317.00, 8.00, 19.71, 3.72 -318.00, 8.00, 20.71, 3.71 -319.00, 8.00, 19.82, 3.72 -320.00, 8.00, 19.56, 3.70 -321.00, 8.00, 19.45, 3.72 -322.00, 8.00, 23.49, 3.70 -323.00, 8.00, 21.68, 3.72 -324.00, 8.00, 20.49, 3.70 -325.00, 8.00, 20.42, 3.70 -326.00, 8.00, 21.32, 3.72 -327.00, 8.00, 20.77, 3.71 -328.00, 8.00, 20.73, 3.72 -329.00, 8.00, 20.62, 3.69 -330.00, 8.00, 20.42, 3.73 -331.00, 8.00, 19.69, 3.70 -332.00, 8.00, 20.73, 3.70 -333.00, 8.00, 20.72, 3.70 -334.00, 8.00, 19.80, 3.71 -335.00, 8.00, 18.87, 3.71 -336.00, 8.00, 20.16, 3.73 -337.00, 8.00, 19.21, 3.69 -338.00, 8.00, 20.10, 3.71 -339.00, 8.00, 20.69, 3.71 -340.00, 8.00, 20.98, 3.71 -341.00, 8.00, 20.21, 3.72 -342.00, 8.00, 21.71, 3.70 -343.00, 8.00, 19.05, 3.70 -344.00, 8.00, 21.40, 3.72 -345.00, 8.00, 19.04, 3.70 -346.00, 8.00, 22.54, 3.73 -347.00, 8.00, 18.92, 3.73 -348.00, 8.00, 20.63, 3.71 -349.00, 8.00, 19.03, 3.71 -350.00, 8.00, 19.11, 3.72 -351.00, 8.00, 20.72, 3.71 -352.00, 8.00, 21.50, 3.70 -353.00, 8.00, 18.73, 3.70 -354.00, 8.00, 18.90, 3.70 -355.00, 8.00, 20.14, 3.71 -356.00, 8.00, 20.87, 3.70 -357.00, 8.00, 19.06, 3.71 -358.00, 8.00, 20.88, 3.70 -359.00, 8.00, 19.73, 3.72 -360.00, 8.00, 18.61, 3.71 -361.00, 8.00, 19.26, 3.70 -362.00, 8.00, 20.51, 3.70 -363.00, 8.00, 20.74, 3.70 -364.00, 8.00, 19.04, 3.70 -365.00, 8.00, 19.62, 3.71 -366.00, 8.00, 19.17, 3.70 -367.00, 8.00, 21.36, 3.69 -368.00, 8.00, 19.51, 3.70 -369.00, 8.00, 18.75, 3.70 -370.00, 8.00, 19.35, 3.72 -371.00, 8.00, 19.69, 3.70 -372.00, 8.00, 19.97, 3.71 -373.00, 8.00, 21.41, 3.69 -374.00, 8.00, 20.37, 3.71 -375.00, 8.00, 19.40, 3.70 -376.00, 8.00, 20.33, 3.72 -377.00, 8.00, 19.59, 3.69 -378.00, 8.00, 22.21, 3.69 -379.00, 8.00, 22.05, 3.71 -380.00, 8.00, 20.55, 3.69 -381.00, 8.00, 20.92, 3.71 -382.00, 8.00, 19.90, 3.69 -383.00, 8.00, 20.28, 3.70 -384.00, 8.00, 22.13, 3.68 -385.00, 8.00, 19.59, 3.71 -386.00, 8.00, 20.56, 3.70 -387.00, 8.00, 21.59, 3.69 -388.00, 8.00, 20.47, 3.70 -389.00, 8.00, 20.70, 3.69 -390.00, 8.00, 20.50, 3.70 -391.00, 8.00, 20.08, 3.70 -392.00, 8.00, 19.55, 3.70 -393.00, 8.00, 20.67, 3.66 -394.00, 8.00, 19.83, 3.70 -395.00, 8.00, 19.47, 3.68 -396.00, 8.00, 19.80, 3.71 -397.00, 8.00, 20.92, 3.70 -398.00, 8.00, 21.29, 3.70 -399.00, 8.00, 19.51, 3.68 -400.00, 8.00, 21.02, 3.69 -401.00, 8.00, 19.55, 3.70 -402.00, 8.00, 19.49, 3.71 -403.00, 8.00, 21.52, 3.69 -404.00, 8.00, 18.23, 3.70 -405.00, 8.00, 20.05, 3.70 -406.00, 8.00, 18.48, 3.70 -407.00, 8.00, 19.28, 3.71 -408.00, 8.00, 20.91, 3.68 -409.00, 8.00, 18.73, 3.70 -410.00, 8.00, 19.94, 3.69 -411.00, 8.00, 20.78, 3.68 -412.00, 8.00, 20.88, 3.68 -413.00, 8.00, 20.20, 3.68 -414.00, 8.00, 21.11, 3.70 -415.00, 8.00, 21.17, 3.71 -416.00, 8.00, 20.26, 3.68 -417.00, 8.00, 20.32, 3.71 -418.00, 8.00, 22.21, 3.69 -419.00, 8.00, 21.09, 3.69 -420.00, 8.00, 18.96, 3.70 -421.00, 8.00, 20.15, 3.68 -422.00, 8.00, 19.82, 3.69 -423.00, 8.00, 19.38, 3.71 -424.00, 8.00, 19.32, 3.68 -425.00, 8.00, 21.67, 3.68 -426.00, 8.00, 18.12, 3.70 -427.00, 8.00, 20.66, 3.70 -428.00, 8.00, 19.47, 3.69 -429.00, 8.00, 18.82, 3.70 -430.00, 8.00, 20.58, 3.68 -431.00, 8.00, 18.77, 3.70 -432.00, 8.00, 18.97, 3.69 -433.00, 8.00, 19.55, 3.68 -434.00, 8.00, 17.89, 3.68 -435.00, 8.00, 20.68, 3.69 -436.00, 8.00, 20.19, 3.70 -437.00, 8.00, 19.26, 3.69 -438.00, 8.00, 20.36, 3.68 -439.00, 8.00, 18.03, 3.68 -440.00, 8.00, 19.69, 3.69 -441.00, 8.00, 20.16, 3.70 -442.00, 8.00, 21.89, 3.66 -443.00, 8.00, 19.00, 3.68 -444.00, 8.00, 19.49, 3.68 -445.00, 8.00, 20.72, 3.71 -446.00, 8.00, 20.42, 3.68 -447.00, 8.00, 19.20, 3.69 -448.00, 8.00, 19.36, 3.68 -449.00, 8.00, 19.59, 3.68 -450.00, 8.00, 19.10, 3.66 -451.00, 8.00, 20.87, 3.66 -452.00, 8.00, 19.60, 3.68 -453.00, 8.00, 18.19, 3.69 -454.00, 8.00, 20.02, 3.66 -455.00, 8.00, 21.53, 3.68 -456.00, 8.00, 19.98, 3.68 -457.00, 8.00, 22.05, 3.68 -458.00, 8.00, 20.35, 3.66 -459.00, 8.00, 20.05, 3.68 -460.00, 8.00, 20.98, 3.66 -461.00, 8.00, 20.01, 3.68 -462.00, 8.00, 18.82, 3.67 -463.00, 8.00, 19.74, 3.69 -464.00, 8.00, 17.82, 3.70 -465.00, 8.00, 17.95, 3.68 -466.00, 8.00, 19.28, 3.67 -467.00, 8.00, 20.50, 3.69 -468.00, 8.00, 19.17, 3.67 -469.00, 8.00, 21.74, 3.65 -470.00, 8.00, 21.40, 3.66 -471.00, 8.00, 20.90, 3.68 -472.00, 8.00, 18.41, 3.68 -473.00, 8.00, 17.75, 3.66 -474.00, 8.00, 21.44, 3.66 -475.00, 8.00, 20.82, 3.67 -476.00, 8.00, 19.18, 3.69 -477.00, 8.00, 19.20, 3.67 -478.00, 8.00, 20.09, 3.67 -479.00, 8.00, 20.46, 3.67 -480.00, 8.00, 19.18, 3.69 -481.00, 8.00, 18.94, 3.67 -482.00, 8.00, 19.11, 3.70 -483.00, 8.00, 20.00, 3.66 -484.00, 8.00, 20.62, 3.67 -485.00, 8.00, 20.00, 3.67 -486.00, 8.00, 20.40, 3.67 -487.00, 8.00, 20.17, 3.67 -488.00, 8.00, 20.13, 3.67 -489.00, 8.00, 20.70, 3.68 -490.00, 8.00, 20.92, 3.67 -491.00, 8.00, 20.47, 3.66 -492.00, 8.00, 20.03, 3.68 -493.00, 8.00, 18.45, 3.68 -494.00, 8.00, 18.37, 3.66 -495.00, 8.00, 18.62, 3.67 -496.00, 8.00, 20.55, 3.67 -497.00, 8.00, 19.06, 3.67 -498.00, 8.00, 20.47, 3.68 -499.00, 8.00, 20.50, 3.67 -500.00, 8.00, 19.32, 3.65 -501.00, 8.00, 20.46, 3.65 -502.00, 8.00, 19.71, 3.67 -503.00, 8.00, 21.67, 3.67 -504.00, 8.00, 19.44, 3.68 -505.00, 8.00, 19.62, 3.68 -506.00, 8.00, 19.44, 3.68 -507.00, 8.00, 20.44, 3.67 -508.00, 8.00, 21.52, 3.67 -509.00, 8.00, 20.83, 3.68 -510.00, 8.00, 19.74, 3.67 -511.00, 8.00, 19.40, 3.66 -512.00, 8.00, 20.93, 3.65 -513.00, 8.00, 20.62, 3.67 -514.00, 8.00, 20.35, 3.66 -515.00, 8.00, 20.67, 3.67 -516.00, 8.00, 20.43, 3.67 -517.00, 8.00, 19.84, 3.66 -518.00, 8.00, 19.78, 3.67 -519.00, 8.00, 20.34, 3.67 -520.00, 8.00, 21.25, 3.66 -521.00, 8.00, 20.16, 3.67 -522.00, 8.00, 18.51, 3.67 -523.00, 8.00, 19.94, 3.66 -524.00, 8.00, 21.80, 3.64 -525.00, 8.00, 19.81, 3.68 -526.00, 8.00, 18.28, 3.66 -527.00, 8.00, 19.86, 3.66 -528.00, 8.00, 21.04, 3.65 -529.00, 8.00, 20.15, 3.66 -530.00, 8.00, 20.12, 3.69 -531.00, 8.00, 19.46, 3.68 -532.00, 8.00, 19.86, 3.67 -533.00, 8.00, 22.19, 3.66 -534.00, 8.00, 20.51, 3.65 -535.00, 8.00, 21.90, 3.68 -536.00, 8.00, 18.21, 3.67 -537.00, 8.00, 21.19, 3.68 -538.00, 8.00, 20.64, 3.66 -539.00, 8.00, 20.34, 3.65 -540.00, 8.00, 20.26, 3.67 -541.00, 8.00, 19.69, 3.66 -542.00, 8.00, 19.85, 3.66 -543.00, 8.00, 20.57, 3.65 -544.00, 8.00, 18.51, 3.64 -545.00, 8.00, 20.07, 3.65 -546.00, 8.00, 19.54, 3.67 -547.00, 8.00, 18.92, 3.65 -548.00, 8.00, 18.92, 3.65 -549.00, 8.00, 20.21, 3.68 -550.00, 8.00, 20.09, 3.65 -551.00, 8.00, 19.67, 3.66 -552.00, 8.00, 20.12, 3.65 -553.00, 8.00, 19.15, 3.66 -554.00, 8.00, 20.36, 3.65 -555.00, 8.00, 21.31, 3.65 -556.00, 8.00, 20.54, 3.65 -557.00, 8.00, 22.59, 3.66 -558.00, 8.00, 20.73, 3.66 -559.00, 8.00, 19.32, 3.65 -560.00, 8.00, 20.80, 3.65 -561.00, 8.00, 19.69, 3.67 -562.00, 8.00, 20.79, 3.65 -563.00, 8.00, 18.75, 3.65 -564.00, 8.00, 20.52, 3.65 -565.00, 8.00, 21.39, 3.65 -566.00, 8.00, 20.59, 3.66 -567.00, 8.00, 20.72, 3.66 -568.00, 8.00, 22.19, 3.63 -569.00, 8.00, 18.80, 3.65 -570.00, 8.00, 19.68, 3.66 -571.00, 8.00, 21.11, 3.65 -572.00, 8.00, 18.55, 3.66 -573.00, 8.00, 21.52, 3.65 -574.00, 8.00, 19.95, 3.66 -575.00, 8.00, 21.72, 3.65 -576.00, 8.00, 20.79, 3.64 -577.00, 8.00, 20.69, 3.65 -578.00, 8.00, 19.49, 3.65 -579.00, 8.00, 20.25, 3.65 -580.00, 8.00, 19.25, 3.65 -581.00, 8.00, 20.30, 3.67 -582.00, 8.00, 19.12, 3.65 -583.00, 8.00, 20.84, 3.65 -584.00, 8.00, 19.00, 3.67 -585.00, 8.00, 21.57, 3.64 -586.00, 8.00, 19.80, 3.63 -587.00, 8.00, 21.97, 3.64 -588.00, 8.00, 20.23, 3.65 -589.00, 8.00, 21.06, 3.64 -590.00, 8.00, 20.86, 3.65 -591.00, 8.00, 19.33, 3.63 -592.00, 8.00, 18.17, 3.65 -593.00, 8.00, 21.29, 3.67 -594.00, 8.00, 20.11, 3.66 -595.00, 8.00, 19.67, 3.64 -596.00, 8.00, 20.68, 3.65 -597.00, 8.00, 19.46, 3.65 -598.00, 8.00, 19.21, 3.67 -599.00, 8.00, 18.90, 3.65 -600.00, 8.00, 20.19, 3.65 -601.00, 8.00, 19.84, 3.64 -602.00, 8.00, 21.26, 3.64 -603.00, 8.00, 18.39, 3.63 -604.00, 8.00, 21.37, 3.64 -605.00, 8.00, 20.25, 3.63 -606.00, 8.00, 18.70, 3.67 -607.00, 8.00, 19.71, 3.63 -608.00, 8.00, 20.75, 3.66 -609.00, 8.00, 18.38, 3.66 -610.00, 8.00, 20.05, 3.65 -611.00, 8.00, 19.95, 3.65 -612.00, 8.00, 21.28, 3.64 -613.00, 8.00, 21.25, 3.65 -614.00, 8.00, 19.48, 3.65 -615.00, 8.00, 19.82, 3.64 -616.00, 8.00, 18.94, 3.66 -617.00, 8.00, 20.68, 3.66 -618.00, 8.00, 21.21, 3.65 -619.00, 8.00, 19.48, 3.62 -620.00, 8.00, 18.38, 3.66 -621.00, 8.00, 19.23, 3.66 -622.00, 8.00, 17.79, 3.66 -623.00, 8.00, 19.03, 3.65 -624.00, 8.00, 19.43, 3.63 -625.00, 8.00, 21.34, 3.64 -626.00, 8.00, 19.28, 3.64 -627.00, 8.00, 20.14, 3.62 -628.00, 8.00, 18.42, 3.66 -629.00, 8.00, 20.23, 3.66 -630.00, 8.00, 19.29, 3.65 -631.00, 8.00, 20.29, 3.65 -632.00, 8.00, 21.33, 3.64 -633.00, 8.00, 21.15, 3.65 -634.00, 8.00, 20.48, 3.64 -635.00, 8.00, 19.84, 3.63 -636.00, 8.00, 20.57, 3.65 -637.00, 8.00, 19.75, 3.61 -638.00, 8.00, 19.15, 3.62 -639.00, 8.00, 19.85, 3.66 -640.00, 8.00, 18.83, 3.65 -641.00, 8.00, 20.47, 3.65 -642.00, 8.00, 19.21, 3.64 -643.00, 8.00, 20.41, 3.64 -644.00, 8.00, 19.92, 3.63 -645.00, 8.00, 21.33, 3.64 -646.00, 8.00, 19.59, 3.64 -647.00, 8.00, 22.23, 3.63 -648.00, 8.00, 20.22, 3.63 -649.00, 8.00, 18.66, 3.64 -650.00, 8.00, 20.63, 3.64 -651.00, 8.00, 20.08, 3.64 -652.00, 8.00, 19.94, 3.64 -653.00, 8.00, 20.76, 3.62 -654.00, 8.00, 20.18, 3.63 -655.00, 8.00, 20.51, 3.65 -656.00, 8.00, 20.00, 3.63 -657.00, 8.00, 20.55, 3.62 -658.00, 8.00, 20.33, 3.64 -659.00, 8.00, 19.26, 3.63 -660.00, 8.00, 19.38, 3.64 -661.00, 8.00, 21.43, 3.64 -662.00, 8.00, 19.67, 3.63 -663.00, 8.00, 19.08, 3.64 -664.00, 8.00, 19.84, 3.65 -665.00, 8.00, 19.99, 3.64 -666.00, 8.00, 19.27, 3.64 -667.00, 8.00, 20.20, 3.62 -668.00, 8.00, 19.35, 3.65 -669.00, 8.00, 18.10, 3.63 -670.00, 8.00, 19.32, 3.64 -671.00, 8.00, 18.68, 3.64 -672.00, 8.00, 18.97, 3.63 -673.00, 8.00, 17.26, 3.65 -674.00, 8.00, 19.03, 3.62 -675.00, 8.00, 20.03, 3.62 -676.00, 8.00, 19.04, 3.63 -677.00, 8.00, 19.83, 3.61 -678.00, 8.00, 21.33, 3.63 -679.00, 8.00, 18.69, 3.61 -680.00, 8.00, 19.52, 3.64 -681.00, 8.00, 19.47, 3.64 -682.00, 8.00, 21.71, 3.63 -683.00, 8.00, 20.23, 3.64 -684.00, 8.00, 21.04, 3.64 -685.00, 8.00, 21.21, 3.63 -686.00, 8.00, 18.91, 3.64 -687.00, 8.00, 17.93, 3.62 -688.00, 8.00, 20.72, 3.63 -689.00, 8.00, 22.10, 3.63 -690.00, 8.00, 21.62, 3.63 -691.00, 8.00, 19.58, 3.64 -692.00, 8.00, 19.48, 3.61 -693.00, 8.00, 22.78, 3.65 -694.00, 8.00, 20.94, 3.63 -695.00, 8.00, 18.58, 3.63 -696.00, 8.00, 20.46, 3.62 -697.00, 8.00, 20.83, 3.61 -698.00, 8.00, 20.16, 3.63 -699.00, 8.00, 22.03, 3.63 -700.00, 8.00, 20.26, 3.62 -701.00, 8.00, 20.25, 3.63 -702.00, 8.00, 19.84, 3.62 -703.00, 8.00, 20.93, 3.64 -704.00, 8.00, 20.50, 3.65 -705.00, 8.00, 19.38, 3.63 -706.00, 8.00, 19.18, 3.62 -707.00, 8.00, 19.10, 3.61 -708.00, 8.00, 19.54, 3.62 -709.00, 8.00, 19.48, 3.63 -710.00, 8.00, 20.15, 3.62 -711.00, 8.00, 18.87, 3.63 -712.00, 8.00, 21.29, 3.61 -713.00, 8.00, 20.97, 3.62 -714.00, 8.00, 20.95, 3.64 -715.00, 8.00, 20.63, 3.62 -716.00, 8.00, 19.19, 3.62 -717.00, 8.00, 20.98, 3.61 -718.00, 8.00, 19.54, 3.63 -719.00, 8.00, 19.96, 3.62 -720.00, 8.00, 21.39, 3.63 -721.00, 8.00, 19.54, 3.66 -722.00, 8.00, 20.64, 3.64 -723.00, 8.00, 19.60, 3.62 -724.00, 8.00, 19.50, 3.63 -725.00, 8.00, 19.33, 3.62 -726.00, 8.00, 19.21, 3.62 -727.00, 8.00, 18.13, 3.62 -728.00, 8.00, 20.81, 3.63 -729.00, 8.00, 20.27, 3.62 -730.00, 8.00, 20.59, 3.62 -731.00, 8.00, 20.57, 3.63 -732.00, 8.00, 19.84, 3.63 -733.00, 8.00, 19.52, 3.63 -734.00, 8.00, 19.74, 3.63 -735.00, 8.00, 18.49, 3.62 -736.00, 8.00, 21.14, 3.62 -737.00, 8.00, 19.17, 3.61 -738.00, 8.00, 19.24, 3.63 -739.00, 8.00, 21.08, 3.63 -740.00, 8.00, 21.10, 3.63 -741.00, 8.00, 19.56, 3.63 -742.00, 8.00, 19.31, 3.62 -743.00, 8.00, 19.78, 3.62 -744.00, 8.00, 21.11, 3.62 -745.00, 8.00, 18.55, 3.62 -746.00, 8.00, 21.74, 3.64 -747.00, 8.00, 18.31, 3.63 -748.00, 8.00, 20.53, 3.64 -749.00, 8.00, 20.59, 3.61 -750.00, 8.00, 20.47, 3.60 -751.00, 8.00, 20.05, 3.62 -752.00, 8.00, 22.23, 3.62 -753.00, 8.00, 20.89, 3.62 -754.00, 8.00, 19.73, 3.61 -755.00, 8.00, 19.86, 3.62 -756.00, 8.00, 21.13, 3.61 -757.00, 8.00, 19.26, 3.61 -758.00, 8.00, 19.75, 3.63 -759.00, 8.00, 20.11, 3.61 -760.00, 8.00, 20.90, 3.60 -761.00, 8.00, 19.85, 3.61 -762.00, 8.00, 19.88, 3.61 -763.00, 8.00, 18.82, 3.62 -764.00, 8.00, 21.45, 3.60 -765.00, 8.00, 22.54, 3.62 -766.00, 8.00, 18.86, 3.60 -767.00, 8.00, 20.35, 3.59 -768.00, 8.00, 19.88, 3.61 -769.00, 8.00, 20.58, 3.62 -770.00, 8.00, 19.32, 3.61 -771.00, 8.00, 18.78, 3.63 -772.00, 8.00, 19.15, 3.62 -773.00, 8.00, 20.61, 3.63 -774.00, 8.00, 20.34, 3.61 -775.00, 8.00, 18.85, 3.62 -776.00, 8.00, 21.54, 3.59 -777.00, 8.00, 20.62, 3.62 -778.00, 8.00, 19.22, 3.61 -779.00, 8.00, 20.22, 3.60 -780.00, 8.00, 19.66, 3.61 -781.00, 8.00, 19.88, 3.61 -782.00, 8.00, 20.41, 3.61 -783.00, 8.00, 21.10, 3.60 -784.00, 8.00, 21.11, 3.61 -785.00, 8.00, 19.87, 3.62 -786.00, 8.00, 19.78, 3.63 -787.00, 8.00, 19.20, 3.60 -788.00, 8.00, 20.76, 3.61 -789.00, 8.00, 21.56, 3.62 -790.00, 8.00, 19.52, 3.60 -791.00, 8.00, 17.90, 3.60 -792.00, 8.00, 18.84, 3.60 -793.00, 8.00, 20.22, 3.58 -794.00, 8.00, 18.83, 3.61 -795.00, 8.00, 21.04, 3.60 -796.00, 8.00, 20.10, 3.61 -797.00, 8.00, 21.81, 3.62 -798.00, 8.00, 19.20, 3.60 -799.00, 8.00, 19.81, 3.59 -800.00, 8.00, 19.62, 3.64 -801.00, 8.00, 19.34, 3.59 -802.00, 8.00, 18.06, 3.61 -803.00, 8.00, 21.75, 3.62 -804.00, 8.00, 20.51, 3.61 -805.00, 8.00, 18.50, 3.59 -806.00, 8.00, 20.26, 3.61 -807.00, 8.00, 18.35, 3.61 -808.00, 8.00, 20.24, 3.60 -809.00, 8.00, 20.82, 3.62 -810.00, 8.00, 21.51, 3.61 -811.00, 8.00, 19.99, 3.60 -812.00, 8.00, 19.96, 3.60 -813.00, 8.00, 21.21, 3.59 -814.00, 8.00, 18.11, 3.59 -815.00, 8.00, 19.96, 3.59 -816.00, 8.00, 19.36, 3.60 -817.00, 8.00, 19.89, 3.61 -818.00, 8.00, 20.62, 3.60 -819.00, 8.00, 21.76, 3.61 -820.00, 8.00, 18.75, 3.57 -821.00, 8.00, 19.71, 3.60 -822.00, 8.00, 20.27, 3.59 -823.00, 8.00, 21.47, 3.59 -824.00, 8.00, 19.84, 3.59 -825.00, 8.00, 18.98, 3.62 -826.00, 8.00, 20.56, 3.61 -827.00, 8.00, 20.01, 3.60 -828.00, 8.00, 19.15, 3.60 -829.00, 8.00, 22.11, 3.62 -830.00, 8.00, 20.09, 3.61 -831.00, 8.00, 19.88, 3.61 -832.00, 8.00, 20.32, 3.60 -833.00, 8.00, 21.08, 3.60 -834.00, 8.00, 20.33, 3.61 -835.00, 8.00, 20.99, 3.60 -836.00, 8.00, 21.20, 3.59 -837.00, 8.00, 22.29, 3.59 -838.00, 8.00, 18.98, 3.61 -839.00, 8.00, 20.86, 3.59 -840.00, 8.00, 21.92, 3.60 -841.00, 8.00, 21.64, 3.60 -842.00, 8.00, 21.09, 3.59 -843.00, 8.00, 18.86, 3.60 -844.00, 8.00, 19.84, 3.60 -845.00, 8.00, 17.95, 3.58 -846.00, 8.00, 19.74, 3.59 -847.00, 8.00, 18.45, 3.59 -848.00, 8.00, 21.17, 3.59 -849.00, 8.00, 20.07, 3.60 -850.00, 8.00, 18.87, 3.58 -851.00, 8.00, 18.60, 3.59 -852.00, 8.00, 20.62, 3.60 -853.00, 8.00, 21.09, 3.61 -854.00, 8.00, 18.81, 3.60 -855.00, 8.00, 19.32, 3.60 -856.00, 8.00, 20.99, 3.58 -857.00, 8.00, 20.23, 3.61 -858.00, 8.00, 21.74, 3.59 -859.00, 8.00, 22.12, 3.61 -860.00, 8.00, 19.77, 3.61 -861.00, 8.00, 20.31, 3.58 -862.00, 8.00, 19.78, 3.60 -863.00, 8.00, 19.65, 3.59 -864.00, 8.00, 21.03, 3.59 -865.00, 8.00, 18.63, 3.60 -866.00, 8.00, 21.03, 3.60 -867.00, 8.00, 19.29, 3.60 -868.00, 8.00, 20.22, 3.58 -869.00, 8.00, 20.95, 3.58 -870.00, 8.00, 18.77, 3.59 -871.00, 8.00, 17.99, 3.60 -872.00, 8.00, 19.35, 3.62 -873.00, 8.00, 18.83, 3.59 -874.00, 8.00, 19.76, 3.59 -875.00, 8.00, 19.38, 3.57 -876.00, 8.00, 19.51, 3.57 -877.00, 8.00, 20.13, 3.59 -878.00, 8.00, 18.45, 3.62 -879.00, 8.00, 19.66, 3.59 -880.00, 8.00, 19.88, 3.59 -881.00, 8.00, 20.29, 3.60 -882.00, 8.00, 19.02, 3.60 -883.00, 8.00, 20.89, 3.60 -884.00, 8.00, 19.67, 3.60 -885.00, 8.00, 18.89, 3.60 -886.00, 8.00, 19.86, 3.59 -887.00, 8.00, 17.51, 3.58 -888.00, 8.00, 20.04, 3.59 -889.00, 8.00, 20.16, 3.58 -890.00, 8.00, 19.95, 3.60 -891.00, 8.00, 20.02, 3.59 -892.00, 8.00, 18.77, 3.57 -893.00, 8.00, 20.77, 3.57 -894.00, 8.00, 20.54, 3.60 -895.00, 8.00, 19.62, 3.58 -896.00, 8.00, 19.41, 3.59 -897.00, 8.00, 19.87, 3.60 -898.00, 8.00, 18.88, 3.60 -899.00, 8.00, 21.51, 3.60 -900.00, 8.00, 20.45, 3.58 -901.00, 8.00, 20.79, 3.59 -902.00, 8.00, 18.18, 3.58 -903.00, 8.00, 20.33, 3.59 -904.00, 8.00, 20.83, 3.58 -905.00, 8.00, 19.35, 3.57 -906.00, 8.00, 18.57, 3.58 -907.00, 8.00, 19.73, 3.60 -908.00, 8.00, 20.52, 3.59 -909.00, 8.00, 20.74, 3.60 -910.00, 8.00, 18.95, 3.60 -911.00, 8.00, 22.95, 3.59 -912.00, 8.00, 19.75, 3.58 -913.00, 8.00, 20.05, 3.57 -914.00, 8.00, 21.25, 3.58 -915.00, 8.00, 20.77, 3.59 -916.00, 8.00, 21.38, 3.59 -917.00, 8.00, 19.57, 3.57 -918.00, 8.00, 20.13, 3.59 -919.00, 8.00, 20.28, 3.59 -920.00, 8.00, 22.41, 3.59 -921.00, 8.00, 20.81, 3.58 -922.00, 8.00, 21.62, 3.58 -923.00, 8.00, 19.47, 3.58 -924.00, 8.00, 20.61, 3.58 -925.00, 8.00, 18.15, 3.59 -926.00, 8.00, 19.90, 3.58 -927.00, 8.00, 20.47, 3.57 -928.00, 8.00, 18.92, 3.58 -929.00, 8.00, 21.36, 3.58 -930.00, 8.00, 19.04, 3.57 -931.00, 8.00, 19.28, 3.58 -932.00, 8.00, 20.13, 3.56 -933.00, 8.00, 20.91, 3.58 -934.00, 8.00, 19.24, 3.59 -935.00, 8.00, 19.93, 3.59 -936.00, 8.00, 17.90, 3.58 -937.00, 8.00, 21.39, 3.58 -938.00, 8.00, 18.93, 3.58 -939.00, 8.00, 21.16, 3.58 -940.00, 8.00, 18.96, 3.58 -941.00, 8.00, 19.65, 3.58 -942.00, 8.00, 19.86, 3.60 -943.00, 8.00, 20.88, 3.58 -944.00, 8.00, 19.28, 3.56 -945.00, 8.00, 18.17, 3.57 -946.00, 8.00, 19.68, 3.58 -947.00, 8.00, 21.44, 3.57 -948.00, 8.00, 21.28, 3.58 -949.00, 8.00, 22.38, 3.59 -950.00, 8.00, 19.92, 3.57 -951.00, 8.00, 19.99, 3.58 -952.00, 8.00, 21.27, 3.59 -953.00, 8.00, 22.01, 3.57 -954.00, 8.00, 20.86, 3.58 -955.00, 8.00, 20.25, 3.58 -956.00, 8.00, 19.85, 3.57 -957.00, 8.00, 19.60, 3.57 -958.00, 8.00, 19.32, 3.57 -959.00, 8.00, 19.30, 3.59 -960.00, 8.00, 20.64, 3.56 -961.00, 8.00, 20.34, 3.56 -962.00, 8.00, 19.70, 3.57 -963.00, 8.00, 20.37, 3.56 -964.00, 8.00, 20.99, 3.58 -965.00, 8.00, 20.34, 3.56 -966.00, 8.00, 19.91, 3.57 -967.00, 8.00, 21.22, 3.59 -968.00, 8.00, 19.93, 3.57 -969.00, 8.00, 18.62, 3.58 -970.00, 8.00, 20.13, 3.60 -971.00, 8.00, 19.61, 3.56 -972.00, 8.00, 19.69, 3.58 -973.00, 8.00, 18.08, 3.59 -974.00, 8.00, 19.71, 3.57 -975.00, 8.00, 18.25, 3.57 -976.00, 8.00, 18.98, 3.58 -977.00, 8.00, 23.42, 3.58 -978.00, 8.00, 20.27, 3.58 -979.00, 8.00, 20.20, 3.57 -980.00, 8.00, 19.88, 3.58 -981.00, 8.00, 17.96, 3.56 -982.00, 8.00, 18.79, 3.58 -983.00, 8.00, 19.36, 3.56 -984.00, 8.00, 21.62, 3.56 -985.00, 8.00, 20.75, 3.58 -986.00, 8.00, 18.77, 3.57 -987.00, 8.00, 20.36, 3.58 -988.00, 8.00, 20.31, 3.58 -989.00, 8.00, 20.78, 3.57 -990.00, 8.00, 20.56, 3.58 -991.00, 8.00, 19.34, 3.55 -992.00, 8.00, 19.91, 3.56 -993.00, 8.00, 19.80, 3.57 -994.00, 8.00, 20.50, 3.57 -995.00, 8.00, 21.42, 3.56 -996.00, 8.00, 19.35, 3.58 -997.00, 8.00, 19.06, 3.57 -998.00, 8.00, 19.88, 3.57 -999.00, 8.00, 19.92, 3.56 -1000.00, 8.00, 20.30, 3.56 -1001.00, 8.00, 19.14, 3.56 -1002.00, 8.00, 21.12, 3.56 -1003.00, 8.00, 22.12, 3.56 -1004.00, 8.00, 18.88, 3.56 -1005.00, 8.00, 20.32, 3.54 -1006.00, 8.00, 19.25, 3.58 -1007.00, 8.00, 19.73, 3.56 -1008.00, 8.00, 20.53, 3.57 -1009.00, 8.00, 21.04, 3.57 -1010.00, 8.00, 19.94, 3.55 -1011.00, 8.00, 18.35, 3.55 -1012.00, 8.00, 21.03, 3.57 -1013.00, 8.00, 20.07, 3.57 -1014.00, 8.00, 18.97, 3.57 -1015.00, 8.00, 20.10, 3.56 -1016.00, 8.00, 18.00, 3.58 -1017.00, 8.00, 19.69, 3.55 -1018.00, 8.00, 20.11, 3.56 -1019.00, 8.00, 19.09, 3.55 -1020.00, 8.00, 21.23, 3.57 -1021.00, 8.00, 21.05, 3.56 -1022.00, 8.00, 20.51, 3.56 -1023.00, 8.00, 20.82, 3.57 -1024.00, 8.00, 20.54, 3.56 -1025.00, 8.00, 19.82, 3.53 -1026.00, 8.00, 18.43, 3.55 -1027.00, 8.00, 20.52, 3.57 -1028.00, 8.00, 19.08, 3.54 -1029.00, 8.00, 20.13, 3.56 -1030.00, 8.00, 21.48, 3.53 -1031.00, 8.00, 18.73, 3.57 -1032.00, 8.00, 21.90, 3.57 -1033.00, 8.00, 19.70, 3.57 -1034.00, 8.00, 19.76, 3.55 -1035.00, 8.00, 18.93, 3.58 -1036.00, 8.00, 19.13, 3.56 -1037.00, 8.00, 18.84, 3.56 -1038.00, 8.00, 20.03, 3.56 -1039.00, 8.00, 19.17, 3.57 -1040.00, 8.00, 19.65, 3.54 -1041.00, 8.00, 20.25, 3.55 -1042.00, 8.00, 19.61, 3.56 -1043.00, 8.00, 20.86, 3.57 -1044.00, 8.00, 21.91, 3.54 -1045.00, 8.00, 19.41, 3.54 -1046.00, 8.00, 19.60, 3.55 -1047.00, 8.00, 18.67, 3.56 -1048.00, 8.00, 20.09, 3.55 -1049.00, 8.00, 19.17, 3.55 -1050.00, 8.00, 18.49, 3.55 -1051.00, 8.00, 20.13, 3.54 -1052.00, 8.00, 21.29, 3.55 -1053.00, 8.00, 21.03, 3.56 -1054.00, 8.00, 19.71, 3.54 -1055.00, 8.00, 21.11, 3.56 -1056.00, 8.00, 18.75, 3.55 -1057.00, 8.00, 17.43, 3.55 -1058.00, 8.00, 18.92, 3.58 -1059.00, 8.00, 19.34, 3.56 -1060.00, 8.00, 20.81, 3.55 -1061.00, 8.00, 19.41, 3.55 -1062.00, 8.00, 20.70, 3.55 -1063.00, 8.00, 18.75, 3.56 -1064.00, 8.00, 21.03, 3.54 -1065.00, 8.00, 18.74, 3.56 -1066.00, 8.00, 19.70, 3.54 -1067.00, 8.00, 19.89, 3.56 -1068.00, 8.00, 20.02, 3.56 -1069.00, 8.00, 20.39, 3.54 -1070.00, 8.00, 19.41, 3.56 -1071.00, 8.00, 20.64, 3.56 -1072.00, 8.00, 18.70, 3.55 -1073.00, 8.00, 19.27, 3.55 -1074.00, 8.00, 18.71, 3.54 -1075.00, 8.00, 21.38, 3.55 -1076.00, 8.00, 20.94, 3.57 -1077.00, 8.00, 19.49, 3.55 -1078.00, 8.00, 21.87, 3.53 -1079.00, 8.00, 20.47, 3.54 -1080.00, 8.00, 20.85, 3.54 -1081.00, 8.00, 20.31, 3.54 -1082.00, 8.00, 20.44, 3.55 -1083.00, 8.00, 20.05, 3.57 -1084.00, 8.00, 21.40, 3.55 -1085.00, 8.00, 20.52, 3.54 -1086.00, 8.00, 19.77, 3.54 -1087.00, 8.00, 20.46, 3.55 -1088.00, 8.00, 20.03, 3.55 -1089.00, 8.00, 19.86, 3.55 -1090.00, 8.00, 21.66, 3.57 -1091.00, 8.00, 19.21, 3.55 -1092.00, 8.00, 22.17, 3.54 -1093.00, 8.00, 21.06, 3.55 -1094.00, 8.00, 20.16, 3.56 -1095.00, 8.00, 18.56, 3.54 -1096.00, 8.00, 20.43, 3.55 -1097.00, 8.00, 20.83, 3.53 -1098.00, 8.00, 18.79, 3.53 -1099.00, 8.00, 18.26, 3.55 -1100.00, 8.00, 21.28, 3.52 -1101.00, 8.00, 18.69, 3.55 -1102.00, 8.00, 20.09, 3.56 -1103.00, 8.00, 21.93, 3.54 -1104.00, 8.00, 19.41, 3.54 -1105.00, 8.00, 20.54, 3.54 -1106.00, 8.00, 19.97, 3.54 -1107.00, 8.00, 19.03, 3.54 -1108.00, 8.00, 20.31, 3.54 -1109.00, 8.00, 19.18, 3.55 -1110.00, 8.00, 18.75, 3.54 -1111.00, 8.00, 18.36, 3.54 -1112.00, 8.00, 22.20, 3.53 -1113.00, 8.00, 19.89, 3.55 -1114.00, 8.00, 19.80, 3.54 -1115.00, 8.00, 21.37, 3.54 -1116.00, 8.00, 22.42, 3.56 -1117.00, 8.00, 19.01, 3.55 -1118.00, 8.00, 21.54, 3.54 -1119.00, 8.00, 19.90, 3.53 -1120.00, 8.00, 18.74, 3.53 -1121.00, 8.00, 20.48, 3.53 -1122.00, 8.00, 19.38, 3.54 -1123.00, 8.00, 19.33, 3.54 -1124.00, 8.00, 21.68, 3.54 -1125.00, 8.00, 20.99, 3.52 -1126.00, 8.00, 18.51, 3.54 -1127.00, 8.00, 20.66, 3.53 -1128.00, 8.00, 21.01, 3.53 -1129.00, 8.00, 19.80, 3.54 -1130.00, 8.00, 19.94, 3.53 -1131.00, 8.00, 21.67, 3.56 -1132.00, 8.00, 20.44, 3.52 -1133.00, 8.00, 20.95, 3.54 -1134.00, 8.00, 19.75, 3.52 -1135.00, 8.00, 19.19, 3.53 -1136.00, 8.00, 19.53, 3.53 -1137.00, 8.00, 19.66, 3.53 -1138.00, 8.00, 17.59, 3.52 -1139.00, 8.00, 18.50, 3.55 -1140.00, 8.00, 20.21, 3.54 -1141.00, 8.00, 21.32, 3.54 -1142.00, 8.00, 20.17, 3.54 -1143.00, 8.00, 19.28, 3.54 -1144.00, 8.00, 19.88, 3.53 -1145.00, 8.00, 18.40, 3.55 -1146.00, 8.00, 20.60, 3.54 -1147.00, 8.00, 19.90, 3.53 -1148.00, 8.00, 20.88, 3.54 -1149.00, 8.00, 18.87, 3.54 -1150.00, 8.00, 21.98, 3.54 -1151.00, 8.00, 19.65, 3.53 -1152.00, 8.00, 20.97, 3.55 -1153.00, 8.00, 19.32, 3.54 -1154.00, 8.00, 19.51, 3.53 -1155.00, 8.00, 20.32, 3.52 -1156.00, 8.00, 20.00, 3.53 -1157.00, 8.00, 17.26, 3.53 -1158.00, 8.00, 20.83, 3.53 -1159.00, 8.00, 20.13, 3.55 -1160.00, 8.00, 20.49, 3.54 -1161.00, 8.00, 20.77, 3.53 -1162.00, 8.00, 18.89, 3.52 -1163.00, 8.00, 21.48, 3.53 -1164.00, 8.00, 19.53, 3.54 -1165.00, 8.00, 20.10, 3.53 -1166.00, 8.00, 21.66, 3.51 -1167.00, 8.00, 19.44, 3.52 -1168.00, 8.00, 20.47, 3.55 -1169.00, 8.00, 21.89, 3.53 -1170.00, 8.00, 19.52, 3.54 -1171.00, 8.00, 18.41, 3.53 -1172.00, 8.00, 19.86, 3.53 -1173.00, 8.00, 18.51, 3.52 -1174.00, 8.00, 20.95, 3.52 -1175.00, 8.00, 19.42, 3.52 -1176.00, 8.00, 19.00, 3.53 -1177.00, 8.00, 19.76, 3.53 -1178.00, 8.00, 20.76, 3.55 -1179.00, 8.00, 18.27, 3.52 -1180.00, 8.00, 22.83, 3.55 -1181.00, 8.00, 19.31, 3.53 -1182.00, 8.00, 19.05, 3.54 -1183.00, 8.00, 20.44, 3.53 -1184.00, 8.00, 20.23, 3.53 -1185.00, 8.00, 20.29, 3.53 -1186.00, 8.00, 21.67, 3.51 -1187.00, 8.00, 19.05, 3.54 -1188.00, 8.00, 19.90, 3.53 -1189.00, 8.00, 18.97, 3.55 -1190.00, 8.00, 21.29, 3.53 -1191.00, 8.00, 20.15, 3.50 -1192.00, 8.00, 20.91, 3.53 -1193.00, 8.00, 19.46, 3.53 -1194.00, 8.00, 20.85, 3.50 -1195.00, 8.00, 20.09, 3.53 -1196.00, 8.00, 21.01, 3.53 -1197.00, 8.00, 19.00, 3.52 -1198.00, 8.00, 20.26, 3.52 -1199.00, 8.00, 20.50, 3.53 -1200.00, 8.00, 21.96, 3.53 -1201.00, 8.00, 20.29, 3.51 -1202.00, 8.00, 22.19, 3.53 -1203.00, 8.00, 22.66, 3.55 -1204.00, 8.00, 18.76, 3.54 -1205.00, 8.00, 21.40, 3.51 -1206.00, 8.00, 20.04, 3.53 -1207.00, 8.00, 19.02, 3.53 -1208.00, 8.00, 18.90, 3.51 -1209.00, 8.00, 20.15, 3.53 -1210.00, 8.00, 20.36, 3.50 -1211.00, 8.00, 19.34, 3.53 -1212.00, 8.00, 20.40, 3.51 -1213.00, 8.00, 21.52, 3.51 -1214.00, 8.00, 19.56, 3.52 -1215.00, 8.00, 23.30, 3.52 -1216.00, 8.00, 20.32, 3.51 -1217.00, 8.00, 20.74, 3.51 -1218.00, 8.00, 20.88, 3.52 -1219.00, 8.00, 19.05, 3.51 -1220.00, 8.00, 21.28, 3.52 -1221.00, 8.00, 21.36, 3.54 -1222.00, 8.00, 20.12, 3.52 -1223.00, 8.00, 20.59, 3.52 -1224.00, 8.00, 19.47, 3.52 -1225.00, 8.00, 17.69, 3.50 -1226.00, 8.00, 20.71, 3.52 -1227.00, 8.00, 21.83, 3.52 -1228.00, 8.00, 20.16, 3.53 -1229.00, 8.00, 19.58, 3.52 -1230.00, 8.00, 20.65, 3.50 -1231.00, 8.00, 21.33, 3.52 -1232.00, 8.00, 19.57, 3.52 -1233.00, 8.00, 21.14, 3.52 -1234.00, 8.00, 20.56, 3.52 -1235.00, 8.00, 18.80, 3.52 -1236.00, 8.00, 19.06, 3.53 -1237.00, 8.00, 20.14, 3.52 -1238.00, 8.00, 20.75, 3.51 -1239.00, 8.00, 20.72, 3.52 -1240.00, 8.00, 19.08, 3.50 -1241.00, 8.00, 20.04, 3.50 -1242.00, 8.00, 19.90, 3.51 -1243.00, 8.00, 20.76, 3.51 -1244.00, 8.00, 19.85, 3.51 -1245.00, 8.00, 20.85, 3.53 -1246.00, 8.00, 20.73, 3.52 -1247.00, 8.00, 20.84, 3.51 -1248.00, 8.00, 20.55, 3.52 -1249.00, 8.00, 20.84, 3.52 -1250.00, 8.00, 20.91, 3.52 -1251.00, 8.00, 19.57, 3.50 -1252.00, 8.00, 20.75, 3.51 -1253.00, 8.00, 20.70, 3.52 -1254.00, 8.00, 20.25, 3.52 -1255.00, 8.00, 18.85, 3.52 -1256.00, 8.00, 19.18, 3.51 -1257.00, 8.00, 21.58, 3.51 -1258.00, 8.00, 18.43, 3.53 -1259.00, 8.00, 20.18, 3.51 -1260.00, 8.00, 20.75, 3.51 -1261.00, 8.00, 21.33, 3.51 -1262.00, 8.00, 20.68, 3.50 -1263.00, 8.00, 20.12, 3.50 -1264.00, 8.00, 19.60, 3.51 -1265.00, 8.00, 18.71, 3.50 -1266.00, 8.00, 21.56, 3.52 -1267.00, 8.00, 21.06, 3.51 -1268.00, 8.00, 19.52, 3.51 -1269.00, 8.00, 21.99, 3.49 -1270.00, 8.00, 19.77, 3.50 -1271.00, 8.00, 20.25, 3.50 -1272.00, 8.00, 21.55, 3.50 -1273.00, 8.00, 20.71, 3.51 -1274.00, 8.00, 19.60, 3.51 -1275.00, 8.00, 19.54, 3.52 -1276.00, 8.00, 20.53, 3.51 -1277.00, 8.00, 20.47, 3.52 -1278.00, 8.00, 19.38, 3.49 -1279.00, 8.00, 19.55, 3.50 -1280.00, 8.00, 20.41, 3.53 -1281.00, 8.00, 19.11, 3.51 -1282.00, 8.00, 19.73, 3.50 -1283.00, 8.00, 20.99, 3.52 -1284.00, 8.00, 21.27, 3.50 -1285.00, 8.00, 20.99, 3.49 -1286.00, 8.00, 18.69, 3.52 -1287.00, 8.00, 20.42, 3.51 -1288.00, 8.00, 20.26, 3.50 -1289.00, 8.00, 19.15, 3.50 -1290.00, 8.00, 21.68, 3.52 -1291.00, 8.00, 19.92, 3.52 -1292.00, 8.00, 19.39, 3.49 -1293.00, 8.00, 19.07, 3.51 -1294.00, 8.00, 19.42, 3.50 -1295.00, 8.00, 20.45, 3.50 -1296.00, 8.00, 21.59, 3.50 -1297.00, 8.00, 21.62, 3.52 -1298.00, 8.00, 20.65, 3.52 -1299.00, 8.00, 22.22, 3.49 -1300.00, 8.00, 20.65, 3.49 -1301.00, 8.00, 18.44, 3.52 -1302.00, 8.00, 20.29, 3.50 -1303.00, 8.00, 21.94, 3.51 -1304.00, 8.00, 20.73, 3.50 -1305.00, 8.00, 21.35, 3.50 -1306.00, 8.00, 19.80, 3.49 -1307.00, 8.00, 20.38, 3.52 -1308.00, 8.00, 18.85, 3.51 -1309.00, 8.00, 19.94, 3.49 -1310.00, 8.00, 21.14, 3.50 -1311.00, 8.00, 20.10, 3.51 -1312.00, 8.00, 19.84, 3.49 -1313.00, 8.00, 19.10, 3.51 -1314.00, 8.00, 20.07, 3.51 -1315.00, 8.00, 20.34, 3.52 -1316.00, 8.00, 18.55, 3.50 -1317.00, 8.00, 18.63, 3.51 -1318.00, 8.00, 20.44, 3.49 -1319.00, 8.00, 20.73, 3.50 -1320.00, 8.00, 18.71, 3.50 -1321.00, 8.00, 20.10, 3.48 -1322.00, 8.00, 17.98, 3.49 -1323.00, 8.00, 20.87, 3.49 -1324.00, 8.00, 21.11, 3.49 -1325.00, 8.00, 21.07, 3.49 -1326.00, 8.00, 21.64, 3.51 -1327.00, 8.00, 17.93, 3.50 -1328.00, 8.00, 20.58, 3.49 -1329.00, 8.00, 20.07, 3.51 -1330.00, 8.00, 19.61, 3.51 -1331.00, 8.00, 19.85, 3.51 -1332.00, 8.00, 17.80, 3.50 -1333.00, 8.00, 20.44, 3.49 -1334.00, 8.00, 20.00, 3.51 -1335.00, 8.00, 19.82, 3.49 -1336.00, 8.00, 21.01, 3.49 -1337.00, 8.00, 20.06, 3.50 -1338.00, 8.00, 20.47, 3.50 -1339.00, 8.00, 20.32, 3.49 -1340.00, 8.00, 19.85, 3.49 -1341.00, 8.00, 21.80, 3.50 -1342.00, 8.00, 19.77, 3.49 -1343.00, 8.00, 20.03, 3.52 -1344.00, 8.00, 19.81, 3.50 -1345.00, 8.00, 19.61, 3.50 -1346.00, 8.00, 19.62, 3.48 -1347.00, 8.00, 20.09, 3.49 -1348.00, 8.00, 20.59, 3.51 -1349.00, 8.00, 20.07, 3.48 -1350.00, 8.00, 18.09, 3.50 -1351.00, 8.00, 20.45, 3.50 -1352.00, 8.00, 20.59, 3.48 -1353.00, 8.00, 19.77, 3.49 -1354.00, 8.00, 19.86, 3.47 -1355.00, 8.00, 21.41, 3.48 -1356.00, 8.00, 18.99, 3.48 -1357.00, 8.00, 19.80, 3.49 -1358.00, 8.00, 20.85, 3.47 -1359.00, 8.00, 21.23, 3.48 -1360.00, 8.00, 20.04, 3.48 -1361.00, 8.00, 19.08, 3.50 -1362.00, 8.00, 18.35, 3.48 -1363.00, 8.00, 22.33, 3.49 -1364.00, 8.00, 17.93, 3.50 -1365.00, 8.00, 19.61, 3.48 -1366.00, 8.00, 19.44, 3.48 -1367.00, 8.00, 21.26, 3.50 -1368.00, 8.00, 19.85, 3.50 -1369.00, 8.00, 20.05, 3.48 -1370.00, 8.00, 21.09, 3.48 -1371.00, 8.00, 19.51, 3.49 -1372.00, 8.00, 20.87, 3.48 -1373.00, 8.00, 17.88, 3.49 -1374.00, 8.00, 19.64, 3.50 -1375.00, 8.00, 21.12, 3.48 -1376.00, 8.00, 19.82, 3.50 -1377.00, 8.00, 18.72, 3.48 -1378.00, 8.00, 20.76, 3.50 -1379.00, 8.00, 20.25, 3.49 -1380.00, 8.00, 20.18, 3.49 -1381.00, 8.00, 20.13, 3.46 -1382.00, 8.00, 20.90, 3.49 -1383.00, 8.00, 21.10, 3.50 -1384.00, 8.00, 20.40, 3.47 -1385.00, 8.00, 17.94, 3.47 -1386.00, 8.00, 21.05, 3.49 -1387.00, 8.00, 19.49, 3.49 -1388.00, 8.00, 18.23, 3.48 -1389.00, 8.00, 19.72, 3.48 -1390.00, 8.00, 18.10, 3.49 -1391.00, 8.00, 19.89, 3.49 -1392.00, 8.00, 19.72, 3.49 -1393.00, 8.00, 20.07, 3.48 -1394.00, 8.00, 19.94, 3.50 -1395.00, 8.00, 20.76, 3.50 -1396.00, 8.00, 21.57, 3.49 -1397.00, 8.00, 21.36, 3.49 -1398.00, 8.00, 17.80, 3.47 -1399.00, 8.00, 20.51, 3.48 -1400.00, 8.00, 20.74, 3.49 -1401.00, 8.00, 19.92, 3.47 -1402.00, 8.00, 21.92, 3.49 -1403.00, 8.00, 19.84, 3.50 -1404.00, 8.00, 19.66, 3.48 -1405.00, 8.00, 18.19, 3.47 -1406.00, 8.00, 19.09, 3.48 -1407.00, 8.00, 19.04, 3.48 -1408.00, 8.00, 18.40, 3.47 -1409.00, 8.00, 19.22, 3.46 -1410.00, 8.00, 20.75, 3.47 -1411.00, 8.00, 20.12, 3.49 -1412.00, 8.00, 18.61, 3.48 -1413.00, 8.00, 20.23, 3.49 -1414.00, 8.00, 18.67, 3.48 -1415.00, 8.00, 20.27, 3.49 -1416.00, 8.00, 20.45, 3.48 -1417.00, 8.00, 20.72, 3.48 -1418.00, 8.00, 20.43, 3.47 -1419.00, 8.00, 19.56, 3.49 -1420.00, 8.00, 21.29, 3.48 -1421.00, 8.00, 19.67, 3.48 -1422.00, 8.00, 20.45, 3.48 -1423.00, 8.00, 20.36, 3.47 -1424.00, 8.00, 19.80, 3.48 -1425.00, 8.00, 21.23, 3.49 -1426.00, 8.00, 19.64, 3.47 -1427.00, 8.00, 20.55, 3.47 -1428.00, 8.00, 19.73, 3.49 -1429.00, 8.00, 20.05, 3.48 -1430.00, 8.00, 20.47, 3.49 -1431.00, 8.00, 20.89, 3.48 -1432.00, 8.00, 21.76, 3.47 -1433.00, 8.00, 19.55, 3.48 -1434.00, 8.00, 20.33, 3.47 -1435.00, 8.00, 19.35, 3.48 -1436.00, 8.00, 20.25, 3.46 -1437.00, 8.00, 19.03, 3.49 -1438.00, 8.00, 19.12, 3.49 -1439.00, 8.00, 20.60, 3.48 -1440.00, 8.00, 19.97, 3.49 -1441.00, 8.00, 18.76, 3.48 -1442.00, 8.00, 20.40, 3.49 -1443.00, 8.00, 20.54, 3.46 -1444.00, 8.00, 19.91, 3.47 -1445.00, 8.00, 18.29, 3.47 -1446.00, 8.00, 20.83, 3.47 -1447.00, 8.00, 20.14, 3.50 -1448.00, 8.00, 19.34, 3.46 -1449.00, 8.00, 20.58, 3.45 -1450.00, 8.00, 20.60, 3.49 -1451.00, 8.00, 18.84, 3.46 -1452.00, 8.00, 20.58, 3.47 -1453.00, 8.00, 20.89, 3.47 -1454.00, 8.00, 18.27, 3.46 -1455.00, 8.00, 19.84, 3.47 -1456.00, 8.00, 19.29, 3.49 -1457.00, 8.00, 21.60, 3.47 -1458.00, 8.00, 18.86, 3.46 -1459.00, 8.00, 19.94, 3.49 -1460.00, 8.00, 20.54, 3.49 -1461.00, 8.00, 18.67, 3.48 -1462.00, 8.00, 19.93, 3.48 -1463.00, 8.00, 20.76, 3.49 -1464.00, 8.00, 19.49, 3.49 -1465.00, 8.00, 20.31, 3.47 -1466.00, 8.00, 21.56, 3.47 -1467.00, 8.00, 21.29, 3.46 -1468.00, 8.00, 20.73, 3.48 -1469.00, 8.00, 19.17, 3.46 -1470.00, 8.00, 20.38, 3.49 -1471.00, 8.00, 20.78, 3.48 -1472.00, 8.00, 20.71, 3.48 -1473.00, 8.00, 20.23, 3.46 -1474.00, 8.00, 20.08, 3.45 -1475.00, 8.00, 20.03, 3.48 -1476.00, 8.00, 22.09, 3.46 -1477.00, 8.00, 20.37, 3.46 -1478.00, 8.00, 18.73, 3.49 -1479.00, 8.00, 20.66, 3.48 -1480.00, 8.00, 19.25, 3.47 -1481.00, 8.00, 19.76, 3.48 -1482.00, 8.00, 20.59, 3.47 -1483.00, 8.00, 20.30, 3.47 -1484.00, 8.00, 20.28, 3.46 -1485.00, 8.00, 20.60, 3.47 -1486.00, 8.00, 20.39, 3.47 -1487.00, 8.00, 18.54, 3.47 -1488.00, 8.00, 22.58, 3.46 -1489.00, 8.00, 20.81, 3.46 -1490.00, 8.00, 19.40, 3.47 -1491.00, 8.00, 18.23, 3.46 -1492.00, 8.00, 20.01, 3.46 -1493.00, 8.00, 20.80, 3.47 -1494.00, 8.00, 20.31, 3.46 -1495.00, 8.00, 21.73, 3.48 -1496.00, 8.00, 19.05, 3.46 -1497.00, 8.00, 18.74, 3.48 -1498.00, 8.00, 20.25, 3.46 -1499.00, 8.00, 19.63, 3.47 -1500.00, 8.00, 20.14, 3.45 -1501.00, 8.00, 19.76, 3.48 -1502.00, 8.00, 20.58, 3.46 -1503.00, 8.00, 18.88, 3.46 -1504.00, 8.00, 19.83, 3.47 -1505.00, 8.00, 19.68, 3.46 -1506.00, 8.00, 17.28, 3.46 -1507.00, 8.00, 20.26, 3.46 -1508.00, 8.00, 19.54, 3.46 -1509.00, 8.00, 20.85, 3.46 -1510.00, 8.00, 19.14, 3.46 -1511.00, 8.00, 20.77, 3.48 -1512.00, 8.00, 19.66, 3.46 -1513.00, 8.00, 21.72, 3.47 -1514.00, 8.00, 21.81, 3.48 -1515.00, 8.00, 20.48, 3.48 -1516.00, 8.00, 20.49, 3.48 -1517.00, 8.00, 20.75, 3.47 -1518.00, 8.00, 19.96, 3.46 -1519.00, 8.00, 19.96, 3.47 -1520.00, 8.00, 20.36, 3.46 -1521.00, 8.00, 17.77, 3.49 -1522.00, 8.00, 19.51, 3.48 -1523.00, 8.00, 20.48, 3.47 -1524.00, 8.00, 21.23, 3.47 -1525.00, 8.00, 19.88, 3.45 -1526.00, 8.00, 19.74, 3.45 -1527.00, 8.00, 20.29, 3.48 -1528.00, 8.00, 21.02, 3.47 -1529.00, 8.00, 19.38, 3.46 -1530.00, 8.00, 18.72, 3.46 -1531.00, 8.00, 20.54, 3.44 -1532.00, 8.00, 18.80, 3.46 -1533.00, 8.00, 18.64, 3.47 -1534.00, 8.00, 20.48, 3.45 -1535.00, 8.00, 21.72, 3.44 -1536.00, 8.00, 17.62, 3.46 -1537.00, 8.00, 20.25, 3.46 -1538.00, 8.00, 19.75, 3.47 -1539.00, 8.00, 19.47, 3.47 -1540.00, 8.00, 21.21, 3.48 -1541.00, 8.00, 21.84, 3.46 -1542.00, 8.00, 20.85, 3.48 -1543.00, 8.00, 20.17, 3.46 -1544.00, 8.00, 21.08, 3.46 -1545.00, 8.00, 20.97, 3.50 -1546.00, 8.00, 18.55, 3.47 -1547.00, 8.00, 19.35, 3.46 -1548.00, 8.00, 22.29, 3.45 -1549.00, 8.00, 20.70, 3.48 -1550.00, 8.00, 18.15, 3.46 -1551.00, 8.00, 19.16, 3.47 -1552.00, 8.00, 21.05, 3.47 -1553.00, 8.00, 19.29, 3.45 -1554.00, 8.00, 19.35, 3.47 -1555.00, 8.00, 20.57, 3.48 -1556.00, 8.00, 19.82, 3.45 -1557.00, 8.00, 19.44, 3.45 -1558.00, 8.00, 20.06, 3.46 -1559.00, 8.00, 20.69, 3.46 -1560.00, 8.00, 20.69, 3.46 -1561.00, 8.00, 19.96, 3.45 -1562.00, 8.00, 20.61, 3.48 -1563.00, 8.00, 19.47, 3.46 -1564.00, 8.00, 19.35, 3.46 -1565.00, 8.00, 19.84, 3.45 -1566.00, 8.00, 19.15, 3.47 -1567.00, 8.00, 20.44, 3.48 -1568.00, 8.00, 19.55, 3.46 -1569.00, 8.00, 22.04, 3.46 -1570.00, 8.00, 19.85, 3.46 -1571.00, 8.00, 22.03, 3.47 -1572.00, 8.00, 20.92, 3.46 -1573.00, 8.00, 20.81, 3.45 -1574.00, 8.00, 21.07, 3.45 -1575.00, 8.00, 22.35, 3.47 -1576.00, 8.00, 19.67, 3.47 -1577.00, 8.00, 17.82, 3.44 -1578.00, 8.00, 19.19, 3.46 -1579.00, 8.00, 19.33, 3.48 -1580.00, 8.00, 20.19, 3.45 -1581.00, 8.00, 20.70, 3.45 -1582.00, 8.00, 19.61, 3.46 -1583.00, 8.00, 19.54, 3.46 -1584.00, 8.00, 20.35, 3.45 -1585.00, 8.00, 20.62, 3.46 -1586.00, 8.00, 21.20, 3.46 -1587.00, 8.00, 18.34, 3.46 -1588.00, 8.00, 19.39, 3.46 -1589.00, 8.00, 19.19, 3.45 -1590.00, 8.00, 18.48, 3.48 -1591.00, 8.00, 20.81, 3.46 -1592.00, 8.00, 20.10, 3.46 -1593.00, 8.00, 20.05, 3.45 -1594.00, 8.00, 19.98, 3.45 -1595.00, 8.00, 18.46, 3.46 -1596.00, 8.00, 18.35, 3.46 -1597.00, 8.00, 20.40, 3.46 -1598.00, 8.00, 19.52, 3.44 -1599.00, 8.00, 20.03, 3.46 -1600.00, 8.00, 20.39, 3.47 -1601.00, 8.00, 20.86, 3.47 -1602.00, 8.00, 20.12, 3.47 -1603.00, 8.00, 21.39, 3.45 -1604.00, 8.00, 21.34, 3.46 -1605.00, 8.00, 20.53, 3.46 -1606.00, 8.00, 20.59, 3.45 -1607.00, 8.00, 20.22, 3.44 -1608.00, 8.00, 18.92, 3.47 -1609.00, 8.00, 18.61, 3.45 -1610.00, 8.00, 16.83, 3.46 -1611.00, 8.00, 20.02, 3.46 -1612.00, 8.00, 20.24, 3.46 -1613.00, 8.00, 19.78, 3.47 -1614.00, 8.00, 17.98, 3.47 -1615.00, 8.00, 20.32, 3.47 -1616.00, 8.00, 19.10, 3.46 -1617.00, 8.00, 18.25, 3.45 -1618.00, 8.00, 18.84, 3.45 -1619.00, 8.00, 20.80, 3.46 -1620.00, 8.00, 20.01, 3.44 -1621.00, 8.00, 20.86, 3.45 -1622.00, 8.00, 18.92, 3.45 -1623.00, 8.00, 18.81, 3.45 -1624.00, 8.00, 20.03, 3.47 -1625.00, 8.00, 19.21, 3.44 -1626.00, 8.00, 17.85, 3.43 -1627.00, 8.00, 20.19, 3.44 -1628.00, 8.00, 20.92, 3.45 -1629.00, 8.00, 20.29, 3.43 -1630.00, 8.00, 20.45, 3.47 -1631.00, 8.00, 19.18, 3.44 -1632.00, 8.00, 20.72, 3.45 -1633.00, 8.00, 19.12, 3.45 -1634.00, 8.00, 19.06, 3.46 -1635.00, 8.00, 18.99, 3.46 -1636.00, 8.00, 19.19, 3.45 -1637.00, 8.00, 20.76, 3.46 -1638.00, 8.00, 18.88, 3.46 -1639.00, 8.00, 21.45, 3.45 -1640.00, 8.00, 20.60, 3.44 -1641.00, 8.00, 21.98, 3.46 -1642.00, 8.00, 21.16, 3.47 -1643.00, 8.00, 19.67, 3.45 -1644.00, 8.00, 20.47, 3.45 -1645.00, 8.00, 20.77, 3.44 -1646.00, 8.00, 18.46, 3.43 -1647.00, 8.00, 20.53, 3.46 -1648.00, 8.00, 22.13, 3.47 -1649.00, 8.00, 19.28, 3.45 -1650.00, 8.00, 19.43, 3.45 -1651.00, 8.00, 19.28, 3.44 -1652.00, 8.00, 20.10, 3.42 -1653.00, 8.00, 20.16, 3.43 -1654.00, 8.00, 20.17, 3.46 -1655.00, 8.00, 20.19, 3.46 -1656.00, 8.00, 19.56, 3.45 -1657.00, 8.00, 21.37, 3.45 -1658.00, 8.00, 19.71, 3.44 -1659.00, 8.00, 19.94, 3.45 -1660.00, 8.00, 21.48, 3.46 -1661.00, 8.00, 19.93, 3.44 -1662.00, 8.00, 20.64, 3.46 -1663.00, 8.00, 20.07, 3.46 -1664.00, 8.00, 18.70, 3.46 -1665.00, 8.00, 19.06, 3.47 -1666.00, 8.00, 21.68, 3.45 -1667.00, 8.00, 19.19, 3.45 -1668.00, 8.00, 20.96, 3.45 -1669.00, 8.00, 19.21, 3.46 -1670.00, 8.00, 20.59, 3.45 -1671.00, 8.00, 18.01, 3.43 -1672.00, 8.00, 19.54, 3.45 -1673.00, 8.00, 19.42, 3.44 -1674.00, 8.00, 18.80, 3.45 -1675.00, 8.00, 21.10, 3.45 -1676.00, 8.00, 19.95, 3.44 -1677.00, 8.00, 19.08, 3.43 -1678.00, 8.00, 20.58, 3.45 -1679.00, 8.00, 19.87, 3.46 -1680.00, 8.00, 19.40, 3.44 -1681.00, 8.00, 20.67, 3.45 -1682.00, 8.00, 20.04, 3.45 -1683.00, 8.00, 19.68, 3.44 -1684.00, 8.00, 20.13, 3.43 -1685.00, 8.00, 21.58, 3.46 -1686.00, 8.00, 18.83, 3.44 -1687.00, 8.00, 20.44, 3.46 -1688.00, 8.00, 20.08, 3.44 -1689.00, 8.00, 20.22, 3.45 -1690.00, 8.00, 20.27, 3.46 -1691.00, 8.00, 19.98, 3.43 -1692.00, 8.00, 20.06, 3.44 -1693.00, 8.00, 21.45, 3.43 -1694.00, 8.00, 19.67, 3.45 -1695.00, 8.00, 17.45, 3.45 -1696.00, 8.00, 20.25, 3.44 -1697.00, 8.00, 20.26, 3.45 -1698.00, 8.00, 22.08, 3.44 -1699.00, 8.00, 20.52, 3.44 -1700.00, 8.00, 21.18, 3.44 -1701.00, 8.00, 20.49, 3.44 -1702.00, 8.00, 21.03, 3.45 -1703.00, 8.00, 21.28, 3.44 -1704.00, 8.00, 18.69, 3.45 -1705.00, 8.00, 20.42, 3.44 -1706.00, 8.00, 16.64, 3.45 -1707.00, 8.00, 20.79, 3.46 -1708.00, 8.00, 20.43, 3.45 -1709.00, 8.00, 20.88, 3.43 -1710.00, 8.00, 18.24, 3.44 -1711.00, 8.00, 18.39, 3.45 -1712.00, 8.00, 20.70, 3.43 -1713.00, 8.00, 20.69, 3.47 -1714.00, 8.00, 20.47, 3.44 -1715.00, 8.00, 20.79, 3.46 -1716.00, 8.00, 20.69, 3.48 -1717.00, 8.00, 21.84, 3.43 -1718.00, 8.00, 20.81, 3.47 -1719.00, 8.00, 19.02, 3.45 -1720.00, 8.00, 20.96, 3.46 -1721.00, 8.00, 21.41, 3.47 -1722.00, 8.00, 19.56, 3.45 -1723.00, 8.00, 19.57, 3.45 -1724.00, 8.00, 21.99, 3.46 -1725.00, 8.00, 20.81, 3.44 -1726.00, 8.00, 19.52, 3.45 -1727.00, 8.00, 21.21, 3.45 -1728.00, 8.00, 20.77, 3.44 -1729.00, 8.00, 19.03, 3.46 -1730.00, 8.00, 18.96, 3.45 -1731.00, 8.00, 20.12, 3.46 -1732.00, 8.00, 19.69, 3.46 -1733.00, 8.00, 18.06, 3.44 -1734.00, 8.00, 19.54, 3.44 -1735.00, 8.00, 18.94, 3.45 -1736.00, 8.00, 20.79, 3.46 -1737.00, 8.00, 18.06, 3.45 -1738.00, 8.00, 21.16, 3.46 -1739.00, 8.00, 19.95, 3.45 -1740.00, 8.00, 21.04, 3.46 -1741.00, 8.00, 17.79, 3.46 -1742.00, 8.00, 21.00, 3.45 -1743.00, 8.00, 19.27, 3.48 -1744.00, 8.00, 19.73, 3.46 -1745.00, 8.00, 22.34, 3.45 -1746.00, 8.00, 22.01, 3.47 -1747.00, 8.00, 19.81, 3.46 -1748.00, 8.00, 18.47, 3.46 -1749.00, 8.00, 19.88, 3.44 -1750.00, 8.00, 19.53, 3.46 -1751.00, 8.00, 19.54, 3.44 -1752.00, 8.00, 19.54, 3.46 -1753.00, 8.00, 18.76, 3.45 -1754.00, 8.00, 19.94, 3.44 -1755.00, 8.00, 17.98, 3.45 -1756.00, 8.00, 19.32, 3.43 -1757.00, 8.00, 20.02, 3.42 -1758.00, 8.00, 20.82, 3.45 -1759.00, 8.00, 18.87, 3.46 -1760.00, 8.00, 19.26, 3.44 -1761.00, 8.00, 20.19, 3.44 -1762.00, 8.00, 18.83, 3.44 -1763.00, 8.00, 21.22, 3.44 -1764.00, 8.00, 20.84, 3.45 -1765.00, 8.00, 18.93, 3.45 -1766.00, 8.00, 19.60, 3.45 -1767.00, 8.00, 20.64, 3.45 -1768.00, 8.00, 19.28, 3.46 -1769.00, 8.00, 19.88, 3.44 -1770.00, 8.00, 19.28, 3.44 -1771.00, 8.00, 20.07, 3.43 -1772.00, 8.00, 18.40, 3.45 -1773.00, 8.00, 19.32, 3.45 -1774.00, 8.00, 17.52, 3.45 -1775.00, 8.00, 19.37, 3.45 -1776.00, 8.00, 20.15, 3.44 -1777.00, 8.00, 20.38, 3.44 -1778.00, 8.00, 20.65, 3.45 -1779.00, 8.00, 20.88, 3.47 -1780.00, 8.00, 19.72, 3.47 -1781.00, 8.00, 20.02, 3.45 -1782.00, 8.00, 20.96, 3.44 -1783.00, 8.00, 18.45, 3.45 -1784.00, 8.00, 18.93, 3.44 -1785.00, 8.00, 18.22, 3.44 -1786.00, 8.00, 20.33, 3.43 -1787.00, 8.00, 20.43, 3.44 -1788.00, 8.00, 20.47, 3.44 -1789.00, 8.00, 19.36, 3.44 -1790.00, 8.00, 20.14, 3.45 -1791.00, 8.00, 19.41, 3.44 -1792.00, 8.00, 19.71, 3.46 -1793.00, 8.00, 20.07, 3.45 -1794.00, 8.00, 19.90, 3.45 -1795.00, 8.00, 19.76, 3.45 -1796.00, 8.00, 19.59, 3.46 -1797.00, 8.00, 19.75, 3.45 -1798.00, 8.00, 19.14, 3.45 -1799.00, 8.00, 20.65, 3.45 -1800.00, 8.00, 19.35, 3.45 -1801.00, 8.00, 19.89, 3.44 -1802.00, 8.00, 19.19, 3.46 -1803.00, 8.00, 18.42, 3.44 -1804.00, 8.00, 20.48, 3.45 -1805.00, 8.00, 21.17, 3.47 -1806.00, 8.00, 20.30, 3.45 -1807.00, 8.00, 19.76, 3.44 -1808.00, 8.00, 19.29, 3.45 -1809.00, 8.00, 19.47, 3.44 -1810.00, 8.00, 20.45, 3.44 -1811.00, 8.00, 20.90, 3.42 -1812.00, 8.00, 18.38, 3.44 -1813.00, 8.00, 20.44, 3.44 -1814.00, 8.00, 18.49, 3.43 -1815.00, 8.00, 19.98, 3.43 -1816.00, 8.00, 20.58, 3.46 -1817.00, 8.00, 19.61, 3.46 -1818.00, 8.00, 21.63, 3.44 -1819.00, 8.00, 19.93, 3.43 -1820.00, 8.00, 19.83, 3.44 -1821.00, 8.00, 18.31, 3.43 -1822.00, 8.00, 22.27, 3.45 -1823.00, 8.00, 18.58, 3.45 -1824.00, 8.00, 20.31, 3.44 -1825.00, 8.00, 19.21, 3.45 -1826.00, 8.00, 18.91, 3.45 -1827.00, 8.00, 21.79, 3.44 -1828.00, 8.00, 20.75, 3.45 -1829.00, 8.00, 19.41, 3.46 -1830.00, 8.00, 20.04, 3.45 -1831.00, 8.00, 19.85, 3.44 -1832.00, 8.00, 19.55, 3.44 -1833.00, 8.00, 18.83, 3.42 -1834.00, 8.00, 19.53, 3.44 -1835.00, 8.00, 19.12, 3.45 -1836.00, 8.00, 20.79, 3.43 -1837.00, 8.00, 19.47, 3.46 -1838.00, 8.00, 19.23, 3.44 -1839.00, 8.00, 20.08, 3.43 -1840.00, 8.00, 19.07, 3.44 -1841.00, 8.00, 19.50, 3.45 -1842.00, 8.00, 19.55, 3.44 -1843.00, 8.00, 18.06, 3.45 -1844.00, 8.00, 18.03, 3.45 -1845.00, 8.00, 19.24, 3.44 -1846.00, 8.00, 21.17, 3.45 -1847.00, 8.00, 19.77, 3.43 -1848.00, 8.00, 18.57, 3.43 -1849.00, 8.00, 20.76, 3.43 -1850.00, 8.00, 19.62, 3.46 -1851.00, 8.00, 20.61, 3.44 -1852.00, 8.00, 20.26, 3.43 -1853.00, 8.00, 20.85, 3.44 -1854.00, 8.00, 19.48, 3.43 -1855.00, 8.00, 19.11, 3.45 -1856.00, 8.00, 20.43, 3.45 -1857.00, 8.00, 19.33, 3.44 -1858.00, 8.00, 19.03, 3.45 -1859.00, 8.00, 18.80, 3.45 -1860.00, 8.00, 19.38, 3.43 -1861.00, 8.00, 17.84, 3.46 -1862.00, 8.00, 19.47, 3.44 -1863.00, 8.00, 21.20, 3.45 -1864.00, 8.00, 21.00, 3.44 -1865.00, 8.00, 20.62, 3.45 -1866.00, 8.00, 20.07, 3.44 -1867.00, 8.00, 19.36, 3.47 -1868.00, 8.00, 20.60, 3.44 -1869.00, 8.00, 19.27, 3.45 -1870.00, 8.00, 18.67, 3.45 -1871.00, 8.00, 18.07, 3.43 -1872.00, 8.00, 19.59, 3.46 -1873.00, 8.00, 19.87, 3.45 -1874.00, 8.00, 19.25, 3.45 -1875.00, 8.00, 21.53, 3.46 -1876.00, 8.00, 20.40, 3.44 -1877.00, 8.00, 19.85, 3.45 -1878.00, 8.00, 19.52, 3.42 -1879.00, 8.00, 19.55, 3.45 -1880.00, 8.00, 20.79, 3.44 -1881.00, 8.00, 20.43, 3.45 -1882.00, 8.00, 20.28, 3.43 -1883.00, 8.00, 19.13, 3.44 -1884.00, 8.00, 19.72, 3.46 -1885.00, 8.00, 22.18, 3.44 -1886.00, 8.00, 19.39, 3.43 -1887.00, 8.00, 20.22, 3.44 -1888.00, 8.00, 19.08, 3.43 -1889.00, 8.00, 19.57, 3.42 -1890.00, 8.00, 20.51, 3.45 -1891.00, 8.00, 20.00, 3.45 -1892.00, 8.00, 20.61, 3.42 -1893.00, 8.00, 19.49, 3.43 -1894.00, 8.00, 21.35, 3.44 -1895.00, 8.00, 19.13, 3.45 -1896.00, 8.00, 20.48, 3.45 -1897.00, 8.00, 20.71, 3.45 -1898.00, 8.00, 19.55, 3.44 -1899.00, 8.00, 18.60, 3.44 -1900.00, 8.00, 19.70, 3.45 -1901.00, 8.00, 19.60, 3.43 -1902.00, 8.00, 18.83, 3.45 -1903.00, 8.00, 20.06, 3.45 -1904.00, 8.00, 20.12, 3.45 -1905.00, 8.00, 20.59, 3.43 -1906.00, 8.00, 20.17, 3.44 -1907.00, 8.00, 20.26, 3.44 -1908.00, 8.00, 19.87, 3.43 -1909.00, 8.00, 19.77, 3.44 -1910.00, 8.00, 21.23, 3.44 -1911.00, 8.00, 19.27, 3.43 -1912.00, 8.00, 20.33, 3.43 -1913.00, 8.00, 21.99, 3.43 -1914.00, 8.00, 20.54, 3.46 -1915.00, 8.00, 19.69, 3.45 -1916.00, 8.00, 20.59, 3.44 -1917.00, 8.00, 19.74, 3.45 -1918.00, 8.00, 20.53, 3.42 -1919.00, 8.00, 19.42, 3.46 -1920.00, 8.00, 20.48, 3.43 -1921.00, 8.00, 19.33, 3.45 -1922.00, 8.00, 19.68, 3.44 -1923.00, 8.00, 20.44, 3.44 -1924.00, 8.00, 19.14, 3.44 -1925.00, 8.00, 21.11, 3.45 -1926.00, 8.00, 20.17, 3.46 -1927.00, 8.00, 21.91, 3.46 -1928.00, 8.00, 18.17, 3.45 -1929.00, 8.00, 20.80, 3.43 -1930.00, 8.00, 18.82, 3.43 -1931.00, 8.00, 19.91, 3.44 -1932.00, 8.00, 20.77, 3.46 -1933.00, 8.00, 19.03, 3.45 -1934.00, 8.00, 21.51, 3.44 -1935.00, 8.00, 19.33, 3.44 -1936.00, 8.00, 20.58, 3.43 -1937.00, 8.00, 19.73, 3.45 -1938.00, 8.00, 19.45, 3.43 -1939.00, 8.00, 19.10, 3.43 -1940.00, 8.00, 20.39, 3.44 -1941.00, 8.00, 20.17, 3.45 -1942.00, 8.00, 21.93, 3.45 -1943.00, 8.00, 20.60, 3.46 -1944.00, 8.00, 20.66, 3.46 -1945.00, 8.00, 19.93, 3.44 -1946.00, 8.00, 20.79, 3.44 -1947.00, 8.00, 19.33, 3.45 -1948.00, 8.00, 20.42, 3.43 -1949.00, 8.00, 19.24, 3.44 -1950.00, 8.00, 18.32, 3.44 -1951.00, 8.00, 18.45, 3.42 -1952.00, 8.00, 21.05, 3.44 -1953.00, 8.00, 18.75, 3.44 -1954.00, 8.00, 19.84, 3.44 -1955.00, 8.00, 20.75, 3.43 -1956.00, 8.00, 20.12, 3.44 -1957.00, 8.00, 19.11, 3.43 -1958.00, 8.00, 21.00, 3.44 -1959.00, 8.00, 19.78, 3.45 -1960.00, 8.00, 20.14, 3.43 -1961.00, 8.00, 20.10, 3.45 -1962.00, 8.00, 19.69, 3.43 -1963.00, 8.00, 19.57, 3.44 -1964.00, 8.00, 18.22, 3.45 -1965.00, 8.00, 20.97, 3.44 -1966.00, 8.00, 19.78, 3.44 -1967.00, 8.00, 19.11, 3.46 -1968.00, 8.00, 19.42, 3.44 -1969.00, 8.00, 19.91, 3.43 -1970.00, 8.00, 20.06, 3.43 -1971.00, 8.00, 20.38, 3.46 -1972.00, 8.00, 19.11, 3.42 -1973.00, 8.00, 18.96, 3.42 -1974.00, 8.00, 19.49, 3.44 -1975.00, 8.00, 20.71, 3.43 -1976.00, 8.00, 20.82, 3.42 -1977.00, 8.00, 20.39, 3.41 -1978.00, 8.00, 19.53, 3.44 -1979.00, 8.00, 18.84, 3.42 -1980.00, 8.00, 18.04, 3.43 -1981.00, 8.00, 18.36, 3.43 -1982.00, 8.00, 19.37, 3.44 -1983.00, 8.00, 22.11, 3.43 -1984.00, 8.00, 20.51, 3.42 -1985.00, 8.00, 18.32, 3.44 -1986.00, 8.00, 17.28, 3.44 -1987.00, 8.00, 20.19, 3.44 -1988.00, 8.00, 20.12, 3.42 -1989.00, 8.00, 20.54, 3.42 -1990.00, 8.00, 18.30, 3.44 -1991.00, 8.00, 19.46, 3.43 -1992.00, 8.00, 18.80, 3.43 -1993.00, 8.00, 18.12, 3.43 -1994.00, 8.00, 19.12, 3.43 -1995.00, 8.00, 20.39, 3.44 -1996.00, 8.00, 19.40, 3.43 -1997.00, 8.00, 20.81, 3.44 -1998.00, 8.00, 19.50, 3.42 -1999.00, 8.00, 18.82, 3.44 -2000.00, 8.00, 19.91, 3.44 -2001.00, 8.00, 20.64, 3.43 -2002.00, 8.00, 20.92, 3.43 -2003.00, 8.00, 20.10, 3.43 -2004.00, 8.00, 19.55, 3.43 -2005.00, 8.00, 20.77, 3.44 -2006.00, 8.00, 19.46, 3.45 -2007.00, 8.00, 20.33, 3.43 -2008.00, 8.00, 19.56, 3.45 -2009.00, 8.00, 20.51, 3.42 -2010.00, 8.00, 21.96, 3.43 -2011.00, 8.00, 19.85, 3.43 -2012.00, 8.00, 20.72, 3.44 -2013.00, 8.00, 20.31, 3.44 -2014.00, 8.00, 20.20, 3.43 -2015.00, 8.00, 18.90, 3.43 -2016.00, 8.00, 19.63, 3.43 -2017.00, 8.00, 20.26, 3.44 -2018.00, 8.00, 19.81, 3.43 -2019.00, 8.00, 20.16, 3.43 -2020.00, 8.00, 20.71, 3.44 -2021.00, 8.00, 21.75, 3.43 -2022.00, 8.00, 19.97, 3.41 -2023.00, 8.00, 19.32, 3.45 -2024.00, 8.00, 20.83, 3.45 -2025.00, 8.00, 19.30, 3.42 -2026.00, 8.00, 19.64, 3.46 -2027.00, 8.00, 21.83, 3.42 -2028.00, 8.00, 19.14, 3.44 -2029.00, 8.00, 19.67, 3.42 -2030.00, 8.00, 19.80, 3.43 -2031.00, 8.00, 18.78, 3.42 -2032.00, 8.00, 18.74, 3.42 -2033.00, 8.00, 19.19, 3.43 -2034.00, 8.00, 18.90, 3.43 -2035.00, 8.00, 19.98, 3.42 -2036.00, 8.00, 19.86, 3.42 -2037.00, 8.00, 19.14, 3.44 -2038.00, 8.00, 20.11, 3.42 -2039.00, 8.00, 19.92, 3.42 -2040.00, 8.00, 19.15, 3.44 -2041.00, 8.00, 21.25, 3.43 -2042.00, 8.00, 18.69, 3.44 -2043.00, 8.00, 20.84, 3.40 -2044.00, 8.00, 21.12, 3.42 -2045.00, 8.00, 20.64, 3.44 -2046.00, 8.00, 19.66, 3.43 -2047.00, 8.00, 18.92, 3.42 -2048.00, 8.00, 19.88, 3.42 -2049.00, 8.00, 19.93, 3.42 -2050.00, 8.00, 18.78, 3.42 -2051.00, 8.00, 20.80, 3.43 -2052.00, 8.00, 19.37, 3.44 -2053.00, 8.00, 20.77, 3.43 -2054.00, 8.00, 20.32, 3.44 -2055.00, 8.00, 20.61, 3.43 -2056.00, 8.00, 19.58, 3.42 -2057.00, 8.00, 18.07, 3.42 -2058.00, 8.00, 20.64, 3.42 -2059.00, 8.00, 19.75, 3.43 -2060.00, 8.00, 19.21, 3.43 -2061.00, 8.00, 18.68, 3.43 -2062.00, 8.00, 19.79, 3.42 -2063.00, 8.00, 20.10, 3.43 -2064.00, 8.00, 19.40, 3.42 -2065.00, 8.00, 20.29, 3.44 -2066.00, 8.00, 19.23, 3.42 -2067.00, 8.00, 20.08, 3.44 -2068.00, 8.00, 20.60, 3.41 -2069.00, 8.00, 19.33, 3.43 -2070.00, 8.00, 20.97, 3.43 -2071.00, 8.00, 20.45, 3.42 -2072.00, 8.00, 19.74, 3.42 -2073.00, 8.00, 19.51, 3.42 -2074.00, 8.00, 17.60, 3.44 -2075.00, 8.00, 21.66, 3.42 -2076.00, 8.00, 20.99, 3.43 -2077.00, 8.00, 18.30, 3.41 -2078.00, 8.00, 20.89, 3.43 -2079.00, 8.00, 21.56, 3.41 -2080.00, 8.00, 21.12, 3.43 -2081.00, 8.00, 19.76, 3.42 -2082.00, 8.00, 19.20, 3.41 -2083.00, 8.00, 20.55, 3.43 -2084.00, 8.00, 20.31, 3.42 -2085.00, 8.00, 19.63, 3.41 -2086.00, 8.00, 19.97, 3.42 -2087.00, 8.00, 19.82, 3.42 -2088.00, 8.00, 18.70, 3.41 -2089.00, 8.00, 20.87, 3.41 -2090.00, 8.00, 21.25, 3.42 -2091.00, 8.00, 21.36, 3.40 -2092.00, 8.00, 19.88, 3.41 -2093.00, 8.00, 20.84, 3.44 -2094.00, 8.00, 20.75, 3.42 -2095.00, 8.00, 19.17, 3.40 -2096.00, 8.00, 21.91, 3.43 -2097.00, 8.00, 19.71, 3.42 -2098.00, 8.00, 18.99, 3.41 -2099.00, 8.00, 20.30, 3.43 -2100.00, 8.00, 20.26, 3.44 -2101.00, 8.00, 20.44, 3.41 -2102.00, 8.00, 19.03, 3.42 -2103.00, 8.00, 18.58, 3.42 -2104.00, 8.00, 19.17, 3.44 -2105.00, 8.00, 20.04, 3.42 -2106.00, 8.00, 18.08, 3.41 -2107.00, 8.00, 19.10, 3.43 -2108.00, 8.00, 21.21, 3.42 -2109.00, 8.00, 20.31, 3.42 -2110.00, 8.00, 19.96, 3.41 -2111.00, 8.00, 18.97, 3.42 -2112.00, 8.00, 20.52, 3.42 -2113.00, 8.00, 20.35, 3.43 -2114.00, 8.00, 19.48, 3.41 -2115.00, 8.00, 19.95, 3.42 -2116.00, 8.00, 20.06, 3.40 -2117.00, 8.00, 19.32, 3.42 -2118.00, 8.00, 18.89, 3.40 -2119.00, 8.00, 19.90, 3.42 -2120.00, 8.00, 20.66, 3.40 -2121.00, 8.00, 19.70, 3.41 -2122.00, 8.00, 18.94, 3.42 -2123.00, 8.00, 21.66, 3.42 -2124.00, 8.00, 20.95, 3.43 -2125.00, 8.00, 19.48, 3.43 -2126.00, 8.00, 19.08, 3.42 -2127.00, 8.00, 18.79, 3.41 -2128.00, 8.00, 19.92, 3.42 -2129.00, 8.00, 21.63, 3.41 -2130.00, 8.00, 21.49, 3.41 -2131.00, 8.00, 19.37, 3.42 -2132.00, 8.00, 21.53, 3.42 -2133.00, 8.00, 18.68, 3.42 -2134.00, 8.00, 21.55, 3.42 -2135.00, 8.00, 21.20, 3.41 -2136.00, 8.00, 18.66, 3.42 -2137.00, 8.00, 19.93, 3.40 -2138.00, 8.00, 21.42, 3.42 -2139.00, 8.00, 20.21, 3.42 -2140.00, 8.00, 21.61, 3.43 -2141.00, 8.00, 19.98, 3.41 -2142.00, 8.00, 18.80, 3.42 -2143.00, 8.00, 21.08, 3.42 -2144.00, 8.00, 19.54, 3.40 -2145.00, 8.00, 20.02, 3.42 -2146.00, 8.00, 20.80, 3.43 -2147.00, 8.00, 21.10, 3.40 -2148.00, 8.00, 18.90, 3.41 -2149.00, 8.00, 20.94, 3.40 -2150.00, 8.00, 18.69, 3.40 -2151.00, 8.00, 18.33, 3.41 -2152.00, 8.00, 20.12, 3.40 -2153.00, 8.00, 18.49, 3.41 -2154.00, 8.00, 20.10, 3.40 -2155.00, 8.00, 18.69, 3.39 -2156.00, 8.00, 21.06, 3.42 -2157.00, 8.00, 20.00, 3.41 -2158.00, 8.00, 20.01, 3.41 -2159.00, 8.00, 20.01, 3.42 -2160.00, 8.00, 19.61, 3.40 -2161.00, 8.00, 20.36, 3.40 -2162.00, 8.00, 20.10, 3.42 -2163.00, 8.00, 20.03, 3.41 -2164.00, 8.00, 21.11, 3.42 -2165.00, 8.00, 17.77, 3.41 -2166.00, 8.00, 20.08, 3.40 -2167.00, 8.00, 19.42, 3.39 -2168.00, 8.00, 18.45, 3.40 -2169.00, 8.00, 18.61, 3.42 -2170.00, 8.00, 19.31, 3.39 -2171.00, 8.00, 20.82, 3.39 -2172.00, 8.00, 19.26, 3.40 -2173.00, 8.00, 21.28, 3.41 -2174.00, 8.00, 20.22, 3.41 -2175.00, 8.00, 20.77, 3.41 -2176.00, 8.00, 20.92, 3.41 -2177.00, 8.00, 19.35, 3.39 -2178.00, 8.00, 17.06, 3.40 -2179.00, 8.00, 20.97, 3.40 -2180.00, 8.00, 19.67, 3.41 -2181.00, 8.00, 19.98, 3.38 -2182.00, 8.00, 18.54, 3.41 -2183.00, 8.00, 18.42, 3.41 -2184.00, 8.00, 19.37, 3.41 -2185.00, 8.00, 18.31, 3.40 -2186.00, 8.00, 19.71, 3.40 -2187.00, 8.00, 20.04, 3.41 -2188.00, 8.00, 18.89, 3.39 -2189.00, 8.00, 19.08, 3.40 -2190.00, 8.00, 21.48, 3.41 -2191.00, 8.00, 20.71, 3.40 -2192.00, 8.00, 20.16, 3.39 -2193.00, 8.00, 20.79, 3.42 -2194.00, 8.00, 20.47, 3.41 -2195.00, 8.00, 18.74, 3.41 -2196.00, 8.00, 19.13, 3.40 -2197.00, 8.00, 20.17, 3.40 -2198.00, 8.00, 20.53, 3.40 -2199.00, 8.00, 20.10, 3.41 -2200.00, 8.00, 18.68, 3.40 -2201.00, 8.00, 19.76, 3.39 -2202.00, 8.00, 19.10, 3.43 -2203.00, 8.00, 18.63, 3.40 -2204.00, 8.00, 19.92, 3.41 -2205.00, 8.00, 21.98, 3.40 -2206.00, 8.00, 19.99, 3.41 -2207.00, 8.00, 22.12, 3.40 -2208.00, 8.00, 20.10, 3.39 -2209.00, 8.00, 20.34, 3.39 -2210.00, 8.00, 21.35, 3.40 -2211.00, 8.00, 19.57, 3.40 -2212.00, 8.00, 17.59, 3.39 -2213.00, 8.00, 20.54, 3.39 -2214.00, 8.00, 21.48, 3.39 -2215.00, 8.00, 20.33, 3.40 -2216.00, 8.00, 19.95, 3.41 -2217.00, 8.00, 19.68, 3.38 -2218.00, 8.00, 19.04, 3.39 -2219.00, 8.00, 20.58, 3.39 -2220.00, 8.00, 19.80, 3.39 -2221.00, 8.00, 19.08, 3.41 -2222.00, 8.00, 20.32, 3.38 -2223.00, 8.00, 19.95, 3.40 -2224.00, 8.00, 18.93, 3.41 -2225.00, 8.00, 20.06, 3.39 -2226.00, 8.00, 18.28, 3.39 -2227.00, 8.00, 16.52, 3.39 -2228.00, 8.00, 18.93, 3.41 -2229.00, 8.00, 21.68, 3.38 -2230.00, 8.00, 19.47, 3.38 -2231.00, 8.00, 20.28, 3.38 -2232.00, 8.00, 18.52, 3.40 -2233.00, 8.00, 19.14, 3.38 -2234.00, 8.00, 19.98, 3.40 -2235.00, 8.00, 20.36, 3.39 -2236.00, 8.00, 20.25, 3.38 -2237.00, 8.00, 20.81, 3.38 -2238.00, 8.00, 20.31, 3.38 -2239.00, 8.00, 21.24, 3.40 -2240.00, 8.00, 19.50, 3.39 -2241.00, 8.00, 17.97, 3.39 -2242.00, 8.00, 19.68, 3.37 -2243.00, 8.00, 19.93, 3.39 -2244.00, 8.00, 18.91, 3.37 -2245.00, 8.00, 20.79, 3.40 -2246.00, 8.00, 19.76, 3.39 -2247.00, 8.00, 21.38, 3.39 -2248.00, 8.00, 20.12, 3.37 -2249.00, 8.00, 19.79, 3.40 -2250.00, 8.00, 20.02, 3.39 -2251.00, 8.00, 19.92, 3.39 -2252.00, 8.00, 20.44, 3.38 -2253.00, 8.00, 20.09, 3.38 -2254.00, 8.00, 20.28, 3.41 -2255.00, 8.00, 19.12, 3.39 -2256.00, 8.00, 19.62, 3.39 -2257.00, 8.00, 20.82, 3.38 -2258.00, 8.00, 21.36, 3.38 -2259.00, 8.00, 19.78, 3.39 -2260.00, 8.00, 20.29, 3.41 -2261.00, 8.00, 21.16, 3.40 -2262.00, 8.00, 18.64, 3.38 -2263.00, 8.00, 18.77, 3.41 -2264.00, 8.00, 17.86, 3.38 -2265.00, 8.00, 21.36, 3.37 -2266.00, 8.00, 19.30, 3.39 -2267.00, 8.00, 20.33, 3.39 -2268.00, 8.00, 21.02, 3.39 -2269.00, 8.00, 20.30, 3.39 -2270.00, 8.00, 19.16, 3.37 -2271.00, 8.00, 18.47, 3.39 -2272.00, 8.00, 19.98, 3.39 -2273.00, 8.00, 21.99, 3.40 -2274.00, 8.00, 20.56, 3.38 -2275.00, 8.00, 18.52, 3.39 -2276.00, 8.00, 21.78, 3.37 -2277.00, 8.00, 20.24, 3.39 -2278.00, 8.00, 20.27, 3.39 -2279.00, 8.00, 18.85, 3.40 -2280.00, 8.00, 19.12, 3.36 -2281.00, 8.00, 18.22, 3.40 -2282.00, 8.00, 18.27, 3.39 -2283.00, 8.00, 21.02, 3.37 -2284.00, 8.00, 18.98, 3.40 -2285.00, 8.00, 21.09, 3.38 -2286.00, 8.00, 20.42, 3.37 -2287.00, 8.00, 19.89, 3.38 -2288.00, 8.00, 19.51, 3.37 -2289.00, 8.00, 20.27, 3.38 -2290.00, 8.00, 19.85, 3.37 -2291.00, 8.00, 19.84, 3.38 -2292.00, 8.00, 20.26, 3.38 -2293.00, 8.00, 20.93, 3.38 -2294.00, 8.00, 19.55, 3.37 -2295.00, 8.00, 19.25, 3.38 -2296.00, 8.00, 19.29, 3.37 -2297.00, 8.00, 21.38, 3.38 -2298.00, 8.00, 20.14, 3.37 -2299.00, 8.00, 21.80, 3.38 -2300.00, 8.00, 18.68, 3.36 -2301.00, 8.00, 20.46, 3.37 -2302.00, 8.00, 21.83, 3.36 -2303.00, 8.00, 20.84, 3.37 -2304.00, 8.00, 20.10, 3.37 -2305.00, 8.00, 18.70, 3.36 -2306.00, 8.00, 18.69, 3.38 -2307.00, 8.00, 19.04, 3.36 -2308.00, 8.00, 19.80, 3.35 -2309.00, 8.00, 18.78, 3.37 -2310.00, 8.00, 18.45, 3.39 -2311.00, 8.00, 20.94, 3.36 -2312.00, 8.00, 21.62, 3.37 -2313.00, 8.00, 19.61, 3.37 -2314.00, 8.00, 19.19, 3.38 -2315.00, 8.00, 20.15, 3.38 -2316.00, 8.00, 20.97, 3.36 -2317.00, 8.00, 20.61, 3.35 -2318.00, 8.00, 19.65, 3.37 -2319.00, 8.00, 18.56, 3.36 -2320.00, 8.00, 19.44, 3.36 -2321.00, 8.00, 21.27, 3.37 -2322.00, 8.00, 20.03, 3.38 -2323.00, 8.00, 20.82, 3.36 -2324.00, 8.00, 18.83, 3.35 -2325.00, 8.00, 17.47, 3.38 -2326.00, 8.00, 17.68, 3.38 -2327.00, 8.00, 22.39, 3.37 -2328.00, 8.00, 20.83, 3.37 -2329.00, 8.00, 20.43, 3.38 -2330.00, 8.00, 19.98, 3.36 -2331.00, 8.00, 17.69, 3.38 -2332.00, 8.00, 19.61, 3.36 -2333.00, 8.00, 19.54, 3.38 -2334.00, 8.00, 18.95, 3.36 -2335.00, 8.00, 18.73, 3.37 -2336.00, 8.00, 20.40, 3.37 -2337.00, 8.00, 21.24, 3.36 -2338.00, 8.00, 20.26, 3.37 -2339.00, 8.00, 19.42, 3.36 -2340.00, 8.00, 20.52, 3.37 -2341.00, 8.00, 20.01, 3.37 -2342.00, 8.00, 19.97, 3.38 -2343.00, 8.00, 19.80, 3.37 -2344.00, 8.00, 21.33, 3.36 -2345.00, 8.00, 18.55, 3.37 -2346.00, 8.00, 20.23, 3.36 -2347.00, 8.00, 19.26, 3.36 -2348.00, 8.00, 20.67, 3.37 -2349.00, 8.00, 20.38, 3.36 -2350.00, 8.00, 20.84, 3.36 -2351.00, 8.00, 20.26, 3.36 -2352.00, 8.00, 20.26, 3.35 -2353.00, 8.00, 19.86, 3.37 -2354.00, 8.00, 19.90, 3.36 -2355.00, 8.00, 19.62, 3.36 -2356.00, 8.00, 21.18, 3.35 -2357.00, 8.00, 20.22, 3.34 -2358.00, 8.00, 21.74, 3.35 -2359.00, 8.00, 19.63, 3.37 -2360.00, 8.00, 20.75, 3.36 -2361.00, 8.00, 19.96, 3.36 -2362.00, 8.00, 21.30, 3.36 -2363.00, 8.00, 18.87, 3.36 -2364.00, 8.00, 20.16, 3.36 -2365.00, 8.00, 19.82, 3.36 -2366.00, 8.00, 19.93, 3.36 -2367.00, 8.00, 20.35, 3.36 -2368.00, 8.00, 19.80, 3.35 -2369.00, 8.00, 20.37, 3.35 -2370.00, 8.00, 18.55, 3.35 -2371.00, 8.00, 20.68, 3.36 -2372.00, 8.00, 23.02, 3.36 -2373.00, 8.00, 19.25, 3.36 -2374.00, 8.00, 19.96, 3.36 -2375.00, 8.00, 21.33, 3.36 -2376.00, 8.00, 19.45, 3.35 -2377.00, 8.00, 19.58, 3.34 -2378.00, 8.00, 20.74, 3.37 -2379.00, 8.00, 19.37, 3.36 -2380.00, 8.00, 19.63, 3.36 -2381.00, 8.00, 19.55, 3.35 -2382.00, 8.00, 20.00, 3.36 -2383.00, 8.00, 20.81, 3.35 -2384.00, 8.00, 21.35, 3.36 -2385.00, 8.00, 19.66, 3.36 -2386.00, 8.00, 19.52, 3.34 -2387.00, 8.00, 18.67, 3.35 -2388.00, 8.00, 20.18, 3.37 -2389.00, 8.00, 19.62, 3.35 -2390.00, 8.00, 19.23, 3.34 -2391.00, 8.00, 19.44, 3.36 -2392.00, 8.00, 19.76, 3.34 -2393.00, 8.00, 20.96, 3.38 -2394.00, 8.00, 20.40, 3.35 -2395.00, 8.00, 21.15, 3.34 -2396.00, 8.00, 19.71, 3.34 -2397.00, 8.00, 19.76, 3.36 -2398.00, 8.00, 18.79, 3.35 -2399.00, 8.00, 19.87, 3.34 -2400.00, 8.00, 18.86, 3.35 -2401.00, 8.00, 19.61, 3.35 -2402.00, 8.00, 18.77, 3.35 -2403.00, 8.00, 20.21, 3.36 -2404.00, 8.00, 19.24, 3.33 -2405.00, 8.00, 18.24, 3.36 -2406.00, 8.00, 20.13, 3.35 -2407.00, 8.00, 20.34, 3.35 -2408.00, 8.00, 20.81, 3.34 -2409.00, 8.00, 19.53, 3.34 -2410.00, 8.00, 20.75, 3.35 -2411.00, 8.00, 21.86, 3.34 -2412.00, 8.00, 20.37, 3.35 -2413.00, 8.00, 20.29, 3.33 -2414.00, 8.00, 20.01, 3.36 -2415.00, 8.00, 19.36, 3.35 -2416.00, 8.00, 19.94, 3.36 -2417.00, 8.00, 19.04, 3.34 -2418.00, 8.00, 17.62, 3.35 -2419.00, 8.00, 21.59, 3.36 -2420.00, 8.00, 19.96, 3.35 -2421.00, 8.00, 19.38, 3.35 -2422.00, 8.00, 21.35, 3.35 -2423.00, 8.00, 19.83, 3.35 -2424.00, 8.00, 19.35, 3.34 -2425.00, 8.00, 21.65, 3.36 -2426.00, 8.00, 21.82, 3.35 -2427.00, 8.00, 20.80, 3.36 -2428.00, 8.00, 21.42, 3.34 -2429.00, 8.00, 21.67, 3.36 -2430.00, 8.00, 19.44, 3.35 -2431.00, 8.00, 19.73, 3.35 -2432.00, 8.00, 17.97, 3.36 -2433.00, 8.00, 19.37, 3.34 -2434.00, 8.00, 18.10, 3.36 -2435.00, 8.00, 18.50, 3.35 -2436.00, 8.00, 18.64, 3.34 -2437.00, 8.00, 19.39, 3.34 -2438.00, 8.00, 19.10, 3.34 -2439.00, 8.00, 19.37, 3.34 -2440.00, 8.00, 19.03, 3.34 -2441.00, 8.00, 19.99, 3.34 -2442.00, 8.00, 21.84, 3.35 -2443.00, 8.00, 19.19, 3.34 -2444.00, 8.00, 20.62, 3.34 -2445.00, 8.00, 18.17, 3.33 -2446.00, 8.00, 20.96, 3.35 -2447.00, 8.00, 19.40, 3.34 -2448.00, 8.00, 21.85, 3.35 -2449.00, 8.00, 20.60, 3.34 -2450.00, 8.00, 21.03, 3.33 -2451.00, 8.00, 19.31, 3.37 -2452.00, 8.00, 21.75, 3.33 -2453.00, 8.00, 21.34, 3.33 -2454.00, 8.00, 19.63, 3.34 -2455.00, 8.00, 20.79, 3.34 -2456.00, 8.00, 20.76, 3.33 -2457.00, 8.00, 20.76, 3.33 -2458.00, 8.00, 20.96, 3.35 -2459.00, 8.00, 19.84, 3.34 -2460.00, 8.00, 21.77, 3.35 -2461.00, 8.00, 21.61, 3.33 -2462.00, 8.00, 19.16, 3.34 -2463.00, 8.00, 20.03, 3.34 -2464.00, 8.00, 19.23, 3.33 -2465.00, 8.00, 21.04, 3.35 -2466.00, 8.00, 20.42, 3.34 -2467.00, 8.00, 18.20, 3.33 -2468.00, 8.00, 20.35, 3.34 -2469.00, 8.00, 20.76, 3.35 -2470.00, 8.00, 19.75, 3.34 -2471.00, 8.00, 19.18, 3.35 -2472.00, 8.00, 19.66, 3.35 -2473.00, 8.00, 18.29, 3.34 -2474.00, 8.00, 20.14, 3.35 -2475.00, 8.00, 19.23, 3.34 -2476.00, 8.00, 22.37, 3.33 -2477.00, 8.00, 19.46, 3.33 -2478.00, 8.00, 20.28, 3.34 -2479.00, 8.00, 19.51, 3.33 -2480.00, 8.00, 20.41, 3.35 -2481.00, 8.00, 19.65, 3.34 -2482.00, 8.00, 21.84, 3.32 -2483.00, 8.00, 18.22, 3.34 -2484.00, 8.00, 18.56, 3.34 -2485.00, 8.00, 19.28, 3.33 -2486.00, 8.00, 19.89, 3.34 -2487.00, 8.00, 20.06, 3.35 -2488.00, 8.00, 18.77, 3.34 -2489.00, 8.00, 20.11, 3.34 -2490.00, 8.00, 18.83, 3.34 -2491.00, 8.00, 19.07, 3.33 -2492.00, 8.00, 19.84, 3.33 -2493.00, 8.00, 20.77, 3.34 -2494.00, 8.00, 21.37, 3.33 -2495.00, 8.00, 20.33, 3.33 -2496.00, 8.00, 19.56, 3.32 -2497.00, 8.00, 20.39, 3.34 -2498.00, 8.00, 18.56, 3.34 -2499.00, 8.00, 19.31, 3.33 -2500.00, 8.00, 19.56, 3.34 -2501.00, 8.00, 19.90, 3.32 -2502.00, 8.00, 18.98, 3.33 -2503.00, 8.00, 19.05, 3.32 -2504.00, 8.00, 19.37, 3.32 -2505.00, 8.00, 20.04, 3.34 -2506.00, 8.00, 18.92, 3.33 -2507.00, 8.00, 18.73, 3.32 -2508.00, 8.00, 22.69, 3.33 -2509.00, 8.00, 23.55, 3.34 -2510.00, 8.00, 19.91, 3.34 -2511.00, 8.00, 18.81, 3.34 -2512.00, 8.00, 19.51, 3.34 -2513.00, 8.00, 19.62, 3.34 -2514.00, 8.00, 19.59, 3.32 -2515.00, 8.00, 18.63, 3.37 -2516.00, 8.00, 20.37, 3.34 -2517.00, 8.00, 19.44, 3.32 -2518.00, 8.00, 19.27, 3.33 -2519.00, 8.00, 22.84, 3.33 -2520.00, 8.00, 18.30, 3.32 -2521.00, 8.00, 19.15, 3.33 -2522.00, 8.00, 20.49, 3.36 -2523.00, 8.00, 19.44, 3.33 -2524.00, 8.00, 18.50, 3.32 -2525.00, 8.00, 20.46, 3.32 -2526.00, 8.00, 20.38, 3.33 -2527.00, 8.00, 20.41, 3.33 -2528.00, 8.00, 19.06, 3.33 -2529.00, 8.00, 19.72, 3.34 -2530.00, 8.00, 18.54, 3.34 -2531.00, 8.00, 19.75, 3.34 -2532.00, 8.00, 20.33, 3.32 -2533.00, 8.00, 19.44, 3.32 -2534.00, 8.00, 20.58, 3.32 -2535.00, 8.00, 20.94, 3.32 -2536.00, 8.00, 19.12, 3.32 -2537.00, 8.00, 19.11, 3.32 -2538.00, 8.00, 19.42, 3.32 -2539.00, 8.00, 19.17, 3.34 -2540.00, 8.00, 19.22, 3.32 -2541.00, 8.00, 18.99, 3.31 -2542.00, 8.00, 21.22, 3.31 -2543.00, 8.00, 20.44, 3.32 -2544.00, 8.00, 21.92, 3.32 -2545.00, 8.00, 19.06, 3.33 -2546.00, 8.00, 18.95, 3.33 -2547.00, 8.00, 19.13, 3.32 -2548.00, 8.00, 20.17, 3.32 -2549.00, 8.00, 20.12, 3.31 -2550.00, 8.00, 19.88, 3.32 -2551.00, 8.00, 19.27, 3.33 -2552.00, 8.00, 20.46, 3.32 -2553.00, 8.00, 21.28, 3.33 -2554.00, 8.00, 19.60, 3.33 -2555.00, 8.00, 19.04, 3.33 -2556.00, 8.00, 20.40, 3.32 -2557.00, 8.00, 20.03, 3.32 -2558.00, 8.00, 19.96, 3.31 -2559.00, 8.00, 19.17, 3.31 -2560.00, 8.00, 20.03, 3.33 -2561.00, 8.00, 19.84, 3.33 -2562.00, 8.00, 18.97, 3.32 -2563.00, 8.00, 17.61, 3.33 -2564.00, 8.00, 18.42, 3.33 -2565.00, 8.00, 18.16, 3.33 -2566.00, 8.00, 20.00, 3.32 -2567.00, 8.00, 19.75, 3.32 -2568.00, 8.00, 19.36, 3.34 -2569.00, 8.00, 21.78, 3.29 -2570.00, 8.00, 19.67, 3.32 -2571.00, 8.00, 19.71, 3.31 -2572.00, 8.00, 19.45, 3.33 -2573.00, 8.00, 19.42, 3.31 -2574.00, 8.00, 19.44, 3.33 -2575.00, 8.00, 19.19, 3.31 -2576.00, 8.00, 20.20, 3.33 -2577.00, 8.00, 21.11, 3.30 -2578.00, 8.00, 21.06, 3.33 -2579.00, 8.00, 20.43, 3.32 -2580.00, 8.00, 20.34, 3.34 -2581.00, 8.00, 19.87, 3.32 -2582.00, 8.00, 18.50, 3.33 -2583.00, 8.00, 19.99, 3.33 -2584.00, 8.00, 21.56, 3.30 -2585.00, 8.00, 20.02, 3.32 -2586.00, 8.00, 20.70, 3.32 -2587.00, 8.00, 19.75, 3.30 -2588.00, 8.00, 19.85, 3.31 -2589.00, 8.00, 19.78, 3.33 -2590.00, 8.00, 19.47, 3.32 -2591.00, 8.00, 21.11, 3.33 -2592.00, 8.00, 20.74, 3.32 -2593.00, 8.00, 18.42, 3.33 -2594.00, 8.00, 20.50, 3.32 -2595.00, 8.00, 20.09, 3.32 -2596.00, 8.00, 18.33, 3.33 -2597.00, 8.00, 19.43, 3.31 -2598.00, 8.00, 19.08, 3.32 -2599.00, 8.00, 20.51, 3.32 -2600.00, 8.00, 20.67, 3.31 -2601.00, 8.00, 19.08, 3.31 -2602.00, 8.00, 20.17, 3.30 -2603.00, 8.00, 21.39, 3.30 -2604.00, 8.00, 19.97, 3.32 -2605.00, 8.00, 18.53, 3.29 -2606.00, 8.00, 18.94, 3.31 -2607.00, 8.00, 21.38, 3.32 -2608.00, 8.00, 21.01, 3.29 -2609.00, 8.00, 19.83, 3.31 -2610.00, 8.00, 20.42, 3.33 -2611.00, 8.00, 20.36, 3.31 -2612.00, 8.00, 20.67, 3.30 -2613.00, 8.00, 20.11, 3.30 -2614.00, 8.00, 20.39, 3.31 -2615.00, 8.00, 18.91, 3.30 -2616.00, 8.00, 18.50, 3.30 -2617.00, 8.00, 19.80, 3.31 -2618.00, 8.00, 18.52, 3.32 -2619.00, 8.00, 19.50, 3.33 -2620.00, 8.00, 20.61, 3.31 -2621.00, 8.00, 19.19, 3.31 -2622.00, 8.00, 21.80, 3.30 -2623.00, 8.00, 19.16, 3.29 -2624.00, 8.00, 18.41, 3.30 -2625.00, 8.00, 21.59, 3.30 -2626.00, 8.00, 20.25, 3.34 -2627.00, 8.00, 18.09, 3.30 -2628.00, 8.00, 22.02, 3.31 -2629.00, 8.00, 20.62, 3.30 -2630.00, 8.00, 19.55, 3.31 -2631.00, 8.00, 18.93, 3.29 -2632.00, 8.00, 18.26, 3.32 -2633.00, 8.00, 21.41, 3.31 -2634.00, 8.00, 20.52, 3.31 -2635.00, 8.00, 19.76, 3.30 -2636.00, 8.00, 19.53, 3.29 -2637.00, 8.00, 20.93, 3.31 -2638.00, 8.00, 19.47, 3.30 -2639.00, 8.00, 20.10, 3.30 -2640.00, 8.00, 20.61, 3.28 -2641.00, 8.00, 20.52, 3.32 -2642.00, 8.00, 19.57, 3.29 -2643.00, 8.00, 19.39, 3.29 -2644.00, 8.00, 20.78, 3.29 -2645.00, 8.00, 21.20, 3.29 -2646.00, 8.00, 20.26, 3.28 -2647.00, 8.00, 20.03, 3.31 -2648.00, 8.00, 18.97, 3.30 -2649.00, 8.00, 20.33, 3.30 -2650.00, 8.00, 20.00, 3.31 -2651.00, 8.00, 19.27, 3.30 -2652.00, 8.00, 18.56, 3.29 -2653.00, 8.00, 19.32, 3.29 -2654.00, 8.00, 19.28, 3.30 -2655.00, 8.00, 19.97, 3.29 -2656.00, 8.00, 19.72, 3.29 -2657.00, 8.00, 18.46, 3.30 -2658.00, 8.00, 20.21, 3.29 -2659.00, 8.00, 19.63, 3.29 -2660.00, 8.00, 18.37, 3.29 -2661.00, 8.00, 19.33, 3.28 -2662.00, 8.00, 19.64, 3.29 -2663.00, 8.00, 20.33, 3.28 -2664.00, 8.00, 20.36, 3.30 -2665.00, 8.00, 19.78, 3.27 -2666.00, 8.00, 20.06, 3.28 -2667.00, 8.00, 20.50, 3.28 -2668.00, 8.00, 18.56, 3.28 -2669.00, 8.00, 19.27, 3.28 -2670.00, 8.00, 20.34, 3.28 -2671.00, 8.00, 21.60, 3.30 -2672.00, 8.00, 19.89, 3.29 -2673.00, 8.00, 20.30, 3.28 -2674.00, 8.00, 19.99, 3.29 -2675.00, 8.00, 17.77, 3.28 -2676.00, 8.00, 21.21, 3.27 -2677.00, 8.00, 19.59, 3.29 -2678.00, 8.00, 20.64, 3.25 -2679.00, 8.00, 21.21, 3.28 -2680.00, 8.00, 19.92, 3.26 -2681.00, 8.00, 21.31, 3.30 -2682.00, 8.00, 19.15, 3.29 -2683.00, 8.00, 18.92, 3.27 -2684.00, 8.00, 19.63, 3.26 -2685.00, 8.00, 20.50, 3.28 -2686.00, 8.00, 20.66, 3.27 -2687.00, 8.00, 21.08, 3.27 -2688.00, 8.00, 18.76, 3.26 -2689.00, 8.00, 20.57, 3.26 -2690.00, 8.00, 20.59, 3.27 -2691.00, 8.00, 20.01, 3.28 -2692.00, 8.00, 20.69, 3.27 -2693.00, 8.00, 21.78, 3.26 -2694.00, 8.00, 19.81, 3.26 -2695.00, 8.00, 19.92, 3.26 -2696.00, 8.00, 20.89, 3.26 -2697.00, 8.00, 18.92, 3.27 -2698.00, 8.00, 18.00, 3.28 -2699.00, 8.00, 20.24, 3.26 -2700.00, 8.00, 20.77, 3.27 -2701.00, 8.00, 16.85, 3.26 -2702.00, 8.00, 19.42, 3.25 -2703.00, 8.00, 20.98, 3.25 -2704.00, 8.00, 21.65, 3.26 -2705.00, 8.00, 19.19, 3.26 -2706.00, 8.00, 19.39, 3.24 -2707.00, 8.00, 19.39, 3.25 -2708.00, 8.00, 20.47, 3.23 -2709.00, 8.00, 21.10, 3.27 -2710.00, 8.00, 20.12, 3.25 -2711.00, 8.00, 21.16, 3.26 -2712.00, 8.00, 17.85, 3.26 -2713.00, 8.00, 18.98, 3.23 -2714.00, 8.00, 20.48, 3.24 -2715.00, 8.00, 20.06, 3.24 -2716.00, 8.00, 20.01, 3.24 -2717.00, 8.00, 20.62, 3.23 -2718.00, 8.00, 20.81, 3.24 -2719.00, 8.00, 18.54, 3.25 -2720.00, 8.00, 20.59, 3.25 -2721.00, 8.00, 21.07, 3.23 -2722.00, 8.00, 17.94, 3.24 -2723.00, 8.00, 18.89, 3.23 -2724.00, 8.00, 19.38, 3.24 -2725.00, 8.00, 19.75, 3.22 -2726.00, 8.00, 20.74, 3.21 -2727.00, 8.00, 18.83, 3.24 -2728.00, 8.00, 20.48, 3.21 -2729.00, 8.00, 20.31, 3.23 -2730.00, 8.00, 19.95, 3.25 -2731.00, 8.00, 17.96, 3.22 -2732.00, 8.00, 20.53, 3.25 -2733.00, 8.00, 21.76, 3.22 -2734.00, 8.00, 18.90, 3.21 -2735.00, 8.00, 20.19, 3.21 -2736.00, 8.00, 19.63, 3.22 -2737.00, 8.00, 21.44, 3.23 -2738.00, 8.00, 19.99, 3.21 diff --git a/examples/eol_event.py b/examples/eol_event.py deleted file mode 100644 index 0e5c8e39..00000000 --- a/examples/eol_event.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -""" -This example demonstrates a use case where someone wants to predict the first event (i.e., End Of Life (EOL)) of a system. Many system models have multiple events that can occur. In some prognostics applications, users are not interested in predicting a specific event, and are instead interested in when the first event occurs, regardless of the event. This example demonstrates how to predict the first event of a system. - -Method: An instance of ThrownObject is used for this example. In this case it is trivial because the event 'falling' will always occur before 'impact', but for some other models that might not be true. The ThrownObject class is subclassed to add a new event 'EOL' which occurs if any other event occurs. The new model is then instantiated and used for prognostics like in basic_example. Prediction specifically specifies EOL as the event to be predicted. - -Results: - - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time the event 'EOL' is predicted to occur (with uncertainty) - iii) Histogram of the event 'EOL' -""" - -import matplotlib.pyplot as plt -from prog_models.models import ThrownObject -from prog_algs.predictors import MonteCarlo -from prog_algs.uncertain_data import ScalarData - -def run_example(): - # Step 1: Define subclass with EOL event - # Similar to the prog_models 'events' example, but with an EOL event - class ThrownObjectWithEOL(ThrownObject): - events = ThrownObject.events + ['EOL'] - - def event_state(self, x): - es = super().event_state(x) - # Add EOL Event (minimum event state) - es['EOL'] = min(list(es.values())) - return es - - def threshold_met(self, x): - t_met = super().threshold_met(x) - # Add EOL Event (if any events have occured) - t_met['EOL'] = any(list(t_met.values())) - return t_met - - # Step 2: Create instance of subclass - m = ThrownObjectWithEOL(process_noise=1) - - # Step 3: Setup for prediction - pred = MonteCarlo(m) - def future_loading(t=None, x=None): - return {} # No future loading for ThrownObject - state = ScalarData(m.initialize()) - - # Step 4: Predict to EOL event - pred_results = pred.predict(state, future_loading, events=['EOL'], dt=0.01, n_samples=50) - # In this case EOL is when the object starts falling - # But for some models where events aren't sequential, there might be a mixture of events in the EOL - - # Step 5: Plot results - pred_results.time_of_event.plot_hist() - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/horizon.py b/examples/horizon.py deleted file mode 100644 index bd5e034b..00000000 --- a/examples/horizon.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation and prediction with uncertainty given a Prognostics Model with a specific prediction horizon. This prediction horizon marks the end of the "time of interest" for the prediction. Often this represents the end of a mission or sufficiently in the future where the user is unconcerned with the events - -Method: An instance of the Thrown Object model in prog_models is created, and the prediction process is achieved in three steps: - 1) State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate - 2) Prediction of future states (with uncertainty) and the times at which the event thresholds will be reached, within the prediction horizon. All events outside the horizon come back as None and are ignored in metrics - -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) -""" - -from prog_models.models.thrown_object import ThrownObject -from prog_algs import * -from pprint import pprint - -def run_example(): - # Step 1: Setup model & future loading - def future_loading(t, x = None): - return {} - m = ThrownObject(process_noise = 0.25, measurement_noise = 0.2) - initial_state = m.initialize() - - # Step 2: Demonstrating state estimator - print("\nPerforming State Estimation Step...") - - # Step 2a: Setup - NUM_SAMPLES = 1000 - filt = state_estimators.ParticleFilter(m, initial_state, num_particles = NUM_SAMPLES) - # VVV Uncomment this to use UKF State Estimator VVV - # filt = state_estimators.UnscentedKalmanFilter(batt, initial_state) - - # Step 2b: One step of state estimator - u = m.InputContainer({}) # No input for ThrownObject - filt.estimate(0.1, u, m.output(initial_state)) - - # Note: in a prognostic application the above state estimation - # step would be repeated each time there is new data. - # Here we're doing one step to demonstrate how the state estimator is used - - # Step 3: Demonstrating Predictor - print("\nPerforming Prediction Step...") - - # Step 3a: Setup Predictor - mc = predictors.MonteCarlo(m) - - # Step 3b: Perform a prediction - # THIS IS WHERE WE DIVERGE FROM THE THROWN_OBJECT_EXAMPLE - # Here we set a prediction horizon - # We're saying we are not interested in any events that occur after this time - PREDICTION_HORIZON = 7.75 - samples = filt.x # Since we're using a particle filter, which is also sample-based, we can directly use the samples, without changes - STEP_SIZE = 0.01 - mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon = PREDICTION_HORIZON) - print("\nPredicted Time of Event:") - metrics = mc_results.time_of_event.metrics() - pprint(metrics) # Note this takes some time - mc_results.time_of_event.plot_hist(keys = 'impact') - mc_results.time_of_event.plot_hist(keys = 'falling') - - print("\nSamples where impact occurs before horizon: {:.2f}%".format(metrics['impact']['number of samples']/NUM_SAMPLES*100)) - - # Step 4: Show all plots - import matplotlib.pyplot as plt # For plotting - plt.show() - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/kalman_filter.py b/examples/kalman_filter.py deleted file mode 100644 index a914f8c2..00000000 --- a/examples/kalman_filter.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example demonstrates use of the Kalman Filter State Estimator with a LinearModel. - -First, a linear model is defined. Then the KF State estimator is used with fake data to estimate state. -""" - -import numpy as np -from prog_models import LinearModel -from prog_algs.state_estimators import KalmanFilter - - -# Linear Model for an object thrown into the air -class ThrownObject(LinearModel): - """ - Model that similates an object thrown into the air without air resistance - - Events (2) - | falling: The object is falling - | impact: The object has hit the ground - - Inputs/Loading: (0) - - States: (2) - | x: Position in space (m) - | v: Velocity in space (m/s) - - Outputs/Measurements: (1) - | x: Position in space (m) - - Keyword Args - ------------ - process_noise : Optional, float or Dict[Srt, float] - Process noise (applied at dx/next_state). - Can be number (e.g., .2) applied to every state, a dictionary of values for each - state (e.g., {'x1': 0.2, 'x2': 0.3}), or a function (x) -> x - process_noise_dist : Optional, String - distribution for process noise (e.g., normal, uniform, triangular) - measurement_noise : Optional, float or Dict[Srt, float] - Measurement noise (applied in output eqn). - Can be number (e.g., .2) applied to every output, a dictionary of values for each - output (e.g., {'z1': 0.2, 'z2': 0.3}), or a function (z) -> z - measurement_noise_dist : Optional, String - distribution for measurement noise (e.g., normal, uniform, triangular) - g : Optional, float - Acceleration due to gravity (m/s^2). Default is 9.81 m/s^2 (standard gravity) - thrower_height : Optional, float - Height of the thrower (m). Default is 1.83 m - throwing_speed : Optional, float - Speed at which the ball is thrown (m/s). Default is 40 m/s - """ - - inputs = [] # no inputs, no way to control - states = [ - 'x', # Position (m) - 'v' # Velocity (m/s) - ] - outputs = [ - 'x' # Position (m) - ] - events = [ - 'impact' # Event- object has impacted ground - ] - - A = np.array([[0, 1], [0, 0]]) - E = np.array([[0], [-9.81]]) - C = np.array([[1, 0]]) - F = None # Will override method - - # The Default parameters. - # Overwritten by passing parameters dictionary into constructor - default_parameters = { - 'thrower_height': 1.83, # m - 'throwing_speed': 40, # m/s - 'g': -9.81 # Acceleration due to gravity in m/s^2 - } - - def initialize(self, u=None, z=None): - return self.StateContainer({ - 'x': self.parameters['thrower_height'], - # Thrown, so initial altitude is height of thrower - 'v': self.parameters['throwing_speed'] - # Velocity at which the ball is thrown - this guy is a professional baseball pitcher - }) - - # This is actually optional. Leaving thresholds_met empty will use the event state to define thresholds. - # Threshold is met when Event State == 0. - # However, this implementation is more efficient, so we included it - def threshold_met(self, x): - return { - 'falling': x['v'] < 0, - 'impact': x['x'] <= 0 - } - - def event_state(self, x): - x_max = x['x'] + np.square(x['v'])/(-self.parameters['g']*2) # Use speed and position to estimate maximum height - return { - 'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0), # Throwing speed is max speed - 'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1 # 1 until falling begins, then it's fraction of height - } - -def run_example(): - # Step 1: Instantiate the model - m = ThrownObject(process_noise = 0, measurement_noise = 0) - - # Step 2: Instantiate the Kalman Filter State Estimator - # Define the initial state to be slightly off of actual - x_guess = m.StateContainer({'x': 1.75, 'v': 35}) # Guess of initial state - # Note: actual is {'x': 1.83, 'v': 40} - kf = KalmanFilter(m, x_guess) - - # Step 3: Run the Kalman Filter State Estimator - # Here we're using simulated data from the thrown_object. - # In a real application you would be using sensor data from the system - dt = 0.01 # Time step (s) - print_freq = 50 # Print every print_freq'th iteration - x = m.initialize() - u = m.InputContainer({}) # No input for this model - - for i in range(500): - # Get simulated output (would be measured in a real application) - z = m.output(x) - - # Estimate New State - kf.estimate(i*dt, u, z) - x_est = kf.x.mean - - # Print Results - if i%print_freq == 0: # Print every print_freq'th iteration - print(f"t: {i*dt:.2f}\n\tEstimate: {x_est}\n\tTruth: {x}") - diff = {key: x_est[key] - x[key] for key in x.keys()} - print(f"\t Diff: {diff}") - - # Update Real state for next step - x = m.next_state(x, u, dt) - -if __name__ == '__main__': - run_example() diff --git a/examples/measurement_eqn_example.py b/examples/measurement_eqn_example.py deleted file mode 100644 index 6b9674e8..00000000 --- a/examples/measurement_eqn_example.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs a state estimation with uncertainty given a Prognostics Model for a system in which not all output values are measured. - -Method: An instance of the BatteryCircuit model in prog_models is created. We assume that we are only measuring one of the output values, and we define a subclass to remove the other output value. - Estimation of the current state is performed at various time steps, using the defined state_estimator. - -Results: - i) Estimate of the current state given various times - ii) Display of results, such as prior and posterior state estimate values and SOC -""" - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs import * - -def run_example(): - # Step 1: Subclass model with measurement equation - # In this case we're only measuring 'v' (i.e., removing temperature) - # To do this we're creating a new class that's subclassed from the complete model. - # To change the outputs we just have to override outputs (the list of keys) - class MyBattery(Battery): - outputs = ['v'] - - # Step 2: Setup model & future loading - batt = MyBattery() - loads = [ # Define loads here to accelerate prediction - batt.InputContainer({'i': 2}), - batt.InputContainer({'i': 1}), - batt.InputContainer({'i': 4}), - batt.InputContainer({'i': 2}), - batt.InputContainer({'i': 3}) - ] - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - return loads[0] - elif (t < 900): - return loads[1] - elif (t < 1800): - return loads[2] - elif (t < 3000): - return loads[3] - return loads[-1] - - x0 = batt.parameters['x0'] - - # Step 3: Use the updated model - filt = state_estimators.ParticleFilter(batt, x0) - - # Step 4: Run step and print results - print('Running state estimation step with only one of 2 outputs measured') - - # Print Prior - print("\nPrior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - # Estimate Step - # Note, only voltage was needed in the measurement step, since that is the only output we're measuring - t = 0.1 - load = future_loading(t) - filt.estimate(t, load, {'v': 3.915}) - - # Print Posterior - print("\nPosterior State:", filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - # Another Estimate Step - t = 0.2 - load = future_loading(t) - filt.estimate(t, load, {'v': 3.91}) - - # Print Posterior Again - print("\nPosterior State (t={}):".format(t), filt.x.mean) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - - # Note that the particle filter was still able to perform state estimation. - # The updated outputs can be used for any case where the measurement doesn't match the model outputs - # For example, when units are different, or when the measurement is some combination of the outputs - # These are a little more complicated, since they require an instance of the parent class. For example: - - parent = Battery() - - - class MyBattery(Battery): - outputs = ['tv'] # output is temperature * voltage (for some reason) - - def output(self, x): - parent.parameters = self.parameters # only needed if you expect to change parameters - z = parent.output(x) - return self.OutputContainer({'tv': z['v'] * z['t']}) - - batt = MyBattery() - filt = state_estimators.ParticleFilter(batt, x0) - - print('-----------------\n\nExample 2') - print("\nPrior State:", filt.x.mean) - print("\toutput: ", batt.output(filt.x.mean)) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - t = 0.1 - load = future_loading(t) - filt.estimate(t, load, {'tv': 80}) - print("\nPosterior State:", filt.x.mean) - print("\toutput: ", batt.output(filt.x.mean)) - print('\tSOC: ', batt.event_state(filt.x.mean)['EOD']) - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/new_state_estimator_example.py b/examples/new_state_estimator_example.py deleted file mode 100644 index 374d2cd7..00000000 --- a/examples/new_state_estimator_example.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -An example illustrating the creation of a new state estimator. - -In this example a basic state estimator is constructed by subclassing the StateEstimator class. This StateEstimator is then demonstrated with a ThrownObject model -""" - -from prog_algs.state_estimators import StateEstimator -from prog_algs.uncertain_data import ScalarData -import random - - -class BlindlyStumbleEstimator(StateEstimator): - """ - A new state estimator. This is not a very effective state estimator, but one that technically works. It blindly stumbles towards the correct state by randomly generating a new state each timestep and selecting the state that's most consistant with the measurements. - - I do not in any universe recommend using this state estimator for anything other then demonstrating a bad state estimator. It's intended as an example of creating a new state estimation algorithm. - - This state estimator was created by copying the state estimator template and filling out each function with the logic for this algorithm - """ - def __init__(self, model, x0, measurement = None): - """ - Initialize the state estimator - - Args: - model (PrognosticsModel): Model to be used in state estimation - x0 (dict): Initial State - """ - self.m = model - self.state = x0 - - def estimate(self, t, u, z): - """ - Update the state estimate - - Args: - t (Number): Time - u (dict): Inputs (load) for time t - z (dict): Measured output at time t - """ - # Generate new candidate state - x2 = {key : float(value) + 10*(random.random()-0.5) for (key,value) in self.state.items()} - - # Calculate outputs - z_est = self.m.output(t, self.state) - z_est2 = self.m.output(t, x2) - - # Now score them each by how close they are to the measured z - z_est_score = sum([abs(z_est[key] - z[key]) for key in self.m.outputs]) - z_est2_score = sum([abs(z_est2[key] - z[key]) for key in self.m.outputs]) - - # Now choose the closer one - if z_est2_score < z_est_score: - self.state = x2 - - @property - def x(self): - """ - Measured state - """ - return ScalarData(self.state) - -# Model used in example -class ThrownObject(): - """ - Model that similates an object thrown into the air without air resistance - """ - - inputs = [] # no inputs, no way to control - states = [ - 'x', # Position (m) - 'v' # Velocity (m/s) - ] - outputs = [ # Anything we can measure - 'x' # Position (m) - ] - events = [ - 'falling', # Event- object is falling - 'impact' # Event- object has impacted ground - ] - - # The Default parameters. Overwritten by passing parameters dictionary into constructor - parameters = { - 'thrower_height': 1.83, # m - 'throwing_speed': 40, # m/s - 'g': -9.81, # Acceleration due to gravity in m/s^2 - 'process_noise': 0.0 # amount of noise in each step - } - - def initialize(self, u = None, z = None): - self.max_x = 0.0 - return { - 'x': self.parameters['thrower_height'], # Thrown, so initial altitude is height of thrower - 'v': self.parameters['throwing_speed'] # Velocity at which the ball is thrown - this guy is an professional baseball pitcher - } - - def dx(self, t, x, u = None): - # apply_process_noise is used to add process noise to each step - return { - 'x': x['v'], - 'v': self.parameters['g'] # Acceleration of gravity - } - - def output(self, t, x): - return { - 'x': x['x'] - } - - def event_state(self, t, x): - self.max_x = max(self.max_x, x['x']) # Maximum altitude - return { - 'falling': max(x['v']/self.parameters['throwing_speed'],0), # Throwing speed is max speed - 'impact': max(x['x']/self.max_x,0) # 1 until falling begins, then it's fraction of height - } - -def run_example(): - # This example creates a new state estimator, instead of using the included algorihtms. - # The new state estimator was defined above and can now be used like the UKF or PF - - # First we define the model to be used with the state estimator - m = ThrownObject() - - # Lets pretend we have no idea what the state is, we'll provide an estimate of 0 - x0 = {key : 0 for key in m.states} - filt = BlindlyStumbleEstimator(m, x0) - - # Now lets simulate it forward and see what it looks like - dt = 0.1 - x = m.initialize() - print('t: {}. State: {} (Ground truth: {})'.format(0, filt.x.mean, x)) - for i in range(1, int(8.4/dt)): - # Update ground truth state - x = {key : x[key] + m.dx(i*dt, x)[key] * dt for key in m.states} - - # Run estimation step - filt.estimate(i*dt, None, m.output(i*dt, x)) - - # Print result - print('t: {}. State: {} (Ground truth: {})'.format(i*dt, filt.x.mean, x)) - - # The results probably should show that it is estimating the state with a significant delay - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/particle_filter_battery_example.py b/examples/particle_filter_battery_example.py deleted file mode 100644 index 68168e9a..00000000 --- a/examples/particle_filter_battery_example.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -In this example the BatteryElectroChemEOD model is used with a particle filter to estimate the state of the battery -""" - -import matplotlib.pyplot as plt -import numpy as np -from prog_algs import * -from prog_models.models import BatteryElectroChemEOD - -def run_example(): - ## Setup - # Save battery model - # Time increment - dt = 1 - # Process noise - Q_vars = { - 'tb': 1, - 'Vo': 0.01, - 'Vsn': 0.01, - 'Vsp': 0.01, - 'qnB': 1, - 'qnS': 1, - 'qpB': 1, - 'qpS': 1 - } - # Measurement noise - R_vars = { - 't': 2, - 'v': 0.02 - } - battery = BatteryElectroChemEOD(process_noise= Q_vars, - measurement_noise = R_vars, - dt = dt) - load = battery.InputContainer({"i": 1}) # Optimization - def future_loading(t, x=None): - return load - - # Simulate data until EOD - start_u = future_loading(0) - start_x = battery.initialize(start_u) - start_y = battery.output(start_x) - sim_results = battery.simulate_to_threshold(future_loading, start_y, save_freq = 1) - - # Run particle filter - all_particles = [] - n_times = int(np.round(np.random.uniform(len(sim_results.times)*.25,len(sim_results.times)*.45,1)))# Random current time - - for i in range(n_times): - if i == 0: - batt_pf = state_estimators.ParticleFilter(model = battery, x0 = sim_results.states[i], num_particles = 250) - else: - batt_pf.estimate(t = sim_results.times[i], u = sim_results.inputs[i], z = sim_results.outputs[i]) - all_particles.append(batt_pf.particles) - - # Mean of the particles - alpha = 0.05 - states_vsn = [s['tb'] for s in sim_results.states] - pf_mean = [{key: np.mean(ps[key]) for key in battery.states} for ps in all_particles] - pf_low = [{key: np.quantile(ps[key], alpha / 2.0) for key in battery.states} for ps in all_particles] - pf_upp = [{key: np.quantile(ps[key], 1.0 - alpha / 2.0) for key in battery.states} for ps in all_particles] - print("First State:", pf_mean[0]) - print("Current State:", pf_mean[-1]) - plt.plot(sim_results.times[:n_times],[p['tb'] for p in pf_mean],linewidth=0.7,color="blue") - plt.plot(sim_results.times[:n_times], states_vsn[:n_times],"--",linewidth=0.7,color="red") - plt.fill_between(sim_results.times[:n_times],[p['tb'] for p in pf_low],[p['tb'] for p in pf_upp],alpha=0.5,color="blue") - plt.show() - - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() \ No newline at end of file diff --git a/examples/playback.py b/examples/playback.py deleted file mode 100644 index 638baf78..00000000 --- a/examples/playback.py +++ /dev/null @@ -1,144 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This example performs state estimation and prediction using playback data. - -Method: An instance of the BatteryCircuit model in prog_models is created, the state estimation is set up by defining a state_estimator, and the prediction method is set up by defining a predictor. - Prediction is then performed using playback data. For each data point: - 1) The necessary data is extracted (time, current load, output values) and corresponding values defined (t, i, and z) - 2) The current state estimate is performed and samples are drawn from this distribution - 3) Prediction performed to get future states (with uncertainty) and the times at which the event threshold will be reached - -Results: - i) Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction - ii) Time event is predicted to occur (with uncertainty) - iii) Various prediction metrics - iv) Figures illustrating results -""" - -import csv -import numpy as np -from prog_algs.predictors import ToEPredictionProfile -from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist - -from prog_models.models import BatteryCircuit as Battery -# VVV Uncomment this to use Electro Chemistry Model VVV -# from prog_models.models import BatteryElectroChem as Battery - -from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator -# VVV Uncomment this to use UnscentedKalmanFilter instead VVV -# from prog_algs.state_estimators import ParticleFilter as StateEstimator - -from prog_algs.predictors import UnscentedTransformPredictor as Predictor -# VVV Uncomment this to use MonteCarloPredictor instead -# from prog_algs.predictors import MonteCarlo as Predictor - -# Constants -NUM_SAMPLES = 20 -NUM_PARTICLES = 1000 # For state estimator (if using ParticleFilter) -TIME_STEP = 1 -PREDICTION_UPDATE_FREQ = 50 # Number of steps between prediction update -PLOT = True -PROCESS_NOISE = 1e-4 # Percentage process noise -MEASUREMENT_NOISE = 1e-4 # Percentage measurement noise -X0_COV = 1 # Covariance percentage with initial state -GROUND_TRUTH = {'EOD':2780} -ALPHA = 0.05 -BETA = 0.90 -LAMBDA_VALUE = 1500 - -def run_example(): - # Setup Model - batt = Battery() - - # Initial state - x0 = batt.initialize() - batt.parameters['process_noise'] = {key: PROCESS_NOISE * value for key, value in x0.items()} - z0 = batt.output(x0) - batt.parameters['measurement_noise'] = {key: MEASUREMENT_NOISE * value for key, value in z0.items()} - x0 = MultivariateNormalDist(x0.keys(), list(x0.values()), np.diag([max(1e-9, X0_COV * abs(x)) for x in x0.values()])) - - # Setup State Estimation - filt = StateEstimator(batt, x0, num_particles = NUM_PARTICLES) - - # Setup Prediction - load = batt.InputContainer({'i': 2.35}) - def future_loading(t, x=None): - return load - Q = np.diag([batt.parameters['process_noise'][key] for key in batt.states]) - R = np.diag([batt.parameters['measurement_noise'][key] for key in batt.outputs]) - mc = Predictor(batt, Q = Q, R = R) - - # Run Playback - step = 0 - profile = ToEPredictionProfile() - - with open('examples/data_const_load.csv', 'r') as f: - reader = csv.reader(f) - next(reader) # Skip header - for row in reader: - step += 1 - print("{} s: {} W, {} C, {} V".format(*row)) - t = float(row[0]) - i = {'i': float(row[1])/float(row[3])} - z = {'t': float(row[2]), 'v': float(row[3])} - - # State Estimation Step - filt.estimate(t, i, z) - eod = batt.event_state(filt.x.mean)['EOD'] - print(" - Event State: ", eod) - - # Prediction Step (every PREDICTION_UPDATE_FREQ steps) - if (step%PREDICTION_UPDATE_FREQ == 0): - mc_results = mc.predict(filt.x, future_loading, t0 = t, n_samples=NUM_SAMPLES, dt=TIME_STEP) - metrics = mc_results.time_of_event.metrics() - print(' - ToE: {} (sigma: {})'.format(metrics['EOD']['mean'], metrics['EOD']['std'])) - profile.add_prediction(t, mc_results.time_of_event) - - # Calculating Prognostic Horizon once the loop completes - from prog_algs.uncertain_data.uncertain_data import UncertainData - from prog_algs.metrics import samples as metrics - - def criteria_eqn(tte : UncertainData, ground_truth_tte : dict) -> dict: - """ - Sample criteria equation for playback. - # UPDATE THIS CRITERIA EQN AND WHAT IS CALCULATED - - Args: - tte : UncertainData - Time to event in UncertainData format. - ground_truth_tte : dict - Dictionary of ground truth of time to event. - """ - - # Set an alpha value - bounds = {} - for key, value in ground_truth_tte.items(): - # Set bounds for precentage_in_bounds by adding/subtracting to the ground_truth - alpha_calc = value * ALPHA - bounds[key] = [value - alpha_calc, value + alpha_calc] # Construct bounds for all events - percentage_in_bounds = tte.percentage_in_bounds(bounds) - - # Verify if percentage in bounds for this ground truth meets beta distribution percentage limit - return {key: percentage_in_bounds[key] > BETA for key in percentage_in_bounds.keys()} - - # Generate plots for playback example - playback_plots = profile.plot(GROUND_TRUTH, ALPHA, True) - - # Calculate prognostic horizon with ground truth, and print - ph = profile.prognostic_horizon(criteria_eqn, GROUND_TRUTH) - print(f"Prognostic Horizon for 'EOD': {ph['EOD']}") - - # Calculate alpha lambda with ground truth, lambda, alpha, and beta, and print - al = profile.alpha_lambda(GROUND_TRUTH, LAMBDA_VALUE, ALPHA, BETA) - print(f"Alpha Lambda for 'EOD': {al['EOD']}") - - # Calculate cumulative relative accuracy with ground truth, and print - cra = profile.cumulative_relative_accuracy(GROUND_TRUTH) - print(f"Cumulative Relative Accuracy for 'EOD': {cra['EOD']}") - - input('Press any key to exit') - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/examples/predict_specific_event.py b/examples/predict_specific_event.py deleted file mode 100644 index 816a3c67..00000000 --- a/examples/predict_specific_event.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -In this example we are using the UTPredictor to predict a specific event, in this case impact. This will then ignore the other events which are not of interest. -""" - -from prog_algs import state_estimators, predictors -from prog_models.models.thrown_object import ThrownObject - -def run_example(): - ## Setup - m = ThrownObject() - initial_state = m.initialize() - load = m.InputContainer({}) # Optimization - create once - def future_loading(t, x = None): - return load - - ## State Estimation - perform a single ukf state estimate step - filt = state_estimators.UnscentedKalmanFilter(m, initial_state) - filt.estimate(0.1, {}, m.output(initial_state)) - - ## Prediction - Predict EOD given current state - # Setup prediction - pred = predictors.UnscentedTransformPredictor(m) - - # Predict with a step size of 0.1 - mc_results = pred.predict(filt.x, future_loading, dt=0.1, save_freq= 1, events=['impact']) - - # Print Results - for i, time in enumerate(mc_results.times): - print('\nt = {}'.format(time)) - print('\tu = {}'.format(mc_results.inputs.snapshot(i).mean)) - print('\tx = {}'.format(mc_results.states.snapshot(i).mean)) - print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean)) - print('\tevent state = {}'.format(mc_results.states.snapshot(i).mean)) - - # Note only impact event is shown here - print('\nToE:', mc_results.time_of_event.mean) - -# This allows the module to be executed directly -if __name__ == '__main__': - run_example() diff --git a/forms/Corporate CLA.pdf b/forms/Corporate CLA.pdf deleted file mode 100644 index 5175701e..00000000 Binary files a/forms/Corporate CLA.pdf and /dev/null differ diff --git a/forms/Individual CLA.pdf b/forms/Individual CLA.pdf deleted file mode 100644 index 86fb7d73..00000000 Binary files a/forms/Individual CLA.pdf and /dev/null differ diff --git a/license.pdf b/license.pdf deleted file mode 100644 index 4947abed..00000000 Binary files a/license.pdf and /dev/null differ diff --git a/predictor_template.py b/predictor_template.py deleted file mode 100644 index df000dfc..00000000 --- a/predictor_template.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from copy import deepcopy - -from prog_algs.predictors import Predictor, Prediction, PredictionResults -# Replace the following with whatever form of UncertainData you would like to use to represent ToE -from prog_algs.uncertain_data import ScalarData - - -class TemplatePredictor(Predictor): - """ - Template class for performing model-based prediction - """ - - # REPLACE THE FOLLOWING LIST WITH CONFIGURED PARAMETERS - default_parameters = { # Default Parameters, used as config for UKF - 'Example Parameter': 0.0 - } - - def __init__(self, model, **kwargs): - """ - Constructor (optional) - """ - super().__init__(model, **kwargs) - # ADD PARAMETER CHECKS HERE - # e.g., self.parameters['some_value'] < 0 - - # INITIALIZE PREDICTOR - - def predict(self, state, future_loading_eqn, **kwargs): - """ - Perform a single prediction - - Parameters - ---------- - state : UncertainData - Estimate of the state at the time of prediction, reprecented by UncertainData - future_loading_eqn : function (t, x) -> z - Function to generate an estimate of loading at future time t and state z - options : dict, optional - Dictionary of any additional configuration values. See default parameters, above - - Returns (namedtuple) - ------- - times : List[float] - Times for each savepoint such that inputs.snapshot(i), states.snapshot(i), outputs.snapshot(i), and event_states.snapshot(i) are all at times[i] - inputs : Prediction - Inputs at each savepoint such that inputs.snapshot(i) is the input distribution (type UncertainData) at times[i] - states : Prediction - States at each savepoint such that states.snapshot(i) is the state distribution (type UncertainData) at times[i] - outputs : Prediction - Outputs at each savepoint such that outputs.snapshot(i) is the output distribution (type UncertainData) at times[i] - event_states : Prediction - Event states at each savepoint such that event_states.snapshot(i) is the event state distribution (type UncertainData) at times[i] - time_of_event : UncertainData - Distribution of predicted Time of Event (ToE) for each predicted event, represented by some subclass of UncertaintData (e.g., MultivariateNormalDist) - """ - params = deepcopy(self.parameters) # copy default parameters - params.update(kwargs) - - # PERFORM PREDICTION HERE, REPLACE THE FOLLOWING LISTS - - # Times of each savepoint (specified by savepts and save_freq) - times = [] # array of float (e.g., [0.0, 0.5, 1.0, ...]) - - # Inputs, State, Outputs, and Event States at each savepoint are stored by type Prediction - # Replace [] with estimates of the appropriate property in the form of a subclass of UncertainData (e.g, ScalarData) - inputs = Prediction(times, []) - states = Prediction(times, []) - outputs = Prediction(times, []) - event_states = Prediction(times, []) - - # Time of event is represented by some type of UncertainData (e.g., MultivariateNormalDist) - time_of_event = ScalarData({'event1': 748, 'event2': 300}) - # Save the final state when each event occurs like this, with each final state represented by an UncertainData object (e.g., MultivariateNormalDist) - # time_of_event.final_state = {'event1': ScalarData({'state1': 10, 'state2': 20}), 'event2': ScalarData({'state1': 12, 'state2': 18})} - - return PredictionResults( - times, - inputs, - states, - outputs, - event_states, - time_of_event - ) diff --git a/scripts/test_copyright.py b/scripts/test_copyright.py deleted file mode 100644 index cb268f83..00000000 --- a/scripts/test_copyright.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. - -import os -COPYRIGHT_TAG = "Copyright © 2021 United States Government as represented by the Administrator" # String to check file lines for - -def check_copyright(directory : str, invalid_files : list) -> bool: - result = True - - for filename in os.listdir(directory): - path = os.path.join(directory, filename) - - # If path is subdirectory, recursively check files/subdirectories within - if os.path.isdir(path): - result = result and check_copyright(path, invalid_files) - # If path is a file, ensure it is of type py and check for copyright - elif os.path.isfile(path) and path[-2:] == "py": - file = open(path, 'r') - copyright_met = False - # Iterate over lines in file, check each line against COPYRIGHT_TAG - for line in file: - if COPYRIGHT_TAG in line: # File contains copyright, skip rest of lines - file.close() - copyright_met = True - if copyright_met: - break - if not copyright_met: - result = False - invalid_files.append(path) - file.close() - - return result - -def main(): - print("\n\nTesting Files for Copyright Information") - - root = '../prog_algs' - invalid_files = [] - copyright_confirmed = check_copyright(root, invalid_files) - - if not copyright_confirmed: - raise Exception(f"Failed test\nFiles missing copyright information: {invalid_files}") - -if __name__ == '__main__': - main() diff --git a/setup.py b/setup.py deleted file mode 100644 index cea3c002..00000000 --- a/setup.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from setuptools import setup, find_packages -import pathlib - -here = pathlib.Path(__file__).parent.resolve() - -# Get the long description from the README file -long_description = (here / 'README.md').read_text(encoding='utf-8') - -setup( - name = 'prog_algs', - version = '1.5.0', - description = "The NASA Prognostics Algorithm Package is a framework for model-based prognostics (computation of remaining useful life) of engineering systems. It includes algorithms for state estimation and prediction, including uncertainty propagation. The algorithms use prognostic models (see prog_models) to perform estimation and prediction. The package enables rapid development of prognostics solutions for given models of components and systems. Algorithms can be swapped for comparative studies and evaluations", - long_description=long_description, - long_description_content_type='text/markdown', - url = 'https://nasa.github.io/progpy/prog_algs_guide.html', - author = 'Christopher Teubert', - author_email = 'christopher.a.teubert@nasa.gov', - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Developers', - 'Intended Audience :: Manufacturing', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', - 'Topic :: Scientific/Engineering :: Physics', - 'License :: Other/Proprietary License ', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3 :: Only' - ], - keywords = ['prognostics', 'diagnostics', 'fault detection', 'fdir', 'prognostics and health management', 'PHM', 'health management', 'ivhm'], - package_dir = {"":"src"}, - packages = find_packages(where = 'src'), - python_requires='>=3.7, <3.12', - install_requires = [ - 'numpy', - 'scipy', - 'filterpy', - 'matplotlib', - 'prog_models>=1.5.0' - ], - license = 'NOSA', - project_urls={ # Optional - 'Bug Reports': 'https://github.com/nasa/prog_algs/issues', - 'Docs': 'https://nasa.github.io/progpy/prog_algs_guide.html', - 'Organization': 'https://www.nasa.gov/content/diagnostics-prognostics', - 'Source': 'https://github.com/nasa/prog_algs', - } -) diff --git a/src/prog_algs/__init__.py b/src/prog_algs/__init__.py deleted file mode 100644 index dd5420db..00000000 --- a/src/prog_algs/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -__all__ = ['predictors', 'uncertain_data', 'state_estimators', 'run_prog_playback', 'metrics'] -from . import predictors, state_estimators, uncertain_data - -import warnings - -__version__ = '1.5.0' - -def run_prog_playback(obs, pred, future_loading, output_measurements, **kwargs): - warnings.warn("Depreciated in 1.2.0, will be removed in a future release.", DeprecationWarning) - config = {# Defaults - 'predict_rate': 0, # Default- predict every step - 'num_samples': 10, - 'predict_config': {} - } - config.update(kwargs) - - next_predict = output_measurements[0][0] + config['predict_rate'] - times = [] - inputs = [] - states = [] - outputs = [] - event_states = [] - toes = [] - index = 0 - for (t, measurement) in output_measurements: - obs.estimate(t, future_loading(t), measurement) - if t >= next_predict: - pred_results = pred.predict(obs.x.sample(config['num_samples']), future_loading, **config['predict_config']) - times.append(pred_results.times) - inputs.append(pred_results.inputs) - states.append(pred_results.states) - outputs.append(pred_results.outputs) - event_states.append(pred_results.event_states) - toes.append(pred_results.time_of_event) - index += 1 - next_predict += config['predict_rate'] - return predictors.PredictionResults( - times, - inputs, - states, - outputs, - event_states, - toes - ) diff --git a/src/prog_algs/metrics/__init__.py b/src/prog_algs/metrics/__init__.py deleted file mode 100644 index 4c39fe28..00000000 --- a/src/prog_algs/metrics/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from . import samples -from .toe_metrics import prob_success -from .uncertain_data_metrics import calc_metrics -from .toe_profile_metrics import alpha_lambda, prognostic_horizon, cumulative_relative_accuracy, monotonicity - -__all__ = ['alpha_lambda', 'calc_metrics', 'prob_success', 'prognostic_horizon', 'cumulative_relative_accuracy', 'monotonicity'] diff --git a/src/prog_algs/metrics/samples.py b/src/prog_algs/metrics/samples.py deleted file mode 100644 index bd276cd8..00000000 --- a/src/prog_algs/metrics/samples.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -# This file is kept for backwards compatability -from numpy import mean, sqrt -from warnings import warn - -from .uncertain_data_metrics import calc_metrics as eol_metrics -from .toe_metrics import prob_success -from .toe_profile_metrics import alpha_lambda - -def mean_square_error(values: list, ground_truth: float) -> float: - """Mean Square Error - Args: - values (List[float]): Times of Event (ToE) for a single event, output from predictor - ground_truth (float): Ground truth ToE - Returns: - float: mean square error of ToE predictions - """ - return sum([(mean(x) - ground_truth)**2 for x in values])/len(values) - -def root_mean_square_error(values, ground_truth): - """Root Mean Square Error - Args: - values (List[float]): Times of Event (ToE) for a single event, output from predictor - ground_truth (float): Ground truth ToE - Returns: - float: root mean square error of ToE predictions - """ - return sqrt(sum([(mean(x) - ground_truth)**2 for x in values])/len(values)) - -def percentage_in_bounds(toe: list, bounds: tuple) -> float: - """Calculate percentage of ToE dist is within specified bounds - - Args: - toe (List[float]): Times of Event (ToE) for a single event, output from predictor - bounds ((float, float)): Lower and upper bounds - - Returns: - float: Percentage within bounds (where 1 = 100%) - """ - warn('percentage_in_bounds has been deprecated in favor of UncertainData.percentage_in_bounds(bounds). This function will be removed in a future release') - return sum([x < bounds[1] and x > bounds[0] for x in toe])/ len(toe) diff --git a/src/prog_algs/metrics/toe_metrics.py b/src/prog_algs/metrics/toe_metrics.py deleted file mode 100644 index 836b70f7..00000000 --- a/src/prog_algs/metrics/toe_metrics.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This file includes functions for calculating metrics specific to Time of Event (ToE) from a single event or multiple events given the same time of prediction -""" -from typing import Iterable -from numpy import isscalar - -from ..uncertain_data import UncertainData, UnweightedSamples - -def prob_success(toe: UncertainData, time: float, **kwargs) -> float: - """Calculate probability of success - i.e., probability that event will not occur within a given time (i.e., success) - - Args: - toe (UncertainData or list[float]): Times of event for a single event (array[float]) or multiple events, output from predictor - time (float): time for calculation - **kwargs (optional): Configuration parameters. Supported parameters include: - * n_samples (int): Number of samples to use for calculating metrics (if ToE is not UnweightedSamples). Defaults to 10,000. - * keys (list of strings, optional): Keys to calculate metrics for. Defaults to all keys. - - Returns: - float: Probability of success - - Example: - :: - - # ToE estimate distribution is returned from a predictor's predict method - result = predictor.predict(...) - toe = result.time_of_event - - from prog_algs.metrics import prob_success - now = 10 # Current time - p_success = prob_success(toe, now) - p_success = prob_success(toe, now, n_samples = 100) # Can also specify number of samples - p_success = prob_success(toe, now, keys = ['event1']) # Can specify specific keys to consider - """ - params = { - 'n_samples': 10000, # Default - } - params.update(kwargs) - - if isinstance(toe, UncertainData): - # Default to all keys - keys = params.setdefault('keys', toe.keys()) - if isinstance(keys, str): - keys = [keys] - - if isinstance(toe, UnweightedSamples): - samples = toe - else: - # Some other distribution besides unweighted samples - # Generate Samples - samples = toe.sample(params['n_samples']) - - # If unweighted_samples, calculate metrics for each key - return {key: prob_success(samples.key(key), - time, - **kwargs) for key in keys} - elif isinstance(toe, Iterable): - if len(toe) == 0: - raise ValueError('Time of Event must not be empty') - # Is list or array - if isscalar(toe[0]) or toe[0] is None: - # list of numbers - this is the case that we can calculate - pass - elif isinstance(toe[0], dict): - # list of dicts - Supported for backwards compatabilities - toe = UnweightedSamples(toe) - return prob_success(toe, time, **kwargs) - else: - raise TypeError("ToE must be type Uncertain Data or array of dicts, was {}".format(type(toe))) - else: - raise TypeError("ToE must be type Uncertain Data or array of dicts, was {}".format(type(toe))) - - return sum([e is None or e > time for e in toe])/len(toe) diff --git a/src/prog_algs/metrics/toe_profile_metrics.py b/src/prog_algs/metrics/toe_profile_metrics.py deleted file mode 100644 index b39439e3..00000000 --- a/src/prog_algs/metrics/toe_profile_metrics.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This file includes functions for calculating metrics given a Time of Event (ToE) profile (i.e., ToE's calculated at different times of prediction resulting from running prognostics multiple times, e.g., on playback data). The metrics calculated here are specific to multiple ToE estimates (e.g. alpha-lambda metric) -""" -from numpy import sign -from collections import defaultdict -from typing import Callable, Dict - -from ..predictors import ToEPredictionProfile - -def alpha_lambda(toe_profile: ToEPredictionProfile, ground_truth: dict, lambda_value: float, alpha: float, beta: float, **kwargs) -> dict: - """ - Compute alpha lambda metric, a common metric in prognostics. Alpha-Lambda is met if alpha % of the Time to Event (TtE) distribution is within beta % of the ground truth at prediction time lambda. - - Args: - toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions - ground_truth (dict): Ground Truth time of event for each event (e.g., {'event1': 748, 'event2', 2233, ...}) - lambda_value (float): Prediction time at or after which metric is evaluated. Evaluation occurs at this time (if a prediction exists) or the next prediction following. - alpha (float): percentage bounds around time to event (where 0.2 allows 20% error TtE) - beta (float): portion of prediction that must be within those bounds - kwargs (optional): configuration arguments. Accepted args include: - * keys (list[string], optional): list of keys to use. If not provided, all keys are used. - - Returns: - dict: dictionary containing key value pairs for each key and whether the alpha-lambda was met. - """ - params = { - 'print': False - } - params.update(kwargs) - - for (t_prediction, toe) in toe_profile.items(): - if (t_prediction >= lambda_value): - # If keys not provided, use all - keys = params.setdefault('keys', toe.keys()) - - bounds = {key : [gt - alpha*(gt-t_prediction), gt + alpha*(gt-t_prediction)] for key, gt in ground_truth.items()} - pib = toe.percentage_in_bounds(bounds) - result = {key: pib[key] >= beta for key in keys} - if params['print']: - for key in keys: - print('\n', key) - print('\ttoe:', toe.key(key)) - print('\tBounds: [{} - {}]({}%)'.format(bounds[key][0], bounds[key][1], pib[key])) - return result - -def prognostic_horizon(toe_profile: ToEPredictionProfile, criteria_eqn: Callable, ground_truth: dict, **kwargs) -> dict: - """ - Compute prognostic horizon metric, defined as the difference between a time ti, when the predictions meet specified performance criteria, and the time corresponding to the true Time of Event (ToE), for each event. - PH = ToE - ti - Args: - toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions - criteria_eqn (Callable function): A function (tte: UncertainData, ground_truth_tte: dict[str, float]) -> dict[str, bool] calculating whether a prediction in ToEPredictionProfile meets some criteria. \n - | Args: - | * tte (UncertainData): A single prediction of Time of Event (ToE) - | * ground truth tte (dict[str, float]): Ground truth passed into prognostics_horizon - | Returns: Map of event names to boolean representing if the event has been met. - | e.g., {'event1': True, 'event2': False} - ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1} - kwargs (optional): configuration arguments. Accepted args include: - * print (bool): Boolean specifying whether the prognostic horizon metric should be printed. - - Returns: - dict: Dictionary containing prognostic horizon calculations (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1} - """ - params = { - 'print': False - } - params.update(kwargs) - - ph_result = {k:None for k in ground_truth.keys()} # False means not yet met; will be either a numerical value or None if met - for (t_prediction, toe) in toe_profile.items(): - # Convert to TtE for toe and ground_truth - tte = toe - t_prediction - ground_truth_tte = {} - ground_truth_tte = {k:v-t_prediction for k,v in ground_truth.items()} - # Pass to criteria_eqn - criteria_eqn_dict = criteria_eqn(tte, ground_truth_tte) # -> dict[event_names as str, bool] - for k,v in criteria_eqn_dict.items(): - if v and (ph_result[k] == None): - ph_calc = ground_truth[k] - t_prediction - if ph_calc > 0: - ph_result[k] = ph_calc # PH = EOL - ti # ground truth is a dictionary {'EOD': 3005.2} should be ph_result[k] = g_truth[key] - t_prediction - if (all(v != None for v in ph_result.values())): - # Return PH once all criteria are met - return ph_result - # Return PH when criteria not met for at least one event key - return ph_result - -def cumulative_relative_accuracy(toe_profile: ToEPredictionProfile, ground_truth: dict, **kwargs) -> Dict[str, float]: - """ - Compute cumulative relative accuracy for a given profile, defined as the normalized sum of relative prediction accuracies at specific time instances. - - CRA = Σ(RA)/N for each event - Where Σ is summation of all relative accuracies for a given profile and N is the total count of profiles (Journal Prognostics Health Management, Saxena et al.) - Args: - toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions - ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1} - kwargs (optional): configuration arguments. Accepted args include: - - Returns: - dict: Dictionary containing cumulative relative accuracy (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1} - """ - ra_sums = defaultdict(int) - for uncertaindata in toe_profile.values(): - for event,value in uncertaindata.relative_accuracy(ground_truth).items(): - ra_sums[event] += value - return {event:ra_sum/len(toe_profile) for event, ra_sum in ra_sums.items()} - -def monotonicity(toe_profile: ToEPredictionProfile, **kwargs) -> Dict[str, float]: - """Calculate monotonicty for a prediction profile. - Given a prediction profile, for each prediction: go through all predicted events and compare those to the next one. - Calculates monotonicity for each prediction key using its associated mean value in UncertainData. - - monotonoicity = |Σsign(i+1 - i) / N-1| - Where N is number of measurements and sign indicates sign of calculation. - Coble, J., et. al. (2021). Identifying Optimal Prognostic Parameters from Data: A Genetic Algorithms Approach. Annual Conference of the PHM Society. - http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404 - Baptistia, M., et. al. (2022). Relation between prognostics predictor evaluation metrics and local interpretability SHAP values. Aritifical Intelligence, Volume 306. - https://www.sciencedirect.com/science/article/pii/S0004370222000078 - - Args: - toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions - Returns: - dict (str, float): Dictionary where keys represent an event and values are float representing its respective monotonicitiy value between [0, 1]. - """ - result = dict() - by_event = defaultdict(list) - for time,uncertaindata in toe_profile.items(): - # Collect and organize mean values for each event in the individual prediction v - for event,value in uncertaindata.mean.items(): - by_event[event].append(value - time) - # For each event of this prediction v, calculate monotonicity using formula - for key,l in by_event.items(): - mono_sum = [] - for i in range(len(l)-1): - mono_sum.append(sign(l[i+1] - l[i])) - result[key] = abs(sum(mono_sum) / (len(l)-1)) - return result diff --git a/src/prog_algs/metrics/uncertain_data_metrics.py b/src/prog_algs/metrics/uncertain_data_metrics.py deleted file mode 100644 index c85938ab..00000000 --- a/src/prog_algs/metrics/uncertain_data_metrics.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -""" -This file includes functions for calculating general metrics (i.e. mean, std, percentiles, etc.) on any distribution of type UncertainData (e.g. states, event_states, an EOL distribution, etc.) -""" -from typing import Iterable, Union -from numpy import isscalar, mean, std, array -from scipy import stats -from warnings import warn - -from ..uncertain_data import UncertainData, UnweightedSamples - -def calc_metrics(data: UncertainData, ground_truth: Union[float, dict] = None, **kwargs) -> dict: - """Calculate all time of event metrics - - Args: - data (array[float] or UncertainData): data from a single event - ground_truth (float, optional): Ground truth value. Defaults to None. dict when data is of type UncertainData. - **kwargs (optional): Configuration parameters. Supported parameters include: - * n_samples (int): Number of samples to use for calculating metrics (if data is not UnweightedSamples). Defaults to 10,000. - * keys (list of strings, optional): Keys to calculate metrics for. Defaults to all keys. - - Returns: - dict: collection of metrics - """ - params = { - 'n_samples': 10000, # Default is enough to get every percentile - } - params.update(kwargs) - - if isinstance(data, UncertainData): - # Default to all keys - keys = params.setdefault('keys', data.keys()) - if isinstance(keys, str): - keys = [keys] - - if ground_truth and isscalar(ground_truth): - # If ground truth is scalar, create dict (expected below) - ground_truth = {key: ground_truth for key in keys} - - if isinstance(data, UnweightedSamples): - samples = data - else: - # Some other distribution besides unweighted samples - # Generate Samples - samples = data.sample(params['n_samples']) - - # If unweighted_samples, calculate metrics for each key - result = {key: calc_metrics(samples.key(key), - ground_truth if not ground_truth else ground_truth[key], # If ground_truth is a dict, use key - **kwargs) for key in keys} - - # Set values specific to distribution - for key in keys: - result[key]['mean'] = data.mean[key] - result[key]['median'] = data.median[key] - result[key]['percentiles']['50'] = data.median[key] - - return result - elif isinstance(data, Iterable): - if len(data) == 0: - raise ValueError('Data must not be empty') - # Is list or array - if isscalar(data[0]) or data[0] is None: - # list of numbers - this is the case that we can calculate - pass - elif isinstance(data[0], dict): - # list of dicts - Supported for backwards compatabilities - data = UnweightedSamples(data) - return calc_metrics(data, ground_truth, **kwargs) - else: - raise TypeError("Data must be type Uncertain Data or array of dicts, was {}".format(type(data))) - else: - raise TypeError("Data must be type Uncertain Data or array of dicts, was {}".format(type(data))) - - # If we get here then Data is a list of numbers- calculate metrics for numbers - data_abridged = array([d for d in data if d is not None]) # Must be array - if len(data_abridged) == 0: - raise ValueError('All samples were none') - if len(data_abridged) < len(data): - warn("Some samples were None, resulting metrics only consider non-None samples. Note: in some cases, this will bias the metrics.") - data_abridged.sort() - m = mean(data_abridged) - median = data_abridged[int(len(data_abridged)/2)] - metrics = { - 'min': data_abridged[0], - 'percentiles': { - '0.01': data_abridged[int(len(data_abridged)/10000)] if len(data_abridged) >= 10000 else None, - '0.1': data_abridged[int(len(data_abridged)/1000)] if len(data_abridged) >= 1000 else None, - '1': data_abridged[int(len(data_abridged)/100)] if len(data_abridged) >= 100 else None, - '10': data_abridged[int(len(data_abridged)/10)] if len(data_abridged) >= 10 else None, - '25': data_abridged[int(len(data_abridged)/4)] if len(data_abridged) >= 4 else None, - '50': median, - '75': data_abridged[int(3*len(data_abridged)/4)] if len(data_abridged) >= 4 else None, - }, - 'median': median, - 'mean': m, - 'std': std(data_abridged), - 'max': data_abridged[-1], - 'median absolute deviation': sum([abs(x - median) for x in data_abridged])/len(data_abridged), - 'mean absolute deviation': sum([abs(x - m) for x in data_abridged])/len(data_abridged), - 'number of samples': len(data_abridged) - } - - if ground_truth is not None: - # Metrics comparing to ground truth - metrics['mean absolute error'] = sum([abs(x - ground_truth) for x in data_abridged])/len(data_abridged) - metrics['mean absolute percentage error'] = metrics['mean absolute error']/ ground_truth - metrics['relative accuracy'] = 1 - abs(ground_truth - metrics['mean'])/ground_truth - metrics['ground truth percentile'] = stats.percentileofscore(data_abridged, ground_truth) - - return metrics diff --git a/src/prog_algs/predictors/__init__.py b/src/prog_algs/predictors/__init__.py deleted file mode 100644 index 29642d4d..00000000 --- a/src/prog_algs/predictors/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from .monte_carlo import MonteCarlo -from .predictor import Predictor -from .prediction import Prediction, UnweightedSamplesPrediction, PredictionResults -from .toe_prediction_profile import ToEPredictionProfile -from .unscented_transform import UnscentedTransformPredictor - -# For naming consistancy -# Unfortunately, prog_algs was released with inconsistent naming (UnscentedTransformPredictor vs MonteCarlo). -# For naming consistency and to avoid confusion, we created aliases for the two classes. -# They can be called by the name of the method (e.g., UnscentedTranform) or with 'predictor' at the end (e.g., UnscentedTransformPredictor). -UnscentedTransform = UnscentedTransformPredictor -MonteCarloPredictor = MonteCarlo - -__all__ = ['predictor', 'monte_carlo', 'unscented_transform', 'MonteCarlo', 'Predictor', 'Prediction', 'UnweightedSamplesPrediction', 'ToEPredictionProfile', 'UnscentedTransformPredictor', 'UnscentedTransform', 'MonteCarloPredictor'] diff --git a/src/prog_algs/predictors/monte_carlo.py b/src/prog_algs/predictors/monte_carlo.py deleted file mode 100644 index 8a1bd125..00000000 --- a/src/prog_algs/predictors/monte_carlo.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from copy import deepcopy -from typing import Callable -from prog_models.sim_result import SimResult, LazySimResult -from prog_algs.uncertain_data import UnweightedSamples, UncertainData - -from .prediction import UnweightedSamplesPrediction, PredictionResults -from .predictor import Predictor - - -class MonteCarlo(Predictor): - """ - Class for performing a monte-carlo model-based prediction. - - A Predictor using the monte carlo algorithm. The provided initial states are simulated until either a specified time horizon is met, or the threshold for all simulated events is reached for all samples. A provided future loading equation is used to compute the inputs to the system at any given time point. - - The following configuration parameters are supported (as kwargs in constructor or as parameters in predict method): - - Configuration Parameters - ------------------------------ - t0 : float - Initial time at which prediction begins, e.g., 0 - dt : float - Simulation step size (s), e.g., 0.1 - events : list[str] - Events to predict (subset of model.events) e.g., ['event1', 'event2'] - horizon : float - Prediction horizon (s) - n_samples : int - Number of samples to use. If not specified, a default value is used. If state is type UnweightedSamples and n_samples is not provided, the provided unweighted samples will be used directly. - save_freq : float - Frequency at which results are saved (s) - save_pts : list[float] - Any additional savepoints (s) e.g., [10.1, 22.5] - """ - - default_parameters = { - 'n_samples': 100 # Default number of samples to use, if none specified - } - - def predict(self, state: UncertainData, future_loading_eqn: Callable, **kwargs) -> PredictionResults: - if isinstance(state, dict) or isinstance(state, self.model.StateContainer): - from prog_algs.uncertain_data import ScalarData - state = ScalarData(state, _type = self.model.StateContainer) - elif isinstance(state, UncertainData): - state._type = self.model.StateContainer - else: - raise TypeError("state must be UncertainData, dict, or StateContainer") - - params = deepcopy(self.parameters) # copy parameters - params.update(kwargs) # update for specific run - params['print'] = False - params['progress'] = False - - if len(params['events']) == 0 and 'horizon' not in params: - raise ValueError("If specifying no event (i.e., simulate to time), must specify horizon") - - # Sample from state if n_samples specified or state is not UnweightedSamples - if not isinstance(state, UnweightedSamples) or len(state) != params['n_samples']: - state = state.sample(params['n_samples']) - - es_eqn = self.model.event_state - tm_eqn = self.model.threshold_met - simulate_to_threshold = self.model.simulate_to_threshold - - time_of_event_all = [] - last_states = [] - times_all = [] - inputs_all = [] - states_all = [] - outputs_all = [] - event_states_all = [] - - # Perform prediction - t0 = params.get('t0', 0) - for x in state: - first_output = self.model.output(x) - - time_of_event = {} - last_state = {} - - params['t0'] = t0 - params['x'] = x - - if 'save_freq' in params and not isinstance(params['save_freq'], tuple): - params['save_freq'] = (params['t0'], params['save_freq']) - - if len(params['events']) == 0: # Predict to time - (times, inputs, states, outputs, event_states) = simulate_to_threshold(future_loading_eqn, - first_output, - threshold_keys = [], - **params - ) - else: - events_remaining = params['events'].copy() - - times = [] - inputs = SimResult(_copy = False) - states = SimResult(_copy = False) - outputs = LazySimResult(fcn = self.model.output, _copy = False) - event_states = LazySimResult(fcn = es_eqn, _copy = False) - - # Non-vectorized prediction - while len(events_remaining) > 0: # Still events to predict - (t, u, xi, z, es) = simulate_to_threshold(future_loading_eqn, - first_output, - threshold_keys = events_remaining, - **params - ) - - # Add results - times.extend(t) - inputs.extend(u) - states.extend(xi) - outputs.extend(z, _copy = False) - event_states.extend(es, _copy = False) - - # Get which event occurs - t_met = tm_eqn(states[-1]) - t_met = {key: t_met[key] for key in events_remaining} # Only look at remaining keys - - try: - event = list(t_met.keys())[list(t_met.values()).index(True)] - except ValueError: - # no event has occured - hit horizon - for event in events_remaining: - time_of_event[event] = None - last_state[event] = None - break - - # An event has occured - time_of_event[event] = times[-1] - events_remaining.remove(event) # No longer an event to predect to - - # Remove last state (event) - params['t0'] = times.pop() - inputs.pop() - params['x'] = states.pop() - last_state[event] = params['x'].copy() - outputs.pop() - event_states.pop() - - # Add to "all" structures - if len(times) > len(times_all): # Keep longest - times_all = times - inputs_all.append(inputs) - states_all.append(states) - outputs_all.append(outputs) - event_states_all.append(event_states) - time_of_event_all.append(time_of_event) - last_states.append(last_state) - - inputs_all = UnweightedSamplesPrediction(times_all, inputs_all) - states_all = UnweightedSamplesPrediction(times_all, states_all) - outputs_all = UnweightedSamplesPrediction(times_all, outputs_all) - event_states_all = UnweightedSamplesPrediction(times_all, event_states_all) - time_of_event = UnweightedSamples(time_of_event_all) - - # Transform final states: - time_of_event.final_state = { - key: UnweightedSamples([sample[key] for sample in last_states], _type = self.model.StateContainer) for key in time_of_event.keys() - } - - return PredictionResults( - times_all, - inputs_all, - states_all, - outputs_all, - event_states_all, - time_of_event - ) diff --git a/src/prog_algs/predictors/prediction.py b/src/prog_algs/predictors/prediction.py deleted file mode 100644 index 6fb55697..00000000 --- a/src/prog_algs/predictors/prediction.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from collections import UserList, defaultdict, namedtuple -from typing import Dict, List -from numpy import sign -from warnings import warn - -from ..uncertain_data import UnweightedSamples, UncertainData - -PredictionResults = namedtuple('PredictionResults', ["times", "inputs", "states", "outputs", "event_states", "time_of_event"]) - - -class Prediction(): - """ - Class for the result of a prediction. Is returned by the predict method of a predictor. - - Args: - times (list[float]): - Times for each data point where times[n] corresponds to data[n] - data (list[UncertainData]): - Data points for each time in times - """ - - def __init__(self, times : list, data : list): - self.times = times - self.data = data - - def __eq__(self, other: "Prediction") -> bool: - """Compare 2 Predictions - - Args: - other (Prediction): - - Returns: - bool: If the two Predictions are equal - """ - return self.times == other.times and self.data == other.data - - def snapshot(self, time_index: int) -> UncertainData: - """Get all samples from a specific timestep - - Args: - index (int): - Timestep (index number from times) - - Returns: - UncertainData: Distribution for time corresponding to times[timestep] - """ - return self.data[time_index] - - @property - def mean(self) -> List[dict]: - """Estimate of the mean value of the prediction at each time - - Returns: - list[dict]: - Mean value of the prediction at each time where mean[n] corresponds to the mean value of the prediction at time times[n].\n - The mean value at each time is a dictionary. \n - e.g., [{'state1': 1.2, 'state2': 1.3, ...}, ...] - - Example: - mean_value = data.mean - """ - return [dist.mean for dist in self.data] - - def time(self, index: int): - warn("Deprecated. Please use prediction.times[index] instead.") - return self.times[index] - - def monotonicity(self) -> Dict[str, float]: - """Calculate monotonicty for a single prediction. - Given a single prediction, for each event: go through all predicted states and compare those to the next one. - Calculates monotonicity for each event key using its associated mean value in UncertainData. - - :math:`monotonoicity = \| \Sigma \dfrac{sign(i+1 - i)}{N-1}\|` - - Where N is number of measurements and sign indicates sign of calculation [0]_ [1]_. - - Returns: - dict (str, float): Value between [0, 1] indicating monotonicity of a given event for the Prediction. - - References: - .. [0] Coble, J., et. al. (2021). Identifying Optimal Prognostic Parameters from Data: A Genetic Algorithms Approach. Annual Conference of the PHM Society. http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404 - .. [1] Baptistia, M., et. al. (2022). Relation between prognostics predictor evaluation metrics and local interpretability SHAP values. Aritifical Intelligence, Volume 306. https://www.sciencedirect.com/science/article/pii/S0004370222000078 - - """ - # Collect and organize mean values for each event - by_event = defaultdict(list) - for uncertaindata in self.data: - for key,value in uncertaindata.mean.items(): - by_event[key].append(value) - - # For each event, calculate monotonicity using formula - result = {} - for key,l in by_event.items(): - mono_sum = [] - for i in range(len(l)-1): - mono_sum.append(sign(l[i+1] - l[i])) - result[key] = abs(sum(mono_sum) / (len(l)-1)) - return result - -class UnweightedSamplesPrediction(Prediction, UserList): - """ - Immutable data class for the result of a prediction, where the predictions are stored as UnweightedSamples. Is returned from the predict method of a sample based prediction class (e.g., MonteCarlo). Objects of this class can be iterated and accessed like a list (e.g., prediction[0]), where prediction[n] represents a profile for sample n. - - Args: - times (list[float]): - Times for each data point where times[n] corresponds to data[:][n] - data (list[SimResult]): - Data points where data[n] is a SimResult for sample n - """ - - def __init__(self, times: list, data: list): - super(UnweightedSamplesPrediction, self).__init__(times, data) - self.__transformed = False # If transform has been calculated - - def __calculate_tranform(self): - """ - Calculate tranform of the data from data[sample_id][time_id] to data[time_id][sample_id]. Result is cached as self.__transform and is used in methods which look at a snapshot for a specific time - """ - # Lazy calculation of tranform - only if needed - # Note: prediction stops when event is reached, so for the length of all will not be the same. - # If the prediction doesn't go this far, then the value is set to None - self.__transform = [UnweightedSamples([sample[time_index] if len(sample) > time_index else None for sample in self.data]) for time_index in range(len(self.times))] - self.__transformed = True - - def __str__(self) -> str: - return "UnweightedSamplesPrediction with {} savepoints".format(len(self.times)) - - @property - def mean(self) -> list: - if not self.__transformed: - self.__calculate_tranform() - return [dist.mean for dist in self.__transform] - - def sample(self, sample_id: int): - warn("Deprecated. Please use prediction[sample_id] instead.") - return self[sample_id] - - def snapshot(self, time_index: int) -> UnweightedSamples: - """Get all samples from a specific timestep - - Args: - index (int): Timestep (index number from times) - - Returns: - UnweightedSamples: Samples for time corresponding to times[timestep] - """ - if not self.__transformed: - self.__calculate_tranform() - return self.__transform[time_index] - - def __not_implemented(self, *args, **kw): - """ - This function is not implemented. Calling this will raise an error. Is is only included to make the class immutable. - - Raises: - ValueError: - """ - raise ValueError("UnweightedSamplesPrediction is immutable (i.e., read only)") - - append = __not_implemented - extend = __not_implemented - clear = __not_implemented - reverse = __not_implemented - remove = __not_implemented - insert = __not_implemented - pop = __not_implemented - __setitem__ = __not_implemented - __setslice__ = __not_implemented - __delitem__ = __not_implemented diff --git a/src/prog_algs/predictors/predictor.py b/src/prog_algs/predictors/predictor.py deleted file mode 100644 index 2fc06c5a..00000000 --- a/src/prog_algs/predictors/predictor.py +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from abc import ABC, abstractmethod -from copy import deepcopy -from typing import Callable - -from prog_algs.predictors.prediction import PredictionResults -from ..uncertain_data import UncertainData - - -class Predictor(ABC): - """ - Interface class for predictors - - Abstract base class for creating predictors that perform prediction. Predictor subclasses must implement this interface. Equivilant to "Predictors" in NASA's Matlab Prognostics Algorithm Library - - Parameters - ---------- - model : PrognosticsModel - See: :py:mod:`prog_models` package\n - A prognostics model to be used in prediction - kwargs : optional, keyword arguments - """ - default_parameters = {} - - def __init__(self, model, **kwargs): - if not hasattr(model, 'output'): - raise NotImplementedError("model must have `output` method") - if not hasattr(model, 'next_state'): - raise NotImplementedError("model must have `next_state` method") - if not hasattr(model, 'inputs'): - raise NotImplementedError("model must have `inputs` property") - if not hasattr(model, 'outputs'): - raise NotImplementedError("model must have `outputs` property") - if not hasattr(model, 'states'): - raise NotImplementedError("model must have `states` property") - if not hasattr(model, 'simulate_to_threshold'): - raise NotImplementedError("model must have `simulate_to_threshold` property") - self.model = model - - self.parameters = deepcopy(self.default_parameters) - self.parameters['events'] = self.model.events.copy() # Events to predict to - self.parameters.update(kwargs) - - @abstractmethod - def predict(self, state: UncertainData, future_loading_eqn: Callable, **kwargs) -> PredictionResults: - """ - Perform a single prediction - - Parameters - ---------- - state : UncertainData - Distribution representing current state of the system - future_loading_eqn : function (t, x) -> z - Function to generate an estimate of loading at future time t, and state x - - Return - ---------- - result from prediction, including: NameTuple - * times (List[float]): Times for each savepoint such that inputs.snapshot(i), states.snapshot(i), outputs.snapshot(i), and event_states.snapshot(i) are all at times[i] - * inputs (Prediction): Inputs at each savepoint such that inputs.snapshot(i) is the input distribution (type UncertainData) at times[i] - * states (Prediction): States at each savepoint such that states.snapshot(i) is the state distribution (type UncertainData) at times[i] - * outputs (Prediction): Outputs at each savepoint such that outputs.snapshot(i) is the output distribution (type UncertainData) at times[i] - * event_states (Prediction): Event states at each savepoint such that event_states.snapshot(i) is the event state distribution (type UncertainData) at times[i] - * time_of_event (UncertainData): Distribution of predicted Time of Event (ToE) for each predicted event, represented by some subclass of UncertaintData (e.g., MultivariateNormalDist) - """ - pass diff --git a/src/prog_algs/predictors/toe_prediction_profile.py b/src/prog_algs/predictors/toe_prediction_profile.py deleted file mode 100644 index 43106978..00000000 --- a/src/prog_algs/predictors/toe_prediction_profile.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -import matplotlib.pyplot as plt -from collections import UserDict -from typing import Dict -import numpy as np - -from prog_algs.uncertain_data import UncertainData - -class ToEPredictionProfile(UserDict): - """ - Data structure for storing the result of multiple predictions, including time of prediction. This data structure can be treated as a dictionary of time of prediction to Time of Event (ToE) prediction. Iteration of this data structure is in order of increasing time of prediction - """ - def add_prediction(self, time_of_prediction: float, toe_prediction: UncertainData): - """Add a single prediction to the profile - - Args: - time_of_prediction (float): Time that the prediction was made - toe_prediction (UncertainData): Distribution of predicted ToEs - """ - self[time_of_prediction] = toe_prediction - - # Functions below are defined to ensure that any iteration is in order of increasing time of prediction - def __iter__(self): - return iter(sorted(super(ToEPredictionProfile, self).__iter__())) - - def items(self): - """ - Get iterators for the items (time_of_prediction, toe_prediction) of the prediction profile - """ - return iter((k, self[k]) for k in self) - - def keys(self): - """ - Get iterator for the keys (i.e., time_of_prediction) of the prediction profile - """ - return sorted(super(ToEPredictionProfile, self).keys()) - - def values(self): - """ - Get iterator for the values (i.e., toe_prediction) of the prediction profile - """ - return [self[k] for k in self.keys()] - - def alpha_lambda(self, ground_truth: Dict[str, float], lambda_value: float, alpha: float, beta: float, **kwargs) -> Dict[str, bool]: - """Calculate Alpha lambda metric for the prediction profile - - Args: - ground_truth (dict[str, float]): - Ground Truth time of event for each event (e.g., {'event1': 748, 'event2', 2233, ...}) - lambda_value (float): - Prediction time at or after which metric is evaluated. Evaluation occurs at this time (if a prediction exists) or the next prediction following. - alpha (float): - percentage bounds around time to event (where 0.2 allows 20% error TtE) - beta (float): - portion of prediction that must be within those bounds - - Keyword Args: - keys (list[str], optional): - list of keys to use. If not provided, all keys are used. - print (bool, optional) - If True, print the results to the screen. Default is False. - - Returns: - dict[str, bool]: If alpha lambda was met for each key (e.g., {'event1': True, 'event2', False, ...}) - """ - from ..metrics import alpha_lambda - return alpha_lambda(self, ground_truth, lambda_value, alpha, beta, **kwargs) - - def prognostic_horizon(self, criteria_eqn, ground_truth, **kwargs) -> Dict[str, float]: - """ - Compute prognostic horizon metric, defined as the difference between a time ti, when the predictions meet specified performance criteria, and the time corresponding to the true Time of Event (ToE), for each event. - - :math:`PH = ToE - ti` - - Args: - toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions - criteria_eqn (Callable function): A function (toe: UncertainData, ground_truth: dict[str, float]) -> dict[str, bool] calculating whether a prediction in ToEPredictionProfile meets some criteria. \n - | Args: - | * toe (UncertainData): A single prediction of Time of Event (ToE) - | * ground truth (dict[str, float]): Ground truth passed into prognostics_horizon - | Returns: Map of event names to boolean representing if the event has been met. - | e.g., {'event1': True, 'event2': False} - ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1} - - Keyword Args: - print (bool): - Boolean specifying whether the prognostic horizon metric should be printed. - - Returns: - dict: Dictionary containing prognostic horizon calculations (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1} - """ - from ..metrics import prognostic_horizon - return prognostic_horizon(self, criteria_eqn, ground_truth, **kwargs) - - def cumulative_relative_accuracy(self, ground_truth, **kwargs) -> Dict[str, float]: - r""" - Compute cumulative relative accuracy for a given profile, defined as the normalized sum of relative prediction accuracies at specific time instances. - - :math:`CRA = \Sigma \left( \dfrac{RA}{N} \right)` for each event - - Where :math:`\Sigma` is summation of all relative accuracies for a given profile and N is the total count of profiles [0]_ - - Args: - ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1} - - Returns: - dict: Dictionary containing cumulative relative accuracy (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1} - - References: - .. [0] Journal Prognostics Health Management, Saxena et al. - """ - from ..metrics import cumulative_relative_accuracy - return cumulative_relative_accuracy(self, ground_truth, **kwargs) - - def monotonicity(self, **kwargs) -> Dict[str, float]: - r"""Calculate monotonicty for a prediction profile. - Given a prediction profile, for each prediction: go through all predicted states and compare those to the next one. - Calculates monotonicity for each prediction key using its associated mean value in :py:class:`prog_algs.uncertain_data.UncertainData`. - - :math:`monotonoicity = \|\Sigma \left( \dfrac{sign(i+1 - i)}{N-1} \right) \|` - - Where N is number of measurements and sign indicates sign of calculation. [0]_ [1]_ - - Args: - toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions - - Returns: - dict (str, dict): Dictionary where keys represent a profile and dict is a subdictionary representing an event and its respective monotonicitiy value between [0, 1]. - - References: - .. [1] Coble, J., et. al. (2021). Identifying Optimal Prognostic Parameters from Data: A Genetic Algorithms Approach. Annual Conference of the PHM Society. http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404 - .. [2] Baptistia, M., et. al. (2022). Relation between prognostics predictor evaluation metrics and local interpretability SHAP values. Aritifical Intelligence, Volume 306. https://www.sciencedirect.com/science/article/pii/S0004370222000078 - """ - from ..metrics import monotonicity - return monotonicity(self, **kwargs) - - def plot(self, ground_truth: dict = None , alpha: float = None, show: bool = True) -> dict: # use ground truth, alpha if given, - """Produce an alpha-beta plot depicting the TtE distribution by time of prediction for each event. - - Args: - ground_truth (dict): - Optional dictionary argument containing event and its respective ground truth value; none by default and plotted if specified - alpha (float): - Optional alpha value; none by default and plotted if specified - show (bool): - Optional bool value; specify whether to display generated plots. Default is true - - Returns: - dict[str, Figure] : - Collection of generated matplotlib figures for each event in profile\n - e.g., {'event1': Fig, 'event2': Fig} - - Example: - :: - - gt = {'event1': 3442, 'event2': 175} # Ground Truth - figs = profile.plot(gt) # Figure with ground truth line - figs = profile.plot(gt, alpha = 0.2) # Figure with ground truth line and 20% alpha bounds - figs = profile.plot(gt, alpha = 0.2, show=False) # Dont display figure - """ - result_figs = {} - for t,v in self.items(): - raw_samples = v.sample(100) # sample distribution (red scatter plot) - for key in v.keys(): - if key not in result_figs: - # Prepare Figure for Plot - fig_window = plt.figure() # Create new figure for this event key - fig_sub = fig_window.subplots() - fig_sub.grid() - fig_sub.set_title(f"{key} Event") - fig_sub.set_xlabel('Time of Prediction (s)') # time to prediction - fig_sub.set_ylabel('Time to Event (s)') # time to event - result_figs[key] = fig_window - # Create scatter plot for this event - samples = [e[key]-t for e in raw_samples] - result_figs[key].get_axes()[0].scatter([t]*len(samples), samples, color='red') # Adding single distribution of estimates - - if ground_truth: # If ground_truth is specified, add ground_truth to each event plot (green line) - for key, val in ground_truth.items(): - gt_x = range(int(val)) - gt_y = range(int(val), 0, -1) - result_figs[key].get_axes()[0].plot(gt_x, gt_y, color='green') - if alpha: # if ground_truth and alpha are specified, add alpha bounds (faded green highlight) - result_figs[key].get_axes()[0].fill_between(gt_x, np.array(gt_y)*(1-alpha), np.array(gt_y)*(1+alpha), color='green', alpha=0.2) - result_figs[key].get_axes()[0].set_xlim(0, val+1) - - if show: # Optionally not display plots and just return plot objects - plt.show() - return result_figs diff --git a/src/prog_algs/predictors/unscented_transform.py b/src/prog_algs/predictors/unscented_transform.py deleted file mode 100644 index b1b52be6..00000000 --- a/src/prog_algs/predictors/unscented_transform.py +++ /dev/null @@ -1,283 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from copy import deepcopy -from filterpy import kalman -from numpy import diag, array, transpose, isnan -from typing import Callable - -from .prediction import Prediction, UnweightedSamplesPrediction, PredictionResults -from .predictor import Predictor -from prog_algs.uncertain_data import MultivariateNormalDist, UncertainData, ScalarData - - -class LazyUTPrediction(Prediction): - def __init__(self, state_prediction, sigma_fcn : Callable, ut_fcn : Callable, transform_fcn : Callable): - self.times = state_prediction.times - self.__states = state_prediction - self.__data = None - self.__sigma_fcn = sigma_fcn - self.__transform = transform_fcn - self.__ut_fcn = ut_fcn - - @property - def data(self): - if self.__data == None: - self.__data = [] - # For each timepoint - for i in range(len(self.times)): - x = self.__states.snapshot(i) - - # Get Sigma points - keys = x.keys() - mean = [x.mean[key] for key in keys] # Maintain ordering - covar = x.cov - sigma_pts = self.__sigma_fcn.sigma_points(mean, covar) - - # Apply Tranformation (e.g., output, event_state) - sigma_pt_tranformed = [ - self.__transform({key: value for key, value in zip(keys, sigma_pt)}) - for sigma_pt in sigma_pts - ] - # result is [sigma_pt][ -> output/event_state (dict)] - - transformed_keys = sigma_pt_tranformed[0].keys() - - # Flatten - sigma_pt_tranformed = array([array(list(sigma_pt.values())) for sigma_pt in sigma_pt_tranformed]) # map -> array - - # Apply Unscented Transform to form output distribution - mean, cov = self.__ut_fcn(sigma_pt_tranformed, self.__sigma_fcn.Wm, self.__sigma_fcn.Wc) - self.__data.append(MultivariateNormalDist(transformed_keys, mean, cov)) - - return self.__data - - -class UnscentedTransformPredictor(Predictor): - """ - Class for performing model-based prediction using an unscented transform. - - This class defines logic for performing model-based state prediction using sigma points and an unscented transform. The Unscented Transform Predictor propagates the sigma-points in the state-space in time domain until the event threshold is met. The step at which the i-th sigma point reaches the threshold is the step at which the i-th sigma point will be placed along the time dimension. By repeating the procedure for all sigma-points, we obtain the sigma-points defining the distribution of the time of event (ToE); for example, the End Of Life (EOL) event. The provided future loading equation is used to compute the inputs to the system at any given time point. - - The following configuration parameters are supported (as kwargs in constructor or as parameters in predict method): - - Configuration Parameters - ------------------------------ - alpha, beta, kappa: float - UKF Scaling parameters. See: https://en.wikipedia.org/wiki/Kalman_filter#Unscented_Kalman_filter - Q: np.array - Process noise covariance matrix [nStates x nStates] - t0 : float - Initial time at which prediction begins, e.g., 0 - dt : float - Simulation step size (s), e.g., 0.1 - events : list[str] - Events to predict (subset of model.events) e.g., ['event1', 'event2'] - horizon : float - Prediction horizon (s) - save_freq : float - Frequency at which results are saved (s) - save_pts : list[float] - Any additional savepoints (s) e.g., [10.1, 22.5] - - Note - ---- - The resulting sigma-points along the time dimension are used to compute mean and covariance of the event time (ToE), under the hypothesis that the ToE distribution would also be well represented by a Gaussian. This is a strong assumption that likely cannot be satisfied for real systems with strong non-linear state propagation or nonlinear ToE curves. Therefore, the user should be cautious and verify that modeling the event time using a Gaussian distribution is satisfactory. - """ - default_parameters = { - 'alpha': 1, - 'beta': 0, - 'kappa': -1, - 't0': 0, - 'dt': 0.5, - 'horizon': 1e99, - 'save_pts': [], - 'save_freq': 1e99 - } - - def __init__(self, model, **kwargs): - super().__init__(model, **kwargs) - - self.model = model - self.__input = None # Input at an individual step. Note, this needs to be a member to pass between state_transition and predict - - # setup UKF - num_states = model.n_states - num_measurements = model.n_outputs - - if 'Q' not in self.parameters: - # Default - self.parameters['Q'] = diag([1.0e-8 for _ in range(num_states)]) - - def measure(x): - x = model.StateContainer(x) - z = model.output(x) - return model.OutputContainer(z) - - def state_transition(x, dt): - x = model.StateContainer(x) - x = model.next_state(x, self.__input, dt) - x = model.apply_limits(x) - return array(list(x.values())) - - self.sigma_points = kalman.MerweScaledSigmaPoints(num_states, alpha=self.parameters['alpha'], beta=self.parameters['beta'], kappa=self.parameters['kappa']) - self.filter = kalman.UnscentedKalmanFilter(num_states, num_measurements, self.parameters['dt'], measure, state_transition, self.sigma_points) - self.filter.Q = self.parameters['Q'] - - def predict(self, state, future_loading_eqn: Callable, **kwargs) -> PredictionResults: - """ - Perform a single prediction - - Parameters - ---------- - state (UncertaintData): Distribution of states - future_loading_eqn : function (t, x={}) -> z - Function to generate an estimate of loading at future time t - options (optional, kwargs): configuration options\n - Any additional configuration values. Note: These parameters can also be specified in the predictor constructor. The following configuration parameters are supported: \n - * alpha, beta, kappa: UKF Scaling parameters - * t0: Starting time (s) - * dt : Step size (s) - * horizon : Prediction horizon (s) - * events : List of events to be predicted (subset of model.events, default is all events) - - Returns (PredictionResults) - ------- - times: [number] - Times for each simulated point in format times[index] - inputs: [[dict]] - Future input (from future_loading_eqn) for each sample and time in times - where inputs[sample_id][index] corresponds to time times[sample_id][index] - states: [[dict]] - Estimated states for each sample and time in times - where states[sample_id][index] corresponds to time times[sample_id][index] - outputs: [[dict]] - Estimated outputs for each sample and time in times - where outputs[sample_id][index] corresponds to time times[sample_id][index] - event_states: [[dict]] - Estimated event state (e.g., SOH), between 1-0 where 0 is event occurance, for each sample and time in times - where event_states[sample_id][index] corresponds to time times[sample_id][index] - time_of_event: UncertainData - Estimated time where a predicted event will occur for each sample. Note: Mean and Covariance Matrix will both - be nan if every sigma point doesnt reach threshold within horizon - Also, includes member final_state (time_of_event.final_state) which is the state at the last time step. - time_of_event.final_state is a dict of the form {'state_name': state_value}, is equal to None if event does not occur within horizon - """ - if isinstance(state, dict) or isinstance(state, self.model.StateContainer) or isinstance(state, ScalarData): - raise TypeError("state must be a distribution (e.g., MultivariateNormalDist, UnweightedSamples), not scalar") - elif isinstance(state, UncertainData): - state._type = self.model.StateContainer - else: - raise TypeError("state must be UncertainData, dict, or StateContainer") - - params = deepcopy(self.parameters) # copy parameters - params.update(kwargs) # update for specific run - - if len(params['events']) == 0 and 'horizon' not in params: - raise ValueError("If specifying no event (i.e., simulate to time), must specify horizon") - - # Optimizations - events_to_predict = params['events'] - dt = params['dt'] - model = self.model - filt = self.filter - sigma_points = self.sigma_points - n_points = sigma_points.num_sigmas() - threshold_met = model.threshold_met - StateContainer = model.StateContainer - - # Update State - self.__state_keys = state_keys = state.mean.keys() # Used to maintain ordering as we strip keys and return - filt.x = [x for x in state.mean.values()] - filt.P = state.cov - - # Setup first states - t = params['t0'] - save_pt_index = 0 - ToE = {key: [float('nan') for i in range(n_points)] for key in events_to_predict} # Keep track of final ToE values - last_state = {key: [None for i in range(n_points)] for key in events_to_predict} # Keep track of final state values - - times = [] - inputs = [] - states = [] - save_freq = params['save_freq'] - next_save = t + save_freq - save_pts = params['save_pts'] - save_pts.append(1e99) # Add last endpoint - def update_all(): - times.append(t) - inputs.append(deepcopy(self.__input)) # Avoid optimization where u is not copied - x_dict = MultivariateNormalDist(self.__state_keys, filt.x, filt.P, _type = self.model.StateContainer) - states.append(x_dict) # Avoid optimization where x is not copied - - # Simulation - self.__input = future_loading_eqn(t, state.mean) - update_all() # First State - while t < params['horizon']: - # Iterate through time - t += dt - mean_state = StateContainer({key: x for (key, x) in zip(state_keys, filt.x)}) - self.__input = future_loading_eqn(t, mean_state) - filt.predict(dt=dt) - - # Record States - if (t >= next_save): - next_save += save_freq - update_all() - if (t >= save_pts[save_pt_index]): - save_pt_index += 1 - update_all() - - # Check that any sigma point has hit event - points = sigma_points.sigma_points(filt.x, filt.P) - all_failed = True - for i, point in zip(range(n_points), points): - # x = StateContainer({key: x for (key, x) in zip(state_keys, point)}) - x = StateContainer(point) - t_met = threshold_met(x) - - # Check Thresholds - for key in events_to_predict: - if t_met[key]: - if isnan(ToE[key][i]): - # First time event has been reached - ToE[key][i] = t - last_state[key][i] = x.copy() - else: - all_failed = False # This event for this sigma point hasn't been met yet - if all_failed: - # If all events have been reched for every sigma point - break - - # Prepare Results - pts = array([[e for e in ToE[key]] for key in ToE.keys()]) - pts = transpose(pts) - mean, cov = kalman.unscented_transform(pts, sigma_points.Wm, sigma_points.Wc) - - # Transform final state into {event_name: MultivariateNormalDist} - final_state = {} - for event_key in last_state.keys(): - if any([last_state_i is None for last_state_i in last_state[event_key]]): - # If any sigma point has not met the event threshold - final_state[event_key] = None - continue - last_state_pts = array([[last_state_i[state_key] for state_key in state_keys] for last_state_i in last_state[event_key]]) - # last_state_pts = transpose(last_state_pts) - last_state_mean, last_state_cov = kalman.unscented_transform(last_state_pts, sigma_points.Wm, sigma_points.Wc) - final_state[event_key] = MultivariateNormalDist(state_keys, last_state_mean, last_state_cov, _type = self.model.StateContainer) - - # At this point only time of event, inputs, and state are calculated - inputs_prediction = UnweightedSamplesPrediction(times, [inputs]) - state_prediction = Prediction(times, states) - output_prediction = LazyUTPrediction(state_prediction, sigma_points, kalman.unscented_transform, model.output) - event_state_prediction = LazyUTPrediction(state_prediction, sigma_points, kalman.unscented_transform, model.event_state) - time_of_event = MultivariateNormalDist(ToE.keys(), mean, cov) - time_of_event.final_state = final_state - return PredictionResults( - times, - inputs_prediction, - state_prediction, - output_prediction, - event_state_prediction, - time_of_event - ) - \ No newline at end of file diff --git a/src/prog_algs/state_estimators/__init__.py b/src/prog_algs/state_estimators/__init__.py deleted file mode 100644 index 4c034066..00000000 --- a/src/prog_algs/state_estimators/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from .kalman_filter import KalmanFilter -from .particle_filter import ParticleFilter -from .state_estimator import StateEstimator -from .unscented_kalman_filter import UnscentedKalmanFilter -__all__ = ['KalmanFilter', 'state_estimator', 'StateEstimator', 'unscented_kalman_filter', 'UnscentedKalmanFilter', 'particle_filter', 'ParticleFilter'] diff --git a/src/prog_algs/state_estimators/kalman_filter.py b/src/prog_algs/state_estimators/kalman_filter.py deleted file mode 100644 index 417ab45c..00000000 --- a/src/prog_algs/state_estimators/kalman_filter.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from copy import deepcopy -from filterpy import kalman -import numpy as np -from warnings import warn - -from prog_models import LinearModel - -from . import state_estimator -from ..uncertain_data import MultivariateNormalDist, UncertainData - -class KalmanFilter(state_estimator.StateEstimator): - """ - A Kalman Filter (KF) for state estimation - - This class defines the logic for performing a kalman filter with a LinearModel (see Prognostics Model Package). This filter uses measurement data with noise to generate a state estimate and covariance matrix. - - The supported configuration parameters (keyword arguments) for UKF construction are described below: - - Args: - model (PrognosticsModel): - A prognostics model to be used in state estimation - See: Prognostics Model Package - x0 (UncertainData, model.StateContainer, or dict): - Initial (starting) state, with keys defined by model.states \n - e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def'] - - Keyword Args: - alpha (float, optional): - KF Scaling parameter. An alpha > 1 turns this into a fading memory filter. - t0 (float, optional): - Starting time (s) - dt (float, optional): - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - Q (list[list[float]], optional): - Kalman Process Noise Matrix - R (list[list[float]], optional): - Kalman Measurement Noise Matrix - """ - default_parameters = { - 'alpha': 1, - 't0': -1e-10, - 'dt': 1 - } - - def __init__(self, model, x0, **kwargs): - # Note: Measurement equation kept in constructor to keep it consistent with other state estimators. This way measurement equation can be provided as an ordered argument, and will just be ignored here - if not isinstance(model, LinearModel): - raise Exception('Kalman Filter only supports Linear Models (i.e., models derived from prog_models.LinearModel)') - - super().__init__(model, x0, **kwargs) - - self.x0 = x0 - - if 'Q' not in self.parameters: - self.parameters['Q'] = np.diag([1.0e-3 for i in x0.keys()]) - if 'R' not in self.parameters: - # Size of what's being measured (not output) - # This is determined by running the measure function on the first state - self.parameters['R'] = np.diag([1.0e-3 for i in range(model.n_outputs)]) - - num_states = len(x0.keys()) - num_inputs = model.n_inputs + 1 - num_measurements = model.n_outputs - F = deepcopy(model.A) - B = deepcopy(model.B) - if np.size(B) == 0: - # If B is empty, replace with E. - # Append wont work if B is empty - B = deepcopy(model.E) - else: - B = np.append(B, deepcopy(model.E), 1) - - self.filter = kalman.KalmanFilter(num_states, num_measurements, num_inputs) - - self.__state_keys = list(x0.keys()) - if isinstance(x0, dict) or isinstance(x0, model.StateContainer): - warn("Warning: Use UncertainData type if estimating filtering with uncertain data.") - self.filter.x = np.array([[x0[key]] for key in model.states]) # x0.keys() - self.filter.P = self.parameters['Q'] / 10 - elif isinstance(x0, UncertainData): - x_mean = x0.mean - self.filter.x = np.array([[x_mean[key]] for key in model.states]) - - # Reorder covariance to be in same order as model.states - mapping = {i: list(x0.keys()).index(key) for i, key in enumerate(model.states)} - cov = x0.cov # Set covariance in case it has been calculated - mapped_cov = [[cov[mapping[i]][mapping[j]] for j in range(len(cov))] for i in range(len(cov))] # Set covariance based on mapping - self.filter.P = np.array(mapped_cov) - else: - raise TypeError("TypeError: x0 initial state must be of type {{dict, UncertainData}}") - - self.filter.Q = self.parameters['Q'] - self.filter.R = self.parameters['R'] - self.filter.F = F - self.filter.B = B - - def estimate(self, t: float, u, z, **kwargs): - """ - Perform one state estimation step (i.e., update the state estimate) - - Parameters - ---------- - t : double - Current timestamp in seconds (≥ 0.0) - e.g., t = 3.4 - u : dict - Measured inputs, with keys defined by model.inputs. - e.g., u = {'i':3.2} given inputs = ['i'] - z : dict - Measured outputs, with keys defined by model.outputs. - e.g., z = {'t':12.4, 'v':3.3} given inputs = ['t', 'v'] - - Keyword Arguments - ----------------- - dt : float, optional - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - """ - assert t > self.t, "New time must be greater than previous" - dt = kwargs.get('dt', self.parameters['dt']) - dt = min(t - self.t, dt) # Ensure dt is not larger than the maximum time step - # Create u array, ensuring order of model.inputs. And reshaping to (n,1), n can be 0. - inputs = np.array([u[key] for key in self.model.inputs]).reshape((-1,1)) - - # Add row of ones (to account for constant E term) - if np.size(inputs) == 0: - inputs = np.array([[1]]) - else: - inputs = np.append(inputs, [[1]], 0) - - # Update equations - # prog_models is dx = Ax + Bu + E - # kalman_models is x' = Fx + Bu, where x' is the next state - # Therefore we need to add the diagnol matrix 1 to A to convert - # And A and B should be multiplied by the time step - B = np.multiply(self.filter.B, dt) - F = np.multiply(self.filter.F, dt) + np.diag([1]* self.model.n_states) - - # Predict - while self.t < t : - self.filter.predict(u = inputs, B = B, F = F) - self.t += dt - - # Create z array, ensuring order of model.outputs - outputs = np.array([z[key] for key in self.model.outputs]) - - # Subtract D from outputs - # This is done because prog_models expects the form: - # z = Cx + D - # While kalman expects - # z = Cx - outputs = outputs - self.model.D - - self.filter.update(outputs, H=self.model.C) - - @property - def x(self) -> MultivariateNormalDist: - """ - Getter for property 'x', the current estimated state. - - Example - ------- - state = observer.x - """ - return MultivariateNormalDist(self.model.states, self.filter.x.ravel(), self.filter.P, _type = self.model.StateContainer) diff --git a/src/prog_algs/state_estimators/particle_filter.py b/src/prog_algs/state_estimators/particle_filter.py deleted file mode 100644 index d85ad773..00000000 --- a/src/prog_algs/state_estimators/particle_filter.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from filterpy.monte_carlo import residual_resample -import numpy as np -from numpy import array, empty, take, exp, max, take, float64 -from scipy.stats import norm -from warnings import warn - -from prog_models.utils.containers import DictLikeMatrixWrapper - -from . import state_estimator -from ..uncertain_data import UnweightedSamples, ScalarData, UncertainData - - -class ParticleFilter(state_estimator.StateEstimator): - """ - Estimates state using a Particle Filter (PF) algorithm. - - This class defines logic for a PF using a Prognostics Model (see Prognostics Model Package). This filter uses measurement data with noise to estimate the state of the system using a particles. At each step, particles are predicted forward (with noise). Particles are resampled with replacement from the existing particles according to how well the particles match the observed measurements. - - The supported configuration parameters (keyword arguments) for UKF construction are described below: - - Args: - model (PrognosticsModel): - A prognostics model to be used in state estimation - See: Prognostics Model Package - x0 (UncertainData, model.StateContainer, or dict): - Initial (starting) state, with keys defined by model.states \n - e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def'] - - Keyword Args: - t0 (float, optional): - Starting time (s) - dt (float, optional): - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - num_particles (int, optional): - Number of particles in particle filter - resample_fcn (function, optional): - Resampling function ([weights]) -> [indexes] e.g., filterpy.monte_carlo.residual_resample - """ - default_parameters = { - 't0': -1e-99, # practically 0, but allowing for a 0 first estimate - 'num_particles': None, - 'resample_fcn': residual_resample, - } - - def __init__(self, model, x0, **kwargs): - super().__init__(model, x0, **kwargs) - - self._measure = model.output - - # Build array inplace - if isinstance(x0, DictLikeMatrixWrapper) or isinstance(x0, dict): - x0 = ScalarData(x0) - elif not isinstance(x0, UncertainData): - raise TypeError(f"x0 must be of type UncertainData or StateContainer, was {type(x0)}.") - - if self.parameters['num_particles'] is None and isinstance(x0, UnweightedSamples): - sample_gen = x0 # Directly use samples passed in - self.parameters['num_particles'] = len(x0) - else: - if self.parameters['num_particles'] is None: - # Default to 100 particles - self.parameters['num_particles'] = 100 - else: - # Added to avoid float/int issues - self.parameters['num_particles'] = int(self.parameters['num_particles']) - sample_gen = x0.sample(self.parameters['num_particles']) - samples = [array(sample_gen.key(k), dtype=float64) for k in x0.keys()] - - self.particles = model.StateContainer(array(samples, dtype=float64)) - - if 'R' in self.parameters: - # For backwards compatibility - warn("'R' is deprecated. Use 'measurement_noise' instead.", DeprecationWarning) - self.parameters['measurement_noise'] = self.parameters['R'] - elif 'measurement_noise' not in self.parameters: - self.parameters['measurement_noise'] = {key: 0.0 for key in model.outputs} - - def __str__(self): - return "{} State Estimator".format(self.__class__) - - def estimate(self, t : float, u, z, dt = None): - """ - Perform one state estimation step (i.e., update the state estimate, filt.x) - - Args - ---------- - t : float - Current timestamp in seconds (≥ 0.0) - e.g., t = 3.4 - u : InputContainer - Measured inputs, with keys defined by model.inputs. - e.g., u = m.InputContainer({'i':3.2}) given inputs = ['i'] - z : OutputContainer - Measured outputs, with keys defined by model.outputs. - e.g., z = m.OutputContainer({'t':12.4, 'v':3.3}) given outputs = ['t', 'v'] - - Keyword Args - ------------ - dt : float, optional - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - - Note - ---- - This method updates the state estimate stored in filt.x, but doesn't return the updated estimate. Call filt.x to get the updated estimate. - """ - assert t > self.t, "New time must be greater than previous" - if dt is None: - dt = min(t - self.t, self.parameters['dt']) - - # Check Types - if isinstance(u, dict): - u = self.model.InputContainer(u) - if isinstance(z, dict): - z = self.model.OutputContainer(z) - - # Optimization - particles = self.particles - next_state = self.model.next_state - apply_process_noise = self.model.apply_process_noise - apply_limits = self.model.apply_limits - output = self._measure - # apply_measurement_noise = self.model.apply_measurement_noise - noise_params = self.parameters['measurement_noise'] - num_particles = self.parameters['num_particles'] - # Check which output keys are present (i.e., output of measurement function) - measurement_keys = output(self.model.StateContainer({key: particles[key][0] for key in particles.keys()})).keys() - zPredicted = {key: empty(num_particles) for key in measurement_keys} - - if self.model.is_vectorized: - # Propagate particles state - while self.t < t: - dt_i = min(dt, t-self.t) - particles = apply_process_noise(next_state(particles, u, dt_i), dt_i) - self.particles = apply_limits(particles) - self.t += dt_i - - # Get particle measurements - zPredicted = output(self.particles) - else: - # Propagate and calculate weights - for i in range(num_particles): - t_i = self.t # Used to mark time for each particle - x = self.model.StateContainer({key: particles[key][i] for key in particles.keys()}) - while t_i < t: - dt_i = min(dt, t-t_i) - x = next_state(x, u, dt_i) - x = apply_process_noise(x, dt_i) - x = apply_limits(x) - t_i += dt_i - for key in particles.keys(): - self.particles[key][i] = x[key] - z = output(x) - for key in measurement_keys: - zPredicted[key][i] = z[key] - self.t = t - - # Calculate pdf values - pdfs = array([norm(zPredicted[key], noise_params[key]).logpdf(z[key]) - for key in zPredicted.keys()]) - - # Calculate log weights - log_weights = pdfs.sum(0) - - # Scale - # We subtract the max log weights for numerical stability. - # Sometimes log weights can be a large negative value - # when you exponentiate that value the computer will round the result to 0 for most of the weights (sometimes all of them) - # this causes problems when trying to sample from the particles. - # We shift them up by the max log weight (essentially making the max log weight 0) to help avoid that problem. - # When we normalize the weights by dividing by the sum of all the weights, that constant cancels out. - max_log_weight = max(log_weights) - scaled_weights = log_weights - max_log_weight - - # Convert to weights - unnorm_weights = exp(scaled_weights) - - # Normalize - total_weight = sum(unnorm_weights) - self.weights = unnorm_weights / total_weight - - # Resample indices - indexes = self.parameters['resample_fcn'](self.weights) - - # Resampled particles - samples = [take(self.particles[state], indexes) - for state in self.particles.keys()] - - # Particles as a dictionary - self.particles = self.model.StateContainer(array(samples)) - - @property - def x(self) -> UnweightedSamples: - """ - Getter for property 'x', the current estimated state. - - Example - ------- - state = observer.x - """ - return UnweightedSamples(self.particles, _type = self.model.StateContainer) diff --git a/src/prog_algs/state_estimators/state_estimator.py b/src/prog_algs/state_estimators/state_estimator.py deleted file mode 100644 index 75cac51f..00000000 --- a/src/prog_algs/state_estimators/state_estimator.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from abc import ABC, abstractmethod, abstractproperty -from copy import deepcopy - -from ..uncertain_data import UncertainData - - -class StateEstimator(ABC): - """ - Interface class for state estimators - - Abstract base class for creating state estimators that perform state estimation. Subclasses must implement this interface. Equivalent to "Observers" in NASA's Matlab Prognostics Algorithm Library - - Args: - model (PrognosticsModel): - A prognostics model to be used in state estimation - See: Prognostics Model Package - x0 (UncertainData, model.StateContainer, or dict): - Initial (starting) state, with keys defined by model.states \n - e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def'] - - Keyword Args: - t0 (float): - Initial time at which prediction begins, e.g., 0 - dt (float): - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - **kwargs: - See state-estimator specific documentation for specific keyword arguments. - """ - - default_parameters = { - 't0': -1e-10, - 'dt': float('inf') - } - - def __init__(self, model, x0, **kwargs): - # Check model - if not hasattr(model, 'output'): - raise NotImplementedError("model must have `output` method") - if not hasattr(model, 'next_state'): - raise NotImplementedError("model must have `next_state` method") - if not hasattr(model, 'outputs'): - raise NotImplementedError("model must have `outputs` property") - if not hasattr(model, 'states'): - raise NotImplementedError("model must have `states` property") - self.model = model - - # Check x0 - for key in model.states: - if key not in x0: - raise KeyError("x0 missing state `{}`".format(key)) - - # Process kwargs (configuration) - self.parameters = deepcopy(StateEstimator.default_parameters) - self.parameters.update(self.default_parameters) - self.parameters.update(kwargs) - - if isinstance(self.parameters['t0'], int): - self.parameters['t0'] = float(self.parameters['t0']) - if isinstance(self.parameters['dt'], int): - self.parameters['dt'] = float(self.parameters['dt']) - - if not isinstance(self.parameters['t0'], float): - raise TypeError(f"t0 must be float, was {type(self.parameters['t0'])}") - if not isinstance(self.parameters['dt'], float): - raise TypeError(f"dt must be float, was {type(self.parameters['dt'])}") - if self.parameters['dt'] <= 0: - raise ValueError(f"dt must be positive, was {self.parameters['dt']}") - - self.t = self.parameters['t0'] # Initial Time - - @abstractmethod - def estimate(self, t: float, u, z, **kwargs) -> None: - """ - Perform one state estimation step (i.e., update the state estimate, filt.x) - - Args - ---------- - t : float - Current timestamp in seconds (≥ 0.0) - e.g., t = 3.4 - u : InputContainer - Measured inputs, with keys defined by model.inputs. - e.g., u = m.InputContainer({'i':3.2}) given inputs = ['i'] - z : OutputContainer - Measured outputs, with keys defined by model.outputs. - e.g., z = m.OutputContainer({'t':12.4, 'v':3.3}) given outputs = ['t', 'v'] - - Keyword Args - ------------- - dt : float, optional - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - **kwargs: - See state-estimator specific documentation for specific keyword arguments. - - Note - ---- - This method updates the state estimate stored in filt.x, but doesn't return the updated estimate. Call filt.x to get the updated estimate. - """ - - @property - @abstractproperty - def x(self) -> UncertainData: - """ - The current estimated state. - - Example - ------- - state = filt.x - """ diff --git a/src/prog_algs/state_estimators/unscented_kalman_filter.py b/src/prog_algs/state_estimators/unscented_kalman_filter.py deleted file mode 100644 index 38c34eea..00000000 --- a/src/prog_algs/state_estimators/unscented_kalman_filter.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from filterpy import kalman -from numpy import diag, array -from warnings import warn - -from prog_algs.state_estimators import state_estimator -from prog_algs.uncertain_data import MultivariateNormalDist, UncertainData - -class UnscentedKalmanFilter(state_estimator.StateEstimator): - """ - An Unscented Kalman Filter (UKF) for state estimation - - This class defines logic for performing an unscented kalman filter with a Prognostics Model (see Prognostics Model Package). This filter uses measurement data with noise to generate a state estimate and covariance matrix. - - The supported configuration parameters (keyword arguments) for UKF construction are described below: - - Args: - model (PrognosticsModel): - A prognostics model to be used in state estimation - See: Prognostics Model Package - x0 (UncertainData, model.StateContainer, or dict): - Initial (starting) state, with keys defined by model.states \n - e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def'] - - Keyword Args: - alpha (float, optional): - UKF Scaling parameter - beta (float, optional): - UKF Scaling parameter - kappa (float, optional): - UKF Scaling parameter - t0 (float, optional): - Starting time (s) - dt (float, optional): - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - Q (list[list[float]], optional): - Process Noise Matrix - R (list[list[float]], optional): - Measurement Noise Matrix - """ - default_parameters = { - 'alpha': 1, - 'beta': 0, - 'kappa': -1, - } - - def __init__(self, model, x0, **kwargs): - super().__init__(model, x0, **kwargs) - - self.__input = None - self.x0 = x0 - # Saving for reduce pickling - - def measure(x): - x = model.StateContainer({key: value for (key, value) in zip(x0.keys(), x)}) - R_err = model.parameters['measurement_noise'].copy() - model.parameters['measurement_noise'] = dict.fromkeys(R_err, 0) - z = model.output(x) - model.parameters['measurement_noise'] = R_err - return array(list(z.values())).ravel() - - if 'Q' not in self.parameters: - self.parameters['Q'] = diag([1.0e-3 for _ in x0.keys()]) - - def state_transition(x, dt): - x = model.StateContainer({key: value for (key, value) in zip(x0.keys(), x)}) - Q_err = model.parameters['process_noise'].copy() - model.parameters['process_noise'] = dict.fromkeys(Q_err, 0) - x = model.next_state(x, self.__input, dt) - return array(list(x.values())).ravel() - - num_states = len(x0.keys()) - num_measurements = model.n_outputs - points = kalman.MerweScaledSigmaPoints(num_states, alpha=self.parameters['alpha'], beta=self.parameters['beta'], kappa=self.parameters['kappa']) - self.filter = kalman.UnscentedKalmanFilter(num_states, num_measurements, self.parameters['dt'], measure, state_transition, points) - - if isinstance(x0, dict) or isinstance(x0, model.StateContainer): - warn("Use UncertainData type if estimating filtering with uncertain data.") - self.filter.x = array(list(x0.values())) - self.filter.P = self.parameters['Q'] / 10 - elif isinstance(x0, UncertainData): - x_mean = x0.mean - self.filter.x = array(list(x_mean.values())) - self.filter.P = x0.cov - else: - raise TypeError("TypeError: x0 initial state must be of type {{dict, UncertainData}}") - - if 'R' not in self.parameters: - # Size of what's being measured (not output) - # This is determined by running the measure function on the first state - self.parameters['R'] = diag([1.0e-3 for i in range(len(measure(self.filter.x)))]) - self.filter.Q = self.parameters['Q'] - self.filter.R = self.parameters['R'] - - def estimate(self, t: float, u, z, **kwargs): - """ - Perform one state estimation step (i.e., update the state estimate) - - Parameters - ---------- - t : double - Current timestamp in seconds (≥ 0.0) - e.g., t = 3.4 - u : dict - Measured inputs, with keys defined by model.inputs. - e.g., u = {'i':3.2} given inputs = ['i'] - z : dict - Measured outputs, with keys defined by model.outputs. - e.g., z = {'t':12.4, 'v':3.3} given inputs = ['t', 'v'] - - Keyword Args - ------------ - dt : float, optional - Maximum timestep for prediction in seconds. By default, the timestep dt is the difference between the last and current call of .estimate(). Some models are unstable at larger dt. Setting a smaller dt will force the model to take smaller steps; resulting in multiple prediction steps for each estimate step. Default is the parameters['dt'] - e.g., dt = 1e-2 - """ - assert t > self.t, "New time must be greater than previous" - dt = kwargs.get('dt', self.parameters['dt']) - dt = min(t - self.t, dt) - self.__input = u - while self.t < t: - self.filter.predict(dt=dt) - self.t += dt - self.filter.update(array(list(z.values()))) - - @property - def x(self) -> MultivariateNormalDist: - """ - Getter for property 'x', the current estimated state. - - Example - ------- - state = observer.x - """ - return MultivariateNormalDist(self.x0.keys(), self.filter.x, self.filter.P, _type=self.model.StateContainer) diff --git a/src/prog_algs/uncertain_data/__init__.py b/src/prog_algs/uncertain_data/__init__.py deleted file mode 100644 index 42b58be8..00000000 --- a/src/prog_algs/uncertain_data/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from .uncertain_data import UncertainData -from .unweighted_samples import UnweightedSamples -from .scalar_data import ScalarData -from .multivariate_normal_dist import MultivariateNormalDist - -__all__ = ['UncertainData', 'UnweightedSamples', 'ScalarData', 'MultivariateNormalDist'] diff --git a/src/prog_algs/uncertain_data/multivariate_normal_dist.py b/src/prog_algs/uncertain_data/multivariate_normal_dist.py deleted file mode 100644 index 793ea111..00000000 --- a/src/prog_algs/uncertain_data/multivariate_normal_dist.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from numpy import array -from numpy.random import multivariate_normal - -from . import UncertainData, UnweightedSamples - - -class MultivariateNormalDist(UncertainData): - """ - Data represented by a multivariate normal distribution with mean and covariance matrix - - Args: - labels (list[str]): Labels for states, in order of mean values - mean (array[float]): Mean values for state in the same order as labels - covar (array[array[float]]): Covariance matrix for state - """ - def __init__(self, labels, mean: array, covar : array, _type = dict): - self.__labels = list(labels) - self.__mean = array(list(mean)) - self.__covar = array(list(covar)) - super().__init__(_type) - - def __reduce__(self): - return (MultivariateNormalDist, (self.__labels, self.__mean, self.__covar)) - - def __eq__(self, other: "MultivariateNormalDist") -> bool: - return isinstance(other, MultivariateNormalDist) and self.keys() == other.keys() and self.mean == other.mean and (self.cov == other.cov).all() - - def __add__(self, other: int) -> "UncertainData": - if other == 0: - return self - return MultivariateNormalDist(self.__labels, array([i+other for i in self.__mean]), self.__covar) - - def __radd__(self, other: int) -> "UncertainData": - return self.__add__(other) - - def __iadd__(self, other: int) -> "UncertainData": - if not isinstance(other, (int, float)): - raise TypeError(f" unsupported operand type(s) for +: '{type(other)}' and '{type(self.__mean[0])}'") - if other != 0: - self.__mean = array([i+other for i in self.__mean]) - return self - - def __sub__(self, other: int) -> "UncertainData": - if other == 0: - return self - return MultivariateNormalDist(self.__labels, array([i-other for i in self.__mean]), self.__covar) - - def __rsub__(self, other: int) -> "UncertainData": - return self.__sub__(other) - - def __isub__(self, other: int) -> "UncertainData": - if not isinstance(other, (int, float)): - raise TypeError(f" unsupported operand type(s) for -: '{type(other)}' and '{type(self.__mean[0])}'") - if other != 0: - self.__mean = array([i-other for i in self.__mean]) - return self - - def sample(self, num_samples: int = 1) -> UnweightedSamples: - if len(self.__mean) != len(self.__labels): - raise Exception("labels must be provided for each value") - - samples = multivariate_normal(self.__mean, self.__covar, num_samples) - samples = [{key: value for (key, value) in zip(self.__labels, x)} for x in samples] - return UnweightedSamples(samples, _type = self._type) - - def keys(self) -> list: - return self.__labels - - @property - def median(self) -> float: - # For normal distribution medain = mean - return self.mean - - @property - def mean(self) -> dict: - return self._type({key: value for (key, value) in zip(self.__labels, self.__mean)}) - - def __str__(self) -> str: - return 'MultivariateNormalDist(mean: {}, covar: {})'.format(self.__mean, self.__covar) - - @property - def cov(self) -> array: - return self.__covar diff --git a/src/prog_algs/uncertain_data/scalar_data.py b/src/prog_algs/uncertain_data/scalar_data.py deleted file mode 100644 index 6dfb207d..00000000 --- a/src/prog_algs/uncertain_data/scalar_data.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from typing import Union -from numpy import array - -from . import UncertainData, UnweightedSamples - - -class ScalarData(UncertainData): - """ - Data without uncertainty- single value - - Args: - state (dict or Container): Single state in the form of dict or model.*Container (InputContainer, OutputContainer, Statecontainer) representing states and respective values. - """ - def __init__(self, state, _type=dict): - self.__state = state - super().__init__(_type) - - def __reduce__(self): - return (ScalarData, (self.__state, )) - - def __eq__(self, other: "ScalarData") -> bool: - return isinstance(other, ScalarData) and other.mean == self.__state - - def __add__(self, other: int) -> "UncertainData": - if other == 0: - return self - new_state = dict() - for k,v in self.__state.items(): - new_state[k] = v + other - return ScalarData(new_state) - - def __radd__(self, other: int) -> "UncertainData": - return self.__add__(other) - - def __iadd__(self, other: int) -> "UncertainData": - if other != 0: - for k in self.__state.keys(): - self.__state[k] += other - return self - - def __sub__(self, other: int) -> "UncertainData": - new_state = dict() - for k,v in self.__state.items(): - new_state[k] = v - other - return ScalarData(new_state) - - def __rsub__(self, other: int) -> "UncertainData": - return self.__sub__(other) - - def __isub__(self, other: int) -> "UncertainData": - if other != 0: - for k in self.__state.keys(): - self.__state[k] -= other - return self - - @property - def median(self) -> dict: - return self.mean - - @property - def mean(self) -> dict: - return self._type(self.__state) - - @property - def cov(self) -> array: - return [[0 for _ in range(len(self.__state))] for _ in range(len(self.__state))] - - def keys(self): - return self.__state.keys() - - def sample(self, num_samples : int = 1) -> UnweightedSamples: - return UnweightedSamples([self.__state] * num_samples, _type = self._type) - - def __str__(self) -> str: - return 'ScalarData({})'.format(self.__state) - - def percentage_in_bounds(self, bounds: Union[list, dict]) -> dict: - if isinstance(bounds, list): - bounds = {key: bounds for key in self.keys()} - if not isinstance(bounds, dict) and all([isinstance(b, list) for b in bounds]): - raise TypeError("Bounds must be list [lower, upper] or dict (key: [lower, upper]), was {}".format(type(bounds))) - return {key: (1 if bounds[key][0] < x and bounds[key][1] > x else 0) for (key, x) in self.__state.items()} diff --git a/src/prog_algs/uncertain_data/uncertain_data.py b/src/prog_algs/uncertain_data/uncertain_data.py deleted file mode 100644 index baea862d..00000000 --- a/src/prog_algs/uncertain_data/uncertain_data.py +++ /dev/null @@ -1,283 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from abc import ABC, abstractmethod, abstractproperty -from collections import defaultdict -from matplotlib.figure import Figure -from numpy import array - -from ..utils.table import print_table_recursive -from ..visualize import plot_scatter, plot_hist -from prog_models.utils.containers import DictLikeMatrixWrapper - - -class UncertainData(ABC): - """ - Abstract base class for data with uncertainty. Any new uncertainty type must implement this class - """ - - def __init__(self, _type=dict): - self._type = _type - - @abstractmethod - def sample(self, nSamples: int = 1): - """Generate samples from data - - Args: - nSamples (int, optional): Number of samples to generate. Defaults to 1. - - Returns: - samples (UnweightedSamples): Array of nSamples samples - - Example: - :: - - samples = data.samples(100) - """ - - @property - @abstractproperty - def median(self) -> dict: - """The median of the UncertainData distribution or samples - - Returns: - dict[str, float]: Median value. e.g., {'key1': 23.2, ...} - - Example: - :: - - median_value = data.median - """ - - @property - @abstractproperty - def mean(self) -> dict: - """The mean of the UncertainData distribution or samples - - Returns: - dict[str, float]: Mean value. e.g., {'key1': 23.2, ...} - - Example: - :: - - mean_value = data.mean - """ - - @property - @abstractproperty - def cov(self) -> array: - """The covariance matrix of the UncertiantyData distribution or samples in order of keys (i.e., cov[1][1] is the standard deviation for key keys()[1]) - - Returns: - np.array[np.array[float]]: Covariance matrix - - Example: - :: - - covariance_matrix = data.cov - """ - - def relative_accuracy(self, ground_truth: dict) -> dict: - """The relative accuracy is how close the mean of the distribution is to the ground truth, on relative terms - - :math:`RA = 1 - \dfrac{\| r-p \|}{r}` - - Where r is ground truth and p is mean of predicted distribution [0]_ - - Returns: - dict[str, float]: Relative accuracy for each event where value is relative accuracy between [0,1] - - Example: - :: - - ra = data.relative_accuracy({'key1': 22, 'key2': 57}) - - References: - .. [0] Prognostics: The Science of Making Predictions (Goebel et al, 239) - """ - # if this check isn't here, goes to divide by zero check and raises AttributeError instead of TypeError. Keep? There are unittests checking for type - if not (isinstance(ground_truth, dict) or isinstance(ground_truth, DictLikeMatrixWrapper)): - raise TypeError("Ground truth must be passed as a dictionary or *.container argument.") - if not all(ground_truth.values()): - raise ZeroDivisionError("Ground truth values must be non-zero in calculating relative accuracy.") - return {k:1 - (abs(ground_truth[k] - v)/ground_truth[k]) for k,v in self.mean.items()} - - @abstractmethod - def keys(self): - """Get the keys for the property represented - - Returns: - list[str]: keys - - Example: - :: - - keys = data.keys() - """ - - def __contains__(self, key): - return key in self.keys() - - def percentage_in_bounds(self, bounds: tuple, keys: list = None, n_samples: int = 1000) -> dict: - """Calculate percentage of dist is within specified bounds - - Args: - bounds (tuple[float, float] or dict): Lower and upper bounds. \n - if tuple: (lower, upper)\n - if dict: {key: (lower, upper), ...} - keys (list[str], optional): UncertainData keys to consider when calculating. Defaults to all keys. - n_samples (int, optional): Number of samples to use when calculating - - Returns: - dict: Percentage within bounds for each key in keys (where 0.5 = 50%). e.g., {'key1': 1, 'key2': 0.75} - - Example: - :: - - data.percentage_in_bounds((1025, 1075)) - data.percentage_in_bounds({'key1': (1025, 1075), 'key2': (2520, 2675)}) - data.percentage_in_bounds((1025, 1075), keys=['key1', 'key3']) - """ - return self.sample(n_samples).percentage_in_bounds(bounds, keys=keys) - - def metrics(self, **kwargs) -> dict: - """Calculate Metrics for this dist - - Keyword Args: - ground_truth (int or dict, optional): Ground truth value. Defaults to None. - n_samples (int, optional): Number of samples to use for calculating metrics (if not UnweightedSamples) - keys (list[str], optional): Keys to calculate metrics for. Defaults to all keys. - - Returns: - dict: Dictionary of metrics - - Example: - :: - - print(data.metrics()) - m = data.metrics(ground_truth={'key1': 200, 'key2': 350}) - m = data.metrics(keys=['key1', 'key3']) - """ - from ..metrics import calc_metrics - return calc_metrics(self, **kwargs) - - def plot_scatter(self, fig : Figure = None, keys : list = None, num_samples : int = 100, **kwargs) -> Figure: - """ - Produce a scatter plot - - Args: - fig (Figure, optional): Existing figure previously used to plot states. If passed a figure argument additional data will be added to the plot. Defaults to creating new figure - keys (list[str], optional): Keys to plot. Defaults to all keys. - num_samples (int, optional): Number of samples to plot. Defaults to 100 - **kwargs (optional): Additional keyword arguments passed to scatter function. - - Returns: - Figure - - Example: - :: - - m = [5, 7, 3] - c = [[0.3, 0.5, 0.1], [0.6, 0.7, 1e-9], [1e-9, 1e-10, 1]] - d = MultivariateNormalDist(['a', 'b', 'c'], m, c) - d.plot_scatter() # With 100 samples - states.plot_scatter(num_samples=5) # Specifying the number of samples to plot - states.plot_scatter(keys=['a', 'b']) # only plot those keys - """ - if keys is None: - keys = self.keys() - samples = self.sample(num_samples) - return plot_scatter(samples, fig=fig, keys=keys, **kwargs) - - def plot_hist(self, fig = None, keys = None, num_samples = 100, **kwargs): - """ - Create a histogram - - Args: - fig (MatPlotLib Figure, optional): Existing histogram figure to be overritten. Defaults to create new figure. - num_samples (int, optional): Number of samples to plot. Defaults to 100 - keys (list(String), optional): Keys to be plotted. Defaults to None. - - Example: - :: - - m = [5, 7, 3] - c = [[0.3, 0.5, 0.1], [0.6, 0.7, 1e-9], [1e-9, 1e-10, 1]] - d = MultivariateNormalDist(['a', 'b', 'c'], m, c) - d.plot_hist() # With 100 samples - states.plot_hist(num_samples=20) # Specifying the number of samples to plot - states.plot_hist(keys=['a', 'b']) # only plot those keys - """ - if keys is None: - keys = self.keys() - samples = self.sample(num_samples) - return plot_hist(samples, fig=fig, keys=keys, **kwargs) - - def describe(self, title : str = "UncertainData Metrics", print : bool = True) -> defaultdict: - """ - Print and view basic statistical information about this UncertainData object in a text-based printed table. - - Args: - title : str - Title of the table, printed before data rows. - print : bool = True - Optional argument specifying whether to print or not; default true. - - Returns: - defaultdict - Dictionary of lists used to print metrics. - - Example: - :: - - data.describe() - """ - recursive_metrics_table = print_table_recursive(self.metrics(), title, print) - return recursive_metrics_table - - @abstractmethod - def __add__(self, other : int) -> "UncertainData": - """Overriding __add__ (+ operator) for UncertainData. - - Args: - other (int): Integer value to be applied to class where appropriate. - """ - - @abstractmethod - def __radd__(self, other : int) -> "UncertainData": - """Overriding __radd__ (+ operator right) for UncertainData. - - Args: - other (int): Integer value to be applied to class where appropriate. - """ - - @abstractmethod - def __iadd__(self, other : int) -> "UncertainData": - """Overriding __iadd__ (+= operator) for UncertainData. - - Args: - other (int): Integer value to be applied to class where appropriate. - """ - - @abstractmethod - def __sub__(self, other : int) -> "UncertainData": - """Overriding __sub__ (- operator) for UncertainData. - - Args: - other (int): Integer value to be applied to class where appropriate. - """ - - @abstractmethod - def __rsub__(self, other : int) -> "UncertainData": - """Overriding __rsub__ (- operator right) for UncertainData. - - Args: - other (int): Integer value to be applied to class where appropriate. - """ - - @abstractmethod - def __isub__(self, other : int) -> "UncertainData": - """Overriding __isub__ (-= operator) for UncertainData. - - Args: - other (int): Integer value to be applied to class where appropriate. - """ diff --git a/src/prog_algs/uncertain_data/unweighted_samples.py b/src/prog_algs/uncertain_data/unweighted_samples.py deleted file mode 100644 index 353d7267..00000000 --- a/src/prog_algs/uncertain_data/unweighted_samples.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from collections import UserList -from collections.abc import Iterable -from numpy import array, cov, random -from warnings import warn - -from prog_models.utils.containers import DictLikeMatrixWrapper - -from . import UncertainData - - -class UnweightedSamples(UncertainData, UserList): - """ - Uncertain Data represented by a set of samples. Objects of this class can be treated like a list where samples[n] returns the nth sample (Dict). - - Args: - samples (array, dict, or model.*Container, optional): array of samples. Defaults to empty array.\n - If dict, must be of the form of {key: [value, ...], ...}\n - If list, must be of the form of [{key: value, ...}, ...]\n - If InputContainer, OutputContainer, or StateContainer, must be of the form of *Container({'key': value, ...}) - """ - def __init__(self, samples: list = [], _type=dict): - super().__init__(_type) - if isinstance(samples, dict) or isinstance(samples, DictLikeMatrixWrapper): - # Is in form of {key: [value, ...], ...} - # Convert to array of samples - if len(samples.keys()) == 0: - self.data = [] # is empty - return - n_samples = len(list(samples.values())[0]) # Number of samples - self.data = [{key: value[i] for key, value in samples.items()} for i in range(n_samples)] - elif isinstance(samples, Iterable): - # is in form of [{key: value, ...}, ...] - self.data = samples - else: - raise ValueError('Invalid input. Must be list or dict, was {}'.format(type(samples))) - - def __eq__(self, other): - return isinstance(other, UnweightedSamples) and self.data == other.data - - def __getitem__(self, n): - datem = self.data[n] - return self._type(datem) if datem is not None else None - - def __add__(self, other: int) -> "UncertainData": - if other == 0: - return self - result = [] - for i in range(len(self.data)): - new_dict = {} - for k,v in self.data[i].items(): - new_dict[k] = v + other - result.append(new_dict) - return UnweightedSamples(result) - - def __radd__(self, other: int) -> "UncertainData": - return self.__add__(other) - - def __iadd__(self, other: int) -> "UncertainData": - if other != 0: - for i in range(len(self.data)): - for k,v in self.data[i].items(): - self.data[i][k] += other - return self - - def __sub__(self, other: int) -> "UncertainData": - if other == 0: - return self - result = [] - for i in range(len(self.data)): - new_dict = {} - for k,v in self.data[i].items(): - new_dict[k] = v - other - result.append(new_dict) - return UnweightedSamples(result) - - def __rsub__(self, other: int) -> "UncertainData": - return self.__sub__(other) - - def __isub__(self, other: int) -> "UncertainData": - if other != 0: - for i in range(len(self.data)): - for k,v in self.data[i].items(): - self.data[i][k] -= other - return self - - def __reduce__(self): - return (UnweightedSamples, (self.data, )) - - def sample(self, num_samples: int = 1, replace: bool = True) -> "UnweightedSamples": - # Completely random resample - indices = random.choice(len(self.data), int(num_samples), replace=replace) - return UnweightedSamples([self.data[i] for i in indices], _type=self._type) - - def keys(self) -> list: - if len(self.data) == 0: - return [] # is empty - for sample in self: - if sample is not None: - return sample.keys() - return [] # Every element is none - - def key(self, key) -> list: - """Return samples for given key - - Args: - key (str): key - - Returns: - list: list of values for given key - """ - return [sample[key] for sample in self.data if sample is not None] - - @property - def median(self) -> dict: - # Calculate Geometric median of all samples - min_value = float('inf') - none_flag = False - for i, datem in enumerate(self.data): - if datem is None: - continue - p1 = array([d for d in datem.values() if d is not None]) - if not none_flag and len(p1) < len(datem): - none_flag = True - warn("Some samples were None, resulting median is of all non-None samples. Note: in some cases, this will bias the median result.") - total_dist = sum( - sum((p1 - array([di for di in d.values() if di is not None]))**2) # Distance between 2 points - for d in self.data if d is not None) # For each point - if total_dist < min_value: - min_index = i - min_value = total_dist - return self._type(self[min_index]) - - @property - def mean(self) -> dict: - mean = {} - for key in self.keys(): - values = array([x[key] for x in self.data if x is not None and x[key] is not None]) - if len(values) < len(self.data): - warn("Some samples were None, resulting mean is of all non-None samples. Note: in some cases, this will bias the mean result.") - mean[key] = values.mean() - return self._type(mean) - - @property - def cov(self) -> dict: - if len(self.data) == 0: - return [[]] - unlabeled_samples = array([[x[key] for x in self.data if x is not None and x[key] is not None] for key in self.keys()]) - if len(unlabeled_samples) < len(self.data): - warn("Some samples were None, resulting covariance is of all non-None samples. Note: in some cases, this will bias the covariance result.") - return cov(unlabeled_samples) - - def __str__(self): - return 'UnweightedSamples({})'.format(self.data) - - @property - def size(self) -> int: - """Get the number of samples. Note: kept for backwards compatibility, prefer using len() instead. - - Returns: - int: Number of samples - """ - return len(self) - - def percentage_in_bounds(self, bounds, keys: list = None) -> dict: - if not keys: - keys = self.keys() - if isinstance(keys, str): - keys = [keys] - if isinstance(bounds, list): - bounds = {key: bounds for key in self.keys()} - if not isinstance(bounds, dict) or all([isinstance(b, list) and len(b) == 2 for b in bounds]): - raise TypeError("Bounds must be list [lower, upper] or dict (key: [lower, upper]), was {}".format(type(bounds))) - n_elements = len(self.data) - return {key: sum([x is not None and x < bounds[key][1] and x > bounds[key][0] for x in self.key(key)])/n_elements for key in keys} - - def raw_samples(self): - warn("raw_samples is deprecated and will be removed in the future.") - return self.data diff --git a/src/prog_algs/utils/__init__.py b/src/prog_algs/utils/__init__.py deleted file mode 100644 index d84bee52..00000000 --- a/src/prog_algs/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. - -from .table import print_table_recursive - diff --git a/src/prog_algs/utils/table.py b/src/prog_algs/utils/table.py deleted file mode 100644 index 76bf8831..00000000 --- a/src/prog_algs/utils/table.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the -# National Aeronautics and Space Administration. All Rights Reserved. - -from collections import defaultdict -from typing import Union - -MAX_COLUMN_WIDTH = 5 # numerical value will actually be min MAX_COLUMN_WIDTH-2 due to allocating spaces - -def print_table_recursive(input_dict : dict, title : str, print_bool : bool = True) -> defaultdict: - """ - Prints a table where keys are column headers and values are items in a row. - Returns the table formatted as a dictionary of tables represented by a list of str. - - Arguments - --------- - input_dict : dict - A dictionary of keys and values to print out in a table. Values can be dictionaries. - title : str - Title of the table, printed before data rows. - print_flag : bool = True - An optional boolean value determining whether the generated table is printed. - """ - row_list = _print_table_recursive_helper([], input_dict, title)[:-7] - sub_tables = defaultdict(list) - new_sub_table = [] - for row in row_list: - new_sub_table.append(row) - if len(new_sub_table) == 7: - if sub_tables[len(new_sub_table[0])]: - sub_tables[len(new_sub_table[0])].extend([new_sub_table[5], new_sub_table[6]]) - else: - sub_tables[len(new_sub_table[0])].extend(new_sub_table) - new_sub_table = [] - - if print_bool: - for k in sorted(sub_tables.keys(), reverse=True): - print(*sub_tables[k], sep='\n') - return sub_tables - -def _set_width(max_width : int, input_value : Union[float, int]) -> str: - if input_value < (10**max_width): - ndigits = len(str(input_value)) - return f"{input_value:^{ndigits}.{max_width-ndigits}f}" - else: - scientific_input = f"{input_value:e}" - split_e = scientific_input.split("e+") - num_space = max_width - len(str(split_e[1])) - 2 - split_e[0] = str(split_e[0])[:num_space] - return f"{split_e[0]}e+{split_e[1]}" - # using this approach because e+ can't be formatted with f"{x:{some_width}g}" - # what happens if we have 9.999999e+100 but are limited to 5? - # the exponent itself will occupy 5, leaving no space for the numbers in front - -def _print_table_recursive_helper(table_prog : list, input_dict : dict, title : str, key : str = None) -> list: - """ - Helper function to recursively build subtables as a list of str. - - Arguments - --------- - table_prog : list - A list of the table built so far. List of strings, where each string is a printable representation of a row. - input_dict : dict - A dictionary of keys and values to print out in a table. Values can be dictionaries. - title : str - Title of the table, printed before data rows. - key : str = None - Key for a value row, identifying what event the values belong to. - """ - col_name_row = "| key |" - value_row = f"| {str(key):^3} |" - for k,v in input_dict.items(): - if isinstance(v, dict): - if key != None: - to_pass = key - _print_table_recursive_helper(table_prog, v, f"{title} {k}", to_pass) - else: - to_pass = k - _print_table_recursive_helper(table_prog, v, f"{title}", to_pass) - else: - col_len = len(max(str(k), str(v))) + 2 - col_name_row += f"{str(k):^{col_len}}|" - if isinstance(v, (int, float)): - adj_width = _set_width(MAX_COLUMN_WIDTH-2, v) - value_row += f"{adj_width:^{col_len}}|" - else: - value_row += f"{str(v):^{col_len}}|" - - break_row = "+{}+".format((len(col_name_row)-2)*'-') - title_row = f"+{title:^{len(break_row)-2}}+".title() - table_prog.extend([break_row, title_row, break_row, col_name_row, break_row, value_row, break_row]) - - return table_prog - diff --git a/src/prog_algs/visualize/__init__.py b/src/prog_algs/visualize/__init__.py deleted file mode 100644 index d37a5535..00000000 --- a/src/prog_algs/visualize/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from .plot_scatter import plot_scatter -from .plot_hist import plot_hist -__all__ = ['plot_scatter', 'plot_hist'] diff --git a/src/prog_algs/visualize/plot_hist.py b/src/prog_algs/visualize/plot_hist.py deleted file mode 100644 index 267a5511..00000000 --- a/src/prog_algs/visualize/plot_hist.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from matplotlib.figure import Figure -import matplotlib.pyplot as plt -from numpy import array -from typing import List - -def plot_hist(samples : array, fig : Figure = None, keys : List[str] = None, **kwargs) -> Figure: - """Create a histogram - - Args: - samples (Array(Dict)): A set of samples (e.g., states or eol estimates) - fig (MatPlotLib Figure, optional): Existing histogram figure to be overritten. Defaults to create new figure. - keys (List[String], optional): Keys to be plotted. Defaults to All. - """ - - # Input checks - if len(samples) <= 0: - raise Exception('Must include atleast one sample to plot') - - if keys is not None: - if isinstance(keys, str): - keys = [keys] - try: - iter(keys) - except TypeError: - raise TypeError("Keys should be a list of strings (e.g., ['state1', 'state2'], was {}".format(type(keys))) - - for key in keys: - if key not in samples[0].keys(): - raise TypeError("Key {} was not present in samples (keys: {})".format(key, list(samples[0].keys()))) - else: - keys = samples[0].keys() - keys = list(keys) - - # Handle input - parameters = { # defaults - 'legend': True - } - parameters.update(kwargs) - - if fig is None: - # If no figure provided, create one - fig = plt.figure() - ax = fig.add_subplot(111) - else: - ax = fig.axes[0] - - # Plot - for key in keys: - ax.hist([sample[key] for sample in samples if sample[key] is not None], label=key, **kwargs) - - # Set legend - if parameters['legend']: - ax.legend().remove() # Remove any existing legend - prevents "ghost effect" - ax.legend(loc='upper right') - - return fig diff --git a/src/prog_algs/visualize/plot_scatter.py b/src/prog_algs/visualize/plot_scatter.py deleted file mode 100644 index cab22872..00000000 --- a/src/prog_algs/visualize/plot_scatter.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -from matplotlib.collections import PathCollection -from matplotlib.figure import Figure -import matplotlib.pyplot as plt -from math import sqrt -from typing import List - -def plot_scatter(samples : List[dict], fig : Figure = None, keys : List[str] = None, legend : str = 'auto', **kwargs) -> Figure: - """ - Produce a scatter plot for a given list of states - - Args: - samples ([dict]): Non-empty list of states where each element is a dictionary containing a single sample - fig (Figure, optional): Existing figure previously used to plot states. If passed a figure argument additional data will be added to the plot. Defaults to creating new figure - keys (list of strings, optional): Keys to plot. Defaults to all keys. - legend (optional): When the legend should be shown, options: - False: Dont show legend - "auto": Show legend automatically if more than one data set - True: Always show legend - **kwargs (optional): Additional keyword arguments passed to scatter function. Includes those supported by scatter - - Returns: - Figure - - Example: - states = UnweightedSamples([1, 2, 3, 4, 5]) - plot_scatter(states.sample(100)) # With 100 samples - plot_scatter(states.sample(100), keys=['state1', 'state2']) # only plot those keys - """ - # Input checks - if len(samples) <= 0: - raise Exception('Must include atleast one sample to plot') - - if keys is not None: - try: - iter(keys) - except TypeError: - raise TypeError("Keys should be a list of strings (e.g., ['state1', 'state2'], was {}".format(type(keys))) - - # Handle input - parameters = { # defaults - 'alpha': 0.5 - } - parameters.update(kwargs) - - if keys is None: - keys = samples[0].keys() - keys = list(keys) - - n = len(keys) - if n < 2: - raise Exception("At least 2 states required for scatter, got {}".format(n)) - - if fig is None: - # If no figure provided, create one - fig = plt.figure() - axes = [[fig.add_subplot(n-1, n-1, 1 + i + j*(n-1)) for i in range(n-1)] for j in range(n-1)] - else: - # Check size of axes - if len(fig.axes) != (n-1)*(n-1): - raise Exception("Cannot use existing figure - Existing figure graphs {} states, data includes {} states".format(sqrt(len(fig.axes))+1, n)) - - # Unpack axes - axes = [[fig.axes[i + j*(n-1)] for i in range(n-1)] for j in range(n-1)] - - for i in range(n-1): - # For each column - x_key = keys[i] - - # Set labels on extremes - axes[-1][i].set_xlabel(x_key) # Bottom row - axes[i][0].set_ylabel(keys[i+1]) # Left column - - # plot - for j in range(i, n-1): - # for each row - y_key = keys[j+1] - x1 = [x[x_key] for x in samples if x is not None] - x2 = [x[y_key] for x in samples if x is not None] - axes[j][i].scatter(x1, x2, **parameters) - - # Hide axes not used in plots - for j in range(0, i): - axes[j][i].set_visible(False) - - # Set legend - if legend == 'auto' or legend: - labels = [thing.get_label() for thing in axes[0][0].get_children() - if isinstance(thing, PathCollection)] - if legend == 'auto' and len(labels) > 0 or legend: - fig.legend().remove() # Remove any existing legend - prevents "ghost effect" - fig.legend(labels=labels, loc='upper right') - - return fig diff --git a/state_estimator_template.py b/state_estimator_template.py deleted file mode 100644 index 9aec63c6..00000000 --- a/state_estimator_template.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from prog_algs import state_estimators -# Replace the following with whatever form of UncertainData you would like to use to represent state -from prog_algs.uncertain_data import UncertainData, ScalarData - - -class TemplateStateEstimator(state_estimators.StateEstimator): - """ - Template for State Estimator - """ - # REPLACE THE FOLLOWING LIST WITH CONFIGURED PARAMETERS - default_parameters = { # Default Parameters, used as config - 'Example Parameter': 0.0, - 't0' : 0.0 # Initial timestamp - } - - def __init__(self, model, x0, **kwargs): - """ - Constructor (optional) - """ - super().__init__(model, x0, **kwargs) - # ADD PARAMETER CHECKS HERE - # e.g., self.parameters['some_value'] < 0 - - # ADD ANY STATE ESTIMATOR INITIALIZATION LOGIC - - def estimate(self, t, u, z) -> None: - """ - Perform one state estimation step (i.e., update the state estimate) - - Parameters - ---------- - t : double - Current timestamp in seconds (≥ 0.0) - e.g., t = 3.4 - u : dict - Applied inputs, with keys defined by model.inputs. - e.g., u = {'i':3.2} given inputs = ['i'] - z : dict - Measured outputs, with keys defined by model.outputs. - e.g., z = {'t':12.4, 'v':3.3} given inputs = ['t', 'v'] - """ - # REPLACE WITH UPDATE STATE ESTIMATION - - # Note, returns none. State is accessed using the property state_estimator.x - - @property - def x(self) -> UncertainData: - """ - Getter for property 'x', the current estimated state. - - Example - ------- - state = observer.x - """ - # REPLACE THE FOLLOWING WITH THE LOGIC TO CONSTRUCT/RETURN THE STATE - - # Here we're using ScalarData, but the state could be represented by any other type of UncertainData (e.g., MultivariateNormalDist) - x = ScalarData(self.model.StateContainer({key: 0.0 for key in self.model.states})) - - return x diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index e1396156..00000000 --- a/test_requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -ipython>=8.10.0 -ipykernel>=6.21.2 -testbook>=0.4.2 -. \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 22e6d337..00000000 --- a/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -__all__ = ['test_examples', 'test_integration', 'test_metrics', 'test_predictors', 'test_state_estimators', 'test_uncertain_data', 'test_visualize', 'test_tutorials', 'test_manual'] diff --git a/tests/__main__.py b/tests/__main__.py deleted file mode 100644 index d38fc221..00000000 --- a/tests/__main__.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from .test_state_estimators import run_tests as state_est_main -from .test_predictors import run_tests as pred_main -from .test_uncertain_data import run_tests as udata_main -from .test_examples import main as examples_main -from .test_metrics import run_tests as metrics_main -from .test_visualize import run_tests as visualize_main -from .test_tutorials import run_tests as tutorials_main - -import unittest -import sys -from examples import basic_example -from io import StringIO -from timeit import timeit -from unittest.mock import patch - -def run_basic_ex(): - _stdout = sys.stdout - sys.stdout = StringIO() - with patch('matplotlib.pyplot') as p: - basic_example.run_example() - - # Reset stdout - sys.stdout = _stdout - -if __name__ == '__main__': - l = unittest.TestLoader() - - try: - print("\nExample Runtime: ", timeit(run_basic_ex, number=3)) - except Exception: - print("\nFailed benchmarking") - was_successful = False - - was_successful = True - try: - state_est_main() - except Exception: - was_successful = False - - try: - pred_main() - except Exception: - was_successful = False - - try: - udata_main() - except Exception: - was_successful = False - - try: - visualize_main() - except Exception: - was_successful = False - - try: - examples_main() - except Exception: - was_successful = False - - try: - metrics_main() - except Exception: - was_successful = False - - try: - tutorials_main() - except Exception: - was_successful = False - - if not was_successful: - raise Exception('Tests Failed') diff --git a/tests/benchmark.py b/tests/benchmark.py deleted file mode 100644 index a9778489..00000000 --- a/tests/benchmark.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -import time -from io import StringIO -import sys - -FORMAT_STR = '{:40s}' -CLOCK = time.process_time - -if __name__ == "__main__": - print(FORMAT_STR.format('import main'), end='') - t = CLOCK() - from prog_algs import * - t2 = CLOCK() - print(t2-t) - - from prog_models.models import BatteryElectroChemEOD as Battery - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - R_vars = { - 't': 2, - 'v': 0.02 - } - batt = Battery(measurement_noise = R_vars) - initial_state = batt.parameters['x0'] - - print('PF') - print(FORMAT_STR.format(' Initialize'), end='') - temp_out = StringIO() - sys.stdout = temp_out - sys.stderr = temp_out - t = CLOCK() - filt = state_estimators.ParticleFilter(batt, initial_state) - t2 = CLOCK() - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - print(t2-t) - - print(FORMAT_STR.format(' Step'), end='') - example_measurements = {'t': 32.2, 'v': 3.915} - t = 0.1 - t = CLOCK() - filt.estimate(t, future_loading(t), example_measurements) - t2 = CLOCK() - print(t2-t) - - print('UKF') - print(FORMAT_STR.format(' Initialize '), end='') - temp_out = StringIO() - sys.stdout = temp_out - sys.stderr = temp_out - t = CLOCK() - filt = state_estimators.UnscentedKalmanFilter(batt, initial_state) - t2 = CLOCK() - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - print(t2-t) - - # print(FORMAT_STR.format(' Step'), end='') - # example_measurements = {'t': 32.2, 'v': 3.915} - # t = 0.1 - # temp_out = StringIO() - # sys.stdout = temp_out - # sys.stderr = temp_out - # t = CLOCK() - # filt.estimate(t, future_loading(t), example_measurements) - # t2 = CLOCK() - # sys.stdout = sys.__stdout__ - # sys.stderr = sys.__stderr__ - # print(t2-t) - - print(FORMAT_STR.format('Plot Results'), end='') - t = CLOCK() - filt.x.plot_scatter(label='prior') - t2 = CLOCK() - print(t2-t) - - print('MC') - print(FORMAT_STR.format(' Initialize '), end='') - t = CLOCK() - mc = predictors.MonteCarlo(batt) - t2 = CLOCK() - print(t2-t) - - print(FORMAT_STR.format(' Prediction'), end='') - NUM_SAMPLES = 5 - STEP_SIZE = 0.1 - t = CLOCK() - mc_results = mc.predict(batt.initialize(), future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE) - t2 = CLOCK() - print(t2-t) - - print(FORMAT_STR.format('Metrics'), end='') - t = CLOCK() - mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]) - mc_results.time_of_event.metrics(ground_truth=3005.25) - t2 = CLOCK() - print(t2-t) - - print(FORMAT_STR.format('Plot Scatter'), end='') - t = CLOCK() - fig = mc_results.states.snapshot(0).plot_scatter(label = "t={} s".format(int(mc_results.times[0]))) # 0 - quarter_index = int(len(mc_results.times)/4) - mc_results.states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index]))) # 25% - mc_results.states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*2]))) # 50% - mc_results.states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*3]))) # 75% - mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[-1]))) # 100% - t2 = CLOCK() - print(t2-t) - - print(FORMAT_STR.format('Plot Hist'), end='') - t = CLOCK() - mc_results.time_of_event.plot_hist() - t2 = CLOCK() - print(t2-t) diff --git a/tests/test_examples.py b/tests/test_examples.py deleted file mode 100644 index 87fd03f5..00000000 --- a/tests/test_examples.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -# This ensures that the directory containing examples is in the python search directories - -from importlib import import_module -from io import StringIO -from matplotlib import pyplot as plt -from os.path import dirname, join -import pkgutil -import sys -import unittest -from unittest.mock import patch - -sys.path.append(join(dirname(__file__), "..")) # puts examples in path -from examples import * - -skipped_examples = ['playback'] - -def make_test_function(example): - def test(self): - ex = import_module("examples." + example) - - with patch('matplotlib.pyplot.show'): - ex.run_example() - return test - - -class TestExamples(unittest.TestCase): - def setUp(self): - # set stdout (so it wont print) - self._stdout = sys.stdout - sys.stdout = StringIO() - - def tearDown(self): - # reset stdout - sys.stdout = self._stdout - -# This allows the module to be executed directly -def run_tests(): - unittest.main() - -def main(): - # Create tests for each example - for _, name, _ in pkgutil.iter_modules(['examples']): - if name in skipped_examples: - continue - test_func = make_test_function(name) - setattr(TestExamples, 'test_{0}'.format(name), test_func) - - - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Examples") - with patch('matplotlib.pyplot.show'): - result = runner.run(l.loadTestsFromTestCase(TestExamples)).wasSuccessful() - - plt.close('all') - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - main() diff --git a/tests/test_manual.py b/tests/test_manual.py deleted file mode 100644 index e5f5a23a..00000000 --- a/tests/test_manual.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -from matplotlib import pyplot as plt -import sys -import unittest -from unittest.mock import patch - -""" -This file includes tests that are too long to be run as part of the automated tests. Instead, these tests are run manually as part of the release process. -""" - -class TestManual(unittest.TestCase): - # Test playback example - def test_playback_example(self): - from examples import playback - playback.run_example() - -# This allows the module to be executed directly -def run_tests(): - unittest.main() - -def main(): - # This ensures that the directory containing ProgModelTemplate is in the python search directory - from os.path import dirname, join - sys.path.append(join(dirname(__file__), "..")) - - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Manual") - - with patch('matplotlib.pyplot.show'): - result = runner.run(l.loadTestsFromTestCase(TestManual)).wasSuccessful() - plt.close('all') - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - main() - diff --git a/tests/test_metrics.py b/tests/test_metrics.py deleted file mode 100644 index 560576a6..00000000 --- a/tests/test_metrics.py +++ /dev/null @@ -1,546 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -import unittest -from prog_algs import uncertain_data -from prog_algs.metrics import prob_success -from prog_algs.metrics import calc_metrics as toe_metrics -from prog_algs.uncertain_data import UncertainData, UnweightedSamples, MultivariateNormalDist, ScalarData - - -class TestMetrics(unittest.TestCase): - def test_toe_metrics_prev_name(self): - # This is kept for backwards compatability - from prog_algs.metrics import samples - self.assertIs(samples.eol_metrics, toe_metrics) - self.assertIs(samples.prob_success, prob_success) - - def test_toe_metrics_list_dict(self): - # This is kept for backwards compatability - - # Common checks - def check_metrics(metrics): - # True for all keys - for key in keys: - self.assertAlmostEqual(metrics[key]['min'], 0) - for percentile in ['0.01', '0.1', '1']: - # Not enough samples for these - self.assertIsNone(metrics[key]['percentiles'][percentile]) - - # Key specific - self.assertAlmostEqual(metrics['a']['percentiles']['10'], 1) - self.assertAlmostEqual(metrics['a']['percentiles']['25'], 2) - self.assertAlmostEqual(metrics['a']['mean'], 4.5) - self.assertAlmostEqual(metrics['a']['percentiles']['50'], 5) - self.assertAlmostEqual(metrics['a']['percentiles']['75'], 7) - self.assertAlmostEqual(metrics['a']['mean absolute deviation'], 2.5) - self.assertAlmostEqual(metrics['a']['max'], 9) - self.assertAlmostEqual(metrics['a']['std'], 2.8722813232690143) - self.assertAlmostEqual(metrics['b']['percentiles']['10'], 1.1) - self.assertAlmostEqual(metrics['b']['percentiles']['25'], 2.2) - self.assertAlmostEqual(metrics['b']['mean'], 4.95) - self.assertAlmostEqual(metrics['b']['percentiles']['50'], 5.5) - self.assertAlmostEqual(metrics['b']['percentiles']['75'], 7.7) - self.assertAlmostEqual(metrics['b']['mean absolute deviation'], 2.75) - self.assertAlmostEqual(metrics['b']['max'], 9.9) - self.assertAlmostEqual(metrics['b']['std'], 3.159509455595916) - self.assertAlmostEqual(metrics['c']['percentiles']['10'], 0.04) - self.assertAlmostEqual(metrics['c']['percentiles']['25'], 0.16) - self.assertAlmostEqual(metrics['c']['mean'], 1.14) - self.assertAlmostEqual(metrics['c']['percentiles']['50'], 1.0) - self.assertAlmostEqual(metrics['c']['percentiles']['75'], 1.96) - self.assertAlmostEqual(metrics['c']['mean absolute deviation'], 0.928) - self.assertAlmostEqual(metrics['c']['max'], 3.24) - self.assertAlmostEqual(metrics['c']['std'], 1.074094967868298) - - u_samples = [{'a': i, 'b': i*1.1, 'c': (i/5)**2} for i in range(10)] - keys = ['a', 'b', 'c'] - metrics = toe_metrics(u_samples) - - check_metrics(metrics) - for key in keys: - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertNotIn(key2, metrics[key]) - - metrics = toe_metrics(u_samples, 5.0) - check_metrics(metrics) - for key in keys: - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertIn(key2, metrics[key]) - self.assertAlmostEqual(metrics['a']['mean absolute error'], 2.5) - self.assertAlmostEqual(metrics['b']['mean absolute error'], 2.75) - self.assertAlmostEqual(metrics['c']['mean absolute error'], 3.86) - - metrics = toe_metrics(u_samples, ground_truth = {'a': 5.0, 'b': 4.5, 'c': 1.5}) - check_metrics(metrics) - for key in keys: - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertIn(key2, metrics[key]) - self.assertAlmostEqual(metrics['a']['mean absolute error'], 2.5) - self.assertAlmostEqual(metrics['b']['mean absolute error'], 2.75) - self.assertAlmostEqual(metrics['c']['mean absolute error'], 1.012) - - # Empty Sample Set - try: - toe_metrics([]) - except ValueError: - pass - - def test_toe_metrics_mvnd(self): - # Common checks - def check_metrics(metrics): - mean_dict = {key: value for (key, value) in zip(keys, mean)} - for key in keys: - self.assertAlmostEqual(metrics[key]['mean'], mean_dict[key]) - self.assertAlmostEqual(metrics[key]['median'], mean_dict[key]) - self.assertAlmostEqual(metrics[key]['percentiles']['50'], mean_dict[key]) - self.assertAlmostEqual(metrics[key]['std'], 1, 1) - mean = [10, 11, 12] - keys = ['a', 'b', 'c'] - covar = [ - [1, 0.1, 0.1], - [0.1, 1, 0.1], - [0.1, 0.1, 1]] - dist = MultivariateNormalDist(keys, mean, covar) - metrics = toe_metrics(dist) - dist.metrics() - check_metrics(metrics) - - metrics = toe_metrics(dist, 11) - dist.metrics(ground_truth=11) - check_metrics(metrics) - self.assertAlmostEqual(metrics['a']['ground truth percentile'], 84.3, -1) - self.assertAlmostEqual(metrics['b']['ground truth percentile'], 50, -1) - self.assertAlmostEqual(metrics['c']['ground truth percentile'], 15.4, -1) - - # P(success) - p_success = prob_success(dist, 11) - self.assertAlmostEqual(p_success['a'], 0.1575, 1) - self.assertAlmostEqual(p_success['b'], 0.5, 1) - self.assertAlmostEqual(p_success['c'], 0.8425, 1) - - def test_toe_metrics_scalar(self): - # Common checks - def check_metrics(metrics): - for key in scalar.keys(): - self.assertAlmostEqual(metrics[key]['min'], data[key]) - for value in metrics[key]['percentiles'].values(): - self.assertAlmostEqual(value, data[key]) - self.assertAlmostEqual(metrics[key]['mean'], data[key]) - self.assertAlmostEqual(metrics[key]['median'], data[key]) - self.assertAlmostEqual(metrics[key]['max'], data[key]) - self.assertAlmostEqual(metrics[key]['std'], 0) - self.assertAlmostEqual(metrics[key]['mean absolute deviation'], 0) - - data = { - 'a': 10, - 'b': 11, - 'c': 12 - } - scalar = ScalarData(data) - metrics = toe_metrics(scalar) - self.assertDictEqual(scalar.metrics(), metrics) - check_metrics(metrics) - - # Check with ground truth - metrics = toe_metrics(scalar, 11) - self.assertDictEqual(scalar.metrics(ground_truth=11), metrics) - check_metrics(metrics) - for key in data.keys(): - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertIn(key2, metrics[key]) - self.assertAlmostEqual(metrics['a']['mean absolute error'], 1) - self.assertAlmostEqual(metrics['b']['mean absolute error'], 0) - self.assertAlmostEqual(metrics['c']['mean absolute error'], 1) - - # Check with limited samples - metrics = toe_metrics(scalar, n_samples = 1000) - self.assertDictEqual(scalar.metrics(n_samples = 1000), metrics) - for key in scalar.keys(): - self.assertIsNone(metrics[key]['percentiles']['0.01']) - metrics[key]['percentiles']['0.01'] = data[key] # Fill so we can check everything else below - check_metrics(metrics) - - # Check broken samples - try: - toe_metrics(scalar, n_samples = 'abc') - except TypeError: - pass - - try: - toe_metrics(scalar, n_samples = []) - except TypeError: - pass - - # P(success) - p_success = prob_success(scalar, 11) - self.assertAlmostEqual(p_success['a'], 0) # After all samples - self.assertAlmostEqual(p_success['b'], 0) # Exactly equal - self.assertAlmostEqual(p_success['c'], 1) # Before all samples - - def test_toe_metrics_specific_keys(self): - data = { - 'a': 10, - 'b': 11, - 'c': 12 - } - scalar = ScalarData(data) - metrics = scalar.metrics(keys = ['a', 'b']) - self.assertNotIn('c', metrics) - self.assertIn('a', metrics) - self.assertIn('b', metrics) - - # Check P(MS) - p_success = prob_success(scalar, 11, keys = ['a', 'c']) - self.assertNotIn('b', p_success) - self.assertIn('a', p_success) - self.assertIn('c', p_success) - - def test_toe_metrics_u_samples(self): - # Common checks - def check_metrics(metrics): - # True for all keys - for key in u_samples.keys(): - self.assertAlmostEqual(metrics[key]['min'], 0) - for percentile in ['0.01', '0.1', '1']: - # Not enough samples for these - self.assertIsNone(metrics[key]['percentiles'][percentile]) - - # Key specific - self.assertAlmostEqual(metrics['a']['percentiles']['10'], 1) - self.assertAlmostEqual(metrics['a']['percentiles']['25'], 2) - self.assertAlmostEqual(metrics['a']['mean'], 4.5) - self.assertAlmostEqual(metrics['a']['percentiles']['50'], 5) - self.assertAlmostEqual(metrics['a']['percentiles']['75'], 7) - self.assertAlmostEqual(metrics['a']['mean absolute deviation'], 2.5) - self.assertAlmostEqual(metrics['a']['max'], 9) - self.assertAlmostEqual(metrics['a']['std'], 2.8722813232690143) - self.assertAlmostEqual(metrics['b']['percentiles']['10'], 1.1) - self.assertAlmostEqual(metrics['b']['percentiles']['25'], 2.2) - self.assertAlmostEqual(metrics['b']['mean'], 4.95) - self.assertAlmostEqual(metrics['b']['percentiles']['50'], 5.5) - self.assertAlmostEqual(metrics['b']['percentiles']['75'], 7.7) - self.assertAlmostEqual(metrics['b']['mean absolute deviation'], 2.75) - self.assertAlmostEqual(metrics['b']['max'], 9.9) - self.assertAlmostEqual(metrics['b']['std'], 3.159509455595916) - self.assertAlmostEqual(metrics['c']['percentiles']['10'], 0.04) - self.assertAlmostEqual(metrics['c']['percentiles']['25'], 0.16) - self.assertAlmostEqual(metrics['c']['mean'], 1.14) - self.assertAlmostEqual(metrics['c']['percentiles']['50'], 1.0) - self.assertAlmostEqual(metrics['c']['percentiles']['75'], 1.96) - self.assertAlmostEqual(metrics['c']['mean absolute deviation'], 0.928) - self.assertAlmostEqual(metrics['c']['max'], 3.24) - self.assertAlmostEqual(metrics['c']['std'], 1.074094967868298) - - data = [{'a': i, 'b': i*1.1, 'c': (i/5)**2} for i in range(10)] - u_samples = UnweightedSamples(data) - metrics = toe_metrics(u_samples) - self.assertDictEqual(u_samples.metrics(), metrics) - - check_metrics(metrics) - for key in u_samples.keys(): - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertNotIn(key2, metrics[key]) - - metrics = toe_metrics(u_samples, 5.0) - self.assertDictEqual(u_samples.metrics(ground_truth=5.0), metrics) - check_metrics(metrics) - for key in u_samples.keys(): - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertIn(key2, metrics[key]) - self.assertAlmostEqual(metrics['a']['mean absolute error'], 2.5) - self.assertAlmostEqual(metrics['b']['mean absolute error'], 2.75) - self.assertAlmostEqual(metrics['c']['mean absolute error'], 3.86) - - ground_truth = {'a': 5.0, 'b': 4.5, 'c': 1.5} - metrics = toe_metrics(u_samples, ground_truth = ground_truth) - self.assertDictEqual(u_samples.metrics(ground_truth = ground_truth), metrics) - check_metrics(metrics) - for key in u_samples.keys(): - for key2 in ['mean absolute percentage error', 'relative accuracy', 'ground truth percentile']: - self.assertIn(key2, metrics[key]) - self.assertAlmostEqual(metrics['a']['mean absolute error'], 2.5) - self.assertAlmostEqual(metrics['b']['mean absolute error'], 2.75) - self.assertAlmostEqual(metrics['c']['mean absolute error'], 1.012) - - # Empty Sample Set - try: - toe_metrics(UnweightedSamples([])) - except ValueError: - pass - - # P(success) - p_success = prob_success(u_samples, 5.0) - self.assertAlmostEqual(p_success['a'], 0.4) - self.assertAlmostEqual(p_success['b'], 0.5) - self.assertAlmostEqual(p_success['c'], 0) - - def test_toe_metrics_ground_truth(self): - # Wrong type - try: - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth='abc') - except TypeError: - pass - - # Below samples - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth=0) - - # At sample - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth=1) - - # Above samples - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth=2) - - # NaN - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth=float('nan')) - - # Inf - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth=float('inf')) - - # -Inf - toe_metrics(UnweightedSamples([{'a': 1.0}]), ground_truth=-float('inf')) - - def test_toe_profile_metrics(self): - from prog_algs.predictors import ToEPredictionProfile - profile = ToEPredictionProfile() # Empty profile - for i in range(10): - # a will shift upward from 0-19 to 9-28 - # b is always a-1 - # c is always a * 2, and will therefore always have twice the spread - data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)] - profile.add_prediction( - 10-i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - - from prog_algs.metrics import alpha_lambda - - # Test 1: Ground truth at median - ground_truth = {'a': 9.0, 'b': 8.0, 'c': 18.0} - lambda_value = 8 # Almost at prediction - alpha = 0.5 - beta = 0.05 # 5% is really bad - metrics = alpha_lambda(profile, ground_truth, lambda_value, alpha, beta) - # Result at t=8 - # a - # toe: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] - # Bounds: [8.5 - 9.5](0.05%) - # b - # toe: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] - # Bounds: [8.0 - 8.0](0.0%) - # c - # toe: [-0.5, 1.5, 3.5, 5.5, 7.5, 9.5, 11.5, 13.5, 15.5, 17.5, 19.5, 21.5, 23.5, 25.5, 27.5, 29.5, 31.5, 33.5, 35.5, 37.5] - # Bounds: [13.0 - 23.0](0.25%) - # {'a': True, 'b': False, 'c': True} - self.assertTrue(metrics['a']) - self.assertFalse(metrics['b']) - self.assertTrue(metrics['c']) - - # Now lets do it at t=5 - lambda_value = 5 - metrics = alpha_lambda(profile, ground_truth, lambda_value, alpha, beta) - # Here all should be true - # a - # toe: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] - # Bounds: [7.0 - 11.0](0.15%) - # b - # toe: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] - # Bounds: [6.5 - 9.5](0.15%) - # c - # toe: [5.5, 7.5, 9.5, 11.5, 13.5, 15.5, 17.5, 19.5, 21.5, 23.5, 25.5, 27.5, 29.5, 31.5, 33.5, 35.5, 37.5, 39.5, 41.5, 43.5] - # Bounds: [11.5 - 24.5](0.3%) - # {'a': True, 'b': True, 'c': True} - self.assertTrue(metrics['a']) - self.assertTrue(metrics['b']) - self.assertTrue(metrics['c']) - self.assertDictEqual(metrics, profile.alpha_lambda(ground_truth, lambda_value, alpha, beta)) - - # Now lets try specifying only keys a and b - metrics = alpha_lambda(profile, ground_truth, lambda_value, alpha, beta, keys=['a', 'b']) - self.assertIn('a', metrics) - self.assertIn('b', metrics) - self.assertNotIn('c', metrics) - - def test_toe_profile_metrics(self): - from prog_algs.predictors import ToEPredictionProfile - profile = ToEPredictionProfile() # Empty profile - for i in range(10): - # a will shift upward from 0-19 to 9-28 - # b is always a-1 - # c is always a * 2, and will therefore always have twice the spread - data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)] - profile.add_prediction( - 10-i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - - from prog_algs.metrics import alpha_lambda - - # Test 1: Ground truth at median - ground_truth = {'a': 9.0, 'b': 8.0, 'c': 18.0} - lambda_value = 8 # Almost at prediction - alpha = 0.5 - beta = 0.05 # 5% is really bad - metrics = alpha_lambda(profile, ground_truth, lambda_value, alpha, beta) - - # Test pickling ToEPredictionProfile - import pickle - pickle.dump(profile, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(profile, pickle_converted_result) - # Test pickling alpha_lambda result - pickle.dump(metrics, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(metrics, pickle_converted_result) - - def test_toe_profile_prognostic_horizon(self): - from prog_algs.predictors import ToEPredictionProfile - profile = ToEPredictionProfile() # Empty profile - # Define test sample ground truth - GROUND_TRUTH = {'a': 9.0, 'b': 8.0, 'c': 18.0} - # Create rudimentary criteria equation - def criteria_eqn(tte : UncertainData, ground_truth_tte : dict) -> dict: - """ - Sample criteria equation for unittesting. - - Args: - tte : UncertainData - Time to event in UncertainData format. - ground_truth_tte : dict - Dictionary of ground truth of time to event. - """ - result = {} - for key, value in ground_truth_tte.items(): - result[key] = abs(tte.mean[key] - value) < 0.6 - return result - from prog_algs.metrics import prognostic_horizon - # Prognostic horizon metric testing - # Test 0: Empty profile (should return None for all) - self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': None, 'c': None}) - - # Loading profile - for i in range(10): - # a will shift upward from 0-19 to 9-28 - # b is always a-1 - # c is always a * 2, and will therefore always have twice the spread - data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)] - profile.add_prediction( - 10-i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - # Test 1: simple 1 criteria met - self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': None, 'c': 10.0}) - # Test 2: all criteria are met at different times - GROUND_TRUTH = {'a': 10.0, 'b': 10.0, 'c': 18.0} # Adjust ground truth to match 3 criteria - self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': 1.0, 'b': 2.0, 'c': 10.0}) - # Test 3: 2 criteria are met at once (at 10.0) - GROUND_TRUTH = {'a': 9.0, 'b': 14.0, 'c': 18.0} - self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': 10.0, 'c': 10.0}) - # Test 4: at least one criteria is met at beginning of the prediction - GROUND_TRUTH = {'a': 10, 'b': 8.0, 'c': 10.0} - self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': 1.0, 'b': None, 'c': None}) - # Test 5: criteria not met for any - GROUND_TRUTH = {'a': 9.0, 'b': 8.0, 'c': 8.0} - self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': None, 'c': None}) - - def test_toe_profile_cumulative_relative_accuracy(self): - from prog_algs.predictors import ToEPredictionProfile - profile = ToEPredictionProfile() # Empty profile - # Loading profile - for i in range(10): - data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)] - profile.add_prediction( - 10-i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - # Test positive floats ground truth - GROUND_TRUTH = {'a': 9.0, 'b': 8.0, 'c': 18.0} - self.assertEqual(profile.cumulative_relative_accuracy(GROUND_TRUTH), {'a': 0.4444444444444445, 'b': 0.375, 'c': 0.6388888888888888}) - # Test negative floats ground truth - GROUND_TRUTH = {'a': -9.0, 'b': -8.0, 'c': -18.0} - self.assertEqual(profile.cumulative_relative_accuracy(GROUND_TRUTH), {'a': 3.555555555555556, 'b': 3.625, 'c': 3.305555555555556}) - # Test ground truth values of 0; already caught by relative_accuracy - with self.assertRaises(ZeroDivisionError): - GROUND_TRUTH = {'a': 0, 'b': 0, 'c': 0} - raise_error = profile.cumulative_relative_accuracy(GROUND_TRUTH) - # Test ground truth in invalid input types; already caught by relative_accuracy - with self.assertRaises(TypeError): - GROUND_TRUTH = [] - raise_error = profile.cumulative_relative_accuracy(GROUND_TRUTH) - - def test_toe_profile_monotonicity(self): - from prog_algs.predictors import ToEPredictionProfile - - # Test monotonically increasing and decreasing - profile = ToEPredictionProfile() # Empty profile - for i in range(10): - data = [{'a': 1+i/10, 'b': 2-i/5} for i in range(10)] - profile.add_prediction( - i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - self.assertDictEqual(profile.monotonicity(), {'a': 1.0, 'b': 1.0}) - - # Test no monotonicity - profile = ToEPredictionProfile() # Empty profile - for i in range(0,11): - data = [{'a': i + i*(i%2-0.5), 'b': i + i*(i%2-0.5)}]*10 - profile.add_prediction( - i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - self.assertDictEqual(profile.monotonicity(), {'a': 0.0, 'b': 0.0}) - - # Test monotonicity between range [0,1] - profile = ToEPredictionProfile() # Empty profile - for i in range(11): - data = [{'a': i+i*(i%3-0.5), 'b': i+i*(i%3-0.5)}] * 10 - profile.add_prediction( - i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - self.assertDictEqual(profile.monotonicity(), {'a': 0.4, 'b': 0.4}) - - # Test mixed - profile = ToEPredictionProfile() # Empty profile - for i in range(11): - data = [{'a': i+i*(i%3-0.5), 'b': i + i*(i%2-0.5), 'c': 1+i/10}] * 10 - profile.add_prediction( - i, # Time (reverse so data is decreasing) - UnweightedSamples(data) # ToE Prediction - ) - self.assertDictEqual(profile.monotonicity(), {'a': 0.4, 'b': 0.0, 'c': 1.0}) - - # Test MultivariateNormalDist - profile = ToEPredictionProfile() # Empty profile - covar = [[0.1, 0.01], [0.01, 0.1]] - for i in range(11): - data = [{'a': i, 'b': i*(i%3+5)}] * 11 - profile.add_prediction( - i, # Time (reverse so data is decreasing) - MultivariateNormalDist(data[i].keys(), data[i].values(), covar) # ToE Prediction - ) - self.assertDictEqual(profile.monotonicity(), {'a': 0.0, 'b': 0.5}) - - # Test ScalarData - profile = ToEPredictionProfile() # Empty profile - for i in range(11): - data = {'a': i+i*(i%3-0.5), 'b': i+i*(i%3-0.5)} - profile.add_prediction( - i, # Time (reverse so data is decreasing) - ScalarData(data) # ToE Prediction - ) - self.assertDictEqual(profile.monotonicity(), {'a': 0.4, 'b': 0.4}) - - -# This allows the module to be executed directly -def run_tests(): - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Metrics") - result = runner.run(l.loadTestsFromTestCase(TestMetrics)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() diff --git a/tests/test_predictors.py b/tests/test_predictors.py deleted file mode 100644 index 34a1d1eb..00000000 --- a/tests/test_predictors.py +++ /dev/null @@ -1,423 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -import numpy as np -import pickle -import sys -import unittest - -from prog_models import PrognosticsModel -from prog_algs.predictors import UnscentedTransformPredictor, MonteCarlo, ToEPredictionProfile -from prog_algs.uncertain_data import MultivariateNormalDist, UnweightedSamples, ScalarData -from prog_models.models import BatteryCircuit, ThrownObject -from prog_algs.state_estimators import UnscentedKalmanFilter -from prog_algs.predictors.prediction import UnweightedSamplesPrediction, Prediction -from prog_algs.metrics import samples - -# This ensures that the directory containing predictor_template is in the Python search directory -from os.path import dirname, join -sys.path.append(join(dirname(__file__), "..")) - - -class MockProgModel(PrognosticsModel): - states = ['a', 'b', 'c', 't'] - inputs = ['i1', 'i2'] - outputs = ['o1'] - events = ['e1', 'e2'] - default_parameters = { - 'p1': 1.2, - } - - def initialize(self, u = {}, z = {}): - return {'a': 1, 'b': 5, 'c': -3.2, 't': 0} - - def next_state(self, x, u, dt): - x['a']+= u['i1']*dt - x['c']-= u['i2'] - x['t']+= dt - return x - - def output(self, x): - return {'o1': x['a'] + x['b'] + x['c']} - - def event_state(self, x): - t = x['t'] - return { - 'e1': max(1-t/5.0,0), - 'e2': max(1-t/15.0,0) - } - - def threshold_met(self, x): - return {key : value < 1e-6 for (key, value) in self.event_state(x).items()} - - -class TestPredictors(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls._m = ThrownObject(process_noise = 0, measurement_noise = 0) - def future_loading(t, x= None): - return cls._m.InputContainer({}) - cls._s = cls._m.generate_surrogate([future_loading], states = ['v'], dt = 0.1, save_freq = 0.1, threshold_keys='impact') - - def test_pred_template(self): - from predictor_template import TemplatePredictor - m = MockProgModel() - pred = TemplatePredictor(m) - - def test_UTP_ThrownObject(self): - m = ThrownObject() - pred = UnscentedTransformPredictor(m) - samples = MultivariateNormalDist(['x', 'v'], [1.83, 40], [[0.1, 0.01], [0.01, 0.1]]) - def future_loading(t, x={}): - return {} - - mc_results = pred.predict(samples, future_loading, dt=0.01, save_freq=1) - self.assertAlmostEqual(mc_results.time_of_event.mean['impact'], 8.21, 0) - self.assertAlmostEqual(mc_results.time_of_event.mean['falling'], 4.15, 0) - # self.assertAlmostEqual(mc_results.times[-1], 9, 1) # Saving every second, last time should be around the 1s after impact event (because one of the sigma points fails afterwards) - - def test_UTP_ThrownObject_One_Event(self): - # Test thrown object, similar to test_UKP_ThrownObject, but with only the 'falling' event - m = ThrownObject() - pred = UnscentedTransformPredictor(m) - samples = MultivariateNormalDist(['x', 'v'], [1.83, 40], [[0.1, 0.01], [0.01, 0.1]]) - def future_loading(t, x={}): - return {} - - mc_results = pred.predict(samples, future_loading, dt=0.01, events=['falling'], save_freq=1) - self.assertAlmostEqual(mc_results.time_of_event.mean['falling'], 3.8, 0) - self.assertTrue('impact' not in mc_results.time_of_event.mean) - self.assertAlmostEqual(mc_results.times[-1], 3, 1) # Saving every second, last time should be around the nearest 1s before falling event - - def test_UKP_Battery(self): - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - - batt = BatteryCircuit() - - ## State Estimation - perform a single ukf state estimate step - filt = UnscentedKalmanFilter(batt, batt.parameters['x0']) - - example_measurements = {'t': 32.2, 'v': 3.915} - t = 0.1 - filt.estimate(t, future_loading(t), example_measurements) - - ## Prediction - Predict EOD given current state - # Setup prediction - ut = UnscentedTransformPredictor(batt) - - # Predict with a step size of 0.1 - mc_results = ut.predict(filt.x, future_loading, dt=0.1) - self.assertAlmostEqual(mc_results.time_of_event.mean['EOD'], 3004, -2) - - # Test Metrics - s = mc_results.time_of_event.sample(100).key('EOD') - samples.eol_metrics(s) # Kept for backwards compatibility - - def test_MC(self): - m = ThrownObject() - mc = MonteCarlo(m) - def future_loading(t = None, x = None): - return {} - - mc.predict(m.initialize(), future_loading, dt=0.2, num_samples=3, save_freq=1) - - def test_prediction_mvnormaldist(self): - times = list(range(10)) - covar = [[0.1, 0.01], [0.01, 0.1]] - means = [{'a': 1+i/10, 'b': 2-i/5} for i in range(10)] - states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)] - p = Prediction(times, states) - - self.assertEqual(p.mean, means) - self.assertEqual(p.snapshot(0), states[0]) - self.assertEqual(p.snapshot(-1), states[-1]) - self.assertEqual(p.time(0), times[0]) - self.assertEqual(p.times[0], times[0]) - self.assertEqual(p.time(-1), times[-1]) - self.assertEqual(p.times[-1], times[-1]) - - # Out of range - try: - tmp = p.time(10) - self.fail() - except Exception: - pass - - def test_prediction_uwsamples(self): - times = list(range(10)) - states = [UnweightedSamples([{'a': i} for i in range(10)]), - UnweightedSamples([{'a': i} for i in range(1, 11)]), - UnweightedSamples([{'a': i} for i in range(-1, 9)])] - p = UnweightedSamplesPrediction(times, states) - - self.assertEqual(p[0], states[0]) - self.assertEqual(p.sample(0), states[0]) - self.assertEqual(p.sample(-1), states[-1]) - self.assertEqual(p.snapshot(0), UnweightedSamples([{'a': 0}, {'a': 1}, {'a': -1}])) - self.assertEqual(p.snapshot(-1), UnweightedSamples([{'a': 9}, {'a': 10}, {'a': 8}])) - self.assertEqual(p.time(0), times[0]) - self.assertEqual(p.times[0], times[0]) - self.assertEqual(p.time(-1), times[-1]) - - # Out of range - try: - tmp = p[10] - self.fail() - except Exception: - pass - - try: - tmp = p.sample(10) - self.fail() - except Exception: - pass - - try: - tmp = p.time(10) - self.fail() - except Exception: - pass - - # Bad type - try: - tmp = p.sample('abc') - self.fail() - except Exception: - pass - - def test_prediction_profile(self): - profile = ToEPredictionProfile() - self.assertEqual(len(profile), 0) - - profile.add_prediction(0, ScalarData({'a': 1, 'b': 2, 'c': -3.2})) - profile.add_prediction(1, ScalarData({'a': 1.1, 'b': 2.2, 'c': -3.1})) - profile.add_prediction(0.5, ScalarData({'a': 1.05, 'b': 2.1, 'c': -3.15})) - self.assertEqual(len(profile), 3) - for (t_p, t_p_real) in zip(profile.keys(), [0, 0.5, 1]): - self.assertAlmostEqual(t_p, t_p_real) - - profile[0.75] = ScalarData({'a': 1.075, 'b': 2.15, 'c': -3.125}) - self.assertEqual(len(profile), 4) - for (t_p, t_p_real) in zip(profile.keys(), [0, 0.5, 0.75, 1]): - self.assertAlmostEqual(t_p, t_p_real) - self.assertEqual(profile[0.75], ScalarData({'a': 1.075, 'b': 2.15, 'c': -3.125})) - - del profile[0.5] - self.assertEqual(len(profile), 3) - for (t_p, t_p_real) in zip(profile.keys(), [0, 0.75, 1]): - self.assertAlmostEqual(t_p, t_p_real) - for ((t_p, toe), t_p_real) in zip(profile.items(), [0, 0.75, 1]): - self.assertAlmostEqual(t_p, t_p_real) - try: - tmp = profile[0.5] - # 0.5 doesn't exist anymore - except Exception: - pass - - def test_pickle_UTP_ThrownObject_pickle_result(self): # PREDICTION TEST - m = ThrownObject() - pred = UnscentedTransformPredictor(m) - samples = MultivariateNormalDist(['x', 'v'], [1.83, 40], [[0.1, 0.01], [0.01, 0.1]]) - def future_loading(t, x={}): - return {} - - mc_results = pred.predict(samples, future_loading, dt=0.01, save_freq=1) - pickle.dump(mc_results, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(mc_results, pickle_converted_result) - - def test_UTP_ThrownObject_One_Event_pickle_result(self): # PREDICTION TEST - # Test thrown object, similar to test_UKP_ThrownObject, but with only the 'falling' event - m = ThrownObject() - pred = UnscentedTransformPredictor(m) - samples = MultivariateNormalDist(['x', 'v'], [1.83, 40], [[0.1, 0.01], [0.01, 0.1]]) - def future_loading(t, x={}): - return {} - - mc_results = pred.predict(samples, future_loading, dt=0.01, events=['falling'], save_freq=1) - pickle.dump(mc_results, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(mc_results, pickle_converted_result) - - def test_UKP_Battery_pickle_result(self): - def future_loading(t, x = None): - # Variable (piece-wise) future loading scheme - if (t < 600): - i = 2 - elif (t < 900): - i = 1 - elif (t < 1800): - i = 4 - elif (t < 3000): - i = 2 - else: - i = 3 - return {'i': i} - - batt = BatteryCircuit() - - ## State Estimation - perform a single ukf state estimate step - filt = UnscentedKalmanFilter(batt, batt.parameters['x0']) - - example_measurements = {'t': 32.2, 'v': 3.915} - t = 0.1 - filt.estimate(t, future_loading(t), example_measurements) - - ## Prediction - Predict EOD given current state - # Setup prediction - ut = UnscentedTransformPredictor(batt) - - # Predict with a step size of 0.1 - mc_results = ut.predict(filt.x, future_loading, dt=0.1) - pickle.dump(mc_results, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(mc_results, pickle_converted_result) - - def test_pickle_prediction_mvnormaldist(self): - times = list(range(10)) - covar = [[0.1, 0.01], [0.01, 0.1]] - means = [{'a': 1+i/10, 'b': 2-i/5} for i in range(10)] - states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)] - p = Prediction(times, states) - - p2 = pickle.loads(pickle.dumps(p)) - self.assertEqual(p2, p) - - def test_pickle_prediction_uwsamples(self): - times = list(range(10)) - states = [UnweightedSamples(list(range(10))), - UnweightedSamples(list(range(1, 11))), - UnweightedSamples(list(range(-1, 9)))] - p = UnweightedSamplesPrediction(times, states) - - p2 = pickle.loads(pickle.dumps(p)) - self.assertEqual(p2, p) - - def test_pickle_prediction_profile(self): - profile = ToEPredictionProfile() - self.assertEqual(len(profile), 0) - - profile.add_prediction(0, ScalarData({'a': 1, 'b': 2, 'c': -3.2})) - profile.add_prediction(1, ScalarData({'a': 1.1, 'b': 2.2, 'c': -3.1})) - profile.add_prediction(0.5, ScalarData({'a': 1.05, 'b': 2.1, 'c': -3.15})) - self.assertEqual(len(profile), 3) - - p2 = pickle.loads(pickle.dumps(profile)) - self.assertEqual(p2, profile) - - # Testing LazyUTPrediction - def test_pickle_UTP_ThrownObject(self): - m = ThrownObject() - pred = UnscentedTransformPredictor(m) - samples = MultivariateNormalDist(['x', 'v'], [1.83, 40], [[0.1, 0.01], [0.01, 0.1]]) - def future_loading(t, x={}): - return {} - mc_results = pred.predict(samples, future_loading, dt=0.01, save_freq=1) - # LazyUTPrediction objects from pre - pred_op = mc_results.outputs - pred_es = mc_results.event_states - - pickle.dump(pred_op, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(pred_op, pickle_converted_result) - - pickle.dump(pred_es, open('predictor_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb')) - self.assertEqual(pred_es, pickle_converted_result) - - def test_profile_plot(self): - profile = ToEPredictionProfile() - profile.add_prediction(0, ScalarData({'a': 1, 'b': 2, 'c': -3.2})) - profile.add_prediction(1, ScalarData({'a': 1.1, 'b': 2.2, 'c': -3.1})) - profile.add_prediction(0.5, ScalarData({'a': 1.05, 'b': 2.1, 'c': -3.15})) - - # No ground truth or alpha provided - no_gt_alpha_plots = profile.plot(show=True) - - # Ground truth provided, no alpha provided - sample_gt = {'a': 1.075, 'b': 2.15, 'c': -3.125} - gt_no_alpha_plots = profile.plot(ground_truth=sample_gt, show=True) - - # Ground truth and alpha provided - sample_alpha = 0.50 - gt_and_alpha_plots = profile.plot(ground_truth=sample_gt, alpha=sample_alpha, show=True) - - def test_prediction_monotonicity(self): - times = list(range(10)) - covar = [[0.1, 0.01], [0.01, 0.1]] - - # Test monotonically increasing and decreasing - means = [{'a': 1+i/10, 'b': 2-i/5} for i in range(10)] - states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)] - p = Prediction(times, states) - self.assertDictEqual(p.monotonicity(), {'a': 1.0, 'b': 1.0}) - - # Test no monotonicity - means = [{'a': i*(i%2-1), 'b': i*(i%2-1)} for i in range(10)] - states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)] - p = Prediction(times, states) - self.assertDictEqual(p.monotonicity(), {'a': 0.0, 'b': 0.0}) - - # Test monotonicity between range [0,1] - means = [{'a': i*(i%3-1), 'b': i*(i%3-1)} for i in range(10)] - states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)] - p = Prediction(times, states) - self.assertDictEqual(p.monotonicity(), {'a': 0.2222222222222222, 'b': 0.2222222222222222}) - - # Test mixed - means = [{'a': i, 'b': i*(i%3+5)} for i in range(10)] - states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)] - p = Prediction(times, states) - self.assertDictEqual(p.monotonicity(), {'a': 1, 'b': 0.5555555555555556}) - - # Test Scalar - samples = [{'a': 1+i/10, 'b': 2-i/5, 'c': i*(i%2-1), 'd': i*(i%3-1)} for i in range(10)] - states = [ScalarData(samples[i]) for i in range(10)] - p = Prediction(times, states) - self.assertDictEqual(p.monotonicity(), {'a': 1, 'b': 1, 'c': 0, 'd': 0.2222222222222222}) - - # Test UnweightedSamples - samples = [{'a': 1+i/10, 'b': 2-i/5, 'c': i*(i%2-1), 'd': i*(i%3-1)} for i in range(10)] - states = [UnweightedSamples([samples[i]]) for i in range(10)] - p = Prediction(times, states) - self.assertDictEqual(p.monotonicity(), {'a': 1, 'b': 1, 'c': 0, 'd': 0.2222222222222222}) - - def _test_surrogate_pred(self, Predictor, **kwargs): - s = self._s - p = Predictor(s, **kwargs) - def future_loading(t, x= None): - return s.InputContainer({}) - x0 = s.initialize() - x0 = MultivariateNormalDist(x0.keys(), x0.values(), np.diag([1e-8 * xi for xi in x0.values()])) - result = p.predict(x0, future_loading, horizon = 50, dt = 0.1) - - def test_utp_surrogate(self): - self._test_surrogate_pred(UnscentedTransformPredictor, Q = np.diag([1e-3, 1e-3, 1e-7, 1e-7])) - - def test_mc_surrogate(self): - self._test_surrogate_pred(MonteCarlo) - -# This allows the module to be executed directly -def run_tests(): - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Predictor") - from unittest.mock import patch - with patch('matplotlib.pyplot.show') as p: - result = runner.run(l.loadTestsFromTestCase(TestPredictors)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() diff --git a/tests/test_state_estimators.py b/tests/test_state_estimators.py deleted file mode 100644 index a9f18d9a..00000000 --- a/tests/test_state_estimators.py +++ /dev/null @@ -1,577 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. -import unittest -import numpy as np -import random - -from prog_models import PrognosticsModel, LinearModel -from prog_models.models import ThrownObject, BatteryElectroChem, PneumaticValveBase -from prog_algs.state_estimators import ParticleFilter, KalmanFilter, UnscentedKalmanFilter -from prog_algs.uncertain_data import ScalarData, MultivariateNormalDist, UnweightedSamples - -def equal_cov(pair1, pair2): - """ - Compare 2 covariance matricies, considering the order - - Args: - pair1 (tuple[list, array[array[float]]]): - keys (list[str]): Labels for keys - covar (array[array[float]]): Covariance matrix - pair2 (tuple[list, array[array[float]]]): - keys (list[str]): Labels for keys - covar (array[array[float]]): Covariance matrix - """ - (keys1, cov1) = pair1 - (keys2, cov2) = pair2 - mapping = {i: keys2.index(key) for i, key in enumerate(keys1)} - return all([cov1[i][j] == cov2[mapping[i]][mapping[j]] for i in range(len(keys1)) for j in range(len(keys1))]) - - -class MockProgModel(PrognosticsModel): - states = ['a', 'b', 'c', 't'] - inputs = ['i1', 'i2'] - outputs = ['o1'] - events = ['e1', 'e2'] - default_parameters = { - 'p1': 1.2, - } - - def initialize(self, u = {}, z = {}): - return {'a': 1, 'b': 5, 'c': -3.2, 't': 0} - - def next_state(self, x, u, dt): - x['a']+= u['i1']*dt - x['c']-= u['i2'] - x['t']+= dt - return x - - def output(self, x): - return {'o1': x['a'] + x['b'] + x['c']} - - def event_state(self, x): - t = x['t'] - return { - 'e1': max(1-t/5.0,0), - 'e2': max(1-t/15.0,0) - } - - def threshold_met(self, x): - return {key : value < 1e-6 for (key, value) in self.event_state(x).items()} - - -class MockProgModel2(MockProgModel): - outputs = ['o1', 'o2'] - def output(self, x): - return self.OutputContainer({ - 'o1': x['a'] + x['b'] + x['c'], - 'o2': 7 - }) - - -class TestStateEstimators(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls._m_mock = MockProgModel() - cls._m = ThrownObject() - def future_loading(t, x= None): - return cls._m.InputContainer({}) - cls._future_loading = future_loading - cls._s = cls._m.generate_surrogate([future_loading], states = ['v'], dt = 0.1, save_freq = 0.1) - - def test_state_est_template(self): - from state_estimator_template import TemplateStateEstimator - se = TemplateStateEstimator(self._m_mock, {'a': 0.0, 'b': 0.0, 'c': 0.0, 't':0.0}) - - def __test_state_est(self, filt, m): - x = m.initialize() - - self.assertTrue(all(key in filt.x.mean for key in m.states)) - - # run for a while - dt = 0.2 - u = m.InputContainer({}) - last_time = 0 - for i in range(500): - # Get simulated output (would be measured in a real application) - x = m.next_state(x, u, dt) - z = m.output(x) - - # Estimate New State every few steps - if i % 8 == 0: - # This is to test dt - # Without dt, this would fail - last_time = (i+1)*dt - filt.estimate((i+1)*dt, u, z, dt=dt) - - if last_time != (i+1)*dt: - # Final estimate - filt.estimate((i+1)*dt, u, z, dt=dt) - - # Check results - make sure it converged - x_est = filt.x.mean - for key in m.states: - # should be close to right - self.assertAlmostEqual(x_est[key], x[key], delta=0.4) - - def test_UKF(self): - m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2) - x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40} - - filt = UnscentedKalmanFilter(m, x_guess) - self.__test_state_est(filt, m) - - m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2) - - # Test UnscentedKalmanFilter ScalarData - x_scalar = ScalarData({'x': 1.75, 'v': 35}) - filt_scalar = UnscentedKalmanFilter(m, x_scalar) - mean1 = filt_scalar.x.mean - mean2 = x_scalar.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_scalar.keys()), x_scalar.cov), - (list(filt_scalar.x.keys()), filt_scalar.x.cov))) - - # Test UnscentedKalmanFilter MultivariateNormalDist - x_mvnd = MultivariateNormalDist(['x', 'v'], np.array([2, 10]), np.array([[1, 0], [0, 1]])) - filt_mvnd = UnscentedKalmanFilter(m, x_mvnd) - mean1 = filt_mvnd.x.mean - mean2 = x_mvnd.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_mvnd.keys()), x_mvnd.cov), - (list(filt_mvnd.x.keys()), filt_mvnd.x.cov))) - - # Now with a different order - x_mvnd = MultivariateNormalDist(['v', 'x'], np.array([10, 2]), np.array([[1, 0], [0, 2]])) - filt_mvnd = UnscentedKalmanFilter(m, x_mvnd) - mean1 = filt_mvnd.x.mean - mean2 = x_mvnd.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_mvnd.keys()), x_mvnd.cov), - (list(filt_mvnd.x.keys()), filt_mvnd.x.cov)), "Covs are not equal for multivariate in different order") - - # Test UnscentedKalmanFilter UnweightedSamples - x_us = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}]) - filt_us = UnscentedKalmanFilter(m, x_us) - mean1 = filt_us.x.mean - mean2 = x_us.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_us.keys()), x_us.cov), - (list(filt_us.x.keys()), filt_us.x.cov))) - - with self.assertRaises(Exception): - # Not linear model - UnscentedKalmanFilter(BatteryElectroChem, {}) - - with self.assertRaises(Exception): - # Missing states - UnscentedKalmanFilter(ThrownObject, {}) - - def __incorrect_input_tests(self, filter): - class IncompleteModel: - outputs = [] - states = ['a', 'b'] - def next_state(self): - pass - def output(self): - pass - m = IncompleteModel() - x0 = {'a': 0, 'c': 2} - # Missing Key 'b' - with self.assertRaises(KeyError): - filter(m, x0) - - class IncompleteModel: - states = ['a', 'b'] - def next_state(self): - pass - def output(self): - pass - m = IncompleteModel() - x0 = {'a': 0, 'b': 2} - with self.assertRaises(NotImplementedError): - filter(m, x0) - - class IncompleteModel: - outputs = [] - def next_state(self): - pass - def output(self): - pass - m = IncompleteModel() - x0 = {'a': 0, 'b': 2} - with self.assertRaises(NotImplementedError): - filter(m, x0) - - class IncompleteModel: - outputs = [] - states = ['a', 'b'] - def output(self): - pass - m = IncompleteModel() - x0 = {'a': 0, 'b': 2} - with self.assertRaises(NotImplementedError): - filter(m, x0) - class IncompleteModel: - outputs = [] - states = ['a', 'b'] - def next_state(self): - pass - m = IncompleteModel() - x0 = {'a': 0, 'b': 2} - with self.assertRaises(NotImplementedError): - filter(m, x0) - - def test_UKF_incorrect_input(self): - self.__incorrect_input_tests(UnscentedKalmanFilter) - - def test_PF_limit_check(self): - class OneInputOneOutputOneEventLM(LinearModel): - inputs = ['u1'] - states = ['x1'] - outputs = ['x1+1'] - events = ['x1 == 10'] - - A = np.array([[0]]) - B = np.array([[1]]) - C = np.array([[1]]) - D = np.array([[1]]) - F = np.array([[-0.1]]) - G = np.array([[1]]) - - default_parameters = { - 'process_noise': 0.1, - 'measurement_noise': 0.1, - 'x0': { - 'x1': 0 - } - } - - m = OneInputOneOutputOneEventLM() - pf = ParticleFilter(m, {'x1': 10}, num_particles = 5) - - # Without state limits - pf.estimate(1, {'u1': 1}, {'x1+1': 12}) - self.assertAlmostEqual(pf.x.mean['x1'], 11, delta=0.2) - - # With state limits - OneInputOneOutputOneEventLM.state_limits = { - 'x1': (0, 10) - } - pf.estimate(2, {'u1': 1}, {'x1+1': 13}) - self.assertLessEqual(pf.x.mean['x1'], 10) # Limited to 10 now - - def test_PF_step(self): - m = PneumaticValveBase() - - # Generate data - cycle_time = 20 - def future_loading(t, x=None): - t = t % cycle_time - if t < cycle_time/2: - return m.InputContainer({ - 'pL': 3.5e5, - 'pR': 2.0e5, - # Open Valve - 'uTop': False, - 'uBot': True - }) - return m.InputContainer({ - 'pL': 3.5e5, - 'pR': 2.0e5, - # Close Valve - 'uTop': True, - 'uBot': False - }) - - config = { - 'dt': 0.01, - 'save_freq': 1, - } - simulated_results = m.simulate_to(10, future_loading, **config) - - # Setup PF - x0 = m.initialize(future_loading(0)) - filt = ParticleFilter(m, x0, num_particles = 100) - t=0 - for u, z in zip(simulated_results.inputs, simulated_results.outputs): - filt.estimate(t, u, z, dt = 3) - t += config['save_freq'] - - def test_PF(self): - m = ThrownObject(process_noise={'x': 0.75, 'v': 0.75}, measurement_noise=1) - x_guess = {'x': 1.75, 'v': 38.5} # Guess of initial state, actual is {'x': 1.83, 'v': 40} - - filt = ParticleFilter(m, x_guess, num_particles = 1000, measurement_noise = {'x': 1}) - self.__test_state_est(filt, m) - - # Test ParticleFilter ScalarData - x_scalar = ScalarData({'x': 1.75, 'v': 38.5}) - filt_scalar = ParticleFilter(m, x_scalar, num_particles = 20) # Sample count does not affect ScalarData testing - mean1 = filt_scalar.x.mean - mean2 = x_scalar.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue((filt_scalar.x.cov == x_scalar.cov).all()) - - # Test ParticleFilter MultivariateNormalDist - x_mvnd = MultivariateNormalDist(['x', 'v'], np.array([2, 10]), np.array([[1, 0], [0, 1]])) - filt_mvnd = ParticleFilter(m, x_mvnd, num_particles = 100000) - for k, v in filt_mvnd.x.mean.items(): - self.assertAlmostEqual(v, x_mvnd.mean[k], delta = 0.01) - for i in range(len(filt_mvnd.x.cov)): - for j in range(len(filt_mvnd.x.cov[i])): - self.assertAlmostEqual(filt_mvnd.x.cov[i][j], x_mvnd.cov[i][j], delta=0.1) - - # Test ParticleFilter UnweightedSamples - uw_input = [] - x_bounds, v_bounds, x0_samples = 5, 5, 10000 - for i in range(x0_samples): - uw_input.append({'x': random.randrange(-x_bounds, x_bounds), 'v': random.randrange(-v_bounds, v_bounds)}) - x_us = UnweightedSamples(uw_input) - filt_us = ParticleFilter(m, x_us, num_particles = 100000) - for k, v in filt_us.x.mean.items(): - self.assertAlmostEqual(v, x_us.mean[k], delta=0.025) - for i in range(len(filt_us.x.cov)): - for j in range(len(filt_us.x.cov[i])): - self.assertAlmostEqual(filt_us.x.cov[i][j], x_us.cov[i][j], delta=0.1) - - # Test x0 if-else Control - # Case 0: isinstance(x0, UncertainData) - x_scalar = ScalarData({'x': 1.75, 'v': 38.5}) # Testing with ScalarData - filt_scalar = ParticleFilter(m, x_scalar, num_particles = 20) # Sample count does not affect ScalarData testing - mean1 = filt_scalar.x.mean - mean2 = x_scalar.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue((filt_scalar.x.cov == x_scalar.cov).all()) - - def test_PF_incorrect_input(self): - self.__incorrect_input_tests(ParticleFilter) - - def _test_state_est_surrogate(self, StateEst): - m = self._m - s = self._s - - # Setup ParticleFilter - x0 = s.initialize() - filt = StateEst(s, x0) - - # Test - x0 = m.initialize() - x = m.next_state(x0, {}, 0.1) - filt.estimate(0.1, s.InputContainer({}), m.output(x)) - self.assertIsInstance(filt.x.mean, s.StateContainer) - # mean = filt.x.mean - # self.assertAlmostEqual(mean['x'], x['x'], delta=10) - # self.assertAlmostEqual(mean['v'], x['v'], delta=1) - # es = m.event_state(x) - # for key in es.keys(): - # self.assertAlmostEqual(mean[key], es[key], delta=0.2) - - def test_PF_Surrogate(self): - self._test_state_est_surrogate(ParticleFilter) - - def test_UKF_Surrogate(self): - self._test_state_est_surrogate(UnscentedKalmanFilter) - - def test_KF_Surrogate(self): - self._test_state_est_surrogate(KalmanFilter) - - def test_KF(self): - class ThrownObject(LinearModel): - inputs = [] # no inputs, no way to control - states = ['x', 'v'] - outputs = ['x'] - events = ['falling', 'impact'] - - A = np.array([[0, 1], [0, 0]]) - E = np.array([[0], [-9.81]]) - C = np.array([[1, 0]]) - F = None # Will override method - - default_parameters = { - 'thrower_height': 1.83, - 'throwing_speed': 40, - 'g': -9.81 - } - - def initialize(self, u=None, z=None): - return self.StateContainer({ - 'x': self.parameters['thrower_height'], - 'v': self.parameters['throwing_speed'] - }) - - def threshold_met(self, x): - return { - 'falling': x['v'] < 0, - 'impact': x['x'] <= 0 - } - - def event_state(self, x): - x_max = x['x'] + np.square(x['v'])/(-self.parameters['g']*2) # Use speed and position to estimate maximum height - return { - 'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0), # Throwing speed is max speed - 'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1 # 1 until falling begins, then it's fraction of height - } - - m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2) - x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40} - - filt = KalmanFilter(m, x_guess) - - self.__test_state_est(filt, m) - - m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2) - - # Test KalmanFilter ScalarData - x_scalar = ScalarData({'x': 1.75, 'v': 35}) - filt_scalar = KalmanFilter(m, x_scalar) - mean1 = filt_scalar.x.mean - mean2 = x_scalar.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_scalar.keys()), x_scalar.cov), - (list(filt_scalar.x.keys()), filt_scalar.x.cov))) - - # Test KalmanFilter MultivariateNormalDist - x_mvnd = MultivariateNormalDist(['x', 'v'], np.array([2, 10]), np.array([[1, 0], [0, 1]])) - filt_mvnd = KalmanFilter(m, x_mvnd) - mean1 = filt_mvnd.x.mean - mean2 = x_mvnd.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_mvnd.keys()), x_mvnd.cov), - (list(filt_mvnd.x.keys()), filt_mvnd.x.cov))) - - # Now with a different order - x_mvnd = MultivariateNormalDist(['v', 'x'], np.array([10, 2]), np.array([[1, 0], [0, 2]])) - filt_mvnd = KalmanFilter(m, x_mvnd) - mean1 = filt_mvnd.x.mean - mean2 = x_mvnd.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_mvnd.keys()), x_mvnd.cov), - (list(filt_mvnd.x.keys()), filt_mvnd.x.cov)), "Covs are not equal for multivariate in different order") - - # Test KalmanFilter UnweightedSamples - x_us = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}]) - filt_us = KalmanFilter(m, x_us) - mean1 = filt_us.x.mean - mean2 = x_us.mean - self.assertSetEqual(set(mean1.keys()), set(mean2.keys())) - for k in mean1.keys(): - self.assertEqual(mean1[k], mean2[k]) - self.assertTrue( - equal_cov( - (list(x_us.keys()), x_us.cov), - (list(filt_us.x.keys()), filt_us.x.cov))) - - with self.assertRaises(Exception): - # Not linear model - KalmanFilter(BatteryElectroChem, {}) - - with self.assertRaises(Exception): - # Missing states - KalmanFilter(ThrownObject, {}) - - def test_KF_descending(self): - # Example introduced by @CuiiGen in https://github.com/nasa/prog_algs/issues/220 - class Descending(LinearModel): - inputs = ['u'] - states = ['x'] - outputs = ['x'] - events = ['zero'] - A = np.array([[0]]) - B = np.array([[0]]) - E = np.array([[-1]]) - C = np.array([[1]]) - D = np.array([[0]]) - F = None - default_parameters = { - 'x0': { - 'x': 10 - }, - 'process_noise': 0, - 'measurement_noise': 5 - } - - def initialize(self, u=None, z=None): - return self.StateContainer(self.default_parameters['x0']) - - def threshold_met(self, x): - return { - 'zero': x['x'] <= 0 - } - - def event_state(self, x): - return { - 'zero': x['x'] > 0 - } - - m = Descending() - - def future_loading(t, x=None): - return m.InputContainer({'u': 1}) - - config = { - 'dt': 0.01, - 'save_freq': 0.01, - 'threshold_keys': 'zero' - } - simulation_result = m.simulate_to_threshold(future_loading, **config) - - states = simulation_result.states - outputs = simulation_result.outputs - inputs = simulation_result.inputs - states.plot() - outputs.plot() - - x0 = MultivariateNormalDist(['x'], [0], [[0.01]]) - kf = KalmanFilter(m, x0) - times = simulation_result.times - for t, u, z in zip(times, inputs.data, outputs.data): - kf.estimate(t, u, z) - -# This allows the module to be executed directly -def run_tests(): - # This ensures that the directory containing StateEstimatorTemplate is in the python search directory - import sys - from os.path import dirname, join - sys.path.append(join(dirname(__file__), "..")) - - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting State Estimators") - result = runner.run(l.loadTestsFromTestCase(TestStateEstimators)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() diff --git a/tests/test_templates.py b/tests/test_templates.py deleted file mode 100644 index 72d2d3d8..00000000 --- a/tests/test_templates.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -import unittest - - -class TestTemplates(unittest.TestCase): - pass - -# This allows the module to be executed directly -def run_tests(): - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Templates") - result = runner.run(l.loadTestsFromTestCase(TestTemplates)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() diff --git a/tests/test_tutorials.py b/tests/test_tutorials.py deleted file mode 100644 index cd5bb9cc..00000000 --- a/tests/test_tutorials.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -import unittest -from testbook import testbook - -class TestTutorials(unittest.TestCase): - def test_tutorial_ipynb(self): - with testbook('./tutorial.ipynb', execute=True) as tb: - self.assertEqual(tb.__class__.__name__, "TestbookNotebookClient") - - -def run_tests(): - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Tutorials") - result = runner.run(l.loadTestsFromTestCase(TestTutorials)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() - \ No newline at end of file diff --git a/tests/test_uncertain_data.py b/tests/test_uncertain_data.py deleted file mode 100644 index 8b6ab2de..00000000 --- a/tests/test_uncertain_data.py +++ /dev/null @@ -1,573 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -import unittest -from prog_algs.uncertain_data import UnweightedSamples, MultivariateNormalDist, ScalarData -from numpy import array - - -class TestUncertainData(unittest.TestCase): - def test_unweightedsamples(self): - empty_samples = UnweightedSamples() - self.assertEqual(empty_samples.size, 0) - try: - empty_samples.sample() - self.fail() # Cant sample from 0 samples - except ValueError: - pass - - empty_samples.append({'a': 1, 'b': 2}) - self.assertEqual(empty_samples.size, 1) - self.assertDictEqual(empty_samples.mean, {'a': 1, 'b': 2}) - samples = empty_samples.sample() - self.assertDictEqual(samples[0], {'a': 1, 'b': 2}) - self.assertEqual(samples.size, 1) - - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - self.assertDictEqual(s.mean, {'a': 2, 'b': 0}) - self.assertEqual(s.size, 2) - samples = s.sample(10) - self.assertEqual(samples.size, 10) - del s[0] - self.assertEqual(s.size, 1) - k = s.keys() - self.assertEqual(len(s.raw_samples()), 1) - s[0] = {'a': 2, 'b': 10} - self.assertDictEqual(s[0], {'a': 2, 'b': 10}) - for i in range(50): - s.append({'a': i, 'b': 9}) - covar = s.cov - self.assertEqual(len(covar), 2) - self.assertEqual(len(covar[0]), 2) - - # Test median value - data = [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}, {'a': 1, 'b': 4}, {'a': 2, 'b': 3}, {'a': 3, 'b': 1}] - data = UnweightedSamples(data) - self.assertEqual(data.median, {'a': 2, 'b': 3}) - - # Test percentage in bounds - self.assertEqual(data.percentage_in_bounds([0, 2.5]), - {'a':0.6, 'b': 0.4}) - self.assertEqual(data.percentage_in_bounds({'a': [0, 2.5], 'b': [0, 1.5]}), - {'a':0.6, 'b': 0.2}) - - def test_multivariatenormaldist(self): - try: - dist = MultivariateNormalDist() - self.fail() - except Exception: - pass - - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - self.assertDictEqual(dist.mean, {'a': 2, 'b':10}) - self.assertDictEqual(dist.median, {'a': 2, 'b':10}) - self.assertEqual(dist.sample().size, 1) - self.assertEqual(dist.sample(10).size, 10) - self.assertTrue((dist.cov == array([[1, 0], [0, 1]])).all()) - dist.percentage_in_bounds([0, 10]) - - def test_scalardist(self): - data = {'a': 12, 'b': 14} - d = ScalarData(data) - self.assertEqual(d.mean, data) - self.assertEqual(d.median, data) - self.assertListEqual(list(d.sample(10)), [data]*10) - self.assertEqual(d.percentage_in_bounds([13, 20]), {'a': 0, 'b': 1}) - self.assertEqual(d.percentage_in_bounds([0, 10]), {'a': 0, 'b': 0}) - self.assertEqual(d.percentage_in_bounds([0, 20]), {'a': 1, 'b': 1}) - - def test_pickle_unweightedsamples(self): - data = {'a': 12, 'b': 14} - d = ScalarData(data) - import pickle # try pickle'ing - pickle.dump(d, open('data_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('data_test.pkl', 'rb')) - self.assertEqual(d, pickle_converted_result) - - def test_pickle_unweightedsamples(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - import pickle # try pickle'ing - pickle.dump(s, open('data_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('data_test.pkl', 'rb')) - self.assertEqual(s, pickle_converted_result) - - def test_pickle_multivariatenormaldist(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - import pickle # try pickle'ing - pickle.dump(dist, open('data_test.pkl', 'wb')) - pickle_converted_result = pickle.load(open('data_test.pkl', 'rb')) - self.assertEqual(dist, pickle_converted_result) - - def test_unweighted_samples_describe(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - table_list = s.describe() - - def test_scalardata_add_override(self): - data = {'a': 12, 'b': 14} - d = ScalarData(data) - - # Testing __add__ override - mod_d = d + 0 - for k in d.keys(): - self.assertEqual(d.mean[k], mod_d.mean[k]) - mod_d = d + 5 - for k in d.keys(): - self.assertEqual(d.mean[k]+5, mod_d.mean[k]) - mod_d = d + -5 - for k in d.keys(): - self.assertEqual(d.mean[k]-5, mod_d.mean[k]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = d + [] - mod_d = d + {} - mod_d = d + "test" - # Also works with floats - mod_d = d + 5.5 - for k in d.keys(): - self.assertEqual(d.mean[k]+5.5, mod_d.mean[k]) - - def test_scalardata_radd_override(self): - data = {'a': 12, 'b': 14} - d = ScalarData(data) - - # Testing __radd__ override - mod_d = 0 + d - for k in d.keys(): - self.assertEqual(d.mean[k], mod_d.mean[k]) - mod_d = 5 + d - for k in d.keys(): - self.assertEqual(d.mean[k]+5, mod_d.mean[k]) - mod_d = -5 + d - for k in d.keys(): - self.assertEqual(d.mean[k]-5, mod_d.mean[k]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = [] + d - mod_d = {} + d - mod_d = "test" + d - # Also works with floats - mod_d = 5.5 + d - for k in d.keys(): - self.assertEqual(d.mean[k]+5.5, mod_d.mean[k]) - - def test_scalardata_iadd_override(self): - data = {'a': 12, 'b': 14} - data_copy = {'a': 12, 'b': 14} - d = ScalarData(data) - - # Testing __iadd__ override - d += 0 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k]) - d += 5 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k]+5) - d += -5 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k]) - with self.assertRaises(TypeError): - # Test adding invalid type - d += [] - d += {} - d += "test" - # Also works with floats - d += 5.5 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k] + 5.5) - - def test_scalardata_sub_override(self): - data = {'a': 12, 'b': 14} - d = ScalarData(data) - - # Testing __sub__ override - mod_d = d - 0 - for k in d.keys(): - self.assertEqual(d.mean[k], mod_d.mean[k]) - mod_d = d - 5 - for k in d.keys(): - self.assertEqual(d.mean[k]-5, mod_d.mean[k]) - mod_d = d - -5 - for k in d.keys(): - self.assertEqual(d.mean[k]+5, mod_d.mean[k]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = d - [] - mod_d = d - {} - mod_d = d - "test" - # Also works with floats - mod_d = d - 5.5 - for k in d.keys(): - self.assertEqual(d.mean[k]-5.5, mod_d.mean[k]) - - def test_scalardata_rsub_override(self): - data = {'a': 12, 'b': 14} - d = ScalarData(data) - - # Testing __rsub__ override - mod_d = 0 - d - for k in d.keys(): - self.assertEqual(d.mean[k], mod_d.mean[k]) - mod_d = 5 - d - for k in d.keys(): - self.assertEqual(d.mean[k]-5, mod_d.mean[k]) - mod_d = -5 - d - for k in d.keys(): - self.assertEqual(d.mean[k]+5, mod_d.mean[k]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = [] - d - mod_d = {} - d - mod_d = "test" - d - # Also works with floats - mod_d = 5.5 - d - for k in d.keys(): - self.assertEqual(d.mean[k]-5.5, mod_d.mean[k]) - - def test_scalardata_isub_override(self): - data = {'a': 12, 'b': 14} - data_copy = {'a': 12, 'b': 14} - d = ScalarData(data) - - # Testing __isub__ override - d -= 0 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k]) - d -= 5 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k]-5) - d -= -5 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k]) - with self.assertRaises(TypeError): - # Test adding invalid type - d -= [] - d -= {} - d -= "test" - # Also works with floats - d -= 5.5 - for k in d.keys(): - self.assertEqual(d.mean[k], data_copy[k] - 5.5) - - def test_unweightedsamples_add_override(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - - # Testing __add__ override - mod_d = s + 0 - self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - mod_d = s + 5 - self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}]) - mod_d = s + -5 - self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = s + [] - mod_d = s + {} - mod_d = s + "test" - # Also works with floats - mod_d = s + 5.5 - self.assertEqual(mod_d.data, [{'a': 6.5, 'b':7.5}, {'a': 8.5, 'b':3.5}]) - - def test_unweightedsamples_radd_override(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - - # Testing __radd__ override - mod_d = 0 + s - self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - mod_d = 5 + s - self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}]) - mod_d = -5 + s - self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = [] + s - mod_d = {} + s - mod_d = "test" + s - # Also works with floats - mod_d = 5.5 + s - self.assertEqual(mod_d.data, [{'a': 6.5, 'b':7.5}, {'a': 8.5, 'b':3.5}]) - - def test_unweightedsamples_iadd_override(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - - # Testing __iadd__ override - s += 0 - self.assertEqual(s.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - s += 5 - self.assertEqual(s.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}]) - s += -5 - self.assertEqual(s.data, [{'a': 1, 'b': 2}, {'a': 3, 'b': -2}]) - with self.assertRaises(TypeError): - # Test adding invalid type - s += [] - s += {} - s += "test" - # Also works with floats - s += 5.5 - self.assertEqual(s.data, [{'a': 6.5, 'b': 7.5}, {'a': 8.5, 'b': 3.5}]) - - def test_unweightedsamples_sub_override(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - - # Testing __sub__ override - mod_d = s - 0 - self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - mod_d = s - -5 - self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}]) - mod_d = s - 5 - self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = s + [] - mod_d = s + {} - mod_d = s + "test" - # Also works with floats - mod_d = s - 5.5 - self.assertEqual(mod_d.data, [{'a': -4.5, 'b': -3.5}, {'a': -2.5, 'b': -7.5}]) - - def test_unweightedsamples_rsub_override(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - - # Testing __rsub__ override - mod_d = 0 - s - self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - mod_d = -5 - s - self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}]) - mod_d = 5 - s - self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}]) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = [] - s - mod_d = {} - s - mod_d = "test" - s - # Also works with floats - mod_d = 5.5 - s - self.assertEqual(mod_d.data, [{'a': -4.5, 'b': -3.5}, {'a': -2.5, 'b': -7.5}]) - - def test_unweightedsamples_isub_override(self): - s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - - # Testing __isub__ override - s -= 0 - self.assertEqual(s.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - s -= 5 - self.assertEqual(s.data, [{'a': -4, 'b': -3}, {'a': -2, 'b': -7}]) - s -= -5 - self.assertEqual(s.data, [{'a': 1, 'b': 2}, {'a': 3, 'b': -2}]) - with self.assertRaises(TypeError): - # Test adding invalid type - s -= [] - s -= {} - s -= "test" - # Also works with floats - s -= 5.5 - self.assertEqual(s.data, [{'a': -4.5, 'b': -3.5}, {'a': -2.5, 'b': -7.5}]) - - def test_MultivariateNormalDist_add_override(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - - mod_d = dist + 0 - self.assertEqual(mod_d.mean, {'a': 2, 'b': 10}) - mod_d = dist + 5 - self.assertEqual(mod_d.mean, {'a': 7, 'b': 15}) - mod_d = dist + -5 - self.assertEqual(mod_d.mean, {'a': -3, 'b': 5}) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = dist + [] - mod_d = dist + {} - mod_d = dist + "test" - # Also works with floats - mod_d = dist + 5.5 - self.assertEqual(mod_d.mean, {'a': 7.5, 'b': 15.5}) - - # Ensure covariance has not changed - from numpy.testing import assert_array_equal - mod_d = dist + 5 - assert_array_equal(mod_d.cov, dist.cov) - - def test_MultivariateNormalDist_radd_override(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - - mod_d = 0 + dist - self.assertEqual(mod_d.mean, {'a': 2, 'b': 10}) - mod_d = 5 + dist - self.assertEqual(mod_d.mean, {'a': 7, 'b': 15}) - mod_d = -5 + dist - self.assertEqual(mod_d.mean, {'a': -3, 'b': 5}) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = [] + dist - mod_d = {} + dist - mod_d = "test" + dist - # Also works with floats - mod_d = 5.5 + dist - self.assertEqual(mod_d.mean, {'a': 7.5, 'b': 15.5}) - - # Ensure covariance has not changed - from numpy.testing import assert_array_equal - mod_d = 5 + dist - assert_array_equal(mod_d.cov, dist.cov) - - def test_MultivariateNormalDist_iadd_override(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - from numpy import copy - dist_save = copy(dist.cov) - - dist += 0 - self.assertEqual(dist.mean, {'a': 2, 'b': 10}) - dist += 5 - self.assertEqual(dist.mean, {'a': 7, 'b': 15}) - dist += -5 - self.assertEqual(dist.mean, {'a': 2, 'b': 10}) - with self.assertRaises(TypeError): - # Test adding invalid type - dist += [] - dist += {} - dist += "test" - # Also works with floats - dist += 5.5 - self.assertEqual(dist.mean, {'a': 7.5, 'b': 15.5}) - - # Ensure covariance has not changed - from numpy.testing import assert_array_equal - dist += 5 - assert_array_equal(dist.cov, dist_save) - - def test_MultivariateNormalDist_sub_override(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - - mod_d = dist - 0 - self.assertEqual(mod_d.mean, {'a': 2, 'b': 10}) - mod_d = dist - -5 - self.assertEqual(mod_d.mean, {'a': 7, 'b': 15}) - mod_d = dist - 5 - self.assertEqual(mod_d.mean, {'a': -3, 'b': 5}) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = dist - [] - mod_d = dist - {} - mod_d = dist - "test" - # Also works with floats - mod_d = dist - 5.5 - self.assertEqual(mod_d.mean, {'a': -3.5, 'b': 4.5}) - - # Ensure covariance has not changed - from numpy.testing import assert_array_equal - mod_d = dist - 5 - assert_array_equal(mod_d.cov, dist.cov) - - def test_MultivariateNormalDist_rsub_override(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - - mod_d = 0 - dist - self.assertEqual(mod_d.mean, {'a': 2, 'b': 10}) - mod_d = -5 - dist - self.assertEqual(mod_d.mean, {'a': 7, 'b': 15}) - mod_d = 5 - dist - self.assertEqual(mod_d.mean, {'a': -3, 'b': 5}) - with self.assertRaises(TypeError): - # Test adding invalid type - mod_d = [] - dist - mod_d = {} - dist - mod_d = "test" - dist - # Also works with floats - mod_d = 5.5 - dist - self.assertEqual(mod_d.mean, {'a': -3.5, 'b': 4.5}) - - # Ensure covariance has not changed - from numpy.testing import assert_array_equal - mod_d = 5 - dist - assert_array_equal(mod_d.cov, dist.cov) - - def test_MultivariateNormalDist_isub_override(self): - dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - from numpy import copy - dist_save = copy(dist.cov) - - dist -= 0 - self.assertEqual(dist.mean, {'a': 2, 'b': 10}) - dist -= 5 - self.assertEqual(dist.mean, {'a': -3, 'b': 5}) - dist -= -5 - self.assertEqual(dist.mean, {'a': 2, 'b': 10}) - with self.assertRaises(TypeError): - # Test adding invalid type - dist -= [] - dist -= {} - dist -= "test" - # Also works with floats - dist -= 5.5 - self.assertEqual(dist.mean, {'a': -3.5, 'b': 4.5}) - - # Ensure covariance has not changed - from numpy.testing import assert_array_equal - dist -= 5 - assert_array_equal(dist.cov, dist_save) - - def test_relative_accuracy(self): - # Testing for ScalarData - d = ScalarData({'a': 12, 'b': 14}) - gt_std = {'a': 14, 'b': 16} - gt_neg = {'a': -14, 'b': -16} - ra_std = d.relative_accuracy(gt_std) - ra_neg = d.relative_accuracy(gt_neg) - self.assertDictEqual(ra_std, {'a': 0.8571428571428572, 'b': 0.875}) - self.assertDictEqual(ra_neg, {'a': 2.857142857142857, 'b': 2.875}) - with self.assertRaises(ZeroDivisionError): # Passing in ground truth of 0 leads to divide by 0 error - gt_zero = {'a': 0, 'b': 0} - ra_zero = d.relative_accuracy(gt_zero) - with self.assertRaises(TypeError): # Passing in non-dict arg - ra_err_list = d.relative_accuracy([]) - ra_err_str = d.relative_accuracy("") - ra_err_int = d.relative_accuracy(1) - ra_err_float = d.relative_accuracy(0.1) - ra_err_set = d.relative_accuracy(set()) - - # Testing for UnweightedSamples - d = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]) - gt_std = {'a': 5, 'b': 4} - gt_neg = {'a': -5, 'b': -3} - ra_std = d.relative_accuracy(gt_std) - ra_neg = d.relative_accuracy(gt_neg) - self.assertDictEqual(ra_std, {'a': 0.4, 'b': 0.0}) - self.assertDictEqual(ra_neg, {'a': 2.4, 'b': 2.0}) - with self.assertRaises(ZeroDivisionError): # Hits -inf and nan; maybe because 0/0? - gt_zero = {'a': 0, 'b': 0} - ra_zero = d.relative_accuracy(gt_zero) - with self.assertRaises(TypeError): # Passing in non-dict arg - ra_err_list = d.relative_accuracy([]) - ra_err_str = d.relative_accuracy("") - ra_err_int = d.relative_accuracy(1) - ra_err_float = d.relative_accuracy(0.1) - ra_err_set = d.relative_accuracy(set()) - - # Testing for MultivariateNormalDist - d = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]])) - gt_std = {'a': 3, 'b': 3} - gt_neg = {'a': -3, 'b': -3} - ra_std = d.relative_accuracy(gt_std) - ra_neg = d.relative_accuracy(gt_neg) - self.assertDictEqual(ra_std, {'a': 0.6666666666666667, 'b': -1.3333333333333335}) - self.assertDictEqual(ra_neg, {'a': 2.666666666666667, 'b': 5.333333333333333}) - with self.assertRaises(ZeroDivisionError): # Hits -inf and nan; maybe because 0/0? - gt_zero = {'a': 0, 'b': 0} - ra_zero = d.relative_accuracy(gt_zero) - with self.assertRaises(TypeError): # Passing in non-dict arg - ra_err_list = d.relative_accuracy([]) - ra_err_str = d.relative_accuracy("") - ra_err_int = d.relative_accuracy(1) - ra_err_float = d.relative_accuracy(0.1) - ra_err_set = d.relative_accuracy(set()) - - -# This allows the module to be executed directly -def run_tests(): - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Uncertain Data") - result = runner.run(l.loadTestsFromTestCase(TestUncertainData)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() diff --git a/tests/test_visualize.py b/tests/test_visualize.py deleted file mode 100644 index caad35c1..00000000 --- a/tests/test_visualize.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. - -import unittest -from prog_algs.visualize import plot_scatter - - -class TestVisualize(unittest.TestCase): - def test_scatter(self): - # Nominal - data = [{'x': 1, 'y': 2, 'z': 3}, {'x': 1.5, 'y': 2.2, 'z': -1}, {'x': 0.9, 'y': 2.1, 'z': 7}] - fig = plot_scatter(data) - fig = plot_scatter(data, fig=fig) # Add to figure - plot_scatter(data, fig=fig, keys=['x', 'y', 'z']) # All keys - plot_scatter(data, keys=['y', 'z']) # Subset of keys - - # Incorrect keys - try: - plot_scatter(data, keys=7) # Not iterable - self.fail() - except Exception: - pass - - try: - plot_scatter(data, keys=['x', 'i']) # Not present - self.fail() - except Exception: - pass - - # Changing number of keys - fig = plot_scatter(data) - try: - plot_scatter(data, fig=fig, keys=['y', 'z']) # Different number of keys - self.fail() - except Exception: - pass - - # Too few keys - try: - plot_scatter(data, keys=['y']) # Only one key - self.fail() - except Exception: - pass - -# This allows the module to be executed directly -def run_tests(): - l = unittest.TestLoader() - runner = unittest.TextTestRunner() - print("\n\nTesting Visualize") - result = runner.run(l.loadTestsFromTestCase(TestVisualize)).wasSuccessful() - - if not result: - raise Exception("Failed test") - -if __name__ == '__main__': - run_tests() \ No newline at end of file diff --git a/tutorial.ipynb b/tutorial.ipynb deleted file mode 100644 index b9934b17..00000000 --- a/tutorial.ipynb +++ /dev/null @@ -1,620 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Welcome to the Prognostics Algorithms Package Tutorial" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The goal of this notebook is to instruct the user on how to use and extend the NASA Python Prognostics Algorithms Package. \n", - "\n", - "First some background. The Prognostics Algorithms Package (`prog_algs`) contains tools for performing prognostics (event prediction) using the Prognostics Models Package. `prog_algs` also includes tools for analyzing the performance of prognostics algorithms. \n", - "\n", - "A few definitions:\n", - "* state estimation: The process of estimating the (possibly hidden) state of a system given sensor information on observable states\n", - "* prediction: The process of predicting the evolution of a system state with time and the occurrence of events. \n", - "\n", - "The `prog_algs` package has the following primary subpackages\n", - "* `prog_algs.state_estimators` - Tools for performing state estimation\n", - "* `prog_algs.predictors` - Tools for performing prediction\n", - "* `prog_algs.uncertain_data` - Tools for representing data with uncertainty\n", - "\n", - "In addition to the `prog_algs` package, this repo includes examples showing how to use the package (see `examples/`), a template for implementing a new state estimator (`state_estimator_template`), a template for implementing a new predictor (`predictor_template`), documentation (), and this tutorial (`tutorial.ipynb`).\n", - "\n", - "Before you start, install `prog_algs` using pip:\n", - "\n", - " `pip install prog_algs`\n", - "\n", - "or, to use the pre-release, close from GitHub and checkout the dev branch. Then run the following command:\n", - " `pip install -e .`\n", - "\n", - "Now let's get started with some examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## UncertainData - Representing a Distribution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Uncertainty is sometimes present in data used for performing state estimations or making predictions.\n", - "\n", - "In `prog_algs`, data with uncertainty is represented using classes inheriting from `UncertainData`:\n", - "* `prog_algs.uncertain_data.MultivariateNormalDist` - Data represented by a multivariate normal distribution with mean and covariance matrix\n", - "* `prog_algs.uncertain_data.ScalarData` - Data without uncertainty, a single value\n", - "* `prog_algs.uncertain_data.UnweightedSamples` - Data represented by a set of unweighted samples. Objects of this class can be treated like a list where samples[n] returns the nth sample (Dict)\n", - "\n", - "To begin using `UncertainData`, import the type that best portrays the data. In this simple demonstration, we import the `UnweightedSamples` data type. See for full details on the available `UncertainData` types." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.uncertain_data import UnweightedSamples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With `UnweightedSamples` imported, construct an object with samples. This object can be initialized using either a dictionary, list, or model.*Container type from prog_models (e.g., StateContainer). Let's try creating an object using a dictionary. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "samples = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])\n", - "print(samples)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Given an integer value, addition and subtraction can be performed on the `UncertainData` classes to adjust the distribution by a scalar amount." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "samples = samples + 5\n", - "print(samples)\n", - "samples -= 3\n", - "print(samples)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also sample from any `UncertainData` distribution using the `sample` method. In this case it resamples from the existing samples" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(samples.sample()) # A single sample\n", - "print(samples.sample(10)) # 10 samples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can see the keys present using the `.keys()` method:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(samples.keys())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and the data corresponding to a specific key can be retrieved using `.key()`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(samples.key('x'))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Various properties are available to quantify the `UncertainData` distribution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('mean', samples.mean)\n", - "print('median', samples.median)\n", - "print('covariance', samples.cov)\n", - "print('size', samples.size)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These `UncertainData` classes are used throughout the prog_algs package to represent data with uncertainty, as described in the following sections." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## State Estimation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "State estimation is the process of estimating the system state given sensor data and a model. Typically, this is done repeatedly as new sensor data is available.\n", - "\n", - "In `prog_algs` a State Estimator is used to estimate the system state. \n", - "\n", - "To start, import the needed packages. Here we will import the `BatteryCircuit` model and the `UnscentedKalmanFilter` state estimator. See for more details on the available state estimators.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_models.models import BatteryCircuit\n", - "from prog_algs.state_estimators import UnscentedKalmanFilter" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we construct and initialize the model. \n", - "\n", - "We use the resulting model and initial state to construct the state estimator. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = BatteryCircuit()\n", - "x0 = m.initialize()\n", - "\n", - "# Turn into a distribution - this represents uncertainty in the initial state\n", - "from prog_algs.uncertain_data import MultivariateNormalDist\n", - "from numpy import diag\n", - "INITIAL_UNCERT = 0.05 # Uncertainty in initial state (%)\n", - "# Construct covariance matrix (making sure each value is positive)\n", - "cov = diag([max(abs(INITIAL_UNCERT * value), 1e-9) for value in x0.values()])\n", - "x0 = MultivariateNormalDist(x0.keys(), x0.values(), cov)\n", - "\n", - "# Construct State estimator\n", - "est = UnscentedKalmanFilter(m, x0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can use the estimator to estimate the system state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Prior State:\", est.x.mean)\n", - "print('\\tSOC: ', m.event_state(est.x.mean)['EOD'])\n", - "fig = est.x.plot_scatter(label='prior')\n", - "\n", - "t = 0.1\n", - "u = m.InputContainer({'i': 2})\n", - "example_measurements = m.OutputContainer({'t': 32.2, 'v': 3.915})\n", - "est.estimate(t, u, example_measurements)\n", - "\n", - "print(\"Posterior State:\", est.x.mean)\n", - "print('\\tSOC: ', m.event_state(est.x.mean)['EOD'])\n", - "est.x.plot_scatter(fig= fig, label='posterior')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As mentioned previously, this step is typically repeated when there's new data. filt.x may not be accessed every time the estimate is updated, only when it's needed." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prediction Example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Prediction is the practice of using a state estimation, a model, and estimates of future loading to predict future states and when an event will occur.\n", - "\n", - "First we will import a predictor. In this case, we will use the MonteCarlo Predictor, but see documentation for a full list of predictors and their configuration parameters." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.predictors import MonteCarlo" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we initialize it using the model from the above example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mc = MonteCarlo(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's define future loading and the first state. The first state is the output of the state estimator, and the future loading scheme is a simple piecewise function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = est.x # The state estimate\n", - "\n", - "def future_loading(t, x={}):\n", - " # Variable (piece-wise) future loading scheme \n", - " if (t < 600):\n", - " i = 2\n", - " elif (t < 900):\n", - " i = 1\n", - " elif (t < 1800):\n", - " i = 4\n", - " elif (t < 3000):\n", - " i = 2\n", - " else:\n", - " i = 3\n", - " return m.InputContainer({'i': i})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's use the constructed mc predictor to perform a single prediction. Here we're setting dt to 0.25. Note this may take up to a minute" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "mc_results = mc.predict(x, future_loading, dt=0.25, n_samples=20)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format `[sample #][time]` so that `mc_results.states[m][n]` corresponds to the state for sample `m` at time `mc_results.times[m][n]`. Alternately, use the method `snapshot` to get a single point in time. e.g., \n", - "\n", - " `state = mc_results.states.snapshot(3)`\n", - "\n", - "In this case the state snapshot corresponds to time `mc_results.times[3]`. The snapshot method returns type UncertainData. \n", - "\n", - "The `predict` method also returns Time of Event (ToE) as a type UncertainData, representing the predicted time of event (for each event predicted), with uncertainty.\n", - "\n", - "Next, let's use the metrics package to analyze the ToE" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"\\nEOD Predictions (s):\")\n", - "print('\\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))\n", - "print('\\tAssuming ground truth 3005.25: ', mc_results.time_of_event.metrics(ground_truth = 3005.25))\n", - "from prog_algs.metrics import prob_success \n", - "print('\\tP(Success) if mission ends at 3005.25: ', prob_success(mc_results.time_of_event, 3005.25))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These analysis methods applied to ToE can also be applied to anything of type UncertainData (e.g., state snapshot). \n", - "\n", - "You can also visualize the results in a variety of different ways. For example, state transition" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = mc_results.states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(mc_results.times[0])))\n", - "for i in range(1, 4):\n", - " index = int(len(mc_results.times)/4*i)\n", - " mc_results.states.snapshot(index).plot_scatter(fig=fig, label = \"t={:.0f}\".format(mc_results.times[index]))\n", - "mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = \"t={:.0f}\".format(int(mc_results.times[-1])))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or time of event (ToE)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig = mc_results.time_of_event.plot_hist()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note, for this event, there is only one event (End of Discharge). Many models have multiple events that can be predicted. For these models, ToE for each event is returned and can be analyzed.\n", - "\n", - "Alternately, a specific event (or events) can be specified for prediction. See `examples.predict_specific_event` for more details." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Frequently the prediction step is run periodically, less often than the state estimator step" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extending - Adding a new state estimator" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "New state estimators can be created by extending the state_estimator interface. As an example lets use a really dumb state estimator that adds random noise each step - and accepts the state that is closest. \n", - "\n", - "First thing we need to do is import the StateEstimator parent class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.state_estimators.state_estimator import StateEstimator" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next we select how state will be represented. In this case there's no uncertainty- it's just one state, so we represent it as a scaler. Import the appropriate class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from prog_algs.uncertain_data import ScalarData" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we construct the class, implementing the functions of the state estimator template (`state_estimator_template.py`)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import random \n", - "\n", - "class BlindlyStumbleEstimator(StateEstimator):\n", - " def __init__(self, model, x0):\n", - " self.m = model\n", - " self.state = x0\n", - "\n", - " def estimate(self, t, u, z):\n", - " # Generate new candidate state\n", - " x2 = {key : float(value) + 10*(random.random()-0.5) for (key,value) in self.state.items()}\n", - "\n", - " # Calculate outputs\n", - " z_est = self.m.output(self.state)\n", - " z_est2 = self.m.output(x2)\n", - "\n", - " # Now score them each by how close they are to the measured z\n", - " z_est_score = sum([abs(z_est[key] - z[key]) for key in self.m.outputs])\n", - " z_est2_score = sum([abs(z_est2[key] - z[key]) for key in self.m.outputs])\n", - "\n", - " # Now choose the closer one\n", - " if z_est2_score < z_est_score: \n", - " self.state = x2\n", - "\n", - " @property\n", - " def x(self):\n", - " return ScalarData(self.state)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Great, now let's try it out using the model from earlier. with an initial state of all 0s. It should slowly converge towards the correct state" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "x0 = {key: 0 for key in m.states}\n", - "se = BlindlyStumbleEstimator(m, x0)\n", - "\n", - "for i in range(25):\n", - " u = m.InputContainer({'i': 0})\n", - " z = m.OutputContainer({'t': 18.95, 'v': 4.183})\n", - " se.estimate(i, u, z)\n", - " print(se.x.mean)\n", - " print(\"\\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Extending - Adding a new Predictor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Like the example above with StateEstimators, Predictors can be extended by subclassing the Predictor class. Copy `predictor_template.py` as a starting point." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conclusion\n", - "This is just the basics, there's much more to learn. Please see the documentation at and the examples in the `examples/` folder for more details on how to use the package, including:\n", - "* `examples.basic_example` : A basic Example using prog_algs for Prognostics \n", - "* `examples.basic_example_battery` : A basic Example using prog_algs for Prognostics, using the more complex battery model\n", - "* `examples.eol_event` : An example where a model has multiple events, but the user is only interested in predicting the time when the first event occurs (whatever it is).\n", - "* `examples.measurement_eqn_example` : An example where not every output is measured or measurements are not in the same format as outputs, so a measurement equation is defined to translate between outputs and what's measured. \n", - "* `examples.new_state_estimator_example` : An example of extending StateEstimator to create a new state estimator class\n", - "* `examples.playback` : A full example performing prognostics using playback data.\n", - "* `examples.predict_specific_event` : An example where the model has multiple events, but the user is only interested in predicting a specific event (or events).\n", - "\n", - "Thank you for trying out this tutorial. Open an issue on github () or email Chris Teubert (christopher.a.teubert@nasa.gov) with any questions or issues." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.8.9 64-bit", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.9" - }, - "metadata": { - "interpreter": { - "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2" - } - }, - "orig_nbformat": 2, - "vscode": { - "interpreter": { - "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}