Repository: nasa/prog_algs (language: python)

Issue:
t -> t0
propogate change from prog_models


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index a17f772f..8059c5e0 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8]
+        python-version: [3.6, 3.7, 3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v2
@@ -22,10 +22,9 @@ jobs:
       uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
-    - name: Install dependencies
+    - name: Install prog_models
       run: |
-        python -m pip install --upgrade pip
-        python -m pip install -r requirements.txt
+        python -m pip install git+https://github.com/nasa/prog_models.git@dev
     - name: Lint with flake8
       run: |
         python -m pip install flake8
@@ -35,27 +34,47 @@ jobs:
         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: 
+      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' 
+        languages: 'python'
     - name: Perform CodeQL Analysis
       uses: github/codeql-action/analyze@v1
-  test:
+  test-prog_models-dev:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 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: Install
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install -e .
+    - name: Run tests
+      run: python -m tests
+  test-prog_models-released:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8]
+        python-version: [3.6, 3.7, 3.8, 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 
+    - name: Install
       run: |
         python -m pip install --upgrade pip
         python -m pip install -e .
diff --git a/README.md b/README.md
index 74fd5ebb..f8a97681 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,13 @@
 # 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/HEAD?tutorial.ipynb)
 
 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`
 
@@ -10,33 +16,35 @@ See documentation [here](https://nasa.github.io/prog_algs/)
 
 ## Repository Directory Structure 
 
-`prog_algs/` - The prognostics algorithm python package<br />
+`src/prog_algs/` - The prognostics algorithm python package<br />
+&nbsp;&nbsp; |- `metrics/` - Tools for analyzing the results of state estimation and prognostics
 &nbsp;&nbsp; |- `predictors/` - Algorithms for performing the prediction step of model-based prognostics<br />
-&nbsp;&nbsp; |- `samplers/` - Standard tools for performing state sampling<br />
 &nbsp;&nbsp; |- `state_estimators/` - Algorithms for performing the state estimation step of model-based prognostics<br />
-`benchmarking_example` - An example using metrics for benchmarking<br />
-`example.py` - An example python script using prog_algs<br />
+&nbsp;&nbsp; |- `uncertain_data/` - Tools for storing and manipulating data with uncertainty<br />
+&nbsp;&nbsp; |- `visualize/` - Tools for visualizing the results of state estimation and prognostics<br />
+`docs/` - Project documentation (see also [github.io](https://nasa.github.io/prog_algs/))<br />
+`sphinx_config/` - Configuration for automatic documentation generation<br />
+`examples/` - Example Python scripts using prog_algs<br />
+`tests/` - Tests for prog_models<br />
 `README.md` - The readme (this file)<br />
-`requirements.txt` - python library dependiencies required to be met to use this package. Install using `pip install -r requirements.txt`<br />
-`tutorial.ipynb` - Tutorial (Juypter Notebook)
 
 ## Citing this repository
 Use the following to cite this repository:
 
 ```
-@misc{2020_nasa_prog_algs,
+@misc{2021_nasa_prog_algs,
     author    = {Christopher Teubert and Matteo Corbetta and Chetan Kulkarni},
     title     = {Prognostics Algorithm Python Package},
-    month     = Apr,
+    month     = Nov,
     year      = 2021,
-    version   = {0.3.0},
+    version   = {1.2.0},
     url       = {https://github.com/nasa/prog_algs}
     }
 ```
 
 The corresponding reference should look like this:
 
-C. Teubert, M. Corbetta, C. Kulkarni, Prognostics Algorithm Python Package, v0.3.0, Apr. 2021. URL https://github.com/nasa/prog_algs.
+C. Teubert, M. Corbetta, C. Kulkarni, Prognostics Algorithm Python Package, v1.2.0, Nov. 2021. URL https://github.com/nasa/prog_algs.
 
 ## 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
diff --git a/docs/.buildinfo b/docs/.buildinfo
index 1a68ffe6..1da3263c 100644
--- a/docs/.buildinfo
+++ b/docs/.buildinfo
@@ -1,4 +1,4 @@
 # 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: afb74eafadbfe4548fdbc6aeeab7e32b
+config: 7665a6611f13edf5dca89d369f0cdaec
 tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/docs/.doctrees/dev_guide.doctree b/docs/.doctrees/dev_guide.doctree
index f1b8fdd5..63151ae9 100644
Binary files a/docs/.doctrees/dev_guide.doctree and b/docs/.doctrees/dev_guide.doctree differ
diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle
index cf9679ba..bae1c970 100644
Binary files a/docs/.doctrees/environment.pickle and b/docs/.doctrees/environment.pickle differ
diff --git a/docs/.doctrees/exceptions.doctree b/docs/.doctrees/exceptions.doctree
index afb1a37d..a86d22e7 100644
Binary files a/docs/.doctrees/exceptions.doctree and b/docs/.doctrees/exceptions.doctree differ
diff --git a/docs/.doctrees/getting_started.doctree b/docs/.doctrees/getting_started.doctree
index 46c3ecf1..a3b75b1b 100644
Binary files a/docs/.doctrees/getting_started.doctree and b/docs/.doctrees/getting_started.doctree differ
diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree
index b770e16e..bb7886b9 100644
Binary files a/docs/.doctrees/index.doctree and b/docs/.doctrees/index.doctree differ
diff --git a/docs/.doctrees/metrics.doctree b/docs/.doctrees/metrics.doctree
index 94f0a90d..258f721d 100644
Binary files a/docs/.doctrees/metrics.doctree and b/docs/.doctrees/metrics.doctree differ
diff --git a/docs/.doctrees/prediction.doctree b/docs/.doctrees/prediction.doctree
new file mode 100644
index 00000000..17be829e
Binary files /dev/null and b/docs/.doctrees/prediction.doctree differ
diff --git a/docs/.doctrees/predictors.doctree b/docs/.doctrees/predictors.doctree
index 8668312d..a4dd1b27 100644
Binary files a/docs/.doctrees/predictors.doctree and b/docs/.doctrees/predictors.doctree differ
diff --git a/docs/.doctrees/state_estimators.doctree b/docs/.doctrees/state_estimators.doctree
index ba7cb506..c2919080 100644
Binary files a/docs/.doctrees/state_estimators.doctree and b/docs/.doctrees/state_estimators.doctree differ
diff --git a/docs/.doctrees/uncertain_data.doctree b/docs/.doctrees/uncertain_data.doctree
index a6ff956e..1862e740 100644
Binary files a/docs/.doctrees/uncertain_data.doctree and b/docs/.doctrees/uncertain_data.doctree differ
diff --git a/docs/.doctrees/visualize.doctree b/docs/.doctrees/visualize.doctree
index b2799580..469e5a46 100644
Binary files a/docs/.doctrees/visualize.doctree and b/docs/.doctrees/visualize.doctree differ
diff --git a/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb b/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb
new file mode 100644
index 00000000..1cd60d78
--- /dev/null
+++ b/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb
@@ -0,0 +1,361 @@
+{
+ "metadata": {
+  "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.12"
+  },
+  "orig_nbformat": 2,
+  "kernelspec": {
+   "name": "python3",
+   "display_name": "Python 3.8.12 64-bit"
+  },
+  "metadata": {
+   "interpreter": {
+    "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2"
+   }
+  },
+  "interpreter": {
+   "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2,
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "source": [
+    "# Welcome to the Prognostics Algorithms Package Tutorial"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "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 occurance of events. \n",
+    "\n",
+    "The `prog_algs` package has the following structure\n",
+    "* `prog_algs/state_estimators/` - Tools for performing state estimation\n",
+    "* `prog_algs/predictors/` - Tools for performing prediction\n",
+    "* `prog_algs/metrics/` - Tools for analyzing the performance of prognostics algorithms\n",
+    "* `prog_algs/uncertain_data/` - Tools for representing data with uncertainty\n",
+    "* `prog_algs/visualize/` - Tools for visualizing results\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 (`docs/`), and this tutorial (`tutorial.ipynb`).\n",
+    "\n",
+    "Before you start, install `prog_algs` using pip:\n",
+    "      `pip install prog_algs`\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 lets get started with some examples"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## Prediction Example \n",
+    "First thing to do is to import the prog_algs and the model you intend to use"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "from prog_models.models.battery_circuit import BatteryCircuit\n",
+    "from prog_algs import *"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "Next, prepare the model like you did for simulation"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "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 {'i': i}\n",
+    "\n",
+    "batt = BatteryCircuit()"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "Now that we have our model ready, we can construct our state estimator:"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "The filter estimate function can then be run when there is updated data. Each iteration it will produce a new estimate of the system state (with uncertainty). For example:"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "import matplotlib.pyplot as plt  # For plotting\n",
+    "\n",
+    "print(\"Prior State:\", filt.x.mean)\n",
+    "print('\\tSOC: ', batt.event_state(filt.x.mean)['EOD'])\n",
+    "fig = filt.x.plot_scatter(label='prior')\n",
+    "example_measurements = {'t': 32.2, 'v': 3.915}\n",
+    "t = 0.1\n",
+    "filt.estimate(t, future_loading(t), example_measurements)\n",
+    "print(\"Posterior State:\", filt.x.mean)\n",
+    "print('\\tSOC: ', batt.event_state(filt.x.mean)['EOD'])\n",
+    "filt.x.plot_scatter(fig= fig, label='posterior')\n",
+    "\n",
+    "fig.show()"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "That's the state estimation step- now lets prepare for prediction. "
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "# Create predictor \n",
+    "mc = predictors.MonteCarlo(batt)\n",
+    "\n",
+    "# Generate Samples\n",
+    "samples = filt.x.sample(20)"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "Now lets 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"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "(times, inputs, states, outputs, event_states, toe) = mc.predict(samples, future_loading, dt=0.025)\n"
+   ],
+   "outputs": [],
+   "metadata": {
+    "tags": [
+     "outputPrepend"
+    ]
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "The prediction returns the data corresponding to each sample and each save point. The format is `[sample #][time]` so that `states[m][n]` corresponds to the state for sample `m` at time `times[m][n]`\n",
+    "\n",
+    "Next, let's use the metrics package to analyse the results"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "print(\"\\nEOD Predictions (s):\")\n",
+    "from prog_algs.metrics import samples as metrics \n",
+    "print('\\tPercentage between 3005.2 and 3005.6: ', metrics.percentage_in_bounds(toe, [3005.2, 3005.6])*100.0, '%')\n",
+    "print('\\tAssuming ground truth 3005.25: ', metrics.toe_metrics(toe, 3005.25))\n",
+    "print('\\tP(Success) if mission ends at 3005.25: ', metrics.prob_success(toe, 3005.25))"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "You can also visualize the results in a variety of different ways. For example, state transition"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "fig = states.snapshot(0).plot_scatter(label = \"t={}\".format(int(times[0][0])))\n",
+    "states.snapshot(10).plot_scatter(fig = fig, label = \"t={}\".format(int(times[0][10])))\n",
+    "states.snapshot(50).plot_scatter(fig = fig, label = \"t={}\".format(int(times[0][50])))\n",
+    "\n",
+    "states.snapshot(-1).plot_scatter(fig = fig, label = \"t={}\".format(int(times[0][-1])))\n",
+    "fig.show()"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## Extending - Adding a new state estimator\n",
+    "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"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "from prog_algs.state_estimators.state_estimator import StateEstimator"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "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"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "from prog_algs.uncertain_data import ScalarData"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "Now we construct the class, implementing the functions of the state estimator template (`state_estimator_template.py`)"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "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"
+   ],
+   "outputs": [],
+   "metadata": {}
+  },
+  {
+   "cell_type": "markdown",
+   "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"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [
+    "x0 = {key: 0 for key in batt.states}\n",
+    "se = BlindlyStumbleEstimator(batt, 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}\")"
+   ],
+   "outputs": [],
+   "metadata": {
+    "tags": []
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "## Conclusion\n",
+    "Thank you for trying out this tutorial. See the examples in the `examples/` folder for more details on how to use the package. Any questions can be directed to Chris Teubert (christopher.a.teubert@nasa.gov)"
+   ],
+   "metadata": {}
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "source": [],
+   "outputs": [],
+   "metadata": {}
+  }
+ ]
+}
\ No newline at end of file
diff --git a/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py b/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py
new file mode 100644
index 00000000..eaefc6fd
--- /dev/null
+++ b/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py
@@ -0,0 +1,65 @@
+# 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.1
+    (_, _, _, _, _, toe) = mc.predict(samples, future_loading, dt=STEP_SIZE)
+    print("\nPredicted Time of Event:")
+    pprint(toe.metrics())
+    toe.plot_hist(keys = 'impact')
+    toe.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/847c5bdc6f78e586d5a56a90de4f1d95/playback.py b/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py
index bc09c91e..d2972d6d 100644
--- a/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py
+++ b/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py
@@ -1,42 +1,73 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
 """
-Example using playback data. Builds a Battery Model, runs prognostics using the playback data. 
+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
 """
 
-# Constants
-num_samples = 10
-time_step = 0.1
-prediction_update_freq = 5 # Number of steps between prediction update
+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
 
-def run_example():
-    from prog_models.models import BatteryCircuit
-    from prog_algs.metrics import samples as metrics
-    from prog_algs import state_estimators, predictors
+from prog_algs.state_estimators import ParticleFilter as StateEstimator
+# VVV Uncomment this to use UnscentedKalmanFilter instead VVV
+# from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator
+
+from prog_algs.predictors import MonteCarlo, ToEPredictionProfile
+from prog_algs.metrics import samples as metrics
 
-    import csv
-    import matplotlib.pyplot as plt 
+import csv
+import matplotlib.pyplot as plt 
+
+# Constants
+NUM_SAMPLES = 10
+TIME_STEP = 1
+PREDICTION_UPDATE_FREQ = 5 # Number of steps between prediction update
 
+def run_example():
     # Setup Model
-    batt = BatteryCircuit()
+    batt = Battery()
 
     # Setup State Estimation
-    filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])
+    filt = StateEstimator(batt, batt.parameters['x0'])
 
     # Setup Prediction
     def future_loading(t, x=None):
         return {'i': 2.35}
-    mc = predictors.MonteCarlo(batt)
+    mc = MonteCarlo(batt)
 
-    # Prepare Plot
+    # Prepare SOC Plot
     fig, ax = plt.subplots()
     line, = ax.plot([], [])
     ax.grid()
+    ax.set_ylim(-0.05, 1.05)
+    ax.set_xlabel('Time (s)')
+    ax.set_ylabel('SOC')
     xdata, ydata = [], []
-    plt.show()
+    fig.show()
+
+    # Prepare RUL Plot
+    rul_fig, rulax = plt.subplots()
+    rul_line, = rulax.plot([], [])
+    rulax.grid()
+    rulax.set_xlabel('Time (s)')
+    rulax.set_ylabel('RUL (s)')
+    rul_x, rul_y = [], []
+    rul_fig.show()
 
     # Run Playback
     step = 0
+    profile = ToEPredictionProfile()
     with open('examples/data_const_load.csv', 'r') as f:
         reader = csv.reader(f)
         next(reader) # Skip header
@@ -59,15 +90,27 @@ def future_loading(t, x=None):
 
             if t >= xmax:
                 ax.set_xlim(xmin, 2*xmax)
-                ax.figure.canvas.draw()
+                rulax.set_xlim(xmin, 2*xmax)
             line.set_data(xdata, ydata)
+            fig.canvas.draw()
+
+            # Prediction Step (every PREDICTION_UPDATE_FREQ steps)
+            if (step%PREDICTION_UPDATE_FREQ == 0):
+                (times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, n_samples=NUM_SAMPLES, dt=TIME_STEP)
+                m = toe.metrics()
+                print('  - ToE: {} (sigma: {})'.format(m['EOD']['mean'], m['EOD']['std']))
+
+                # Update Plot
+                rul_x.append(t)
+                rul_y.append(m['EOD']['mean']-t)
+                ymin, ymax = rulax.get_ylim()
+                if m['EOD']['mean']-t > ymax:
+                    rulax.set_ylim(0, (m['EOD']['mean']-t)*1.1)
+                rul_line.set_data(rul_x, rul_y)
+                rul_fig.canvas.draw()
+                profile.add_prediction(t, toe)
 
-            # Prediction Step (every prediction_update_freq steps)
-            if (step%prediction_update_freq == 0):
-                samples = filt.x.sample(10)
-                (times, inputs, states, outputs, event_states, eol) = mc.predict(samples, future_loading, dt=1.0)
-                m = metrics.eol_metrics(eol)
-                print('  - RUL: {} (sigma: {})'.format(m['mean'], m['std']))
+    input('Press any key to exit')
 
 # This allows the module to be executed directly 
 if __name__ == '__main__':
diff --git a/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py b/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py
new file mode 100644
index 00000000..4a759da3
--- /dev/null
+++ b/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py
@@ -0,0 +1,69 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from prog_models.models.battery_electrochem 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)
+    times, inputs, states, outputs, event_states = 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(times)*.25,len(times)*.45,1)))# Random current time
+
+    for i in range(n_times):
+        if i == 0:
+            batt_pf = state_estimators.ParticleFilter(model = battery,  x0 = states[i], num_particles = 250)
+        else:
+            batt_pf.estimate(t = times[i], u = inputs[i], z = outputs[i])
+        all_particles.append(batt_pf.particles)
+
+    # Mean of the particles
+    alpha = 0.05
+    states_vsn = [s['tb'] for s in 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(times[:n_times],[p['tb'] for p in pf_mean],linewidth=0.7,color="blue")
+    plt.plot(times[:n_times], states_vsn[:n_times],"--",linewidth=0.7,color="red")
+    plt.fill_between(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
index c61048c6..f87dca7d 100644
--- a/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py
+++ b/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py
@@ -1,10 +1,27 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
-from prog_models.models import BatteryCircuit
+"""
+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 BatteryElectroChem as Battery
+
 from prog_algs import *
 
 def run_example():
-    ## Setup
+    # Step 1: Setup model & future loading
     def future_loading(t, x = None):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -18,33 +35,89 @@ def future_loading(t, x = None):
         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'])
-    filt = state_estimators.ParticleFilter(batt, batt.parameters['x0'])
+    # 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
-    load = future_loading(t)
-    filt.estimate(t, load, {'t': 32.2, 'v': 3.915})
-    print("Posterior State:", filt.x.mean)
+    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'])
-    ## Prediction - Predict EOD given current state
-    # Setup prediction
+    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)
-    if isinstance(filt, state_estimators.UnscentedKalmanFilter):
-        samples = filt.x.sample(20)
-    else: # Particle Filter
-        samples = filt.x.raw_samples()
-    # Predict with a step size of 0.1
-    (times, inputs, states, outputs, event_states, eol) = mc.predict(samples, future_loading, dt=0.1)
-    ## Print Metrics
-    print("\nEOD Predictions (s):")
-    from prog_algs.metrics import samples as metrics 
-    print('\tPercentage between 3005.2 and 3005.6: ', metrics.percentage_in_bounds(eol, [3005.2, 3005.6])*100.0, '%')
-    print('\tAssuming ground truth 3002.25: ', metrics.eol_metrics(eol, 3005.25))
-    print('\tP(Success) if mission ends at 3002.25: ', metrics.prob_success(eol, 3005.25))
+
+    # Step 3b: Perform a prediction
+    NUM_SAMPLES = 5
+    STEP_SIZE = 0.1
+    (times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE)
+    print('ToE', toe.mean)
+
+    # Step 3c: Analyze the results
+
+    # Note: The results of a sample-based prediction can be accessed by sample, e.g.,
+    states_sample_1 = 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 = 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 = toe.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('\Portion between 3005.2 and 3005.6: ', toe.percentage_in_bounds([3005.2, 3005.6]))
+    print('\tAssuming ground truth 3002.25: ', toe.metrics(ground_truth=3005.25))
+    print('\tP(Success) if mission ends at 3002.25: ', prob_success(toe, 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 = states.snapshot(0).plot_scatter(label = "t={} s".format(int(times[0])))  # 0
+    quarter_index = int(len(times)/4)
+    states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(times[quarter_index])))  # 25%
+    states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(times[quarter_index*2])))  # 50%
+    states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(times[quarter_index*3])))  # 75%
+    states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(times[-1])))  # 100%
+
+    toe.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()
\ No newline at end of file
+    run_example()
diff --git a/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py b/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py
new file mode 100644
index 00000000..fb25ecb4
--- /dev/null
+++ b/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py
@@ -0,0 +1,54 @@
+# 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., 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
+    (times, inputs, states, outputs, event_states, toe) = pred.predict(state, future_loading, events=['EOL'], dt=0.025)
+
+    # Step 5: Plot results
+    toe.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
new file mode 100644
index 00000000..d56086a6
--- /dev/null
+++ b/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py
@@ -0,0 +1,80 @@
+# 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 measurement_eqn to remove the other output value.  
+        Estimation of the current state is performed at various time steps, using the defined state_estimator. The state_estimator takes the measurement_eqn as input, to account for the missing output information. 
+
+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: 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 = Battery()
+    x0 = batt.parameters['x0']
+
+    # Step 2: Define Measurement Equation
+    # This example is a little different. Here we are saying that we dont have the complete output. Instead we're only measuring voltage. We use the measurement eqn to remove temp
+    def measure(x):
+        output = batt.output(x)
+        del output['t'] # Not measuring temperature
+        return output
+
+    # Step 3: Setup particle filter to use measurement eqn
+    filt = state_estimators.ParticleFilter(batt, x0, measurement_eqn = measure)
+
+    # 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
+    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 measurement_eqn 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
+
+# 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
new file mode 100644
index 00000000..e2f10dbe
--- /dev/null
+++ b/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py
@@ -0,0 +1,43 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from prog_models.models.thrown_object import ThrownObject
+from prog_algs import *
+from prog_algs.uncertain_data import UnweightedSamples
+
+"""
+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.
+"""
+
+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
+    (times, inputs, states, outputs, event_states, toe) = pred.predict(filt.x, future_loading, dt=0.1, save_freq= 1, events=['impact'])
+
+    # Print Results
+    for i, time in enumerate(times):
+        print('\nt = {}'.format(time))
+        print('\tu = {}'.format(inputs.snapshot(i).mean))
+        print('\tx = {}'.format(states.snapshot(i).mean))
+        print('\tz = {}'.format(outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(event_states.snapshot(i).mean))
+
+    # Note only impact event is shown here
+    print('\nToE:', toe.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
index 329c8922..73bddf8a 100644
--- a/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py
+++ b/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py
@@ -1,10 +1,26 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from prog_models.models import BatteryCircuit
+"""
+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():
-    ## Setup
+    # Step 1: Setup Model and Future Loading
     def future_loading(t, x={}):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -19,34 +35,38 @@ def future_loading(t, x={}):
             i = 3
         return {'i': i}
 
-    batt = BatteryCircuit()
+    batt = Battery()   
 
-    ##  Setup State Estimation 
-    filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])
+    # Step 2: Setup Predictor 
+    pred = predictors.MonteCarlo(batt, dt= 0.05)
 
-    ## Setup Prediction
-    mc = 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})
 
-    # Playback 
+    # 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 
-    print('Run 1 (2 samples)')
-    (t, u, x, z, es, eol) = run_prog_playback(filt, mc, future_loading, [(0.1, {'t': 32.2, 'v': 3.915}), (0.1, {'t': 32.3, 'v': 3.91})], num_samples= 2)
-    print('MSE: ', metrics.mean_square_error(eol, 3005.4))
-    prediction_times = [0.1, 0.2]
-    print('Alpha-lambda met: ', metrics.alpha_lambda(prediction_times, eol, 3005.4, 0.2, 1e-4, 0.65))
-
-    print('Run 2 (5 samples)')
-    (t, u, x, z, es, eol) = run_prog_playback(filt, mc, future_loading, [(0.1, {'t': 32.2, 'v': 3.915}), (0.1, {'t': 32.3, 'v': 3.91})], num_samples= 5)
-    print('MSE: ', metrics.mean_square_error(eol, 3005.4))
-    prediction_times = [0.1, 0.2]
-    print('Alpha-lambda met: ', metrics.alpha_lambda(prediction_times, eol, 3005.4, 0.2, 1e-4, 0.65))
-
-    print('Run 3 (10 samples)')
-    (t, u, x, z, es, eol) = run_prog_playback(filt, mc, future_loading, [(0.1, {'t': 32.2, 'v': 3.915}), (0.1, {'t': 32.3, 'v': 3.91})], num_samples= 10)
-    print('MSE: ', metrics.mean_square_error(eol, 3005.4))
-    prediction_times = [0.1, 0.2]
-    print('Alpha-lambda met: ', metrics.alpha_lambda(prediction_times, eol, 3005.4, 0.2, 1e-4, 0.65))
+
+    # 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()
+        (t, u, x, z, es, toe) = pred.predict(state_estimator.x, future_loading, n_samples = sample_count)
+        toe = toe.key("EOD")  # Looking at EOD event
+        end = time.perf_counter()
+        print('\tMSE:     {:4.2f}s'.format(metrics.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()
\ No newline at end of file
+    run_example()
diff --git a/docs/_images/package_structure.png b/docs/_images/package_structure.png
new file mode 100644
index 00000000..e5b56af1
Binary files /dev/null and b/docs/_images/package_structure.png differ
diff --git a/docs/_sources/dev_guide.rst.txt b/docs/_sources/dev_guide.rst.txt
index 6fb0c301..0e1e3a34 100644
--- a/docs/_sources/dev_guide.rst.txt
+++ b/docs/_sources/dev_guide.rst.txt
@@ -6,13 +6,33 @@ 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 <https://nvie.com/posts/a-successful-git-branching-model/>`_. Release branches are not required. Details specific to each branch are described below. 
+Our project is following the git strategy described `here <https://nvie.com/posts/a-successful-git-branching-model/>`_. 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 commit on the dev branch should be functional. All unit tests must function before commiting to dev or merging another branch. 
+`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.
 
@@ -28,6 +48,8 @@ Release Checklist
 
 NPR 7150
 --------
+This section describes this project's compliance with the NASA's NPR 7150 requirements, documented `here <https://nodis3.gsfc.nasa.gov/displayDir.cfm?t=NPR&c=7150&s=2B>`_.
+
 * Software Classification: Class-E (Research Software)
 * Safety Criticality: Not Safety Critical 
 
diff --git a/docs/_sources/getting_started.rst.txt b/docs/_sources/getting_started.rst.txt
index aef28158..2d99a4d6 100644
--- a/docs/_sources/getting_started.rst.txt
+++ b/docs/_sources/getting_started.rst.txt
@@ -1,48 +1,133 @@
 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 for the Prognostics Models Library to perform research in prognostics with prognostics systems. 
+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 for the Prognostics Models Library to perform research in prognostics with prognostics systems.
 
-A few definitions:
+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`), the version on PyPi 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 <https://github.com/nasa/prog_algs>`__. This isn't recommended for most users as this version may be unstable. To use this version, use the following commands:
+
+.. code-block:: console
 
-* **events**: some state that can be predicted (e.g., system failure). An event has either occured or not. 
+    $ git clone https://github.com/nasa/prog_algs
+    $ cd prog_algs
+    $ git checkout dev 
+    $ pip install -e .
 
-* **event state**: progress towards event occuring. Defined as a number where an event state of 0 indicates the event has occured and 1 indicates no progress towards the event (i.e., fully healthy operation for a failure event). For gradually occuring 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"
+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)
 
+* **observables**: 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**: stochastic process representing uncertainty in the model transition. 
 
 * **measurement noise**: stochastic process representing uncertainty in the measurement process; e.g., sensor sensitivity, sensor misalignements, environmental effects 
 
-Installing Dependencies
------------------------
-You can install dependencies using the included `requirements.txt` file. This file enumerates all the dependencies of this package. Use the following command to install dependencies:
-    `pip install -r requirements.txt`
+The structure of the packages is illustrated below:
+
+.. image:: images/package_structure.png
+
+Prognostics is performed using `State Estimators <state_estimators.html>`__ and `Predictors <predictors.html>`__. State Estimators are resposible for estimating the current state of the modeled system using sensor data and a prognostics model (see: `prog_models package <https://github.com/nasa/prog_models>`__). The state estimator then produces an estimate of the system state with uncertainty in the form of an `uncertain data object <uncertain_data.html>`__. This state estimate is used by the predictor to predict when events will occur (Time of Event, ToE - returned as an `uncertain data object <uncertain_data.html>`__), 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) <https://nasa.github.io/prog_models/sim_result.html>`__ : 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 <uncertain_data.html>`__ : 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.html#id1>`__ : 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 <prediction.html#toe-prediction-profile>`__ : 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 
 ----
-See the examples for examples of use. Run examples using the command `python -m examples.[Example name]` command (e.g., `python -m examples.sim_example`). The examples are summarized below:
+The best way to learn how to use `prog_algs` is through the `tutorial <https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb>`__. 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.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
+    |
 
 Extending
 ---------
-New State Estimators and Predictors are created by extending the :class:`prog_algs.state_estimators.state_estimator.StateEstimator` and :class:`prog_algs.predictors.predictor.Predictor` class, respectively. 
+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.
+
+Updates in v1.1
+---------------
+
+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 event (e.g., toe.final_state['event1'])
 
-See :download:`examples.new_state_estimator_example <../examples/new_state_estimator_example.py>` for an example of this approach.
\ No newline at end of file
+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/index.rst.txt b/docs/_sources/index.rst.txt
index e5981f3a..4cd8de20 100644
--- a/docs/_sources/index.rst.txt
+++ b/docs/_sources/index.rst.txt
@@ -1,40 +1,45 @@
 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 library 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 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 Models Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE).
+The Prognostics Algorithms Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE) and `Diagnostics & Prognostics Group <https://www.nasa.gov/content/diagnostics-prognostics>`__.
 
-If you are new to this package, see `<getting_started.html>`__.
+If you are new to this package, see `getting started <getting_started.html>`__.
 
 .. toctree::
    :maxdepth: 2
    
+   Tutorial <https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb>
    getting_started
    state_estimators
    predictors
    uncertain_data
+   prediction
    metrics
-   visualize
    exceptions
+   ProgModels <https://nasa.github.io/prog_models>
    dev_guide
+   GitHub <https://github.com/nasa/prog_algs>
 
 Citing this repository
 -----------------------
 Use the following to cite this repository:
 
-@misc{2020_nasa_prog_model,
-    author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
-    title     = {Prognostics Algorithms Python Package},
-    month     = Dec,
-    year      = 2020,
-    version   = {0.3.4},
-    url       = {https://github.com/nasa/prog_algs}
-    }
+@misc{2021_nasa_prog_algs,
+  | author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
+  | title     = {Prognostics Algorithms Python Package},
+  | month     = Aug,
+  | year      = 2021,
+  | version   = {1.2.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, v0.2.0, Dec. 2020. URL https://github.com/nasa/prog_algs.
+C. Teubert, C. Kulkarni, M. Corbetta Prognostics Algorithms Python Package, v1.2.0, Aug. 2021. URL https://github.com/nasa/prog_algs.
 
 Indices and tables
 -----------------------
diff --git a/docs/_sources/metrics.rst.txt b/docs/_sources/metrics.rst.txt
index 8825bd1b..04d59aa0 100644
--- a/docs/_sources/metrics.rst.txt
+++ b/docs/_sources/metrics.rst.txt
@@ -3,6 +3,7 @@ Metrics
 
 The metrics module includes tools for calculating various metrics for prognostics results. These metrics are a measurement of the performance of a prognostics algorithms. The analysis functions are described below:
 
-.. automodule:: prog_algs.metrics.samples
+.. automodule:: prog_algs.metrics
    :members:
    :inherited-members:
+   :exclude-members: calc_metrics, alpha_lambda
diff --git a/docs/_sources/prediction.rst.txt b/docs/_sources/prediction.rst.txt
new file mode 100644
index 00000000..34ad13d7
--- /dev/null
+++ b/docs/_sources/prediction.rst.txt
@@ -0,0 +1,27 @@
+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, observables, 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:
\ No newline at end of file
diff --git a/docs/_sources/predictors.rst.txt b/docs/_sources/predictors.rst.txt
index fbc2f20f..f91569b0 100644
--- a/docs/_sources/predictors.rst.txt
+++ b/docs/_sources/predictors.rst.txt
@@ -1,15 +1,41 @@
 Predictors
 =======================
 
-.. autoclass:: prog_algs.predictors.predictor.Predictor
-   :members:
-   :inherited-members:
+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 <https://github.com/nasa/prog_models>`__) to predict both future states (also outputs, observables, 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
 
-The predictor algorithms implemented in this package are described below:
+   >>> 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
+   >>>
+   >>> (t, u, x, z, es, toe) = pred.predict(first_state, future_loading, save_freq=1)
+   >>> toe.plot_hist(events='impact')  # Plot a histogram of when the impact event occurred
 
-MC Predictor
+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
 
-.. autoclass:: prog_algs.predictors.monte_carlo.MonteCarlo
+Unscented Transform Predictor
+*****************************
+.. autoclass:: prog_algs.predictors.UnscentedTransformPredictor
+
+Predictor Interface
+-----------------------
+.. autoclass:: prog_algs.predictors.Predictor
    :members:
    :inherited-members:
diff --git a/docs/_sources/state_estimators.rst.txt b/docs/_sources/state_estimators.rst.txt
index 7b183c7a..af7b1429 100644
--- a/docs/_sources/state_estimators.rst.txt
+++ b/docs/_sources/state_estimators.rst.txt
@@ -1,23 +1,40 @@
 State Estimators
 =======================
+The State Estimator uses sensor information and a Prognostics Model (see: `prog_models package <https://github.com/nasa/prog_models>`__) to produce an estimate of system state (which can be used to estimate outputs, event_states, and observables). This state estimate can either be used by itself or as input to a `Predictor <predictors.html>`__. A state estimator is typically run multiple times as new information is available.
 
-.. autoclass:: prog_algs.state_estimators.state_estimator.StateEstimator
-   :members:
-   :inherited-members:
+Here's an example of its use. In this example we use the unscented kalman filter state estimator and the ThrownObject model. 
 
-The state estimator algorithms implemented in this package are described below:
+.. code-block:: python
 
-UKF State Estimator
------------------------
+   >>> 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)
 
-.. autoclass:: prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter
-   :members:
-   :inherited-members:
+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`
 
+Unscented Kalman Filter
+*************************
+.. autoclass:: prog_algs.state_estimators.UnscentedKalmanFilter
 
-PF State Estimator
------------------------
+Particle Filter
+*************************
+.. autoclass:: prog_algs.state_estimators.ParticleFilter
 
-.. autoclass:: prog_algs.state_estimators.particle_filter.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
index e01bd17d..049b987c 100644
--- a/docs/_sources/uncertain_data.rst.txt
+++ b/docs/_sources/uncertain_data.rst.txt
@@ -1,20 +1,26 @@
 Uncertain Data
 =======================
 
-The `prog_algs.uncertain_data` package includes classes for representing data with uncertainty. 
+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:
-   :inherited-members:
+   :members: key
 
+Multivariate Normal Distribution
+********************************
 .. autoclass:: prog_algs.uncertain_data.MultivariateNormalDist
-   :members:
-   :inherited-members:
 
+Scalar Data (i.e., no uncertainty)
+**********************************
 .. autoclass:: prog_algs.uncertain_data.ScalarData
-   :members:
-   :inherited-members:
diff --git a/docs/_static/basic.css b/docs/_static/basic.css
index be19270e..912859b5 100644
--- a/docs/_static/basic.css
+++ b/docs/_static/basic.css
@@ -130,7 +130,7 @@ ul.search li a {
     font-weight: bold;
 }
 
-ul.search li div.context {
+ul.search li p.context {
     color: #888;
     margin: 2px 0 0 30px;
     text-align: left;
@@ -277,25 +277,25 @@ p.rubric {
     font-weight: bold;
 }
 
-img.align-left, .figure.align-left, object.align-left {
+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, object.align-right {
+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, object.align-center {
+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 {
+img.align-default, figure.align-default, .figure.align-default {
   display: block;
   margin-left: auto;
   margin-right: auto;
@@ -319,7 +319,8 @@ img.align-default, .figure.align-default {
 
 /* -- sidebars -------------------------------------------------------------- */
 
-div.sidebar {
+div.sidebar,
+aside.sidebar {
     margin: 0 0 0.5em 1em;
     border: 1px solid #ddb;
     padding: 7px;
@@ -377,12 +378,14 @@ div.body p.centered {
 /* -- 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 {
@@ -455,20 +458,22 @@ td > :last-child {
 
 /* -- figures --------------------------------------------------------------- */
 
-div.figure {
+div.figure, figure {
     margin: 0.5em;
     padding: 0.5em;
 }
 
-div.figure p.caption {
+div.figure p.caption, figcaption {
     padding: 0.3em;
 }
 
-div.figure p.caption span.caption-number {
+div.figure p.caption span.caption-number,
+figcaption span.caption-number {
     font-style: italic;
 }
 
-div.figure p.caption span.caption-text {
+div.figure p.caption span.caption-text,
+figcaption span.caption-text {
 }
 
 /* -- field list styles ----------------------------------------------------- */
@@ -503,6 +508,63 @@ 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 ----------------------------------------------------- */
 
@@ -629,14 +691,6 @@ dl.glossary dt {
     font-size: 1.1em;
 }
 
-.optional {
-    font-size: 1.3em;
-}
-
-.sig-paren {
-    font-size: larger;
-}
-
 .versionmodified {
     font-style: italic;
 }
@@ -765,8 +819,12 @@ div.code-block-caption code {
 
 table.highlighttable td.linenos,
 span.linenos,
-div.doctest > div.highlight span.gp {  /* gp: Generic.Prompt */
-    user-select: none;
+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 {
@@ -781,16 +839,6 @@ div.literal-block-wrapper {
     margin: 1em 0;
 }
 
-code.descname {
-    background-color: transparent;
-    font-weight: bold;
-    font-size: 1.2em;
-}
-
-code.descclassname {
-    background-color: transparent;
-}
-
 code.xref, a code {
     background-color: transparent;
     font-weight: bold;
diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js
index 61ac9d26..8cbf1b16 100644
--- a/docs/_static/doctools.js
+++ b/docs/_static/doctools.js
@@ -301,12 +301,14 @@ var Documentation = {
               window.location.href = prevHref;
               return false;
             }
+            break;
           case 39: // right
             var nextHref = $('link[rel="next"]').prop('href');
             if (nextHref) {
               window.location.href = nextHref;
               return false;
             }
+            break;
         }
       }
     });
diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js
index 2f08d25f..5be98c46 100644
--- a/docs/_static/documentation_options.js
+++ b/docs/_static/documentation_options.js
@@ -1,6 +1,6 @@
 var DOCUMENTATION_OPTIONS = {
     URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
-    VERSION: '1.0.0',
+    VERSION: '1.2.0',
     LANGUAGE: 'None',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',
diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js
index 1a90152e..58ff35c4 100644
--- a/docs/_static/searchtools.js
+++ b/docs/_static/searchtools.js
@@ -282,7 +282,10 @@ var Search = {
                   complete: function(jqxhr, textstatus) {
                     var data = jqxhr.responseText;
                     if (data !== '' && data !== undefined) {
-                      listItem.append(Search.makeSearchSummary(data, searchterms, hlterms));
+                      var summary = Search.makeSearchSummary(data, searchterms, hlterms);
+                      if (summary) {
+                        listItem.append(summary);
+                      }
                     }
                     Search.output.append(listItem);
                     setTimeout(function() {
@@ -498,6 +501,9 @@ var Search = {
    */
   makeSearchSummary : function(htmlText, keywords, hlwords) {
     var text = Search.htmlToText(htmlText);
+    if (text == "") {
+      return null;
+    }
     var textLower = text.toLowerCase();
     var start = 0;
     $.each(keywords, function() {
@@ -509,7 +515,7 @@ var Search = {
     var excerpt = ((start > 0) ? '...' : '') +
       $.trim(text.substr(start, 240)) +
       ((start + 240 - text.length) ? '...' : '');
-    var rv = $('<div class="context"></div>').text(excerpt);
+    var rv = $('<p class="context"></p>').text(excerpt);
     $.each(hlwords, function() {
       rv = rv.highlightText(this, 'highlighted');
     });
diff --git a/docs/_static/underscore-1.13.1.js b/docs/_static/underscore-1.13.1.js
new file mode 100644
index 00000000..ffd77af9
--- /dev/null
+++ b/docs/_static/underscore-1.13.1.js
@@ -0,0 +1,2042 @@
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define('underscore', factory) :
+  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () {
+    var current = global._;
+    var exports = global._ = factory();
+    exports.noConflict = function () { global._ = current; return exports; };
+  }()));
+}(this, (function () {
+  //     Underscore.js 1.13.1
+  //     https://underscorejs.org
+  //     (c) 2009-2021 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
+  //     Underscore may be freely distributed under the MIT license.
+
+  // Current version.
+  var VERSION = '1.13.1';
+
+  // Establish the root object, `window` (`self`) in the browser, `global`
+  // on the server, or `this` in some virtual machines. We use `self`
+  // instead of `window` for `WebWorker` support.
+  var root = typeof self == 'object' && self.self === self && self ||
+            typeof global == 'object' && global.global === global && global ||
+            Function('return this')() ||
+            {};
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype;
+  var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var push = ArrayProto.push,
+      slice = ArrayProto.slice,
+      toString = ObjProto.toString,
+      hasOwnProperty = ObjProto.hasOwnProperty;
+
+  // Modern feature detection.
+  var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined',
+      supportsDataView = typeof DataView !== 'undefined';
+
+  // All **ECMAScript 5+** native function implementations that we hope to use
+  // are declared here.
+  var nativeIsArray = Array.isArray,
+      nativeKeys = Object.keys,
+      nativeCreate = Object.create,
+      nativeIsView = supportsArrayBuffer && ArrayBuffer.isView;
+
+  // Create references to these builtin functions because we override them.
+  var _isNaN = isNaN,
+      _isFinite = isFinite;
+
+  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
+  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
+  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
+    'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
+
+  // The largest integer that can be represented exactly.
+  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+
+  // Some functions take a variable number of arguments, or a few expected
+  // arguments at the beginning and then a variable number of values to operate
+  // on. This helper accumulates all remaining arguments past the function’s
+  // argument length (or an explicit `startIndex`), into an array that becomes
+  // the last argument. Similar to ES6’s "rest parameter".
+  function restArguments(func, startIndex) {
+    startIndex = startIndex == null ? func.length - 1 : +startIndex;
+    return function() {
+      var length = Math.max(arguments.length - startIndex, 0),
+          rest = Array(length),
+          index = 0;
+      for (; index < length; index++) {
+        rest[index] = arguments[index + startIndex];
+      }
+      switch (startIndex) {
+        case 0: return func.call(this, rest);
+        case 1: return func.call(this, arguments[0], rest);
+        case 2: return func.call(this, arguments[0], arguments[1], rest);
+      }
+      var args = Array(startIndex + 1);
+      for (index = 0; index < startIndex; index++) {
+        args[index] = arguments[index];
+      }
+      args[startIndex] = rest;
+      return func.apply(this, args);
+    };
+  }
+
+  // Is a given variable an object?
+  function isObject(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  }
+
+  // Is a given value equal to null?
+  function isNull(obj) {
+    return obj === null;
+  }
+
+  // Is a given variable undefined?
+  function isUndefined(obj) {
+    return obj === void 0;
+  }
+
+  // Is a given value a boolean?
+  function isBoolean(obj) {
+    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+  }
+
+  // Is a given value a DOM element?
+  function isElement(obj) {
+    return !!(obj && obj.nodeType === 1);
+  }
+
+  // Internal function for creating a `toString`-based type tester.
+  function tagTester(name) {
+    var tag = '[object ' + name + ']';
+    return function(obj) {
+      return toString.call(obj) === tag;
+    };
+  }
+
+  var isString = tagTester('String');
+
+  var isNumber = tagTester('Number');
+
+  var isDate = tagTester('Date');
+
+  var isRegExp = tagTester('RegExp');
+
+  var isError = tagTester('Error');
+
+  var isSymbol = tagTester('Symbol');
+
+  var isArrayBuffer = tagTester('ArrayBuffer');
+
+  var isFunction = tagTester('Function');
+
+  // Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old
+  // v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
+  var nodelist = root.document && root.document.childNodes;
+  if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
+    isFunction = function(obj) {
+      return typeof obj == 'function' || false;
+    };
+  }
+
+  var isFunction$1 = isFunction;
+
+  var hasObjectTag = tagTester('Object');
+
+  // In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`.
+  // In IE 11, the most common among them, this problem also applies to
+  // `Map`, `WeakMap` and `Set`.
+  var hasStringTagBug = (
+        supportsDataView && hasObjectTag(new DataView(new ArrayBuffer(8)))
+      ),
+      isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map));
+
+  var isDataView = tagTester('DataView');
+
+  // In IE 10 - Edge 13, we need a different heuristic
+  // to determine whether an object is a `DataView`.
+  function ie10IsDataView(obj) {
+    return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer);
+  }
+
+  var isDataView$1 = (hasStringTagBug ? ie10IsDataView : isDataView);
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native `Array.isArray`.
+  var isArray = nativeIsArray || tagTester('Array');
+
+  // Internal function to check whether `key` is an own property name of `obj`.
+  function has$1(obj, key) {
+    return obj != null && hasOwnProperty.call(obj, key);
+  }
+
+  var isArguments = tagTester('Arguments');
+
+  // Define a fallback version of the method in browsers (ahem, IE < 9), where
+  // there isn't any inspectable "Arguments" type.
+  (function() {
+    if (!isArguments(arguments)) {
+      isArguments = function(obj) {
+        return has$1(obj, 'callee');
+      };
+    }
+  }());
+
+  var isArguments$1 = isArguments;
+
+  // Is a given object a finite number?
+  function isFinite$1(obj) {
+    return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj));
+  }
+
+  // Is the given value `NaN`?
+  function isNaN$1(obj) {
+    return isNumber(obj) && _isNaN(obj);
+  }
+
+  // Predicate-generating function. Often useful outside of Underscore.
+  function constant(value) {
+    return function() {
+      return value;
+    };
+  }
+
+  // Common internal logic for `isArrayLike` and `isBufferLike`.
+  function createSizePropertyCheck(getSizeProperty) {
+    return function(collection) {
+      var sizeProperty = getSizeProperty(collection);
+      return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX;
+    }
+  }
+
+  // Internal helper to generate a function to obtain property `key` from `obj`.
+  function shallowProperty(key) {
+    return function(obj) {
+      return obj == null ? void 0 : obj[key];
+    };
+  }
+
+  // Internal helper to obtain the `byteLength` property of an object.
+  var getByteLength = shallowProperty('byteLength');
+
+  // Internal helper to determine whether we should spend extensive checks against
+  // `ArrayBuffer` et al.
+  var isBufferLike = createSizePropertyCheck(getByteLength);
+
+  // Is a given value a typed array?
+  var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;
+  function isTypedArray(obj) {
+    // `ArrayBuffer.isView` is the most future-proof, so use it when available.
+    // Otherwise, fall back on the above regular expression.
+    return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) :
+                  isBufferLike(obj) && typedArrayPattern.test(toString.call(obj));
+  }
+
+  var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false);
+
+  // Internal helper to obtain the `length` property of an object.
+  var getLength = shallowProperty('length');
+
+  // Internal helper to create a simple lookup structure.
+  // `collectNonEnumProps` used to depend on `_.contains`, but this led to
+  // circular imports. `emulatedSet` is a one-off solution that only works for
+  // arrays of strings.
+  function emulatedSet(keys) {
+    var hash = {};
+    for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true;
+    return {
+      contains: function(key) { return hash[key]; },
+      push: function(key) {
+        hash[key] = true;
+        return keys.push(key);
+      }
+    };
+  }
+
+  // Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't
+  // be iterated by `for key in ...` and thus missed. Extends `keys` in place if
+  // needed.
+  function collectNonEnumProps(obj, keys) {
+    keys = emulatedSet(keys);
+    var nonEnumIdx = nonEnumerableProps.length;
+    var constructor = obj.constructor;
+    var proto = isFunction$1(constructor) && constructor.prototype || ObjProto;
+
+    // Constructor is a special case.
+    var prop = 'constructor';
+    if (has$1(obj, prop) && !keys.contains(prop)) keys.push(prop);
+
+    while (nonEnumIdx--) {
+      prop = nonEnumerableProps[nonEnumIdx];
+      if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) {
+        keys.push(prop);
+      }
+    }
+  }
+
+  // Retrieve the names of an object's own properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`.
+  function keys(obj) {
+    if (!isObject(obj)) return [];
+    if (nativeKeys) return nativeKeys(obj);
+    var keys = [];
+    for (var key in obj) if (has$1(obj, key)) keys.push(key);
+    // Ahem, IE < 9.
+    if (hasEnumBug) collectNonEnumProps(obj, keys);
+    return keys;
+  }
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  function isEmpty(obj) {
+    if (obj == null) return true;
+    // Skip the more expensive `toString`-based type checks if `obj` has no
+    // `.length`.
+    var length = getLength(obj);
+    if (typeof length == 'number' && (
+      isArray(obj) || isString(obj) || isArguments$1(obj)
+    )) return length === 0;
+    return getLength(keys(obj)) === 0;
+  }
+
+  // Returns whether an object has a given set of `key:value` pairs.
+  function isMatch(object, attrs) {
+    var _keys = keys(attrs), length = _keys.length;
+    if (object == null) return !length;
+    var obj = Object(object);
+    for (var i = 0; i < length; i++) {
+      var key = _keys[i];
+      if (attrs[key] !== obj[key] || !(key in obj)) return false;
+    }
+    return true;
+  }
+
+  // If Underscore is called as a function, it returns a wrapped object that can
+  // be used OO-style. This wrapper holds altered versions of all functions added
+  // through `_.mixin`. Wrapped objects may be chained.
+  function _$1(obj) {
+    if (obj instanceof _$1) return obj;
+    if (!(this instanceof _$1)) return new _$1(obj);
+    this._wrapped = obj;
+  }
+
+  _$1.VERSION = VERSION;
+
+  // Extracts the result from a wrapped and chained object.
+  _$1.prototype.value = function() {
+    return this._wrapped;
+  };
+
+  // Provide unwrapping proxies for some methods used in engine operations
+  // such as arithmetic and JSON stringification.
+  _$1.prototype.valueOf = _$1.prototype.toJSON = _$1.prototype.value;
+
+  _$1.prototype.toString = function() {
+    return String(this._wrapped);
+  };
+
+  // Internal function to wrap or shallow-copy an ArrayBuffer,
+  // typed array or DataView to a new view, reusing the buffer.
+  function toBufferView(bufferSource) {
+    return new Uint8Array(
+      bufferSource.buffer || bufferSource,
+      bufferSource.byteOffset || 0,
+      getByteLength(bufferSource)
+    );
+  }
+
+  // We use this string twice, so give it a name for minification.
+  var tagDataView = '[object DataView]';
+
+  // Internal recursive comparison function for `_.isEqual`.
+  function eq(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a === 1 / b;
+    // `null` or `undefined` only equal to itself (strict comparison).
+    if (a == null || b == null) return false;
+    // `NaN`s are equivalent, but non-reflexive.
+    if (a !== a) return b !== b;
+    // Exhaust primitive checks
+    var type = typeof a;
+    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
+    return deepEq(a, b, aStack, bStack);
+  }
+
+  // Internal recursive comparison function for `_.isEqual`.
+  function deepEq(a, b, aStack, bStack) {
+    // Unwrap any wrapped objects.
+    if (a instanceof _$1) a = a._wrapped;
+    if (b instanceof _$1) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className !== toString.call(b)) return false;
+    // Work around a bug in IE 10 - Edge 13.
+    if (hasStringTagBug && className == '[object Object]' && isDataView$1(a)) {
+      if (!isDataView$1(b)) return false;
+      className = tagDataView;
+    }
+    switch (className) {
+      // These types are compared by value.
+      case '[object RegExp]':
+        // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return '' + a === '' + b;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive.
+        // Object(NaN) is equivalent to NaN.
+        if (+a !== +a) return +b !== +b;
+        // An `egal` comparison is performed for other numeric values.
+        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a === +b;
+      case '[object Symbol]':
+        return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
+      case '[object ArrayBuffer]':
+      case tagDataView:
+        // Coerce to typed array so we can fall through.
+        return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
+    }
+
+    var areArrays = className === '[object Array]';
+    if (!areArrays && isTypedArray$1(a)) {
+        var byteLength = getByteLength(a);
+        if (byteLength !== getByteLength(b)) return false;
+        if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
+        areArrays = true;
+    }
+    if (!areArrays) {
+      if (typeof a != 'object' || typeof b != 'object') return false;
+
+      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+      // from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
+                               isFunction$1(bCtor) && bCtor instanceof bCtor)
+                          && ('constructor' in a && 'constructor' in b)) {
+        return false;
+      }
+    }
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+
+    // Initializing stack of traversed objects.
+    // It's done here since we only need them for objects and arrays comparison.
+    aStack = aStack || [];
+    bStack = bStack || [];
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] === a) return bStack[length] === b;
+    }
+
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+
+    // Recursively compare objects and arrays.
+    if (areArrays) {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      length = a.length;
+      if (length !== b.length) return false;
+      // Deep compare the contents, ignoring non-numeric properties.
+      while (length--) {
+        if (!eq(a[length], b[length], aStack, bStack)) return false;
+      }
+    } else {
+      // Deep compare objects.
+      var _keys = keys(a), key;
+      length = _keys.length;
+      // Ensure that both objects contain the same number of properties before comparing deep equality.
+      if (keys(b).length !== length) return false;
+      while (length--) {
+        // Deep compare each member
+        key = _keys[length];
+        if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return true;
+  }
+
+  // Perform a deep comparison to check if two objects are equal.
+  function isEqual(a, b) {
+    return eq(a, b);
+  }
+
+  // Retrieve all the enumerable property names of an object.
+  function allKeys(obj) {
+    if (!isObject(obj)) return [];
+    var keys = [];
+    for (var key in obj) keys.push(key);
+    // Ahem, IE < 9.
+    if (hasEnumBug) collectNonEnumProps(obj, keys);
+    return keys;
+  }
+
+  // Since the regular `Object.prototype.toString` type tests don't work for
+  // some types in IE 11, we use a fingerprinting heuristic instead, based
+  // on the methods. It's not great, but it's the best we got.
+  // The fingerprint method lists are defined below.
+  function ie11fingerprint(methods) {
+    var length = getLength(methods);
+    return function(obj) {
+      if (obj == null) return false;
+      // `Map`, `WeakMap` and `Set` have no enumerable keys.
+      var keys = allKeys(obj);
+      if (getLength(keys)) return false;
+      for (var i = 0; i < length; i++) {
+        if (!isFunction$1(obj[methods[i]])) return false;
+      }
+      // If we are testing against `WeakMap`, we need to ensure that
+      // `obj` doesn't have a `forEach` method in order to distinguish
+      // it from a regular `Map`.
+      return methods !== weakMapMethods || !isFunction$1(obj[forEachName]);
+    };
+  }
+
+  // In the interest of compact minification, we write
+  // each string in the fingerprints only once.
+  var forEachName = 'forEach',
+      hasName = 'has',
+      commonInit = ['clear', 'delete'],
+      mapTail = ['get', hasName, 'set'];
+
+  // `Map`, `WeakMap` and `Set` each have slightly different
+  // combinations of the above sublists.
+  var mapMethods = commonInit.concat(forEachName, mapTail),
+      weakMapMethods = commonInit.concat(mapTail),
+      setMethods = ['add'].concat(commonInit, forEachName, hasName);
+
+  var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map');
+
+  var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap');
+
+  var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set');
+
+  var isWeakSet = tagTester('WeakSet');
+
+  // Retrieve the values of an object's properties.
+  function values(obj) {
+    var _keys = keys(obj);
+    var length = _keys.length;
+    var values = Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[_keys[i]];
+    }
+    return values;
+  }
+
+  // Convert an object into a list of `[key, value]` pairs.
+  // The opposite of `_.object` with one argument.
+  function pairs(obj) {
+    var _keys = keys(obj);
+    var length = _keys.length;
+    var pairs = Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [_keys[i], obj[_keys[i]]];
+    }
+    return pairs;
+  }
+
+  // Invert the keys and values of an object. The values must be serializable.
+  function invert(obj) {
+    var result = {};
+    var _keys = keys(obj);
+    for (var i = 0, length = _keys.length; i < length; i++) {
+      result[obj[_keys[i]]] = _keys[i];
+    }
+    return result;
+  }
+
+  // Return a sorted list of the function names available on the object.
+  function functions(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (isFunction$1(obj[key])) names.push(key);
+    }
+    return names.sort();
+  }
+
+  // An internal function for creating assigner functions.
+  function createAssigner(keysFunc, defaults) {
+    return function(obj) {
+      var length = arguments.length;
+      if (defaults) obj = Object(obj);
+      if (length < 2 || obj == null) return obj;
+      for (var index = 1; index < length; index++) {
+        var source = arguments[index],
+            keys = keysFunc(source),
+            l = keys.length;
+        for (var i = 0; i < l; i++) {
+          var key = keys[i];
+          if (!defaults || obj[key] === void 0) obj[key] = source[key];
+        }
+      }
+      return obj;
+    };
+  }
+
+  // Extend a given object with all the properties in passed-in object(s).
+  var extend = createAssigner(allKeys);
+
+  // Assigns a given object with all the own properties in the passed-in
+  // object(s).
+  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+  var extendOwn = createAssigner(keys);
+
+  // Fill in a given object with default properties.
+  var defaults = createAssigner(allKeys, true);
+
+  // Create a naked function reference for surrogate-prototype-swapping.
+  function ctor() {
+    return function(){};
+  }
+
+  // An internal function for creating a new object that inherits from another.
+  function baseCreate(prototype) {
+    if (!isObject(prototype)) return {};
+    if (nativeCreate) return nativeCreate(prototype);
+    var Ctor = ctor();
+    Ctor.prototype = prototype;
+    var result = new Ctor;
+    Ctor.prototype = null;
+    return result;
+  }
+
+  // Creates an object that inherits from the given prototype object.
+  // If additional properties are provided then they will be added to the
+  // created object.
+  function create(prototype, props) {
+    var result = baseCreate(prototype);
+    if (props) extendOwn(result, props);
+    return result;
+  }
+
+  // Create a (shallow-cloned) duplicate of an object.
+  function clone(obj) {
+    if (!isObject(obj)) return obj;
+    return isArray(obj) ? obj.slice() : extend({}, obj);
+  }
+
+  // Invokes `interceptor` with the `obj` and then returns `obj`.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  function tap(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  }
+
+  // Normalize a (deep) property `path` to array.
+  // Like `_.iteratee`, this function can be customized.
+  function toPath$1(path) {
+    return isArray(path) ? path : [path];
+  }
+  _$1.toPath = toPath$1;
+
+  // Internal wrapper for `_.toPath` to enable minification.
+  // Similar to `cb` for `_.iteratee`.
+  function toPath(path) {
+    return _$1.toPath(path);
+  }
+
+  // Internal function to obtain a nested property in `obj` along `path`.
+  function deepGet(obj, path) {
+    var length = path.length;
+    for (var i = 0; i < length; i++) {
+      if (obj == null) return void 0;
+      obj = obj[path[i]];
+    }
+    return length ? obj : void 0;
+  }
+
+  // Get the value of the (deep) property on `path` from `object`.
+  // If any property in `path` does not exist or if the value is
+  // `undefined`, return `defaultValue` instead.
+  // The `path` is normalized through `_.toPath`.
+  function get(object, path, defaultValue) {
+    var value = deepGet(object, toPath(path));
+    return isUndefined(value) ? defaultValue : value;
+  }
+
+  // Shortcut function for checking if an object has a given property directly on
+  // itself (in other words, not on a prototype). Unlike the internal `has`
+  // function, this public version can also traverse nested properties.
+  function has(obj, path) {
+    path = toPath(path);
+    var length = path.length;
+    for (var i = 0; i < length; i++) {
+      var key = path[i];
+      if (!has$1(obj, key)) return false;
+      obj = obj[key];
+    }
+    return !!length;
+  }
+
+  // Keep the identity function around for default iteratees.
+  function identity(value) {
+    return value;
+  }
+
+  // Returns a predicate for checking whether an object has a given set of
+  // `key:value` pairs.
+  function matcher(attrs) {
+    attrs = extendOwn({}, attrs);
+    return function(obj) {
+      return isMatch(obj, attrs);
+    };
+  }
+
+  // Creates a function that, when passed an object, will traverse that object’s
+  // properties down the given `path`, specified as an array of keys or indices.
+  function property(path) {
+    path = toPath(path);
+    return function(obj) {
+      return deepGet(obj, path);
+    };
+  }
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  function optimizeCb(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount == null ? 3 : argCount) {
+      case 1: return function(value) {
+        return func.call(context, value);
+      };
+      // The 2-argument case is omitted because we’re not using it.
+      case 3: return function(value, index, collection) {
+        return func.call(context, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(context, accumulator, value, index, collection);
+      };
+    }
+    return function() {
+      return func.apply(context, arguments);
+    };
+  }
+
+  // An internal function to generate callbacks that can be applied to each
+  // element in a collection, returning the desired result — either `_.identity`,
+  // an arbitrary callback, a property matcher, or a property accessor.
+  function baseIteratee(value, context, argCount) {
+    if (value == null) return identity;
+    if (isFunction$1(value)) return optimizeCb(value, context, argCount);
+    if (isObject(value) && !isArray(value)) return matcher(value);
+    return property(value);
+  }
+
+  // External wrapper for our callback generator. Users may customize
+  // `_.iteratee` if they want additional predicate/iteratee shorthand styles.
+  // This abstraction hides the internal-only `argCount` argument.
+  function iteratee(value, context) {
+    return baseIteratee(value, context, Infinity);
+  }
+  _$1.iteratee = iteratee;
+
+  // The function we call internally to generate a callback. It invokes
+  // `_.iteratee` if overridden, otherwise `baseIteratee`.
+  function cb(value, context, argCount) {
+    if (_$1.iteratee !== iteratee) return _$1.iteratee(value, context);
+    return baseIteratee(value, context, argCount);
+  }
+
+  // Returns the results of applying the `iteratee` to each element of `obj`.
+  // In contrast to `_.map` it returns an object.
+  function mapObject(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    var _keys = keys(obj),
+        length = _keys.length,
+        results = {};
+    for (var index = 0; index < length; index++) {
+      var currentKey = _keys[index];
+      results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  }
+
+  // Predicate-generating function. Often useful outside of Underscore.
+  function noop(){}
+
+  // Generates a function for a given object that returns a given property.
+  function propertyOf(obj) {
+    if (obj == null) return noop;
+    return function(path) {
+      return get(obj, path);
+    };
+  }
+
+  // Run a function **n** times.
+  function times(n, iteratee, context) {
+    var accum = Array(Math.max(0, n));
+    iteratee = optimizeCb(iteratee, context, 1);
+    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+    return accum;
+  }
+
+  // Return a random integer between `min` and `max` (inclusive).
+  function random(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  }
+
+  // A (possibly faster) way to get the current timestamp as an integer.
+  var now = Date.now || function() {
+    return new Date().getTime();
+  };
+
+  // Internal helper to generate functions for escaping and unescaping strings
+  // to/from HTML interpolation.
+  function createEscaper(map) {
+    var escaper = function(match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped.
+    var source = '(?:' + keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function(string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  }
+
+  // Internal list of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+
+  // Function for escaping strings to HTML interpolation.
+  var _escape = createEscaper(escapeMap);
+
+  // Internal list of HTML entities for unescaping.
+  var unescapeMap = invert(escapeMap);
+
+  // Function for unescaping strings from HTML interpolation.
+  var _unescape = createEscaper(unescapeMap);
+
+  // By default, Underscore uses ERB-style template delimiters. Change the
+  // following template settings to use alternative delimiters.
+  var templateSettings = _$1.templateSettings = {
+    evaluate: /<%([\s\S]+?)%>/g,
+    interpolate: /<%=([\s\S]+?)%>/g,
+    escape: /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `_.templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'": "'",
+    '\\': '\\',
+    '\r': 'r',
+    '\n': 'n',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
+
+  function escapeChar(match) {
+    return '\\' + escapes[match];
+  }
+
+  // In order to prevent third-party code injection through
+  // `_.templateSettings.variable`, we test it against the following regular
+  // expression. It is intentionally a bit more liberal than just matching valid
+  // identifiers, but still prevents possible loopholes through defaults or
+  // destructuring assignment.
+  var bareIdentifier = /^\s*(\w|\$)+\s*$/;
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  // NB: `oldSettings` only exists for backwards compatibility.
+  function template(text, settings, oldSettings) {
+    if (!settings && oldSettings) settings = oldSettings;
+    settings = defaults({}, settings, _$1.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
+      index = offset + match.length;
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      } else if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      } else if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+
+      // Adobe VMs need the match returned to produce the correct offset.
+      return match;
+    });
+    source += "';\n";
+
+    var argument = settings.variable;
+    if (argument) {
+      // Insure against third-party code injection. (CVE-2021-23358)
+      if (!bareIdentifier.test(argument)) throw new Error(
+        'variable is not a bare identifier: ' + argument
+      );
+    } else {
+      // If a variable is not specified, place data values in local scope.
+      source = 'with(obj||{}){\n' + source + '}\n';
+      argument = 'obj';
+    }
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + 'return __p;\n';
+
+    var render;
+    try {
+      render = new Function(argument, '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    var template = function(data) {
+      return render.call(this, data, _$1);
+    };
+
+    // Provide the compiled source as a convenience for precompilation.
+    template.source = 'function(' + argument + '){\n' + source + '}';
+
+    return template;
+  }
+
+  // Traverses the children of `obj` along `path`. If a child is a function, it
+  // is invoked with its parent as context. Returns the value of the final
+  // child, or `fallback` if any child is undefined.
+  function result(obj, path, fallback) {
+    path = toPath(path);
+    var length = path.length;
+    if (!length) {
+      return isFunction$1(fallback) ? fallback.call(obj) : fallback;
+    }
+    for (var i = 0; i < length; i++) {
+      var prop = obj == null ? void 0 : obj[path[i]];
+      if (prop === void 0) {
+        prop = fallback;
+        i = length; // Ensure we don't continue iterating.
+      }
+      obj = isFunction$1(prop) ? prop.call(obj) : prop;
+    }
+    return obj;
+  }
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  function uniqueId(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  }
+
+  // Start chaining a wrapped Underscore object.
+  function chain(obj) {
+    var instance = _$1(obj);
+    instance._chain = true;
+    return instance;
+  }
+
+  // Internal function to execute `sourceFunc` bound to `context` with optional
+  // `args`. Determines whether to execute a function as a constructor or as a
+  // normal function.
+  function executeBound(sourceFunc, boundFunc, context, callingContext, args) {
+    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+    var self = baseCreate(sourceFunc.prototype);
+    var result = sourceFunc.apply(self, args);
+    if (isObject(result)) return result;
+    return self;
+  }
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context. `_` acts
+  // as a placeholder by default, allowing any combination of arguments to be
+  // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
+  var partial = restArguments(function(func, boundArgs) {
+    var placeholder = partial.placeholder;
+    var bound = function() {
+      var position = 0, length = boundArgs.length;
+      var args = Array(length);
+      for (var i = 0; i < length; i++) {
+        args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
+      }
+      while (position < arguments.length) args.push(arguments[position++]);
+      return executeBound(func, bound, this, this, args);
+    };
+    return bound;
+  });
+
+  partial.placeholder = _$1;
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally).
+  var bind = restArguments(function(func, context, args) {
+    if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function');
+    var bound = restArguments(function(callArgs) {
+      return executeBound(func, bound, context, this, args.concat(callArgs));
+    });
+    return bound;
+  });
+
+  // Internal helper for collection methods to determine whether a collection
+  // should be iterated as an array or as an object.
+  // Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
+  var isArrayLike = createSizePropertyCheck(getLength);
+
+  // Internal implementation of a recursive `flatten` function.
+  function flatten$1(input, depth, strict, output) {
+    output = output || [];
+    if (!depth && depth !== 0) {
+      depth = Infinity;
+    } else if (depth <= 0) {
+      return output.concat(input);
+    }
+    var idx = output.length;
+    for (var i = 0, length = getLength(input); i < length; i++) {
+      var value = input[i];
+      if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
+        // Flatten current level of array or arguments object.
+        if (depth > 1) {
+          flatten$1(value, depth - 1, strict, output);
+          idx = output.length;
+        } else {
+          var j = 0, len = value.length;
+          while (j < len) output[idx++] = value[j++];
+        }
+      } else if (!strict) {
+        output[idx++] = value;
+      }
+    }
+    return output;
+  }
+
+  // Bind a number of an object's methods to that object. Remaining arguments
+  // are the method names to be bound. Useful for ensuring that all callbacks
+  // defined on an object belong to it.
+  var bindAll = restArguments(function(obj, keys) {
+    keys = flatten$1(keys, false, false);
+    var index = keys.length;
+    if (index < 1) throw new Error('bindAll must be passed function names');
+    while (index--) {
+      var key = keys[index];
+      obj[key] = bind(obj[key], obj);
+    }
+    return obj;
+  });
+
+  // Memoize an expensive function by storing its results.
+  function memoize(func, hasher) {
+    var memoize = function(key) {
+      var cache = memoize.cache;
+      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
+      if (!has$1(cache, address)) cache[address] = func.apply(this, arguments);
+      return cache[address];
+    };
+    memoize.cache = {};
+    return memoize;
+  }
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  var delay = restArguments(function(func, wait, args) {
+    return setTimeout(function() {
+      return func.apply(null, args);
+    }, wait);
+  });
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  var defer = partial(delay, _$1, 1);
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  function throttle(func, wait, options) {
+    var timeout, context, args, result;
+    var previous = 0;
+    if (!options) options = {};
+
+    var later = function() {
+      previous = options.leading === false ? 0 : now();
+      timeout = null;
+      result = func.apply(context, args);
+      if (!timeout) context = args = null;
+    };
+
+    var throttled = function() {
+      var _now = now();
+      if (!previous && options.leading === false) previous = _now;
+      var remaining = wait - (_now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0 || remaining > wait) {
+        if (timeout) {
+          clearTimeout(timeout);
+          timeout = null;
+        }
+        previous = _now;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+
+    throttled.cancel = function() {
+      clearTimeout(timeout);
+      previous = 0;
+      timeout = context = args = null;
+    };
+
+    return throttled;
+  }
+
+  // When a sequence of calls of the returned function ends, the argument
+  // function is triggered. The end of a sequence is defined by the `wait`
+  // parameter. If `immediate` is passed, the argument function will be
+  // triggered at the beginning of the sequence instead of at the end.
+  function debounce(func, wait, immediate) {
+    var timeout, previous, args, result, context;
+
+    var later = function() {
+      var passed = now() - previous;
+      if (wait > passed) {
+        timeout = setTimeout(later, wait - passed);
+      } else {
+        timeout = null;
+        if (!immediate) result = func.apply(context, args);
+        // This check is needed because `func` can recursively invoke `debounced`.
+        if (!timeout) args = context = null;
+      }
+    };
+
+    var debounced = restArguments(function(_args) {
+      context = this;
+      args = _args;
+      previous = now();
+      if (!timeout) {
+        timeout = setTimeout(later, wait);
+        if (immediate) result = func.apply(context, args);
+      }
+      return result;
+    });
+
+    debounced.cancel = function() {
+      clearTimeout(timeout);
+      timeout = args = context = null;
+    };
+
+    return debounced;
+  }
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  function wrap(func, wrapper) {
+    return partial(wrapper, func);
+  }
+
+  // Returns a negated version of the passed-in predicate.
+  function negate(predicate) {
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  }
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  function compose() {
+    var args = arguments;
+    var start = args.length - 1;
+    return function() {
+      var i = start;
+      var result = args[start].apply(this, arguments);
+      while (i--) result = args[i].call(this, result);
+      return result;
+    };
+  }
+
+  // Returns a function that will only be executed on and after the Nth call.
+  function after(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  }
+
+  // Returns a function that will only be executed up to (but not including) the
+  // Nth call.
+  function before(times, func) {
+    var memo;
+    return function() {
+      if (--times > 0) {
+        memo = func.apply(this, arguments);
+      }
+      if (times <= 1) func = null;
+      return memo;
+    };
+  }
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  var once = partial(before, 2);
+
+  // Returns the first key on an object that passes a truth test.
+  function findKey(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var _keys = keys(obj), key;
+    for (var i = 0, length = _keys.length; i < length; i++) {
+      key = _keys[i];
+      if (predicate(obj[key], key, obj)) return key;
+    }
+  }
+
+  // Internal function to generate `_.findIndex` and `_.findLastIndex`.
+  function createPredicateIndexFinder(dir) {
+    return function(array, predicate, context) {
+      predicate = cb(predicate, context);
+      var length = getLength(array);
+      var index = dir > 0 ? 0 : length - 1;
+      for (; index >= 0 && index < length; index += dir) {
+        if (predicate(array[index], index, array)) return index;
+      }
+      return -1;
+    };
+  }
+
+  // Returns the first index on an array-like that passes a truth test.
+  var findIndex = createPredicateIndexFinder(1);
+
+  // Returns the last index on an array-like that passes a truth test.
+  var findLastIndex = createPredicateIndexFinder(-1);
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  function sortedIndex(array, obj, iteratee, context) {
+    iteratee = cb(iteratee, context, 1);
+    var value = iteratee(obj);
+    var low = 0, high = getLength(array);
+    while (low < high) {
+      var mid = Math.floor((low + high) / 2);
+      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+    }
+    return low;
+  }
+
+  // Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions.
+  function createIndexFinder(dir, predicateFind, sortedIndex) {
+    return function(array, item, idx) {
+      var i = 0, length = getLength(array);
+      if (typeof idx == 'number') {
+        if (dir > 0) {
+          i = idx >= 0 ? idx : Math.max(idx + length, i);
+        } else {
+          length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
+        }
+      } else if (sortedIndex && idx && length) {
+        idx = sortedIndex(array, item);
+        return array[idx] === item ? idx : -1;
+      }
+      if (item !== item) {
+        idx = predicateFind(slice.call(array, i, length), isNaN$1);
+        return idx >= 0 ? idx + i : -1;
+      }
+      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
+        if (array[idx] === item) return idx;
+      }
+      return -1;
+    };
+  }
+
+  // Return the position of the first occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  var indexOf = createIndexFinder(1, findIndex, sortedIndex);
+
+  // Return the position of the last occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  var lastIndexOf = createIndexFinder(-1, findLastIndex);
+
+  // Return the first value which passes a truth test.
+  function find(obj, predicate, context) {
+    var keyFinder = isArrayLike(obj) ? findIndex : findKey;
+    var key = keyFinder(obj, predicate, context);
+    if (key !== void 0 && key !== -1) return obj[key];
+  }
+
+  // Convenience version of a common use case of `_.find`: getting the first
+  // object containing specific `key:value` pairs.
+  function findWhere(obj, attrs) {
+    return find(obj, matcher(attrs));
+  }
+
+  // The cornerstone for collection functions, an `each`
+  // implementation, aka `forEach`.
+  // Handles raw objects in addition to array-likes. Treats all
+  // sparse array-likes as if they were dense.
+  function each(obj, iteratee, context) {
+    iteratee = optimizeCb(iteratee, context);
+    var i, length;
+    if (isArrayLike(obj)) {
+      for (i = 0, length = obj.length; i < length; i++) {
+        iteratee(obj[i], i, obj);
+      }
+    } else {
+      var _keys = keys(obj);
+      for (i = 0, length = _keys.length; i < length; i++) {
+        iteratee(obj[_keys[i]], _keys[i], obj);
+      }
+    }
+    return obj;
+  }
+
+  // Return the results of applying the iteratee to each element.
+  function map(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    var _keys = !isArrayLike(obj) && keys(obj),
+        length = (_keys || obj).length,
+        results = Array(length);
+    for (var index = 0; index < length; index++) {
+      var currentKey = _keys ? _keys[index] : index;
+      results[index] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  }
+
+  // Internal helper to create a reducing function, iterating left or right.
+  function createReduce(dir) {
+    // Wrap code that reassigns argument variables in a separate function than
+    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
+    var reducer = function(obj, iteratee, memo, initial) {
+      var _keys = !isArrayLike(obj) && keys(obj),
+          length = (_keys || obj).length,
+          index = dir > 0 ? 0 : length - 1;
+      if (!initial) {
+        memo = obj[_keys ? _keys[index] : index];
+        index += dir;
+      }
+      for (; index >= 0 && index < length; index += dir) {
+        var currentKey = _keys ? _keys[index] : index;
+        memo = iteratee(memo, obj[currentKey], currentKey, obj);
+      }
+      return memo;
+    };
+
+    return function(obj, iteratee, memo, context) {
+      var initial = arguments.length >= 3;
+      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
+    };
+  }
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`.
+  var reduce = createReduce(1);
+
+  // The right-associative version of reduce, also known as `foldr`.
+  var reduceRight = createReduce(-1);
+
+  // Return all the elements that pass a truth test.
+  function filter(obj, predicate, context) {
+    var results = [];
+    predicate = cb(predicate, context);
+    each(obj, function(value, index, list) {
+      if (predicate(value, index, list)) results.push(value);
+    });
+    return results;
+  }
+
+  // Return all the elements for which a truth test fails.
+  function reject(obj, predicate, context) {
+    return filter(obj, negate(cb(predicate)), context);
+  }
+
+  // Determine whether all of the elements pass a truth test.
+  function every(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var _keys = !isArrayLike(obj) && keys(obj),
+        length = (_keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = _keys ? _keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  }
+
+  // Determine if at least one element in the object passes a truth test.
+  function some(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var _keys = !isArrayLike(obj) && keys(obj),
+        length = (_keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = _keys ? _keys[index] : index;
+      if (predicate(obj[currentKey], currentKey, obj)) return true;
+    }
+    return false;
+  }
+
+  // Determine if the array or object contains a given item (using `===`).
+  function contains(obj, item, fromIndex, guard) {
+    if (!isArrayLike(obj)) obj = values(obj);
+    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
+    return indexOf(obj, item, fromIndex) >= 0;
+  }
+
+  // Invoke a method (with arguments) on every item in a collection.
+  var invoke = restArguments(function(obj, path, args) {
+    var contextPath, func;
+    if (isFunction$1(path)) {
+      func = path;
+    } else {
+      path = toPath(path);
+      contextPath = path.slice(0, -1);
+      path = path[path.length - 1];
+    }
+    return map(obj, function(context) {
+      var method = func;
+      if (!method) {
+        if (contextPath && contextPath.length) {
+          context = deepGet(context, contextPath);
+        }
+        if (context == null) return void 0;
+        method = context[path];
+      }
+      return method == null ? method : method.apply(context, args);
+    });
+  });
+
+  // Convenience version of a common use case of `_.map`: fetching a property.
+  function pluck(obj, key) {
+    return map(obj, property(key));
+  }
+
+  // Convenience version of a common use case of `_.filter`: selecting only
+  // objects containing specific `key:value` pairs.
+  function where(obj, attrs) {
+    return filter(obj, matcher(attrs));
+  }
+
+  // Return the maximum element (or element-based computation).
+  function max(obj, iteratee, context) {
+    var result = -Infinity, lastComputed = -Infinity,
+        value, computed;
+    if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
+      obj = isArrayLike(obj) ? obj : values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value != null && value > result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = cb(iteratee, context);
+      each(obj, function(v, index, list) {
+        computed = iteratee(v, index, list);
+        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+          result = v;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  }
+
+  // Return the minimum element (or element-based computation).
+  function min(obj, iteratee, context) {
+    var result = Infinity, lastComputed = Infinity,
+        value, computed;
+    if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) {
+      obj = isArrayLike(obj) ? obj : values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value != null && value < result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = cb(iteratee, context);
+      each(obj, function(v, index, list) {
+        computed = iteratee(v, index, list);
+        if (computed < lastComputed || computed === Infinity && result === Infinity) {
+          result = v;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  }
+
+  // Sample **n** random values from a collection using the modern version of the
+  // [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  // If **n** is not specified, returns a single random element.
+  // The internal `guard` argument allows it to work with `_.map`.
+  function sample(obj, n, guard) {
+    if (n == null || guard) {
+      if (!isArrayLike(obj)) obj = values(obj);
+      return obj[random(obj.length - 1)];
+    }
+    var sample = isArrayLike(obj) ? clone(obj) : values(obj);
+    var length = getLength(sample);
+    n = Math.max(Math.min(n, length), 0);
+    var last = length - 1;
+    for (var index = 0; index < n; index++) {
+      var rand = random(index, last);
+      var temp = sample[index];
+      sample[index] = sample[rand];
+      sample[rand] = temp;
+    }
+    return sample.slice(0, n);
+  }
+
+  // Shuffle a collection.
+  function shuffle(obj) {
+    return sample(obj, Infinity);
+  }
+
+  // Sort the object's values by a criterion produced by an iteratee.
+  function sortBy(obj, iteratee, context) {
+    var index = 0;
+    iteratee = cb(iteratee, context);
+    return pluck(map(obj, function(value, key, list) {
+      return {
+        value: value,
+        index: index++,
+        criteria: iteratee(value, key, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  }
+
+  // An internal function used for aggregate "group by" operations.
+  function group(behavior, partition) {
+    return function(obj, iteratee, context) {
+      var result = partition ? [[], []] : {};
+      iteratee = cb(iteratee, context);
+      each(obj, function(value, index) {
+        var key = iteratee(value, index, obj);
+        behavior(result, value, key);
+      });
+      return result;
+    };
+  }
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  var groupBy = group(function(result, value, key) {
+    if (has$1(result, key)) result[key].push(value); else result[key] = [value];
+  });
+
+  // Indexes the object's values by a criterion, similar to `_.groupBy`, but for
+  // when you know that your index values will be unique.
+  var indexBy = group(function(result, value, key) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  var countBy = group(function(result, value, key) {
+    if (has$1(result, key)) result[key]++; else result[key] = 1;
+  });
+
+  // Split a collection into two arrays: one whose elements all pass the given
+  // truth test, and one whose elements all do not pass the truth test.
+  var partition = group(function(result, value, pass) {
+    result[pass ? 0 : 1].push(value);
+  }, true);
+
+  // Safely create a real, live array from anything iterable.
+  var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
+  function toArray(obj) {
+    if (!obj) return [];
+    if (isArray(obj)) return slice.call(obj);
+    if (isString(obj)) {
+      // Keep surrogate pair characters together.
+      return obj.match(reStrSymbol);
+    }
+    if (isArrayLike(obj)) return map(obj, identity);
+    return values(obj);
+  }
+
+  // Return the number of elements in a collection.
+  function size(obj) {
+    if (obj == null) return 0;
+    return isArrayLike(obj) ? obj.length : keys(obj).length;
+  }
+
+  // Internal `_.pick` helper function to determine whether `key` is an enumerable
+  // property name of `obj`.
+  function keyInObj(value, key, obj) {
+    return key in obj;
+  }
+
+  // Return a copy of the object only containing the allowed properties.
+  var pick = restArguments(function(obj, keys) {
+    var result = {}, iteratee = keys[0];
+    if (obj == null) return result;
+    if (isFunction$1(iteratee)) {
+      if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
+      keys = allKeys(obj);
+    } else {
+      iteratee = keyInObj;
+      keys = flatten$1(keys, false, false);
+      obj = Object(obj);
+    }
+    for (var i = 0, length = keys.length; i < length; i++) {
+      var key = keys[i];
+      var value = obj[key];
+      if (iteratee(value, key, obj)) result[key] = value;
+    }
+    return result;
+  });
+
+  // Return a copy of the object without the disallowed properties.
+  var omit = restArguments(function(obj, keys) {
+    var iteratee = keys[0], context;
+    if (isFunction$1(iteratee)) {
+      iteratee = negate(iteratee);
+      if (keys.length > 1) context = keys[1];
+    } else {
+      keys = map(flatten$1(keys, false, false), String);
+      iteratee = function(value, key) {
+        return !contains(keys, key);
+      };
+    }
+    return pick(obj, iteratee, context);
+  });
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N.
+  function initial(array, n, guard) {
+    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+  }
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  function first(array, n, guard) {
+    if (array == null || array.length < 1) return n == null || guard ? void 0 : [];
+    if (n == null || guard) return array[0];
+    return initial(array, array.length - n);
+  }
+
+  // Returns everything but the first entry of the `array`. Especially useful on
+  // the `arguments` object. Passing an **n** will return the rest N values in the
+  // `array`.
+  function rest(array, n, guard) {
+    return slice.call(array, n == null || guard ? 1 : n);
+  }
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array.
+  function last(array, n, guard) {
+    if (array == null || array.length < 1) return n == null || guard ? void 0 : [];
+    if (n == null || guard) return array[array.length - 1];
+    return rest(array, Math.max(0, array.length - n));
+  }
+
+  // Trim out all falsy values from an array.
+  function compact(array) {
+    return filter(array, Boolean);
+  }
+
+  // Flatten out an array, either recursively (by default), or up to `depth`.
+  // Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively.
+  function flatten(array, depth) {
+    return flatten$1(array, depth, false);
+  }
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  var difference = restArguments(function(array, rest) {
+    rest = flatten$1(rest, true, true);
+    return filter(array, function(value){
+      return !contains(rest, value);
+    });
+  });
+
+  // Return a version of the array that does not contain the specified value(s).
+  var without = restArguments(function(array, otherArrays) {
+    return difference(array, otherArrays);
+  });
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // The faster algorithm will not work with an iteratee if the iteratee
+  // is not a one-to-one function, so providing an iteratee will disable
+  // the faster algorithm.
+  function uniq(array, isSorted, iteratee, context) {
+    if (!isBoolean(isSorted)) {
+      context = iteratee;
+      iteratee = isSorted;
+      isSorted = false;
+    }
+    if (iteratee != null) iteratee = cb(iteratee, context);
+    var result = [];
+    var seen = [];
+    for (var i = 0, length = getLength(array); i < length; i++) {
+      var value = array[i],
+          computed = iteratee ? iteratee(value, i, array) : value;
+      if (isSorted && !iteratee) {
+        if (!i || seen !== computed) result.push(value);
+        seen = computed;
+      } else if (iteratee) {
+        if (!contains(seen, computed)) {
+          seen.push(computed);
+          result.push(value);
+        }
+      } else if (!contains(result, value)) {
+        result.push(value);
+      }
+    }
+    return result;
+  }
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  var union = restArguments(function(arrays) {
+    return uniq(flatten$1(arrays, true, true));
+  });
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  function intersection(array) {
+    var result = [];
+    var argsLength = arguments.length;
+    for (var i = 0, length = getLength(array); i < length; i++) {
+      var item = array[i];
+      if (contains(result, item)) continue;
+      var j;
+      for (j = 1; j < argsLength; j++) {
+        if (!contains(arguments[j], item)) break;
+      }
+      if (j === argsLength) result.push(item);
+    }
+    return result;
+  }
+
+  // Complement of zip. Unzip accepts an array of arrays and groups
+  // each array's elements on shared indices.
+  function unzip(array) {
+    var length = array && max(array, getLength).length || 0;
+    var result = Array(length);
+
+    for (var index = 0; index < length; index++) {
+      result[index] = pluck(array, index);
+    }
+    return result;
+  }
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  var zip = restArguments(unzip);
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values. Passing by pairs is the reverse of `_.pairs`.
+  function object(list, values) {
+    var result = {};
+    for (var i = 0, length = getLength(list); i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  }
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](https://docs.python.org/library/functions.html#range).
+  function range(start, stop, step) {
+    if (stop == null) {
+      stop = start || 0;
+      start = 0;
+    }
+    if (!step) {
+      step = stop < start ? -1 : 1;
+    }
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var range = Array(length);
+
+    for (var idx = 0; idx < length; idx++, start += step) {
+      range[idx] = start;
+    }
+
+    return range;
+  }
+
+  // Chunk a single array into multiple arrays, each containing `count` or fewer
+  // items.
+  function chunk(array, count) {
+    if (count == null || count < 1) return [];
+    var result = [];
+    var i = 0, length = array.length;
+    while (i < length) {
+      result.push(slice.call(array, i, i += count));
+    }
+    return result;
+  }
+
+  // Helper function to continue chaining intermediate results.
+  function chainResult(instance, obj) {
+    return instance._chain ? _$1(obj).chain() : obj;
+  }
+
+  // Add your own custom functions to the Underscore object.
+  function mixin(obj) {
+    each(functions(obj), function(name) {
+      var func = _$1[name] = obj[name];
+      _$1.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return chainResult(this, func.apply(_$1, args));
+      };
+    });
+    return _$1;
+  }
+
+  // Add all mutator `Array` functions to the wrapper.
+  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _$1.prototype[name] = function() {
+      var obj = this._wrapped;
+      if (obj != null) {
+        method.apply(obj, arguments);
+        if ((name === 'shift' || name === 'splice') && obj.length === 0) {
+          delete obj[0];
+        }
+      }
+      return chainResult(this, obj);
+    };
+  });
+
+  // Add all accessor `Array` functions to the wrapper.
+  each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _$1.prototype[name] = function() {
+      var obj = this._wrapped;
+      if (obj != null) obj = method.apply(obj, arguments);
+      return chainResult(this, obj);
+    };
+  });
+
+  // Named Exports
+
+  var allExports = {
+    __proto__: null,
+    VERSION: VERSION,
+    restArguments: restArguments,
+    isObject: isObject,
+    isNull: isNull,
+    isUndefined: isUndefined,
+    isBoolean: isBoolean,
+    isElement: isElement,
+    isString: isString,
+    isNumber: isNumber,
+    isDate: isDate,
+    isRegExp: isRegExp,
+    isError: isError,
+    isSymbol: isSymbol,
+    isArrayBuffer: isArrayBuffer,
+    isDataView: isDataView$1,
+    isArray: isArray,
+    isFunction: isFunction$1,
+    isArguments: isArguments$1,
+    isFinite: isFinite$1,
+    isNaN: isNaN$1,
+    isTypedArray: isTypedArray$1,
+    isEmpty: isEmpty,
+    isMatch: isMatch,
+    isEqual: isEqual,
+    isMap: isMap,
+    isWeakMap: isWeakMap,
+    isSet: isSet,
+    isWeakSet: isWeakSet,
+    keys: keys,
+    allKeys: allKeys,
+    values: values,
+    pairs: pairs,
+    invert: invert,
+    functions: functions,
+    methods: functions,
+    extend: extend,
+    extendOwn: extendOwn,
+    assign: extendOwn,
+    defaults: defaults,
+    create: create,
+    clone: clone,
+    tap: tap,
+    get: get,
+    has: has,
+    mapObject: mapObject,
+    identity: identity,
+    constant: constant,
+    noop: noop,
+    toPath: toPath$1,
+    property: property,
+    propertyOf: propertyOf,
+    matcher: matcher,
+    matches: matcher,
+    times: times,
+    random: random,
+    now: now,
+    escape: _escape,
+    unescape: _unescape,
+    templateSettings: templateSettings,
+    template: template,
+    result: result,
+    uniqueId: uniqueId,
+    chain: chain,
+    iteratee: iteratee,
+    partial: partial,
+    bind: bind,
+    bindAll: bindAll,
+    memoize: memoize,
+    delay: delay,
+    defer: defer,
+    throttle: throttle,
+    debounce: debounce,
+    wrap: wrap,
+    negate: negate,
+    compose: compose,
+    after: after,
+    before: before,
+    once: once,
+    findKey: findKey,
+    findIndex: findIndex,
+    findLastIndex: findLastIndex,
+    sortedIndex: sortedIndex,
+    indexOf: indexOf,
+    lastIndexOf: lastIndexOf,
+    find: find,
+    detect: find,
+    findWhere: findWhere,
+    each: each,
+    forEach: each,
+    map: map,
+    collect: map,
+    reduce: reduce,
+    foldl: reduce,
+    inject: reduce,
+    reduceRight: reduceRight,
+    foldr: reduceRight,
+    filter: filter,
+    select: filter,
+    reject: reject,
+    every: every,
+    all: every,
+    some: some,
+    any: some,
+    contains: contains,
+    includes: contains,
+    include: contains,
+    invoke: invoke,
+    pluck: pluck,
+    where: where,
+    max: max,
+    min: min,
+    shuffle: shuffle,
+    sample: sample,
+    sortBy: sortBy,
+    groupBy: groupBy,
+    indexBy: indexBy,
+    countBy: countBy,
+    partition: partition,
+    toArray: toArray,
+    size: size,
+    pick: pick,
+    omit: omit,
+    first: first,
+    head: first,
+    take: first,
+    initial: initial,
+    last: last,
+    rest: rest,
+    tail: rest,
+    drop: rest,
+    compact: compact,
+    flatten: flatten,
+    without: without,
+    uniq: uniq,
+    unique: uniq,
+    union: union,
+    intersection: intersection,
+    difference: difference,
+    unzip: unzip,
+    transpose: unzip,
+    zip: zip,
+    object: object,
+    range: range,
+    chunk: chunk,
+    mixin: mixin,
+    'default': _$1
+  };
+
+  // Default Export
+
+  // Add all of the Underscore functions to the wrapper object.
+  var _ = mixin(allExports);
+  // Legacy Node.js API.
+  _._ = _;
+
+  return _;
+
+})));
+//# sourceMappingURL=underscore-umd.js.map
diff --git a/docs/_static/underscore.js b/docs/_static/underscore.js
index 166240ef..cf177d42 100644
--- a/docs/_static/underscore.js
+++ b/docs/_static/underscore.js
@@ -1,6 +1,6 @@
-!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n=n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){
-//     Underscore.js 1.12.0
+!function(n,r){"object"==typeof exports&&"undefined"!=typeof module?module.exports=r():"function"==typeof define&&define.amd?define("underscore",r):(n="undefined"!=typeof globalThis?globalThis:n||self,function(){var t=n._,e=n._=r();e.noConflict=function(){return n._=t,e}}())}(this,(function(){
+//     Underscore.js 1.13.1
 //     https://underscorejs.org
-//     (c) 2009-2020 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     (c) 2009-2021 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
 //     Underscore may be freely distributed under the MIT license.
-var n="1.12.0",r="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},t=Array.prototype,e=Object.prototype,u="undefined"!=typeof Symbol?Symbol.prototype:null,o=t.push,i=t.slice,a=e.toString,f=e.hasOwnProperty,c="undefined"!=typeof ArrayBuffer,l="undefined"!=typeof DataView,s=Array.isArray,p=Object.keys,v=Object.create,h=c&&ArrayBuffer.isView,y=isNaN,g=isFinite,d=!{toString:null}.propertyIsEnumerable("toString"),b=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],m=Math.pow(2,53)-1;function j(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u<t;u++)e[u]=arguments[u+r];switch(r){case 0:return n.call(this,e);case 1:return n.call(this,arguments[0],e);case 2:return n.call(this,arguments[0],arguments[1],e)}var o=Array(r+1);for(u=0;u<r;u++)o[u]=arguments[u];return o[r]=e,n.apply(this,o)}}function _(n){var r=typeof n;return"function"===r||"object"===r&&!!n}function w(n){return void 0===n}function A(n){return!0===n||!1===n||"[object Boolean]"===a.call(n)}function x(n){var r="[object "+n+"]";return function(n){return a.call(n)===r}}var S=x("String"),O=x("Number"),M=x("Date"),E=x("RegExp"),B=x("Error"),N=x("Symbol"),I=x("ArrayBuffer"),k=x("Function"),T=r.document&&r.document.childNodes;"function"!=typeof/./&&"object"!=typeof Int8Array&&"function"!=typeof T&&(k=function(n){return"function"==typeof n||!1});var D=k,R=x("Object"),F=l&&R(new DataView(new ArrayBuffer(8))),V="undefined"!=typeof Map&&R(new Map),P=x("DataView");var q=F?function(n){return null!=n&&D(n.getInt8)&&I(n.buffer)}:P,U=s||x("Array");function W(n,r){return null!=n&&f.call(n,r)}var z=x("Arguments");!function(){z(arguments)||(z=function(n){return W(n,"callee")})}();var L=z;function C(n){return O(n)&&y(n)}function K(n){return function(){return n}}function J(n){return function(r){var t=n(r);return"number"==typeof t&&t>=0&&t<=m}}function $(n){return function(r){return null==r?void 0:r[n]}}var G=$("byteLength"),H=J(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:K(!1),Y=$("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e<t;++e)r[n[e]]=!0;return{contains:function(n){return r[n]},push:function(t){return r[t]=!0,n.push(t)}}}(r);var t=b.length,u=n.constructor,o=D(u)&&u.prototype||e,i="constructor";for(W(n,i)&&!r.contains(i)&&r.push(i);t--;)(i=b[t])in n&&n[i]!==o[i]&&!r.contains(i)&&r.push(i)}function nn(n){if(!_(n))return[];if(p)return p(n);var r=[];for(var t in n)W(n,t)&&r.push(t);return d&&Z(n,r),r}function rn(n,r){var t=nn(r),e=t.length;if(null==n)return!e;for(var u=Object(n),o=0;o<e;o++){var i=t[o];if(r[i]!==u[i]||!(i in u))return!1}return!0}function tn(n){return n instanceof tn?n:this instanceof tn?void(this._wrapped=n):new tn(n)}function en(n){return new Uint8Array(n.buffer||n,n.byteOffset||0,G(n))}tn.VERSION=n,tn.prototype.value=function(){return this._wrapped},tn.prototype.valueOf=tn.prototype.toJSON=tn.prototype.value,tn.prototype.toString=function(){return String(this._wrapped)};var un="[object DataView]";function on(n,r,t,e){if(n===r)return 0!==n||1/n==1/r;if(null==n||null==r)return!1;if(n!=n)return r!=r;var o=typeof n;return("function"===o||"object"===o||"object"==typeof r)&&function n(r,t,e,o){r instanceof tn&&(r=r._wrapped);t instanceof tn&&(t=t._wrapped);var i=a.call(r);if(i!==a.call(t))return!1;if(F&&"[object Object]"==i&&q(r)){if(!q(t))return!1;i=un}switch(i){case"[object RegExp]":case"[object String]":return""+r==""+t;case"[object Number]":return+r!=+r?+t!=+t:0==+r?1/+r==1/t:+r==+t;case"[object Date]":case"[object Boolean]":return+r==+t;case"[object Symbol]":return u.valueOf.call(r)===u.valueOf.call(t);case"[object ArrayBuffer]":case un:return n(en(r),en(t),e,o)}var f="[object Array]"===i;if(!f&&X(r)){if(G(r)!==G(t))return!1;if(r.buffer===t.buffer&&r.byteOffset===t.byteOffset)return!0;f=!0}if(!f){if("object"!=typeof r||"object"!=typeof t)return!1;var c=r.constructor,l=t.constructor;if(c!==l&&!(D(c)&&c instanceof c&&D(l)&&l instanceof l)&&"constructor"in r&&"constructor"in t)return!1}o=o||[];var s=(e=e||[]).length;for(;s--;)if(e[s]===r)return o[s]===t;if(e.push(r),o.push(t),f){if((s=r.length)!==t.length)return!1;for(;s--;)if(!on(r[s],t[s],e,o))return!1}else{var p,v=nn(r);if(s=v.length,nn(t).length!==s)return!1;for(;s--;)if(p=v[s],!W(t,p)||!on(r[p],t[p],e,o))return!1}return e.pop(),o.pop(),!0}(n,r,t,e)}function an(n){if(!_(n))return[];var r=[];for(var t in n)r.push(t);return d&&Z(n,r),r}function fn(n){var r=Y(n);return function(t){if(null==t)return!1;var e=an(t);if(Y(e))return!1;for(var u=0;u<r;u++)if(!D(t[n[u]]))return!1;return n!==hn||!D(t[cn])}}var cn="forEach",ln="has",sn=["clear","delete"],pn=["get",ln,"set"],vn=sn.concat(cn,pn),hn=sn.concat(pn),yn=["add"].concat(sn,cn,ln),gn=V?fn(vn):x("Map"),dn=V?fn(hn):x("WeakMap"),bn=V?fn(yn):x("Set"),mn=x("WeakSet");function jn(n){for(var r=nn(n),t=r.length,e=Array(t),u=0;u<t;u++)e[u]=n[r[u]];return e}function _n(n){for(var r={},t=nn(n),e=0,u=t.length;e<u;e++)r[n[t[e]]]=t[e];return r}function wn(n){var r=[];for(var t in n)D(n[t])&&r.push(t);return r.sort()}function An(n,r){return function(t){var e=arguments.length;if(r&&(t=Object(t)),e<2||null==t)return t;for(var u=1;u<e;u++)for(var o=arguments[u],i=n(o),a=i.length,f=0;f<a;f++){var c=i[f];r&&void 0!==t[c]||(t[c]=o[c])}return t}}var xn=An(an),Sn=An(nn),On=An(an,!0);function Mn(n){if(!_(n))return{};if(v)return v(n);var r=function(){};r.prototype=n;var t=new r;return r.prototype=null,t}function En(n){return _(n)?U(n)?n.slice():xn({},n):n}function Bn(n){return U(n)?n:[n]}function Nn(n){return tn.toPath(n)}function In(n,r){for(var t=r.length,e=0;e<t;e++){if(null==n)return;n=n[r[e]]}return t?n:void 0}function kn(n,r,t){var e=In(n,Nn(r));return w(e)?t:e}function Tn(n){return n}function Dn(n){return n=Sn({},n),function(r){return rn(r,n)}}function Rn(n){return n=Nn(n),function(r){return In(r,n)}}function Fn(n,r,t){if(void 0===r)return n;switch(null==t?3:t){case 1:return function(t){return n.call(r,t)};case 3:return function(t,e,u){return n.call(r,t,e,u)};case 4:return function(t,e,u,o){return n.call(r,t,e,u,o)}}return function(){return n.apply(r,arguments)}}function Vn(n,r,t){return null==n?Tn:D(n)?Fn(n,r,t):_(n)&&!U(n)?Dn(n):Rn(n)}function Pn(n,r){return Vn(n,r,1/0)}function qn(n,r,t){return tn.iteratee!==Pn?tn.iteratee(n,r):Vn(n,r,t)}function Un(){}function Wn(n,r){return null==r&&(r=n,n=0),n+Math.floor(Math.random()*(r-n+1))}tn.toPath=Bn,tn.iteratee=Pn;var zn=Date.now||function(){return(new Date).getTime()};function Ln(n){var r=function(r){return n[r]},t="(?:"+nn(n).join("|")+")",e=RegExp(t),u=RegExp(t,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,r):n}}var Cn={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},Kn=Ln(Cn),Jn=Ln(_n(Cn)),$n=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Gn=/(.)^/,Hn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Qn=/\\|'|\r|\n|\u2028|\u2029/g;function Xn(n){return"\\"+Hn[n]}var Yn=0;function Zn(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var o=Mn(n.prototype),i=n.apply(o,u);return _(i)?i:o}var nr=j((function(n,r){var t=nr.placeholder,e=function(){for(var u=0,o=r.length,i=Array(o),a=0;a<o;a++)i[a]=r[a]===t?arguments[u++]:r[a];for(;u<arguments.length;)i.push(arguments[u++]);return Zn(n,e,this,this,i)};return e}));nr.placeholder=tn;var rr=j((function(n,r,t){if(!D(n))throw new TypeError("Bind must be called on a function");var e=j((function(u){return Zn(n,e,r,this,t.concat(u))}));return e})),tr=J(Y);function er(n,r,t,e){if(e=e||[],r||0===r){if(r<=0)return e.concat(n)}else r=1/0;for(var u=e.length,o=0,i=Y(n);o<i;o++){var a=n[o];if(tr(a)&&(U(a)||L(a)))if(r>1)er(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f<c;)e[u++]=a[f++];else t||(e[u++]=a)}return e}var ur=j((function(n,r){var t=(r=er(r,!1,!1)).length;if(t<1)throw new Error("bindAll must be passed function names");for(;t--;){var e=r[t];n[e]=rr(n[e],n)}return n}));var or=j((function(n,r,t){return setTimeout((function(){return n.apply(null,t)}),r)})),ir=nr(or,tn,1);function ar(n){return function(){return!n.apply(this,arguments)}}function fr(n,r){var t;return function(){return--n>0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var cr=nr(fr,2);function lr(n,r,t){r=qn(r,t);for(var e,u=nn(n),o=0,i=u.length;o<i;o++)if(r(n[e=u[o]],e,n))return e}function sr(n){return function(r,t,e){t=qn(t,e);for(var u=Y(r),o=n>0?0:u-1;o>=0&&o<u;o+=n)if(t(r[o],o,r))return o;return-1}}var pr=sr(1),vr=sr(-1);function hr(n,r,t,e){for(var u=(t=qn(t,e,1))(r),o=0,i=Y(n);o<i;){var a=Math.floor((o+i)/2);t(n[a])<u?o=a+1:i=a}return o}function yr(n,r,t){return function(e,u,o){var a=0,f=Y(e);if("number"==typeof o)n>0?a=o>=0?o:Math.max(o+f,a):f=o>=0?Math.min(o+1,f):o+f+1;else if(t&&o&&f)return e[o=t(e,u)]===u?o:-1;if(u!=u)return(o=r(i.call(e,a,f),C))>=0?o+a:-1;for(o=n>0?a:f-1;o>=0&&o<f;o+=n)if(e[o]===u)return o;return-1}}var gr=yr(1,pr,hr),dr=yr(-1,vr);function br(n,r,t){var e=(tr(n)?pr:lr)(n,r,t);if(void 0!==e&&-1!==e)return n[e]}function mr(n,r,t){var e,u;if(r=Fn(r,t),tr(n))for(e=0,u=n.length;e<u;e++)r(n[e],e,n);else{var o=nn(n);for(e=0,u=o.length;e<u;e++)r(n[o[e]],o[e],n)}return n}function jr(n,r,t){r=qn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,o=Array(u),i=0;i<u;i++){var a=e?e[i]:i;o[i]=r(n[a],a,n)}return o}function _r(n){var r=function(r,t,e,u){var o=!tr(r)&&nn(r),i=(o||r).length,a=n>0?0:i-1;for(u||(e=r[o?o[a]:a],a+=n);a>=0&&a<i;a+=n){var f=o?o[a]:a;e=t(e,r[f],f,r)}return e};return function(n,t,e,u){var o=arguments.length>=3;return r(n,Fn(t,u,4),e,o)}}var wr=_r(1),Ar=_r(-1);function xr(n,r,t){var e=[];return r=qn(r,t),mr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Sr(n,r,t){r=qn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,o=0;o<u;o++){var i=e?e[o]:o;if(!r(n[i],i,n))return!1}return!0}function Or(n,r,t){r=qn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,o=0;o<u;o++){var i=e?e[o]:o;if(r(n[i],i,n))return!0}return!1}function Mr(n,r,t,e){return tr(n)||(n=jn(n)),("number"!=typeof t||e)&&(t=0),gr(n,r,t)>=0}var Er=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Nn(r),e=r.slice(0,-1),r=r[r.length-1]),jr(n,(function(n){var o=u;if(!o){if(e&&e.length&&(n=In(n,e)),null==n)return;o=n[r]}return null==o?o:o.apply(n,t)}))}));function Br(n,r){return jr(n,Rn(r))}function Nr(n,r,t){var e,u,o=-1/0,i=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;a<f;a++)null!=(e=n[a])&&e>o&&(o=e);else r=qn(r,t),mr(n,(function(n,t,e){((u=r(n,t,e))>i||u===-1/0&&o===-1/0)&&(o=n,i=u)}));return o}function Ir(n,r,t){if(null==r||t)return tr(n)||(n=jn(n)),n[Wn(n.length-1)];var e=tr(n)?En(n):jn(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var o=u-1,i=0;i<r;i++){var a=Wn(i,o),f=e[i];e[i]=e[a],e[a]=f}return e.slice(0,r)}function kr(n,r){return function(t,e,u){var o=r?[[],[]]:{};return e=qn(e,u),mr(t,(function(r,u){var i=e(r,u,t);n(o,r,i)})),o}}var Tr=kr((function(n,r,t){W(n,t)?n[t].push(r):n[t]=[r]})),Dr=kr((function(n,r,t){n[t]=r})),Rr=kr((function(n,r,t){W(n,t)?n[t]++:n[t]=1})),Fr=kr((function(n,r,t){n[t?0:1].push(r)}),!0),Vr=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;function Pr(n,r,t){return r in t}var qr=j((function(n,r){var t={},e=r[0];if(null==n)return t;D(e)?(r.length>1&&(e=Fn(e,r[1])),r=an(n)):(e=Pr,r=er(r,!1,!1),n=Object(n));for(var u=0,o=r.length;u<o;u++){var i=r[u],a=n[i];e(a,i,n)&&(t[i]=a)}return t})),Ur=j((function(n,r){var t,e=r[0];return D(e)?(e=ar(e),r.length>1&&(t=r[1])):(r=jr(er(r,!1,!1),String),e=function(n,t){return!Mr(r,t)}),qr(n,e,t)}));function Wr(n,r,t){return i.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function zr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:Wr(n,n.length-r)}function Lr(n,r,t){return i.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=er(r,!0,!0),xr(n,(function(n){return!Mr(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=qn(t,e));for(var u=[],o=[],i=0,a=Y(n);i<a;i++){var f=n[i],c=t?t(f,i,n):f;r&&!t?(i&&o===c||u.push(f),o=c):t?Mr(o,c)||(o.push(c),u.push(f)):Mr(u,f)||u.push(f)}return u}var $r=j((function(n){return Jr(er(n,!0,!0))}));function Gr(n){for(var r=n&&Nr(n,Y).length||0,t=Array(r),e=0;e<r;e++)t[e]=Br(n,e);return t}var Hr=j(Gr);function Qr(n,r){return n._chain?tn(r).chain():r}function Xr(n){return mr(wn(n),(function(r){var t=tn[r]=n[r];tn.prototype[r]=function(){var n=[this._wrapped];return o.apply(n,arguments),Qr(this,t.apply(tn,n))}})),tn}mr(["pop","push","reverse","shift","sort","splice","unshift"],(function(n){var r=t[n];tn.prototype[n]=function(){var t=this._wrapped;return null!=t&&(r.apply(t,arguments),"shift"!==n&&"splice"!==n||0!==t.length||delete t[0]),Qr(this,t)}})),mr(["concat","join","slice"],(function(n){var r=t[n];tn.prototype[n]=function(){var n=this._wrapped;return null!=n&&(n=r.apply(n,arguments)),Qr(this,n)}}));var Yr=Xr({__proto__:null,VERSION:n,restArguments:j,isObject:_,isNull:function(n){return null===n},isUndefined:w,isBoolean:A,isElement:function(n){return!(!n||1!==n.nodeType)},isString:S,isNumber:O,isDate:M,isRegExp:E,isError:B,isSymbol:N,isArrayBuffer:I,isDataView:q,isArray:U,isFunction:D,isArguments:L,isFinite:function(n){return!N(n)&&g(n)&&!isNaN(parseFloat(n))},isNaN:C,isTypedArray:X,isEmpty:function(n){if(null==n)return!0;var r=Y(n);return"number"==typeof r&&(U(n)||S(n)||L(n))?0===r:0===Y(nn(n))},isMatch:rn,isEqual:function(n,r){return on(n,r)},isMap:gn,isWeakMap:dn,isSet:bn,isWeakSet:mn,keys:nn,allKeys:an,values:jn,pairs:function(n){for(var r=nn(n),t=r.length,e=Array(t),u=0;u<t;u++)e[u]=[r[u],n[r[u]]];return e},invert:_n,functions:wn,methods:wn,extend:xn,extendOwn:Sn,assign:Sn,defaults:On,create:function(n,r){var t=Mn(n);return r&&Sn(t,r),t},clone:En,tap:function(n,r){return r(n),n},get:kn,has:function(n,r){for(var t=(r=Nn(r)).length,e=0;e<t;e++){var u=r[e];if(!W(n,u))return!1;n=n[u]}return!!t},mapObject:function(n,r,t){r=qn(r,t);for(var e=nn(n),u=e.length,o={},i=0;i<u;i++){var a=e[i];o[a]=r(n[a],a,n)}return o},identity:Tn,constant:K,noop:Un,toPath:Bn,property:Rn,propertyOf:function(n){return null==n?Un:function(r){return kn(n,r)}},matcher:Dn,matches:Dn,times:function(n,r,t){var e=Array(Math.max(0,n));r=Fn(r,t,1);for(var u=0;u<n;u++)e[u]=r(u);return e},random:Wn,now:zn,escape:Kn,unescape:Jn,templateSettings:$n,template:function(n,r,t){!r&&t&&(r=t),r=On({},r,tn.templateSettings);var e,u=RegExp([(r.escape||Gn).source,(r.interpolate||Gn).source,(r.evaluate||Gn).source].join("|")+"|$","g"),o=0,i="__p+='";n.replace(u,(function(r,t,e,u,a){return i+=n.slice(o,a).replace(Qn,Xn),o=a+r.length,t?i+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":u&&(i+="';\n"+u+"\n__p+='"),r})),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{e=new Function(r.variable||"obj","_",i)}catch(n){throw n.source=i,n}var a=function(n){return e.call(this,n,tn)},f=r.variable||"obj";return a.source="function("+f+"){\n"+i+"}",a},result:function(n,r,t){var e=(r=Nn(r)).length;if(!e)return D(t)?t.call(n):t;for(var u=0;u<e;u++){var o=null==n?void 0:n[r[u]];void 0===o&&(o=t,u=e),n=D(o)?o.call(n):o}return n},uniqueId:function(n){var r=++Yn+"";return n?n+r:r},chain:function(n){var r=tn(n);return r._chain=!0,r},iteratee:Pn,partial:nr,bind:rr,bindAll:ur,memoize:function(n,r){var t=function(e){var u=t.cache,o=""+(r?r.apply(this,arguments):e);return W(u,o)||(u[o]=n.apply(this,arguments)),u[o]};return t.cache={},t},delay:or,defer:ir,throttle:function(n,r,t){var e,u,o,i,a=0;t||(t={});var f=function(){a=!1===t.leading?0:zn(),e=null,i=n.apply(u,o),e||(u=o=null)},c=function(){var c=zn();a||!1!==t.leading||(a=c);var l=r-(c-a);return u=this,o=arguments,l<=0||l>r?(e&&(clearTimeout(e),e=null),a=c,i=n.apply(u,o),e||(u=o=null)):e||!1===t.trailing||(e=setTimeout(f,l)),i};return c.cancel=function(){clearTimeout(e),a=0,e=u=o=null},c},debounce:function(n,r,t){var e,u,o=function(r,t){e=null,t&&(u=n.apply(r,t))},i=j((function(i){if(e&&clearTimeout(e),t){var a=!e;e=setTimeout(o,r),a&&(u=n.apply(this,i))}else e=or(o,r,this,i);return u}));return i.cancel=function(){clearTimeout(e),e=null},i},wrap:function(n,r){return nr(r,n)},negate:ar,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:fr,once:cr,findKey:lr,findIndex:pr,findLastIndex:vr,sortedIndex:hr,indexOf:gr,lastIndexOf:dr,find:br,detect:br,findWhere:function(n,r){return br(n,Dn(r))},each:mr,forEach:mr,map:jr,collect:jr,reduce:wr,foldl:wr,inject:wr,reduceRight:Ar,foldr:Ar,filter:xr,select:xr,reject:function(n,r,t){return xr(n,ar(qn(r)),t)},every:Sr,all:Sr,some:Or,any:Or,contains:Mr,includes:Mr,include:Mr,invoke:Er,pluck:Br,where:function(n,r){return xr(n,Dn(r))},max:Nr,min:function(n,r,t){var e,u,o=1/0,i=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;a<f;a++)null!=(e=n[a])&&e<o&&(o=e);else r=qn(r,t),mr(n,(function(n,t,e){((u=r(n,t,e))<i||u===1/0&&o===1/0)&&(o=n,i=u)}));return o},shuffle:function(n){return Ir(n,1/0)},sample:Ir,sortBy:function(n,r,t){var e=0;return r=qn(r,t),Br(jr(n,(function(n,t,u){return{value:n,index:e++,criteria:r(n,t,u)}})).sort((function(n,r){var t=n.criteria,e=r.criteria;if(t!==e){if(t>e||void 0===t)return 1;if(t<e||void 0===e)return-1}return n.index-r.index})),"value")},groupBy:Tr,indexBy:Dr,countBy:Rr,partition:Fr,toArray:function(n){return n?U(n)?i.call(n):S(n)?n.match(Vr):tr(n)?jr(n,Tn):jn(n):[]},size:function(n){return null==n?0:tr(n)?n.length:nn(n).length},pick:qr,omit:Ur,first:zr,head:zr,take:zr,initial:Wr,last:function(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[n.length-1]:Lr(n,Math.max(0,n.length-r))},rest:Lr,tail:Lr,drop:Lr,compact:function(n){return xr(n,Boolean)},flatten:function(n,r){return er(n,r,!1)},without:Kr,uniq:Jr,unique:Jr,union:$r,intersection:function(n){for(var r=[],t=arguments.length,e=0,u=Y(n);e<u;e++){var o=n[e];if(!Mr(r,o)){var i;for(i=1;i<t&&Mr(arguments[i],o);i++);i===t&&r.push(o)}}return r},difference:Cr,unzip:Gr,transpose:Gr,zip:Hr,object:function(n,r){for(var t={},e=0,u=Y(n);e<u;e++)r?t[n[e]]=r[e]:t[n[e][0]]=n[e][1];return t},range:function(n,r,t){null==r&&(r=n||0,n=0),t||(t=r<n?-1:1);for(var e=Math.max(Math.ceil((r-n)/t),0),u=Array(e),o=0;o<e;o++,n+=t)u[o]=n;return u},chunk:function(n,r){if(null==r||r<1)return[];for(var t=[],e=0,u=n.length;e<u;)t.push(i.call(n,e,e+=r));return t},mixin:Xr,default:tn});return Yr._=Yr,Yr}));
\ No newline at end of file
+var n="1.13.1",r="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},t=Array.prototype,e=Object.prototype,u="undefined"!=typeof Symbol?Symbol.prototype:null,o=t.push,i=t.slice,a=e.toString,f=e.hasOwnProperty,c="undefined"!=typeof ArrayBuffer,l="undefined"!=typeof DataView,s=Array.isArray,p=Object.keys,v=Object.create,h=c&&ArrayBuffer.isView,y=isNaN,d=isFinite,g=!{toString:null}.propertyIsEnumerable("toString"),b=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"],m=Math.pow(2,53)-1;function j(n,r){return r=null==r?n.length-1:+r,function(){for(var t=Math.max(arguments.length-r,0),e=Array(t),u=0;u<t;u++)e[u]=arguments[u+r];switch(r){case 0:return n.call(this,e);case 1:return n.call(this,arguments[0],e);case 2:return n.call(this,arguments[0],arguments[1],e)}var o=Array(r+1);for(u=0;u<r;u++)o[u]=arguments[u];return o[r]=e,n.apply(this,o)}}function _(n){var r=typeof n;return"function"===r||"object"===r&&!!n}function w(n){return void 0===n}function A(n){return!0===n||!1===n||"[object Boolean]"===a.call(n)}function x(n){var r="[object "+n+"]";return function(n){return a.call(n)===r}}var S=x("String"),O=x("Number"),M=x("Date"),E=x("RegExp"),B=x("Error"),N=x("Symbol"),I=x("ArrayBuffer"),T=x("Function"),k=r.document&&r.document.childNodes;"function"!=typeof/./&&"object"!=typeof Int8Array&&"function"!=typeof k&&(T=function(n){return"function"==typeof n||!1});var D=T,R=x("Object"),F=l&&R(new DataView(new ArrayBuffer(8))),V="undefined"!=typeof Map&&R(new Map),P=x("DataView");var q=F?function(n){return null!=n&&D(n.getInt8)&&I(n.buffer)}:P,U=s||x("Array");function W(n,r){return null!=n&&f.call(n,r)}var z=x("Arguments");!function(){z(arguments)||(z=function(n){return W(n,"callee")})}();var L=z;function $(n){return O(n)&&y(n)}function C(n){return function(){return n}}function K(n){return function(r){var t=n(r);return"number"==typeof t&&t>=0&&t<=m}}function J(n){return function(r){return null==r?void 0:r[n]}}var G=J("byteLength"),H=K(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:C(!1),Y=J("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e<t;++e)r[n[e]]=!0;return{contains:function(n){return r[n]},push:function(t){return r[t]=!0,n.push(t)}}}(r);var t=b.length,u=n.constructor,o=D(u)&&u.prototype||e,i="constructor";for(W(n,i)&&!r.contains(i)&&r.push(i);t--;)(i=b[t])in n&&n[i]!==o[i]&&!r.contains(i)&&r.push(i)}function nn(n){if(!_(n))return[];if(p)return p(n);var r=[];for(var t in n)W(n,t)&&r.push(t);return g&&Z(n,r),r}function rn(n,r){var t=nn(r),e=t.length;if(null==n)return!e;for(var u=Object(n),o=0;o<e;o++){var i=t[o];if(r[i]!==u[i]||!(i in u))return!1}return!0}function tn(n){return n instanceof tn?n:this instanceof tn?void(this._wrapped=n):new tn(n)}function en(n){return new Uint8Array(n.buffer||n,n.byteOffset||0,G(n))}tn.VERSION=n,tn.prototype.value=function(){return this._wrapped},tn.prototype.valueOf=tn.prototype.toJSON=tn.prototype.value,tn.prototype.toString=function(){return String(this._wrapped)};var un="[object DataView]";function on(n,r,t,e){if(n===r)return 0!==n||1/n==1/r;if(null==n||null==r)return!1;if(n!=n)return r!=r;var o=typeof n;return("function"===o||"object"===o||"object"==typeof r)&&function n(r,t,e,o){r instanceof tn&&(r=r._wrapped);t instanceof tn&&(t=t._wrapped);var i=a.call(r);if(i!==a.call(t))return!1;if(F&&"[object Object]"==i&&q(r)){if(!q(t))return!1;i=un}switch(i){case"[object RegExp]":case"[object String]":return""+r==""+t;case"[object Number]":return+r!=+r?+t!=+t:0==+r?1/+r==1/t:+r==+t;case"[object Date]":case"[object Boolean]":return+r==+t;case"[object Symbol]":return u.valueOf.call(r)===u.valueOf.call(t);case"[object ArrayBuffer]":case un:return n(en(r),en(t),e,o)}var f="[object Array]"===i;if(!f&&X(r)){if(G(r)!==G(t))return!1;if(r.buffer===t.buffer&&r.byteOffset===t.byteOffset)return!0;f=!0}if(!f){if("object"!=typeof r||"object"!=typeof t)return!1;var c=r.constructor,l=t.constructor;if(c!==l&&!(D(c)&&c instanceof c&&D(l)&&l instanceof l)&&"constructor"in r&&"constructor"in t)return!1}o=o||[];var s=(e=e||[]).length;for(;s--;)if(e[s]===r)return o[s]===t;if(e.push(r),o.push(t),f){if((s=r.length)!==t.length)return!1;for(;s--;)if(!on(r[s],t[s],e,o))return!1}else{var p,v=nn(r);if(s=v.length,nn(t).length!==s)return!1;for(;s--;)if(p=v[s],!W(t,p)||!on(r[p],t[p],e,o))return!1}return e.pop(),o.pop(),!0}(n,r,t,e)}function an(n){if(!_(n))return[];var r=[];for(var t in n)r.push(t);return g&&Z(n,r),r}function fn(n){var r=Y(n);return function(t){if(null==t)return!1;var e=an(t);if(Y(e))return!1;for(var u=0;u<r;u++)if(!D(t[n[u]]))return!1;return n!==hn||!D(t[cn])}}var cn="forEach",ln="has",sn=["clear","delete"],pn=["get",ln,"set"],vn=sn.concat(cn,pn),hn=sn.concat(pn),yn=["add"].concat(sn,cn,ln),dn=V?fn(vn):x("Map"),gn=V?fn(hn):x("WeakMap"),bn=V?fn(yn):x("Set"),mn=x("WeakSet");function jn(n){for(var r=nn(n),t=r.length,e=Array(t),u=0;u<t;u++)e[u]=n[r[u]];return e}function _n(n){for(var r={},t=nn(n),e=0,u=t.length;e<u;e++)r[n[t[e]]]=t[e];return r}function wn(n){var r=[];for(var t in n)D(n[t])&&r.push(t);return r.sort()}function An(n,r){return function(t){var e=arguments.length;if(r&&(t=Object(t)),e<2||null==t)return t;for(var u=1;u<e;u++)for(var o=arguments[u],i=n(o),a=i.length,f=0;f<a;f++){var c=i[f];r&&void 0!==t[c]||(t[c]=o[c])}return t}}var xn=An(an),Sn=An(nn),On=An(an,!0);function Mn(n){if(!_(n))return{};if(v)return v(n);var r=function(){};r.prototype=n;var t=new r;return r.prototype=null,t}function En(n){return _(n)?U(n)?n.slice():xn({},n):n}function Bn(n){return U(n)?n:[n]}function Nn(n){return tn.toPath(n)}function In(n,r){for(var t=r.length,e=0;e<t;e++){if(null==n)return;n=n[r[e]]}return t?n:void 0}function Tn(n,r,t){var e=In(n,Nn(r));return w(e)?t:e}function kn(n){return n}function Dn(n){return n=Sn({},n),function(r){return rn(r,n)}}function Rn(n){return n=Nn(n),function(r){return In(r,n)}}function Fn(n,r,t){if(void 0===r)return n;switch(null==t?3:t){case 1:return function(t){return n.call(r,t)};case 3:return function(t,e,u){return n.call(r,t,e,u)};case 4:return function(t,e,u,o){return n.call(r,t,e,u,o)}}return function(){return n.apply(r,arguments)}}function Vn(n,r,t){return null==n?kn:D(n)?Fn(n,r,t):_(n)&&!U(n)?Dn(n):Rn(n)}function Pn(n,r){return Vn(n,r,1/0)}function qn(n,r,t){return tn.iteratee!==Pn?tn.iteratee(n,r):Vn(n,r,t)}function Un(){}function Wn(n,r){return null==r&&(r=n,n=0),n+Math.floor(Math.random()*(r-n+1))}tn.toPath=Bn,tn.iteratee=Pn;var zn=Date.now||function(){return(new Date).getTime()};function Ln(n){var r=function(r){return n[r]},t="(?:"+nn(n).join("|")+")",e=RegExp(t),u=RegExp(t,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,r):n}}var $n={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},Cn=Ln($n),Kn=Ln(_n($n)),Jn=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Gn=/(.)^/,Hn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Qn=/\\|'|\r|\n|\u2028|\u2029/g;function Xn(n){return"\\"+Hn[n]}var Yn=/^\s*(\w|\$)+\s*$/;var Zn=0;function nr(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var o=Mn(n.prototype),i=n.apply(o,u);return _(i)?i:o}var rr=j((function(n,r){var t=rr.placeholder,e=function(){for(var u=0,o=r.length,i=Array(o),a=0;a<o;a++)i[a]=r[a]===t?arguments[u++]:r[a];for(;u<arguments.length;)i.push(arguments[u++]);return nr(n,e,this,this,i)};return e}));rr.placeholder=tn;var tr=j((function(n,r,t){if(!D(n))throw new TypeError("Bind must be called on a function");var e=j((function(u){return nr(n,e,r,this,t.concat(u))}));return e})),er=K(Y);function ur(n,r,t,e){if(e=e||[],r||0===r){if(r<=0)return e.concat(n)}else r=1/0;for(var u=e.length,o=0,i=Y(n);o<i;o++){var a=n[o];if(er(a)&&(U(a)||L(a)))if(r>1)ur(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f<c;)e[u++]=a[f++];else t||(e[u++]=a)}return e}var or=j((function(n,r){var t=(r=ur(r,!1,!1)).length;if(t<1)throw new Error("bindAll must be passed function names");for(;t--;){var e=r[t];n[e]=tr(n[e],n)}return n}));var ir=j((function(n,r,t){return setTimeout((function(){return n.apply(null,t)}),r)})),ar=rr(ir,tn,1);function fr(n){return function(){return!n.apply(this,arguments)}}function cr(n,r){var t;return function(){return--n>0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var lr=rr(cr,2);function sr(n,r,t){r=qn(r,t);for(var e,u=nn(n),o=0,i=u.length;o<i;o++)if(r(n[e=u[o]],e,n))return e}function pr(n){return function(r,t,e){t=qn(t,e);for(var u=Y(r),o=n>0?0:u-1;o>=0&&o<u;o+=n)if(t(r[o],o,r))return o;return-1}}var vr=pr(1),hr=pr(-1);function yr(n,r,t,e){for(var u=(t=qn(t,e,1))(r),o=0,i=Y(n);o<i;){var a=Math.floor((o+i)/2);t(n[a])<u?o=a+1:i=a}return o}function dr(n,r,t){return function(e,u,o){var a=0,f=Y(e);if("number"==typeof o)n>0?a=o>=0?o:Math.max(o+f,a):f=o>=0?Math.min(o+1,f):o+f+1;else if(t&&o&&f)return e[o=t(e,u)]===u?o:-1;if(u!=u)return(o=r(i.call(e,a,f),$))>=0?o+a:-1;for(o=n>0?a:f-1;o>=0&&o<f;o+=n)if(e[o]===u)return o;return-1}}var gr=dr(1,vr,yr),br=dr(-1,hr);function mr(n,r,t){var e=(er(n)?vr:sr)(n,r,t);if(void 0!==e&&-1!==e)return n[e]}function jr(n,r,t){var e,u;if(r=Fn(r,t),er(n))for(e=0,u=n.length;e<u;e++)r(n[e],e,n);else{var o=nn(n);for(e=0,u=o.length;e<u;e++)r(n[o[e]],o[e],n)}return n}function _r(n,r,t){r=qn(r,t);for(var e=!er(n)&&nn(n),u=(e||n).length,o=Array(u),i=0;i<u;i++){var a=e?e[i]:i;o[i]=r(n[a],a,n)}return o}function wr(n){var r=function(r,t,e,u){var o=!er(r)&&nn(r),i=(o||r).length,a=n>0?0:i-1;for(u||(e=r[o?o[a]:a],a+=n);a>=0&&a<i;a+=n){var f=o?o[a]:a;e=t(e,r[f],f,r)}return e};return function(n,t,e,u){var o=arguments.length>=3;return r(n,Fn(t,u,4),e,o)}}var Ar=wr(1),xr=wr(-1);function Sr(n,r,t){var e=[];return r=qn(r,t),jr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Or(n,r,t){r=qn(r,t);for(var e=!er(n)&&nn(n),u=(e||n).length,o=0;o<u;o++){var i=e?e[o]:o;if(!r(n[i],i,n))return!1}return!0}function Mr(n,r,t){r=qn(r,t);for(var e=!er(n)&&nn(n),u=(e||n).length,o=0;o<u;o++){var i=e?e[o]:o;if(r(n[i],i,n))return!0}return!1}function Er(n,r,t,e){return er(n)||(n=jn(n)),("number"!=typeof t||e)&&(t=0),gr(n,r,t)>=0}var Br=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Nn(r),e=r.slice(0,-1),r=r[r.length-1]),_r(n,(function(n){var o=u;if(!o){if(e&&e.length&&(n=In(n,e)),null==n)return;o=n[r]}return null==o?o:o.apply(n,t)}))}));function Nr(n,r){return _r(n,Rn(r))}function Ir(n,r,t){var e,u,o=-1/0,i=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=er(n)?n:jn(n)).length;a<f;a++)null!=(e=n[a])&&e>o&&(o=e);else r=qn(r,t),jr(n,(function(n,t,e){((u=r(n,t,e))>i||u===-1/0&&o===-1/0)&&(o=n,i=u)}));return o}function Tr(n,r,t){if(null==r||t)return er(n)||(n=jn(n)),n[Wn(n.length-1)];var e=er(n)?En(n):jn(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var o=u-1,i=0;i<r;i++){var a=Wn(i,o),f=e[i];e[i]=e[a],e[a]=f}return e.slice(0,r)}function kr(n,r){return function(t,e,u){var o=r?[[],[]]:{};return e=qn(e,u),jr(t,(function(r,u){var i=e(r,u,t);n(o,r,i)})),o}}var Dr=kr((function(n,r,t){W(n,t)?n[t].push(r):n[t]=[r]})),Rr=kr((function(n,r,t){n[t]=r})),Fr=kr((function(n,r,t){W(n,t)?n[t]++:n[t]=1})),Vr=kr((function(n,r,t){n[t?0:1].push(r)}),!0),Pr=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;function qr(n,r,t){return r in t}var Ur=j((function(n,r){var t={},e=r[0];if(null==n)return t;D(e)?(r.length>1&&(e=Fn(e,r[1])),r=an(n)):(e=qr,r=ur(r,!1,!1),n=Object(n));for(var u=0,o=r.length;u<o;u++){var i=r[u],a=n[i];e(a,i,n)&&(t[i]=a)}return t})),Wr=j((function(n,r){var t,e=r[0];return D(e)?(e=fr(e),r.length>1&&(t=r[1])):(r=_r(ur(r,!1,!1),String),e=function(n,t){return!Er(r,t)}),Ur(n,e,t)}));function zr(n,r,t){return i.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function Lr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:zr(n,n.length-r)}function $r(n,r,t){return i.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=ur(r,!0,!0),Sr(n,(function(n){return!Er(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=qn(t,e));for(var u=[],o=[],i=0,a=Y(n);i<a;i++){var f=n[i],c=t?t(f,i,n):f;r&&!t?(i&&o===c||u.push(f),o=c):t?Er(o,c)||(o.push(c),u.push(f)):Er(u,f)||u.push(f)}return u}var Gr=j((function(n){return Jr(ur(n,!0,!0))}));function Hr(n){for(var r=n&&Ir(n,Y).length||0,t=Array(r),e=0;e<r;e++)t[e]=Nr(n,e);return t}var Qr=j(Hr);function Xr(n,r){return n._chain?tn(r).chain():r}function Yr(n){return jr(wn(n),(function(r){var t=tn[r]=n[r];tn.prototype[r]=function(){var n=[this._wrapped];return o.apply(n,arguments),Xr(this,t.apply(tn,n))}})),tn}jr(["pop","push","reverse","shift","sort","splice","unshift"],(function(n){var r=t[n];tn.prototype[n]=function(){var t=this._wrapped;return null!=t&&(r.apply(t,arguments),"shift"!==n&&"splice"!==n||0!==t.length||delete t[0]),Xr(this,t)}})),jr(["concat","join","slice"],(function(n){var r=t[n];tn.prototype[n]=function(){var n=this._wrapped;return null!=n&&(n=r.apply(n,arguments)),Xr(this,n)}}));var Zr=Yr({__proto__:null,VERSION:n,restArguments:j,isObject:_,isNull:function(n){return null===n},isUndefined:w,isBoolean:A,isElement:function(n){return!(!n||1!==n.nodeType)},isString:S,isNumber:O,isDate:M,isRegExp:E,isError:B,isSymbol:N,isArrayBuffer:I,isDataView:q,isArray:U,isFunction:D,isArguments:L,isFinite:function(n){return!N(n)&&d(n)&&!isNaN(parseFloat(n))},isNaN:$,isTypedArray:X,isEmpty:function(n){if(null==n)return!0;var r=Y(n);return"number"==typeof r&&(U(n)||S(n)||L(n))?0===r:0===Y(nn(n))},isMatch:rn,isEqual:function(n,r){return on(n,r)},isMap:dn,isWeakMap:gn,isSet:bn,isWeakSet:mn,keys:nn,allKeys:an,values:jn,pairs:function(n){for(var r=nn(n),t=r.length,e=Array(t),u=0;u<t;u++)e[u]=[r[u],n[r[u]]];return e},invert:_n,functions:wn,methods:wn,extend:xn,extendOwn:Sn,assign:Sn,defaults:On,create:function(n,r){var t=Mn(n);return r&&Sn(t,r),t},clone:En,tap:function(n,r){return r(n),n},get:Tn,has:function(n,r){for(var t=(r=Nn(r)).length,e=0;e<t;e++){var u=r[e];if(!W(n,u))return!1;n=n[u]}return!!t},mapObject:function(n,r,t){r=qn(r,t);for(var e=nn(n),u=e.length,o={},i=0;i<u;i++){var a=e[i];o[a]=r(n[a],a,n)}return o},identity:kn,constant:C,noop:Un,toPath:Bn,property:Rn,propertyOf:function(n){return null==n?Un:function(r){return Tn(n,r)}},matcher:Dn,matches:Dn,times:function(n,r,t){var e=Array(Math.max(0,n));r=Fn(r,t,1);for(var u=0;u<n;u++)e[u]=r(u);return e},random:Wn,now:zn,escape:Cn,unescape:Kn,templateSettings:Jn,template:function(n,r,t){!r&&t&&(r=t),r=On({},r,tn.templateSettings);var e=RegExp([(r.escape||Gn).source,(r.interpolate||Gn).source,(r.evaluate||Gn).source].join("|")+"|$","g"),u=0,o="__p+='";n.replace(e,(function(r,t,e,i,a){return o+=n.slice(u,a).replace(Qn,Xn),u=a+r.length,t?o+="'+\n((__t=("+t+"))==null?'':_.escape(__t))+\n'":e?o+="'+\n((__t=("+e+"))==null?'':__t)+\n'":i&&(o+="';\n"+i+"\n__p+='"),r})),o+="';\n";var i,a=r.variable;if(a){if(!Yn.test(a))throw new Error("variable is not a bare identifier: "+a)}else o="with(obj||{}){\n"+o+"}\n",a="obj";o="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{i=new Function(a,"_",o)}catch(n){throw n.source=o,n}var f=function(n){return i.call(this,n,tn)};return f.source="function("+a+"){\n"+o+"}",f},result:function(n,r,t){var e=(r=Nn(r)).length;if(!e)return D(t)?t.call(n):t;for(var u=0;u<e;u++){var o=null==n?void 0:n[r[u]];void 0===o&&(o=t,u=e),n=D(o)?o.call(n):o}return n},uniqueId:function(n){var r=++Zn+"";return n?n+r:r},chain:function(n){var r=tn(n);return r._chain=!0,r},iteratee:Pn,partial:rr,bind:tr,bindAll:or,memoize:function(n,r){var t=function(e){var u=t.cache,o=""+(r?r.apply(this,arguments):e);return W(u,o)||(u[o]=n.apply(this,arguments)),u[o]};return t.cache={},t},delay:ir,defer:ar,throttle:function(n,r,t){var e,u,o,i,a=0;t||(t={});var f=function(){a=!1===t.leading?0:zn(),e=null,i=n.apply(u,o),e||(u=o=null)},c=function(){var c=zn();a||!1!==t.leading||(a=c);var l=r-(c-a);return u=this,o=arguments,l<=0||l>r?(e&&(clearTimeout(e),e=null),a=c,i=n.apply(u,o),e||(u=o=null)):e||!1===t.trailing||(e=setTimeout(f,l)),i};return c.cancel=function(){clearTimeout(e),a=0,e=u=o=null},c},debounce:function(n,r,t){var e,u,o,i,a,f=function(){var c=zn()-u;r>c?e=setTimeout(f,r-c):(e=null,t||(i=n.apply(a,o)),e||(o=a=null))},c=j((function(c){return a=this,o=c,u=zn(),e||(e=setTimeout(f,r),t&&(i=n.apply(a,o))),i}));return c.cancel=function(){clearTimeout(e),e=o=a=null},c},wrap:function(n,r){return rr(r,n)},negate:fr,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:cr,once:lr,findKey:sr,findIndex:vr,findLastIndex:hr,sortedIndex:yr,indexOf:gr,lastIndexOf:br,find:mr,detect:mr,findWhere:function(n,r){return mr(n,Dn(r))},each:jr,forEach:jr,map:_r,collect:_r,reduce:Ar,foldl:Ar,inject:Ar,reduceRight:xr,foldr:xr,filter:Sr,select:Sr,reject:function(n,r,t){return Sr(n,fr(qn(r)),t)},every:Or,all:Or,some:Mr,any:Mr,contains:Er,includes:Er,include:Er,invoke:Br,pluck:Nr,where:function(n,r){return Sr(n,Dn(r))},max:Ir,min:function(n,r,t){var e,u,o=1/0,i=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=er(n)?n:jn(n)).length;a<f;a++)null!=(e=n[a])&&e<o&&(o=e);else r=qn(r,t),jr(n,(function(n,t,e){((u=r(n,t,e))<i||u===1/0&&o===1/0)&&(o=n,i=u)}));return o},shuffle:function(n){return Tr(n,1/0)},sample:Tr,sortBy:function(n,r,t){var e=0;return r=qn(r,t),Nr(_r(n,(function(n,t,u){return{value:n,index:e++,criteria:r(n,t,u)}})).sort((function(n,r){var t=n.criteria,e=r.criteria;if(t!==e){if(t>e||void 0===t)return 1;if(t<e||void 0===e)return-1}return n.index-r.index})),"value")},groupBy:Dr,indexBy:Rr,countBy:Fr,partition:Vr,toArray:function(n){return n?U(n)?i.call(n):S(n)?n.match(Pr):er(n)?_r(n,kn):jn(n):[]},size:function(n){return null==n?0:er(n)?n.length:nn(n).length},pick:Ur,omit:Wr,first:Lr,head:Lr,take:Lr,initial:zr,last:function(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[n.length-1]:$r(n,Math.max(0,n.length-r))},rest:$r,tail:$r,drop:$r,compact:function(n){return Sr(n,Boolean)},flatten:function(n,r){return ur(n,r,!1)},without:Kr,uniq:Jr,unique:Jr,union:Gr,intersection:function(n){for(var r=[],t=arguments.length,e=0,u=Y(n);e<u;e++){var o=n[e];if(!Er(r,o)){var i;for(i=1;i<t&&Er(arguments[i],o);i++);i===t&&r.push(o)}}return r},difference:Cr,unzip:Hr,transpose:Hr,zip:Qr,object:function(n,r){for(var t={},e=0,u=Y(n);e<u;e++)r?t[n[e]]=r[e]:t[n[e][0]]=n[e][1];return t},range:function(n,r,t){null==r&&(r=n||0,n=0),t||(t=r<n?-1:1);for(var e=Math.max(Math.ceil((r-n)/t),0),u=Array(e),o=0;o<e;o++,n+=t)u[o]=n;return u},chunk:function(n,r){if(null==r||r<1)return[];for(var t=[],e=0,u=n.length;e<u;)t.push(i.call(n,e,e+=r));return t},mixin:Yr,default:tn});return Zr._=Zr,Zr}));
\ No newline at end of file
diff --git a/docs/dev_guide.html b/docs/dev_guide.html
index fb62b799..48340263 100644
--- a/docs/dev_guide.html
+++ b/docs/dev_guide.html
@@ -6,10 +6,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Developers Guide &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Developers Guide &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -38,30 +38,54 @@ <h1><a class="toc-backref" href="#contents">Developers Guide</a><a class="header
 <div class="contents topic" id="contents">
 <p class="topic-title">Contents</p>
 <ul class="simple">
-<li><p><a class="reference internal" href="#developers-guide" id="id1">Developers Guide</a></p>
+<li><p><a class="reference internal" href="#developers-guide" id="id2">Developers Guide</a></p>
 <ul>
-<li><p><a class="reference internal" href="#branching-strategy" id="id2">Branching Strategy</a></p>
+<li><p><a class="reference internal" href="#guidance-for-contributors" id="id3">Guidance for Contributors</a></p></li>
+<li><p><a class="reference internal" href="#branching-strategy" id="id4">Branching Strategy</a></p>
 <ul>
-<li><p><a class="reference internal" href="#release-checklist" id="id3">Release Checklist</a></p></li>
+<li><p><a class="reference internal" href="#release-checklist" id="id5">Release Checklist</a></p></li>
 </ul>
 </li>
-<li><p><a class="reference internal" href="#npr-7150" id="id4">NPR 7150</a></p>
+<li><p><a class="reference internal" href="#npr-7150" id="id6">NPR 7150</a></p>
 <ul>
-<li><p><a class="reference internal" href="#compliance-notation-legend" id="id5">Compliance Notation Legend</a></p></li>
-<li><p><a class="reference internal" href="#compliance-matrix" id="id6">Compliance Matrix</a></p></li>
-<li><p><a class="reference internal" href="#aquisition-options" id="id7">Aquisition Options</a></p></li>
-<li><p><a class="reference internal" href="#cybersecurity-assessment" id="id8">Cybersecurity Assessment</a></p></li>
+<li><p><a class="reference internal" href="#compliance-notation-legend" id="id7">Compliance Notation Legend</a></p></li>
+<li><p><a class="reference internal" href="#compliance-matrix" id="id8">Compliance Matrix</a></p></li>
+<li><p><a class="reference internal" href="#aquisition-options" id="id9">Aquisition Options</a></p></li>
+<li><p><a class="reference internal" href="#cybersecurity-assessment" id="id10">Cybersecurity Assessment</a></p></li>
 </ul>
 </li>
 </ul>
 </li>
 </ul>
 </div>
+<section id="guidance-for-contributors">
+<h2><a class="toc-backref" href="#contents">Guidance for Contributors</a><a class="headerlink" href="#guidance-for-contributors" title="Permalink to this headline">¶</a></h2>
+<p>Below are a few design decision made by the authors, documented for the reference of new contributors:</p>
+<ul class="simple">
+<li><p>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.</p></li>
+<li><p>Visualize and metrics subpackages shall be independent (i.e., not have any dependencies with the wider package or other subpackages)</p></li>
+<li><p>Limit introduction of new external dependencies, when possible.</p></li>
+<li><p>This is a research tool, so when making a design decision between operational efficiency and usability, generally choose the more usable option</p></li>
+<li><p>Except in the most extreme cases, maintain backwards compatibility for the convenience of existing users</p>
+<ul>
+<li><p>If a feature is to be removed, mark it as depreciated for at least 2 releases before removing</p></li>
+</ul>
+</li>
+<li><p>Whenever possible, design so a new feature can be used for any model, interchangably.</p></li>
+<li><p>Whenever possible, UncertainData types, state estimators, and predictors should be interchangable</p></li>
+<li><p>Demonstrate common use cases as an example.</p></li>
+<li><p>Every feature should be demonstrated in an example</p>
+<ul>
+<li><p>The most commonly used features should be demonstrated in the tutorial</p></li>
+</ul>
+</li>
+</ul>
+</section>
 <section id="branching-strategy">
 <h2><a class="toc-backref" href="#contents">Branching Strategy</a><a class="headerlink" href="#branching-strategy" title="Permalink to this headline">¶</a></h2>
-<p>Our project is following the git strategy described <a class="reference external" href="https://nvie.com/posts/a-successful-git-branching-model/">here</a>. Release branches are not required. Details specific to each branch are described below.</p>
+<p>Our project is following the git strategy described <a class="reference external" href="https://nvie.com/posts/a-successful-git-branching-model/">here</a>. Details specific to each branch are described below.</p>
 <p><cite>master</cite>: 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.</p>
-<p><cite>dev</cite>: Every commit on the dev branch should be functional. All unit tests must function before commiting to dev or merging another branch.</p>
+<p><cite>dev</cite>: 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.</p>
 <p><cite>Feature Branches</cite>: 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.</p>
 <section id="release-checklist">
 <h3><a class="toc-backref" href="#contents">Release Checklist</a><a class="headerlink" href="#release-checklist" title="Permalink to this headline">¶</a></h3>
@@ -78,6 +102,7 @@ <h3><a class="toc-backref" href="#contents">Release Checklist</a><a class="heade
 </section>
 <section id="npr-7150">
 <h2><a class="toc-backref" href="#contents">NPR 7150</a><a class="headerlink" href="#npr-7150" title="Permalink to this headline">¶</a></h2>
+<p>This section describes this project’s compliance with the NASA’s NPR 7150 requirements, documented <a class="reference external" href="https://nodis3.gsfc.nasa.gov/displayDir.cfm?t=NPR&amp;c=7150&amp;s=2B">here</a>.</p>
 <ul class="simple">
 <li><p>Software Classification: Class-E (Research Software)</p></li>
 <li><p>Safety Criticality: Not Safety Critical</p></li>
@@ -206,18 +231,22 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">Developers Guide</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="#guidance-for-contributors">Guidance for Contributors</a></li>
 <li class="toctree-l2"><a class="reference internal" href="#branching-strategy">Branching Strategy</a></li>
 <li class="toctree-l2"><a class="reference internal" href="#npr-7150">NPR 7150</a></li>
 </ul>
 </li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -232,7 +261,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/exceptions.html b/docs/exceptions.html
index 1b1c138d..068943d1 100644
--- a/docs/exceptions.html
+++ b/docs/exceptions.html
@@ -6,17 +6,17 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Exceptions &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Exceptions &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
     <link rel="index" title="Index" href="genindex.html" />
     <link rel="search" title="Search" href="search.html" />
     <link rel="next" title="Developers Guide" href="dev_guide.html" />
-    <link rel="prev" title="Visualization" href="visualize.html" />
+    <link rel="prev" title="Metrics" href="metrics.html" />
    
   <link rel="stylesheet" href="_static/custom.css" type="text/css" />
   
@@ -39,12 +39,12 @@ <h1>Exceptions<a class="headerlink" href="#exceptions" title="Permalink to this
 <section id="prognostics-model-exceptions">
 <h2>Prognostics Model Exceptions<a class="headerlink" href="#prognostics-model-exceptions" title="Permalink to this headline">¶</a></h2>
 <dl class="py exception">
-<dt id="prog_algs.exceptions.ProgAlgException">
-<em class="property"><span class="pre">exception</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.exceptions.</span></code><code class="sig-name descname"><span class="pre">ProgAlgException</span></code><a class="headerlink" href="#prog_algs.exceptions.ProgAlgException" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.exceptions.ProgAlgException">
+<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.exceptions.</span></span><span class="sig-name descname"><span class="pre">ProgAlgException</span></span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgException" title="Permalink to this definition">¶</a></dt>
 <dd><p>Base Prognostics Model Exception</p>
 <dl class="py method">
-<dt id="prog_algs.exceptions.ProgAlgException.with_traceback">
-<code class="sig-name descname"><span class="pre">with_traceback</span></code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgException.with_traceback" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.exceptions.ProgAlgException.with_traceback">
+<span class="sig-name descname"><span class="pre">with_traceback</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgException.with_traceback" title="Permalink to this definition">¶</a></dt>
 <dd><p>Exception.with_traceback(tb) –
 set self.__traceback__ to tb and return self.</p>
 </dd></dl>
@@ -52,12 +52,12 @@ <h2>Prognostics Model Exceptions<a class="headerlink" href="#prognostics-model-e
 </dd></dl>
 
 <dl class="py exception">
-<dt id="prog_algs.exceptions.ProgAlgInputException">
-<em class="property"><span class="pre">exception</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.exceptions.</span></code><code class="sig-name descname"><span class="pre">ProgAlgInputException</span></code><a class="headerlink" href="#prog_algs.exceptions.ProgAlgInputException" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.exceptions.ProgAlgInputException">
+<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.exceptions.</span></span><span class="sig-name descname"><span class="pre">ProgAlgInputException</span></span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgInputException" title="Permalink to this definition">¶</a></dt>
 <dd><p>Prognostics Input Exception - indicates the method input parameters were incorrect</p>
 <dl class="py method">
-<dt id="prog_algs.exceptions.ProgAlgInputException.with_traceback">
-<code class="sig-name descname"><span class="pre">with_traceback</span></code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgInputException.with_traceback" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.exceptions.ProgAlgInputException.with_traceback">
+<span class="sig-name descname"><span class="pre">with_traceback</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgInputException.with_traceback" title="Permalink to this definition">¶</a></dt>
 <dd><p>Exception.with_traceback(tb) –
 set self.__traceback__ to tb and return self.</p>
 </dd></dl>
@@ -65,12 +65,12 @@ <h2>Prognostics Model Exceptions<a class="headerlink" href="#prognostics-model-e
 </dd></dl>
 
 <dl class="py exception">
-<dt id="prog_algs.exceptions.ProgAlgTypeError">
-<em class="property"><span class="pre">exception</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.exceptions.</span></code><code class="sig-name descname"><span class="pre">ProgAlgTypeError</span></code><a class="headerlink" href="#prog_algs.exceptions.ProgAlgTypeError" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.exceptions.ProgAlgTypeError">
+<em class="property"><span class="pre">exception</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.exceptions.</span></span><span class="sig-name descname"><span class="pre">ProgAlgTypeError</span></span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgTypeError" title="Permalink to this definition">¶</a></dt>
 <dd><p>Prognostics Type Error - indicates the model could not be constructed</p>
 <dl class="py method">
-<dt id="prog_algs.exceptions.ProgAlgTypeError.with_traceback">
-<code class="sig-name descname"><span class="pre">with_traceback</span></code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgTypeError.with_traceback" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.exceptions.ProgAlgTypeError.with_traceback">
+<span class="sig-name descname"><span class="pre">with_traceback</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.exceptions.ProgAlgTypeError.with_traceback" title="Permalink to this definition">¶</a></dt>
 <dd><p>Exception.with_traceback(tb) –
 set self.__traceback__ to tb and return self.</p>
 </dd></dl>
@@ -98,24 +98,27 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">Exceptions</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="#prognostics-model-exceptions">Prognostics Model Exceptions</a></li>
 </ul>
 </li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
 <h3>Related Topics</h3>
 <ul>
   <li><a href="index.html">Documentation overview</a><ul>
-      <li>Previous: <a href="visualize.html" title="previous chapter">Visualization</a></li>
+      <li>Previous: <a href="metrics.html" title="previous chapter">Metrics</a></li>
       <li>Next: <a href="dev_guide.html" title="next chapter">Developers Guide</a></li>
   </ul></li>
 </ul>
@@ -124,7 +127,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/genindex.html b/docs/genindex.html
index 06dd3a58..dc987361 100644
--- a/docs/genindex.html
+++ b/docs/genindex.html
@@ -5,10 +5,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Index &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Index &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -37,11 +37,15 @@ <h1 id="index">Index</h1>
  <a href="#A"><strong>A</strong></a>
  | <a href="#C"><strong>C</strong></a>
  | <a href="#E"><strong>E</strong></a>
+ | <a href="#G"><strong>G</strong></a>
+ | <a href="#I"><strong>I</strong></a>
+ | <a href="#K"><strong>K</strong></a>
  | <a href="#M"><strong>M</strong></a>
  | <a href="#P"><strong>P</strong></a>
- | <a href="#R"><strong>R</strong></a>
  | <a href="#S"><strong>S</strong></a>
+ | <a href="#T"><strong>T</strong></a>
  | <a href="#U"><strong>U</strong></a>
+ | <a href="#V"><strong>V</strong></a>
  | <a href="#W"><strong>W</strong></a>
  | <a href="#X"><strong>X</strong></a>
  
@@ -49,11 +53,11 @@ <h1 id="index">Index</h1>
 <h2 id="A">A</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="metrics.html#prog_algs.metrics.samples.alpha_lambda">alpha_lambda() (in module prog_algs.metrics.samples)</a>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.add_prediction">add_prediction() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.append">append() (prog_algs.uncertain_data.UnweightedSamples method)</a>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.alpha_lambda">alpha_lambda() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -61,48 +65,48 @@ <h2 id="A">A</h2>
 <h2 id="C">C</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.MultivariateNormalDist.cov">cov() (prog_algs.uncertain_data.MultivariateNormalDist property)</a>
-
-      <ul>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.ScalarData.cov">(prog_algs.uncertain_data.ScalarData property)</a>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.clear">clear() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.cov">(prog_algs.uncertain_data.UncertainData property)</a>
+  </ul></td>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="prediction.html#prog_algs.predictors.UnweightedSamplesPrediction.count">count() (prog_algs.predictors.UnweightedSamplesPrediction method)</a>
 </li>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.cov">(prog_algs.uncertain_data.UnweightedSamples property)</a>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.cov">cov (prog_algs.uncertain_data.UncertainData property)</a>
 </li>
-      </ul></li>
   </ul></td>
 </tr></table>
 
 <h2 id="E">E</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="metrics.html#prog_algs.metrics.samples.eol_metrics">eol_metrics() (in module prog_algs.metrics.samples)</a>
-</li>
-      <li><a href="metrics.html#prog_algs.metrics.samples.eol_profile_metrics">eol_profile_metrics() (in module prog_algs.metrics.samples)</a>
+      <li><a href="state_estimators.html#prog_algs.state_estimators.StateEstimator.estimate">estimate() (prog_algs.state_estimators.StateEstimator method)</a>
 </li>
-      <li><a href="state_estimators.html#prog_algs.state_estimators.particle_filter.ParticleFilter.estimate">estimate() (prog_algs.state_estimators.particle_filter.ParticleFilter method)</a>
+      <li>
+    examples.basic_example
 
       <ul>
-        <li><a href="state_estimators.html#prog_algs.state_estimators.state_estimator.StateEstimator.estimate">(prog_algs.state_estimators.state_estimator.StateEstimator method)</a>
+        <li><a href="getting_started.html#module-examples.basic_example">module</a>
 </li>
-        <li><a href="state_estimators.html#prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter.estimate">(prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter method)</a>
+      </ul></li>
+      <li>
+    examples.benchmarking_example
+
+      <ul>
+        <li><a href="getting_started.html#module-examples.benchmarking_example">module</a>
 </li>
       </ul></li>
       <li>
-    examples.basic_example
+    examples.eol_event
 
       <ul>
-        <li><a href="getting_started.html#module-examples.basic_example">module</a>
+        <li><a href="getting_started.html#module-examples.eol_event">module</a>
 </li>
       </ul></li>
-  </ul></td>
-  <td style="width: 33%; vertical-align: top;"><ul>
       <li>
-    examples.benchmarking_example
+    examples.measurement_eqn_example
 
       <ul>
-        <li><a href="getting_started.html#module-examples.benchmarking_example">module</a>
+        <li><a href="getting_started.html#module-examples.measurement_eqn_example">module</a>
 </li>
       </ul></li>
       <li>
@@ -110,6 +114,15 @@ <h2 id="E">E</h2>
 
       <ul>
         <li><a href="getting_started.html#module-examples.new_state_estimator_example">module</a>
+</li>
+      </ul></li>
+  </ul></td>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li>
+    examples.particle_filter_battery_example
+
+      <ul>
+        <li><a href="getting_started.html#module-examples.particle_filter_battery_example">module</a>
 </li>
       </ul></li>
       <li>
@@ -117,6 +130,63 @@ <h2 id="E">E</h2>
 
       <ul>
         <li><a href="getting_started.html#module-examples.playback">module</a>
+</li>
+      </ul></li>
+      <li>
+    examples.predict_specific_event
+
+      <ul>
+        <li><a href="getting_started.html#module-examples.predict_specific_event">module</a>
+</li>
+      </ul></li>
+      <li>
+    examples.thrown_object_example
+
+      <ul>
+        <li><a href="getting_started.html#module-examples.thrown_object_example">module</a>
+</li>
+      </ul></li>
+      <li>
+    examples.utpredictor
+
+      <ul>
+        <li><a href="getting_started.html#module-examples.utpredictor">module</a>
+</li>
+      </ul></li>
+  </ul></td>
+</tr></table>
+
+<h2 id="G">G</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.get">get() (prog_algs.predictors.ToEPredictionProfile method)</a>
+</li>
+  </ul></td>
+</tr></table>
+
+<h2 id="I">I</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="prediction.html#prog_algs.predictors.UnweightedSamplesPrediction.index">index() (prog_algs.predictors.UnweightedSamplesPrediction method)</a>
+</li>
+  </ul></td>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.items">items() (prog_algs.predictors.ToEPredictionProfile method)</a>
+</li>
+  </ul></td>
+</tr></table>
+
+<h2 id="K">K</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.key">key() (prog_algs.uncertain_data.UnweightedSamples method)</a>
+</li>
+  </ul></td>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.keys">keys() (prog_algs.predictors.ToEPredictionProfile method)</a>
+
+      <ul>
+        <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.keys">(prog_algs.uncertain_data.UncertainData method)</a>
 </li>
       </ul></li>
   </ul></td>
@@ -125,17 +195,17 @@ <h2 id="E">E</h2>
 <h2 id="M">M</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.MultivariateNormalDist.mean">mean() (prog_algs.uncertain_data.MultivariateNormalDist property)</a>
+      <li><a href="prediction.html#prog_algs.predictors.Prediction.mean">mean (prog_algs.predictors.Prediction property)</a>
 
       <ul>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.ScalarData.mean">(prog_algs.uncertain_data.ScalarData property)</a>
+        <li><a href="prediction.html#prog_algs.predictors.UnweightedSamplesPrediction.mean">(prog_algs.predictors.UnweightedSamplesPrediction property)</a>
 </li>
         <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.mean">(prog_algs.uncertain_data.UncertainData property)</a>
-</li>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.mean">(prog_algs.uncertain_data.UnweightedSamples property)</a>
 </li>
       </ul></li>
-      <li><a href="metrics.html#prog_algs.metrics.samples.mean_square_error">mean_square_error() (in module prog_algs.metrics.samples)</a>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.median">median (prog_algs.uncertain_data.UncertainData property)</a>
+</li>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.metrics">metrics() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
       <li>
     module
@@ -144,19 +214,29 @@ <h2 id="M">M</h2>
         <li><a href="getting_started.html#module-examples.basic_example">examples.basic_example</a>
 </li>
         <li><a href="getting_started.html#module-examples.benchmarking_example">examples.benchmarking_example</a>
+</li>
+        <li><a href="getting_started.html#module-examples.eol_event">examples.eol_event</a>
+</li>
+        <li><a href="getting_started.html#module-examples.measurement_eqn_example">examples.measurement_eqn_example</a>
 </li>
         <li><a href="getting_started.html#module-examples.new_state_estimator_example">examples.new_state_estimator_example</a>
+</li>
+        <li><a href="getting_started.html#module-examples.particle_filter_battery_example">examples.particle_filter_battery_example</a>
 </li>
         <li><a href="getting_started.html#module-examples.playback">examples.playback</a>
 </li>
-        <li><a href="metrics.html#module-prog_algs.metrics.samples">prog_algs.metrics.samples</a>
+        <li><a href="getting_started.html#module-examples.predict_specific_event">examples.predict_specific_event</a>
 </li>
-        <li><a href="visualize.html#module-prog_algs.visualize">prog_algs.visualize</a>
+        <li><a href="getting_started.html#module-examples.thrown_object_example">examples.thrown_object_example</a>
+</li>
+        <li><a href="getting_started.html#module-examples.utpredictor">examples.utpredictor</a>
+</li>
+        <li><a href="metrics.html#module-prog_algs.metrics">prog_algs.metrics</a>
 </li>
       </ul></li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="predictors.html#prog_algs.predictors.monte_carlo.MonteCarlo">MonteCarlo (class in prog_algs.predictors.monte_carlo)</a>
+      <li><a href="predictors.html#prog_algs.predictors.MonteCarlo">MonteCarlo (class in prog_algs.predictors)</a>
 </li>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.MultivariateNormalDist">MultivariateNormalDist (class in prog_algs.uncertain_data)</a>
 </li>
@@ -166,34 +246,33 @@ <h2 id="M">M</h2>
 <h2 id="P">P</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="state_estimators.html#prog_algs.state_estimators.particle_filter.ParticleFilter">ParticleFilter (class in prog_algs.state_estimators.particle_filter)</a>
+      <li><a href="state_estimators.html#prog_algs.state_estimators.ParticleFilter">ParticleFilter (class in prog_algs.state_estimators)</a>
 </li>
-      <li><a href="metrics.html#prog_algs.metrics.samples.percentage_in_bounds">percentage_in_bounds() (in module prog_algs.metrics.samples)</a>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.percentage_in_bounds">percentage_in_bounds() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
-      <li><a href="predictors.html#prog_algs.predictors.monte_carlo.MonteCarlo.predict">predict() (prog_algs.predictors.monte_carlo.MonteCarlo method)</a>
-
-      <ul>
-        <li><a href="predictors.html#prog_algs.predictors.predictor.Predictor.predict">(prog_algs.predictors.predictor.Predictor method)</a>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.plot_hist">plot_hist() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
-      </ul></li>
-      <li><a href="predictors.html#prog_algs.predictors.predictor.Predictor">Predictor (class in prog_algs.predictors.predictor)</a>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.plot_scatter">plot_scatter() (prog_algs.uncertain_data.UncertainData method)</a>
+</li>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.pop">pop() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
-      <li><a href="metrics.html#prog_algs.metrics.samples.prob_success">prob_success() (in module prog_algs.metrics.samples)</a>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.popitem">popitem() (prog_algs.predictors.ToEPredictionProfile method)</a>
+</li>
+      <li><a href="predictors.html#prog_algs.predictors.Predictor.predict">predict() (prog_algs.predictors.Predictor method)</a>
 </li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li>
-    prog_algs.metrics.samples
-
-      <ul>
-        <li><a href="metrics.html#module-prog_algs.metrics.samples">module</a>
+      <li><a href="prediction.html#prog_algs.predictors.Prediction">Prediction (class in prog_algs.predictors)</a>
+</li>
+      <li><a href="predictors.html#prog_algs.predictors.Predictor">Predictor (class in prog_algs.predictors)</a>
+</li>
+      <li><a href="metrics.html#prog_algs.metrics.prob_success">prob_success() (in module prog_algs.metrics)</a>
 </li>
-      </ul></li>
       <li>
-    prog_algs.visualize
+    prog_algs.metrics
 
       <ul>
-        <li><a href="visualize.html#module-prog_algs.visualize">module</a>
+        <li><a href="metrics.html#module-prog_algs.metrics">module</a>
 </li>
       </ul></li>
       <li><a href="exceptions.html#prog_algs.exceptions.ProgAlgException">ProgAlgException</a>
@@ -205,34 +284,32 @@ <h2 id="P">P</h2>
   </ul></td>
 </tr></table>
 
-<h2 id="R">R</h2>
+<h2 id="S">S</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.raw_samples">raw_samples() (prog_algs.uncertain_data.UnweightedSamples method)</a>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.sample">sample() (prog_algs.uncertain_data.UncertainData method)</a>
+</li>
+      <li><a href="uncertain_data.html#prog_algs.uncertain_data.ScalarData">ScalarData (class in prog_algs.uncertain_data)</a>
+</li>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.setdefault">setdefault() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
   </ul></td>
-</tr></table>
-
-<h2 id="S">S</h2>
-<table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.MultivariateNormalDist.sample">sample() (prog_algs.uncertain_data.MultivariateNormalDist method)</a>
+      <li><a href="prediction.html#prog_algs.predictors.Prediction.snapshot">snapshot() (prog_algs.predictors.Prediction method)</a>
 
       <ul>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.ScalarData.sample">(prog_algs.uncertain_data.ScalarData method)</a>
-</li>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.sample">(prog_algs.uncertain_data.UncertainData method)</a>
-</li>
-        <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.sample">(prog_algs.uncertain_data.UnweightedSamples method)</a>
+        <li><a href="prediction.html#prog_algs.predictors.UnweightedSamplesPrediction.snapshot">(prog_algs.predictors.UnweightedSamplesPrediction method)</a>
 </li>
       </ul></li>
+      <li><a href="state_estimators.html#prog_algs.state_estimators.StateEstimator">StateEstimator (class in prog_algs.state_estimators)</a>
+</li>
   </ul></td>
+</tr></table>
+
+<h2 id="T">T</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.ScalarData">ScalarData (class in prog_algs.uncertain_data)</a>
-</li>
-      <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.size">size() (prog_algs.uncertain_data.UnweightedSamples property)</a>
-</li>
-      <li><a href="state_estimators.html#prog_algs.state_estimators.state_estimator.StateEstimator">StateEstimator (class in prog_algs.state_estimators.state_estimator)</a>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile">ToEPredictionProfile (class in prog_algs.predictors)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -241,12 +318,26 @@ <h2 id="U">U</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData">UncertainData (class in prog_algs.uncertain_data)</a>
+</li>
+      <li><a href="state_estimators.html#prog_algs.state_estimators.UnscentedKalmanFilter">UnscentedKalmanFilter (class in prog_algs.state_estimators)</a>
+</li>
+      <li><a href="predictors.html#prog_algs.predictors.UnscentedTransformPredictor">UnscentedTransformPredictor (class in prog_algs.predictors)</a>
 </li>
   </ul></td>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="state_estimators.html#prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter">UnscentedKalmanFilter (class in prog_algs.state_estimators.unscented_kalman_filter)</a>
-</li>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples">UnweightedSamples (class in prog_algs.uncertain_data)</a>
+</li>
+      <li><a href="prediction.html#prog_algs.predictors.UnweightedSamplesPrediction">UnweightedSamplesPrediction (class in prog_algs.predictors)</a>
+</li>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.update">update() (prog_algs.predictors.ToEPredictionProfile method)</a>
+</li>
+  </ul></td>
+</tr></table>
+
+<h2 id="V">V</h2>
+<table style="width: 100%" class="indextable genindextable"><tr>
+  <td style="width: 33%; vertical-align: top;"><ul>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.values">values() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -268,14 +359,8 @@ <h2 id="W">W</h2>
 <h2 id="X">X</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="state_estimators.html#prog_algs.state_estimators.particle_filter.ParticleFilter.x">x() (prog_algs.state_estimators.particle_filter.ParticleFilter property)</a>
-
-      <ul>
-        <li><a href="state_estimators.html#prog_algs.state_estimators.state_estimator.StateEstimator.x">(prog_algs.state_estimators.state_estimator.StateEstimator property)</a>
-</li>
-        <li><a href="state_estimators.html#prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter.x">(prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter property)</a>
+      <li><a href="state_estimators.html#prog_algs.state_estimators.StateEstimator.x">x (prog_algs.state_estimators.StateEstimator property)</a>
 </li>
-      </ul></li>
   </ul></td>
 </tr></table>
 
@@ -298,14 +383,17 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul>
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -319,7 +407,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/getting_started.html b/docs/getting_started.html
index cf730d50..2de7c6fc 100644
--- a/docs/getting_started.html
+++ b/docs/getting_started.html
@@ -6,10 +6,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Getting Started &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Getting Started &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -35,37 +35,159 @@
             
   <section id="getting-started">
 <h1>Getting Started<a class="headerlink" href="#getting-started" title="Permalink to this headline">¶</a></h1>
+<a class="reference external image-reference" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb"><img alt="https://mybinder.org/badge_logo.svg" src="https://mybinder.org/badge_logo.svg" /></a>
 <p>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 for the Prognostics Models Library to perform research in prognostics with prognostics systems.</p>
-<p>A few definitions:</p>
+<section id="installing">
+<h2>Installing<a class="headerlink" href="#installing" title="Permalink to this headline">¶</a></h2>
+<section id="installing-from-pip-recommended">
+<h3>Installing from pip (recommended)<a class="headerlink" href="#installing-from-pip-recommended" title="Permalink to this headline">¶</a></h3>
+<p>The latest stable release of <cite>prog_algs</cite> is hosted on PyPi. For most users (unless you want to contribute to the development of <cite>prog_algs</cite>), the version on PyPi will be adequate. To install from the command line, use the following command:</p>
+<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>pip install prog_algs
+</pre></div>
+</div>
+</section>
+<section id="installing-pre-release-versions-with-github">
+<h3>Installing pre-release versions with GitHub<a class="headerlink" href="#installing-pre-release-versions-with-github" title="Permalink to this headline">¶</a></h3>
+<p>For users who would like to contribute to <cite>prog_algs</cite> or would like to use pre-release features can do so using the ‘dev’ branch (or a feature branch) on the <a class="reference external" href="https://github.com/nasa/prog_algs">prog_algs GitHub repo</a>. This isn’t recommended for most users as this version may be unstable. To use this version, use the following commands:</p>
+<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>git clone https://github.com/nasa/prog_algs
+<span class="gp">$ </span><span class="nb">cd</span> prog_algs
+<span class="gp">$ </span>git checkout dev
+<span class="gp">$ </span>pip install -e .
+</pre></div>
+</div>
+</section>
+</section>
+<section id="summary">
+<h2>Summary<a class="headerlink" href="#summary" title="Permalink to this headline">¶</a></h2>
+<p>A few definitions to get started:</p>
 <ul class="simple">
-<li><p><strong>events</strong>: some state that can be predicted (e.g., system failure). An event has either occured or not.</p></li>
-<li><p><strong>event state</strong>: progress towards event occuring. Defined as a number where an event state of 0 indicates the event has occured and 1 indicates no progress towards the event (i.e., fully healthy operation for a failure event). For gradually occuring 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”</p></li>
+<li><p><strong>events</strong>: something that can be predicted (e.g., system failure). An event has either occurred or not.</p></li>
+<li><p><strong>event state</strong>: 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”</p></li>
 <li><p><strong>inputs</strong>: control applied to the system being modeled (e.g., current drawn from a battery)</p></li>
 <li><p><strong>outputs</strong>: measured sensor values from a system (e.g., voltage and temperature of a battery)</p></li>
+<li><p><strong>observables</strong>: performance characteristics of a system that are a function of system state, but are not directly measured.</p></li>
 <li><p><strong>states</strong>: 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.</p></li>
 <li><p><strong>process noise</strong>: stochastic process representing uncertainty in the model transition.</p></li>
 <li><p><strong>measurement noise</strong>: stochastic process representing uncertainty in the measurement process; e.g., sensor sensitivity, sensor misalignements, environmental effects</p></li>
 </ul>
-<section id="installing-dependencies">
-<h2>Installing Dependencies<a class="headerlink" href="#installing-dependencies" title="Permalink to this headline">¶</a></h2>
+<p>The structure of the packages is illustrated below:</p>
+<img alt="_images/package_structure.png" src="_images/package_structure.png" />
+<p>Prognostics is performed using <a class="reference external" href="state_estimators.html">State Estimators</a> and <a class="reference external" href="predictors.html">Predictors</a>. State Estimators are resposible for estimating the current state of the modeled system using sensor data and a prognostics model (see: <a class="reference external" href="https://github.com/nasa/prog_models">prog_models package</a>). The state estimator then produces an estimate of the system state with uncertainty in the form of an <a class="reference external" href="uncertain_data.html">uncertain data object</a>. This state estimate is used by the predictor to predict when events will occur (Time of Event, ToE - returned as an <a class="reference external" href="uncertain_data.html">uncertain data object</a>), and future system states (returned as a Prediction object).</p>
+<section id="data-structures">
+<h3>Data Structures<a class="headerlink" href="#data-structures" title="Permalink to this headline">¶</a></h3>
 <dl class="simple">
-<dt>You can install dependencies using the included <cite>requirements.txt</cite> file. This file enumerates all the dependencies of this package. Use the following command to install dependencies:</dt><dd><p><cite>pip install -r requirements.txt</cite></p>
+<dt>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:</dt><dd><ul class="simple">
+<li><p><a class="reference external" href="https://nasa.github.io/prog_models/sim_result.html">SimResult (from prog_models)</a> : 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.</p></li>
+<li><p><a class="reference external" href="uncertain_data.html">UncertainData</a> : 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 <cite>estimate</cite> method, individual snapshots of a prediction, and the time of event estimate from a predictor’s <cite>predict</cite> method.</p></li>
+<li><p><a class="reference external" href="prediction.html#id1">Prediction</a> : Prediction of future values (with uncertainty) of some variable (e.g., input, state, output, event_states, etc.). The <cite>predict</cite> method of predictors return this.</p></li>
+<li><p><a class="reference external" href="prediction.html#toe-prediction-profile">ToEPredictionProfile</a> : 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.</p></li>
+</ul>
 </dd>
 </dl>
 </section>
+</section>
 <section id="use">
 <h2>Use<a class="headerlink" href="#use" title="Permalink to this headline">¶</a></h2>
-<p>See the examples for examples of use. Run examples using the command <cite>python -m examples.[Example name]</cite> command (e.g., <cite>python -m examples.sim_example</cite>). The examples are summarized below:</p>
+<p>The best way to learn how to use <cite>prog_algs</cite> is through the <a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">tutorial</a>. There are also a number of examples which show different aspects of the package, summarized and linked below:</p>
 <ul>
 <li><dl>
-<dt><a class="reference download internal" download="" href="_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.basic_example</span></code></a></dt><dd><span class="target" id="module-examples.basic_example"></span><div class="line-block">
+<dt><a class="reference download internal" download="" href="_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.basic_example</span></code></a></dt><dd><span class="target" id="module-examples.basic_example"></span><p>This example performs a state estimation and prediction with uncertainty given a Prognostics Model.</p>
+<dl class="simple">
+<dt>Method: An instance of the BatteryCircuit model in prog_models is created, and the prediction process is achieved in three steps:</dt><dd><ol class="arabic simple">
+<li><p>State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate</p></li>
+<li><p>Prediction of future states (with uncertainty) and the times at which the event threshold will be reached</p></li>
+<li><p>Metrics tools are used to further investigate the results of prediction</p></li>
+</ol>
+</dd>
+<dt>Results:</dt><dd><ol class="lowerroman simple">
+<li><p>Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction</p></li>
+<li><p>Time event is predicted to occur (with uncertainty)</p></li>
+<li><p>Various prediction metrics</p></li>
+<li><p>Figures illustrating results</p></li>
+</ol>
+</dd>
+</dl>
+<div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
+<li><dl>
+<dt><a class="reference download internal" download="" href="_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.thrown_object_example</span></code></a></dt><dd><span class="target" id="module-examples.thrown_object_example"></span><p>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</p>
+<dl class="simple">
+<dt>Method: An instance of the Thrown Object model in prog_models is created, and the prediction process is achieved in three steps:</dt><dd><ol class="arabic simple">
+<li><p>State estimation of the current state is performed using a chosen state_estimator, and samples are drawn from this estimate</p></li>
+<li><p>Prediction of future states (with uncertainty) and the times at which the event thresholds will be reached</p></li>
+</ol>
+</dd>
+<dt>Results:</dt><dd><ol class="lowerroman simple">
+<li><p>Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction</p></li>
+<li><p>Time event is predicted to occur (with uncertainty)</p></li>
+</ol>
+</dd>
+</dl>
+<div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
+<li><dl>
+<dt><a class="reference download internal" download="" href="_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.utpredictor</span></code></a></dt><dd><span class="target" id="module-examples.utpredictor"></span><div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
+<li><dl>
+<dt><a class="reference download internal" download="" href="_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.benchmarking_example</span></code></a></dt><dd><span class="target" id="module-examples.benchmarking_example"></span><p>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.</p>
+<dl class="simple">
+<dt>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.</dt><dd><p>Prediction of future states (with uncertainty) is then performed for various sample sizes.
+Metrics are calculated and displayed for each run.</p>
+</dd>
+<dt>Results:</dt><dd><ol class="lowerroman simple">
+<li><p>Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction for each distinct sample size</p></li>
+<li><p>Time event is predicted to occur (with uncertainty)</p></li>
+<li><p>Various prediction metrics, including alpha-lambda metric</p></li>
+</ol>
+</dd>
+</dl>
+<div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
+<li><dl>
+<dt><a class="reference download internal" download="" href="_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.eol_event</span></code></a></dt><dd><span class="target" id="module-examples.eol_event"></span><p>This example demonstrates a use case where someone wants to predict the first event (i.e., 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.</p>
+<p>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:</p>
+<blockquote>
+<div><ol class="lowerroman simple">
+<li><p>Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction</p></li>
+<li><p>Time the event ‘EOL’ is predicted to occur (with uncertainty)</p></li>
+<li><p>Histogram of the event ‘EOL’</p></li>
+</ol>
+</div></blockquote>
+<div class="line-block">
 <div class="line"><br /></div>
 </div>
 </dd>
 </dl>
 </li>
 <li><dl>
-<dt><a class="reference download internal" download="" href="_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.benchmarking_example</span></code></a></dt><dd><span class="target" id="module-examples.benchmarking_example"></span><div class="line-block">
+<dt><a class="reference download internal" download="" href="_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.measurement_eqn_example</span></code></a></dt><dd><span class="target" id="module-examples.measurement_eqn_example"></span><p>This example performs a state estimation with uncertainty given a Prognostics Model for a system in which not all output values are measured.</p>
+<dl class="simple">
+<dt>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 measurement_eqn to remove the other output value.</dt><dd><p>Estimation of the current state is performed at various time steps, using the defined state_estimator. The state_estimator takes the measurement_eqn as input, to account for the missing output information.</p>
+</dd>
+<dt>Results:</dt><dd><ol class="lowerroman simple">
+<li><p>Estimate of the current state given various times</p></li>
+<li><p>Display of results, such as prior and posterior state estimate values and SOC</p></li>
+</ol>
+</dd>
+</dl>
+<div class="line-block">
 <div class="line"><br /></div>
 </div>
 </dd>
@@ -79,20 +201,105 @@ <h2>Use<a class="headerlink" href="#use" title="Permalink to this headline">¶</
 </dl>
 </li>
 <li><dl>
-<dt><a class="reference download internal" download="" href="_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.playback</span></code></a></dt><dd><span class="target" id="module-examples.playback"></span><p>Example using playback data. Builds a Battery Model, runs prognostics using the playback data.</p>
+<dt><a class="reference download internal" download="" href="_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.playback</span></code></a></dt><dd><span class="target" id="module-examples.playback"></span><p>This example performs state estimation and prediction using playback data.</p>
+<dl class="simple">
+<dt>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.</dt><dd><p>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</p>
+</dd>
+<dt>Results:</dt><dd><ol class="lowerroman simple">
+<li><p>Predicted future values (inputs, states, outputs, event_states) with uncertainty from prediction</p></li>
+<li><p>Time event is predicted to occur (with uncertainty)</p></li>
+<li><p>Various prediction metrics</p></li>
+<li><p>Figures illustrating results</p></li>
+</ol>
+</dd>
+</dl>
 <div class="line-block">
 <div class="line"><br /></div>
 </div>
 </dd>
 </dl>
 </li>
+<li><dl>
+<dt><a class="reference download internal" download="" href="_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.predict_specific_event</span></code></a></dt><dd><span class="target" id="module-examples.predict_specific_event"></span><div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
+<li><dl>
+<dt><a class="reference download internal" download="" href="_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.particle_filter_battery_example</span></code></a></dt><dd><span class="target" id="module-examples.particle_filter_battery_example"></span><div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
 </ul>
 </section>
 <section id="extending">
 <h2>Extending<a class="headerlink" href="#extending" title="Permalink to this headline">¶</a></h2>
-<p>New State Estimators and Predictors are created by extending the <a class="reference internal" href="state_estimators.html#prog_algs.state_estimators.state_estimator.StateEstimator" title="prog_algs.state_estimators.state_estimator.StateEstimator"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.state_estimators.state_estimator.StateEstimator</span></code></a> and <a class="reference internal" href="predictors.html#prog_algs.predictors.predictor.Predictor" title="prog_algs.predictors.predictor.Predictor"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.predictors.predictor.Predictor</span></code></a> class, respectively.</p>
+<p>New State Estimators and Predictors are created by extending the <a class="reference internal" href="state_estimators.html#prog_algs.state_estimators.StateEstimator" title="prog_algs.state_estimators.StateEstimator"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.state_estimators.StateEstimator</span></code></a> and <a class="reference internal" href="predictors.html#prog_algs.predictors.Predictor" title="prog_algs.predictors.Predictor"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.predictors.Predictor</span></code></a> class, respectively.</p>
 <p>See <a class="reference download internal" download="" href="_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.new_state_estimator_example</span></code></a> for an example of this approach.</p>
 </section>
+<section id="updates-in-v1-1">
+<h2>Updates in v1.1<a class="headerlink" href="#updates-in-v1-1" title="Permalink to this headline">¶</a></h2>
+<section id="note-for-existing-users">
+<h3>Note for existing users<a class="headerlink" href="#note-for-existing-users" title="Permalink to this headline">¶</a></h3>
+<p>This release includes changes to the return format of the MonteCarlo Predictor’s <cite>predict</cite> method. These changes were necessary to support non-sample based predictors. The non backwards-compatible changes are listed below:
+* times:</p>
+<blockquote>
+<div><ul class="simple">
+<li><p>previous <code class="docutils literal notranslate"><span class="pre">`List[List[float]]`</span></code> where times[n][m] corresponds to timepoint m of sample n.</p></li>
+<li><p>new <code class="docutils literal notranslate"><span class="pre">`List[float]`</span></code> where times[m] corresponds to timepoint m for all samples</p></li>
+</ul>
+</div></blockquote>
+<ul class="simple">
+<li><dl class="simple">
+<dt>End of Life (EOL)/ Time of Event (ToE) estimates:</dt><dd><ul>
+<li><p>previous <code class="docutils literal notranslate"><span class="pre">`List[float]`</span></code> where the times correspond to the time that the first event occurs.</p></li>
+<li><p>new <code class="docutils literal notranslate"><span class="pre">`UnweightedSamples`</span></code> where keys correspond to the inidividualevents predicted.</p></li>
+</ul>
+</dd>
+</dl>
+</li>
+<li><dl class="simple">
+<dt>State at time of event (ToE)</dt><dd><ul>
+<li><p>previous: element in states</p></li>
+<li><p>new: member of toe event (e.g., toe.final_state[‘event1’])</p></li>
+</ul>
+</dd>
+</dl>
+</li>
+</ul>
+</section>
+<section id="general-updates">
+<h3>General Updates<a class="headerlink" href="#general-updates" title="Permalink to this headline">¶</a></h3>
+<ul class="simple">
+<li><p>New Feature: Histogram and Scatter Plot of UncertainData</p></li>
+<li><dl class="simple">
+<dt>New Feature: Vectorized particle filter</dt><dd><ul>
+<li><p>Particle Filter State Estimator is now vectorized for vectorized models - this significantly improves performance.</p></li>
+</ul>
+</dd>
+</dl>
+</li>
+<li><dl class="simple">
+<dt>New Feature: Unscented Transform Predictor</dt><dd><ul>
+<li><p>New predictor that propogates sigma points forward to estimate time of event and future states</p></li>
+</ul>
+</dd>
+</dl>
+</li>
+<li><p>New Feature: <cite>Prediction</cite> class to represent predicted future values</p></li>
+<li><p>New Feature: <cite>ToEPredictionProfile</cite> class to represent and operate on the result of multiple predictions generated at different prediction times.</p></li>
+<li><p>Added metrics <cite>percentage_in_bounds</cite> and <cite>metrics</cite> and plots to UncertainData</p></li>
+<li><p>Add support for Python3.9</p></li>
+<li><p>General Bugfixes</p></li>
+</ul>
+</section>
+</section>
 </section>
 
 
@@ -113,19 +320,24 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">Getting Started</a><ul>
-<li class="toctree-l2"><a class="reference internal" href="#installing-dependencies">Installing Dependencies</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#installing">Installing</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#summary">Summary</a></li>
 <li class="toctree-l2"><a class="reference internal" href="#use">Use</a></li>
 <li class="toctree-l2"><a class="reference internal" href="#extending">Extending</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#updates-in-v1-1">Updates in v1.1</a></li>
 </ul>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -141,7 +353,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/index.html b/docs/index.html
index 62ba0a4d..7946d071 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -6,10 +6,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Prognostics Algorithms Python Package &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Prognostics Algorithms Python Package &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -34,56 +34,74 @@
             
   <section id="prognostics-algorithms-python-package">
 <h1>Prognostics Algorithms Python Package<a class="headerlink" href="#prognostics-algorithms-python-package" title="Permalink to this headline">¶</a></h1>
-<p>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 library 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.</p>
-<p>The Prognostics Models Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE).</p>
-<p>If you are new to this package, see <a class="reference external" href="getting_started.html">getting_started.html</a>.</p>
+<a class="reference external image-reference" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb"><img alt="https://mybinder.org/badge_logo.svg" src="https://mybinder.org/badge_logo.svg" /></a>
+<p>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.</p>
+<p>The Prognostics Algorithms Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE) and <a class="reference external" href="https://www.nasa.gov/content/diagnostics-prognostics">Diagnostics &amp; Prognostics Group</a>.</p>
+<p>If you are new to this package, see <a class="reference external" href="getting_started.html">getting started</a>.</p>
 <div class="toctree-wrapper compound">
 <ul>
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a><ul>
-<li class="toctree-l2"><a class="reference internal" href="getting_started.html#installing-dependencies">Installing Dependencies</a></li>
+<li class="toctree-l2"><a class="reference internal" href="getting_started.html#installing">Installing</a></li>
+<li class="toctree-l2"><a class="reference internal" href="getting_started.html#summary">Summary</a></li>
 <li class="toctree-l2"><a class="reference internal" href="getting_started.html#use">Use</a></li>
 <li class="toctree-l2"><a class="reference internal" href="getting_started.html#extending">Extending</a></li>
+<li class="toctree-l2"><a class="reference internal" href="getting_started.html#updates-in-v1-1">Updates in v1.1</a></li>
 </ul>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a><ul>
-<li class="toctree-l2"><a class="reference internal" href="state_estimators.html#ukf-state-estimator">UKF State Estimator</a></li>
-<li class="toctree-l2"><a class="reference internal" href="state_estimators.html#pf-state-estimator">PF State Estimator</a></li>
+<li class="toctree-l2"><a class="reference internal" href="state_estimators.html#included-state-estimators">Included State Estimators</a></li>
+<li class="toctree-l2"><a class="reference internal" href="state_estimators.html#state-estimator-interface">State Estimator Interface</a></li>
 </ul>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a><ul>
-<li class="toctree-l2"><a class="reference internal" href="predictors.html#mc-predictor">MC Predictor</a></li>
+<li class="toctree-l2"><a class="reference internal" href="predictors.html#included-predictors">Included Predictors</a></li>
+<li class="toctree-l2"><a class="reference internal" href="predictors.html#predictor-interface">Predictor Interface</a></li>
+</ul>
+</li>
+<li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="uncertain_data.html#interface">Interface</a></li>
+<li class="toctree-l2"><a class="reference internal" href="uncertain_data.html#implemented-uncertaindata-types">Implemented UncertainData Types</a></li>
+</ul>
+</li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="prediction.html#id1">Prediction</a></li>
+<li class="toctree-l2"><a class="reference internal" href="prediction.html#toe-prediction-profile">ToE Prediction Profile</a></li>
 </ul>
 </li>
-<li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a><ul>
 <li class="toctree-l2"><a class="reference internal" href="exceptions.html#prognostics-model-exceptions">Prognostics Model Exceptions</a></li>
 </ul>
 </li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="dev_guide.html#guidance-for-contributors">Guidance for Contributors</a></li>
 <li class="toctree-l2"><a class="reference internal" href="dev_guide.html#branching-strategy">Branching Strategy</a></li>
 <li class="toctree-l2"><a class="reference internal" href="dev_guide.html#npr-7150">NPR 7150</a></li>
 </ul>
 </li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 </div>
 <section id="citing-this-repository">
 <h2>Citing this repository<a class="headerlink" href="#citing-this-repository" title="Permalink to this headline">¶</a></h2>
 <p>Use the following to cite this repository:</p>
-<dl class="simple">
-<dt>&#64;misc{2020_nasa_prog_model,</dt><dd><p>author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
-title     = {Prognostics Algorithms Python Package},
-month     = Dec,
-year      = 2020,
-version   = {0.3.4},
-url       = {<a class="reference external" href="https://github.com/nasa/prog_algs">https://github.com/nasa/prog_algs</a>}
-}</p>
+<dl>
+<dt>&#64;misc{2021_nasa_prog_algs,</dt><dd><div class="line-block">
+<div class="line">author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},</div>
+<div class="line">title     = {Prognostics Algorithms Python Package},</div>
+<div class="line">month     = Aug,</div>
+<div class="line">year      = 2021,</div>
+<div class="line">version   = {1.2.0},</div>
+<div class="line">url       = {<a class="reference external" href="https://github.com/nasa/prog_algs">https://github.com/nasa/prog_algs</a>}</div>
+<div class="line">}</div>
+</div>
 </dd>
 </dl>
 <p>The corresponding reference should look like this:</p>
 <ol class="upperalpha simple" start="3">
-<li><p>Teubert, C. Kulkarni, M. Corbetta Prognostics Algorithms Python Package, v0.2.0, Dec. 2020. URL <a class="reference external" href="https://github.com/nasa/prog_algs">https://github.com/nasa/prog_algs</a>.</p></li>
+<li><p>Teubert, C. Kulkarni, M. Corbetta Prognostics Algorithms Python Package, v1.2.0, Aug. 2021. URL <a class="reference external" href="https://github.com/nasa/prog_algs">https://github.com/nasa/prog_algs</a>.</p></li>
 </ol>
 </section>
 <section id="indices-and-tables">
@@ -119,14 +137,17 @@ <h1 class="logo"><a href="#">Prognostics Algorithms Python Package</a></h1>
 
 <h3>Navigation</h3>
 <ul>
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -141,7 +162,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/metrics.html b/docs/metrics.html
index 45f126c8..426660ef 100644
--- a/docs/metrics.html
+++ b/docs/metrics.html
@@ -6,17 +6,17 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Metrics &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Metrics &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
     <link rel="index" title="Index" href="genindex.html" />
     <link rel="search" title="Search" href="search.html" />
-    <link rel="next" title="Visualization" href="visualize.html" />
-    <link rel="prev" title="Uncertain Data" href="uncertain_data.html" />
+    <link rel="next" title="Exceptions" href="exceptions.html" />
+    <link rel="prev" title="Prediction" href="prediction.html" />
    
   <link rel="stylesheet" href="_static/custom.css" type="text/css" />
   
@@ -36,126 +36,25 @@
   <section id="metrics">
 <h1>Metrics<a class="headerlink" href="#metrics" title="Permalink to this headline">¶</a></h1>
 <p>The metrics module includes tools for calculating various metrics for prognostics results. These metrics are a measurement of the performance of a prognostics algorithms. The analysis functions are described below:</p>
-<span class="target" id="module-prog_algs.metrics.samples"></span><dl class="py function">
-<dt id="prog_algs.metrics.samples.alpha_lambda">
-<code class="sig-prename descclassname"><span class="pre">prog_algs.metrics.samples.</span></code><code class="sig-name descname"><span class="pre">alpha_lambda</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">times</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">eols</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">lambda_value</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">alpha</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">beta</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.samples.alpha_lambda" title="Permalink to this definition">¶</a></dt>
-<dd><p>Compute alpha lambda metrics</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>times</strong> (<em>[</em><em>float</em><em>]</em>) – Times corresponding to eols (output from predictors)</p></li>
-<li><p><strong>eols</strong> (<em>[</em><em>float</em><em>]</em>) – End of life times, output from predictor</p></li>
-<li><p><strong>ground_truth</strong> (<em>float</em>) – Ground Truth end of life time</p></li>
-<li><p><strong>lambda_value</strong> (<em>fload</em>) – lambda</p></li>
-<li><p><strong>alpha</strong> (<em>float</em>) – alpha</p></li>
-<li><p><strong>beta</strong> (<em>float</em>) – beta</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>if alpha-lambda met</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>bool</p>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py function">
-<dt id="prog_algs.metrics.samples.eol_metrics">
-<code class="sig-prename descclassname"><span class="pre">prog_algs.metrics.samples.</span></code><code class="sig-name descname"><span class="pre">eol_metrics</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">eol</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.samples.eol_metrics" title="Permalink to this definition">¶</a></dt>
-<dd><p>Calculate all end of life metrics</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>eol</strong> (<em>[</em><em>double</em><em>]</em>) – End of life times, output from predictor</p></li>
-<li><p><strong>ground_truth</strong> (<em>float</em><em>, </em><em>optional</em>) – Ground truth end of discharge time. Defaults to None.</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>collection of metrics</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>dict</p>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py function">
-<dt id="prog_algs.metrics.samples.eol_profile_metrics">
-<code class="sig-prename descclassname"><span class="pre">prog_algs.metrics.samples.</span></code><code class="sig-name descname"><span class="pre">eol_profile_metrics</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">eol</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.samples.eol_profile_metrics" title="Permalink to this definition">¶</a></dt>
-<dd><p>Calculate eol profile metrics</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>eol</strong> (<em>[</em><em>float</em><em>]</em>) – End of life times, output from predictor</p></li>
-<li><p><strong>ground_truth</strong> (<em>float</em>) – Ground truth EOL</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>EOL Profile Metrics</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>dict</p>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py function">
-<dt id="prog_algs.metrics.samples.mean_square_error">
-<code class="sig-prename descclassname"><span class="pre">prog_algs.metrics.samples.</span></code><code class="sig-name descname"><span class="pre">mean_square_error</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">values</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.samples.mean_square_error" title="Permalink to this definition">¶</a></dt>
-<dd><p>Mean Square Error</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>values</strong> (<em>[</em><em>float</em><em>]</em>) – End of life times, output from predictor</p></li>
-<li><p><strong>ground_truth</strong> (<em>float</em>) – Ground truth EOL</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>mean square error of eol predictions</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>float</p>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py function">
-<dt id="prog_algs.metrics.samples.percentage_in_bounds">
-<code class="sig-prename descclassname"><span class="pre">prog_algs.metrics.samples.</span></code><code class="sig-name descname"><span class="pre">percentage_in_bounds</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">eol</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">bounds</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.samples.percentage_in_bounds" title="Permalink to this definition">¶</a></dt>
-<dd><p>Calculate percentage of EOL dist is within specified bounds</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>eol</strong> (<em>[</em><em>float</em><em>]</em>) – End of life times, output from predictor</p></li>
-<li><p><strong>bounds</strong> (<em>[</em><em>float</em><em>, </em><em>float</em><em>]</em>) – Lower and upper bounds</p></li>
-</ul>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>Percentage within bounds (where 1 = 100%)</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>float</p>
-</dd>
-</dl>
-</dd></dl>
-
-<dl class="py function">
-<dt id="prog_algs.metrics.samples.prob_success">
-<code class="sig-prename descclassname"><span class="pre">prog_algs.metrics.samples.</span></code><code class="sig-name descname"><span class="pre">prob_success</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">eol</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">time</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.samples.prob_success" title="Permalink to this definition">¶</a></dt>
+<span class="target" id="module-prog_algs.metrics"></span><dl class="py function">
+<dt class="sig sig-object py" id="prog_algs.metrics.prob_success">
+<span class="sig-prename descclassname"><span class="pre">prog_algs.metrics.</span></span><span class="sig-name descname"><span class="pre">prob_success</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">toe</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">time</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.metrics.prob_success" title="Permalink to this definition">¶</a></dt>
 <dd><p>Calculate probability of success - i.e., probability that event will not occur within a given time (i.e., success)</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
 <dd class="field-odd"><ul class="simple">
-<li><p><strong>eol</strong> (<em>[</em><em>float</em><em>]</em>) – End of life times, output from predictor</p></li>
-<li><p><strong>time</strong> (<em>[</em><em>type</em><em>]</em>) – time for calculation</p></li>
+<li><p><strong>toe</strong> (<a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData"><em>UncertainData</em></a><em> or </em><em>array</em><em>[</em><em>float</em><em>]</em>) – Times of event for a single event (array[float]) or multiple events, output from predictor</p></li>
+<li><p><strong>time</strong> (<em>float</em>) – time for calculation</p></li>
+<li><p><strong>**kwargs</strong> (<em>optional</em>) – 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.</p></li>
 </ul>
 </dd>
 <dt class="field-even">Returns</dt>
 <dd class="field-even"><p>Probability of success</p>
 </dd>
 <dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>fload</p>
+<dd class="field-odd"><p>float</p>
 </dd>
 </dl>
 </dd></dl>
@@ -180,22 +79,25 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
 <h3>Related Topics</h3>
 <ul>
   <li><a href="index.html">Documentation overview</a><ul>
-      <li>Previous: <a href="uncertain_data.html" title="previous chapter">Uncertain Data</a></li>
-      <li>Next: <a href="visualize.html" title="next chapter">Visualization</a></li>
+      <li>Previous: <a href="prediction.html" title="previous chapter">Prediction</a></li>
+      <li>Next: <a href="exceptions.html" title="next chapter">Exceptions</a></li>
   </ul></li>
 </ul>
 </div>
@@ -203,7 +105,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/objects.inv b/docs/objects.inv
index d1f16bcb..d1a53f04 100644
Binary files a/docs/objects.inv and b/docs/objects.inv differ
diff --git a/docs/prediction.html b/docs/prediction.html
new file mode 100644
index 00000000..8233d396
--- /dev/null
+++ b/docs/prediction.html
@@ -0,0 +1,342 @@
+
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
+
+    <title>Prediction &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
+    <script src="_static/jquery.js"></script>
+    <script src="_static/underscore.js"></script>
+    <script src="_static/doctools.js"></script>
+    <link rel="index" title="Index" href="genindex.html" />
+    <link rel="search" title="Search" href="search.html" />
+    <link rel="next" title="Metrics" href="metrics.html" />
+    <link rel="prev" title="Uncertain Data" href="uncertain_data.html" />
+   
+  <link rel="stylesheet" href="_static/custom.css" type="text/css" />
+  
+  
+  <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
+
+  </head><body>
+  
+
+    <div class="document">
+      <div class="documentwrapper">
+        <div class="bodywrapper">
+          
+
+          <div class="body" role="main">
+            
+  <section id="prediction">
+<h1>Prediction<a class="headerlink" href="#prediction" title="Permalink to this headline">¶</a></h1>
+<section id="id1">
+<h2>Prediction<a class="headerlink" href="#id1" title="Permalink to this headline">¶</a></h2>
+<p>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, observables, and event states with uncertainty at savepoints.</p>
+<p>Two types of predictions are distributed with this package: <cite>Prediction</cite> and <cite>UnweightedSamplesPrediction</cite>, described below. <cite>UnweightedSamplesPrediction</cite> extends <cite>Prediction</cite> to allow some operations specific to cases where each prediction is represented by an UnweightedSamples object (e.g., accessing SimResult for a single sample).</p>
+<section id="base-prediction">
+<h3>Base Prediction<a class="headerlink" href="#base-prediction" title="Permalink to this headline">¶</a></h3>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.predictors.Prediction">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.predictors.</span></span><span class="sig-name descname"><span class="pre">Prediction</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">times</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.Prediction" title="Permalink to this definition">¶</a></dt>
+<dd><p>Class for the result of a prediction. Is returned by the predict method of a predictor.</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>times</strong> (<em>array</em><em>(</em><em>float</em><em>)</em>) – Times for each data point where times[n] corresponds to data[n]</p></li>
+<li><p><strong>data</strong> (<em>array</em><em>(</em><a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData"><em>UncertainData</em></a><em>)</em>) – Data points for each time in times</p></li>
+</ul>
+</dd>
+</dl>
+<dl class="py property">
+<dt class="sig sig-object py" id="prog_algs.predictors.Prediction.mean">
+<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">mean</span></span><a class="headerlink" href="#prog_algs.predictors.Prediction.mean" title="Permalink to this definition">¶</a></dt>
+<dd><p>Estimate of the mean value of the prediction at each time</p>
+<dl class="field-list simple">
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p><p>Mean value of the prediction at each time where mean[n] corresponds to the mean value of the prediction at time times[n].</p>
+<p>The mean value at each time is a dictionary.</p>
+<p>e.g., [{‘state1’: 1.2, ‘state2’: 1.3, …}, …]</p>
+</p>
+</dd>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>array(dict)</p>
+</dd>
+</dl>
+<p class="rubric">Example</p>
+<p>mean_value = data.mean</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.Prediction.snapshot">
+<span class="sig-name descname"><span class="pre">snapshot</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">time_index</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.Prediction.snapshot" title="Permalink to this definition">¶</a></dt>
+<dd><p>Get all samples from a specific timestep</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>index</strong> (<em>int</em>) – Timestep (index number from times)</p>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Distribution for time corresponding to times[timestep]</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p><a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData">UncertainData</a></p>
+</dd>
+</dl>
+</dd></dl>
+
+</dd></dl>
+
+</section>
+<section id="unweightedsamplesprediction">
+<h3>UnweightedSamplesPrediction<a class="headerlink" href="#unweightedsamplesprediction" title="Permalink to this headline">¶</a></h3>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.predictors.UnweightedSamplesPrediction">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.predictors.</span></span><span class="sig-name descname"><span class="pre">UnweightedSamplesPrediction</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">times</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.UnweightedSamplesPrediction" title="Permalink to this definition">¶</a></dt>
+<dd><p>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.</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>times</strong> – array(float)
+Times for each data point where times[n] corresponds to data[n]</p></li>
+<li><p><strong>data</strong> – array(dict)
+Data points where data[n] is a SimResult for sample n</p></li>
+</ul>
+</dd>
+</dl>
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.UnweightedSamplesPrediction.count">
+<span class="sig-name descname"><span class="pre">count</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">value</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">integer</span> <span class="pre">--</span> <span class="pre">return</span> <span class="pre">number</span> <span class="pre">of</span> <span class="pre">occurrences</span> <span class="pre">of</span> <span class="pre">value</span></span></span><a class="headerlink" href="#prog_algs.predictors.UnweightedSamplesPrediction.count" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.UnweightedSamplesPrediction.index">
+<span class="sig-name descname"><span class="pre">index</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">value</span></em><span class="optional">[</span>, <em class="sig-param"><span class="pre">start</span></em><span class="optional">[</span>, <em class="sig-param"><span class="pre">stop</span></em><span class="optional">]</span><span class="optional">]</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">integer</span> <span class="pre">--</span> <span class="pre">return</span> <span class="pre">first</span> <span class="pre">index</span> <span class="pre">of</span> <span class="pre">value.</span></span></span><a class="headerlink" href="#prog_algs.predictors.UnweightedSamplesPrediction.index" title="Permalink to this definition">¶</a></dt>
+<dd><p>Raises ValueError if the value is not present.</p>
+<p>Supporting start and stop arguments is optional, but
+recommended.</p>
+</dd></dl>
+
+<dl class="py property">
+<dt class="sig sig-object py" id="prog_algs.predictors.UnweightedSamplesPrediction.mean">
+<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">mean</span></span><a class="headerlink" href="#prog_algs.predictors.UnweightedSamplesPrediction.mean" title="Permalink to this definition">¶</a></dt>
+<dd><p>Estimate of the mean value of the prediction at each time</p>
+<dl class="field-list simple">
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p><p>Mean value of the prediction at each time where mean[n] corresponds to the mean value of the prediction at time times[n].</p>
+<p>The mean value at each time is a dictionary.</p>
+<p>e.g., [{‘state1’: 1.2, ‘state2’: 1.3, …}, …]</p>
+</p>
+</dd>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>array(dict)</p>
+</dd>
+</dl>
+<p class="rubric">Example</p>
+<p>mean_value = data.mean</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.UnweightedSamplesPrediction.snapshot">
+<span class="sig-name descname"><span class="pre">snapshot</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">time_index</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.UnweightedSamplesPrediction.snapshot" title="Permalink to this definition">¶</a></dt>
+<dd><p>Get all samples from a specific timestep</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>index</strong> (<em>int</em>) – Timestep (index number from times)</p>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Samples for time corresponding to times[timestep]</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p><a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples" title="prog_algs.uncertain_data.UnweightedSamples">UnweightedSamples</a></p>
+</dd>
+</dl>
+</dd></dl>
+
+</dd></dl>
+
+</section>
+</section>
+<section id="toe-prediction-profile">
+<h2>ToE Prediction Profile<a class="headerlink" href="#toe-prediction-profile" title="Permalink to this headline">¶</a></h2>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.predictors.</span></span><span class="sig-name descname"><span class="pre">ToEPredictionProfile</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">dict</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">/</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile" title="Permalink to this definition">¶</a></dt>
+<dd><p>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 toe prediction. Iteration of this data structure is in order of increasing time of prediction</p>
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.add_prediction">
+<span class="sig-name descname"><span class="pre">add_prediction</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">time_of_prediction</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">toe_prediction</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.uncertain_data.UncertainData"><span class="pre">prog_algs.uncertain_data.uncertain_data.UncertainData</span></a></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.add_prediction" title="Permalink to this definition">¶</a></dt>
+<dd><p>Add a single prediction to the profile</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>time_of_prediction</strong> (<em>float</em>) – Time that the prediction was made</p></li>
+<li><p><strong>toe_prediction</strong> (<a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData"><em>UncertainData</em></a>) – Distribution of predicted ToEs</p></li>
+</ul>
+</dd>
+</dl>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.alpha_lambda">
+<span class="sig-name descname"><span class="pre">alpha_lambda</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">Dict</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">lambda_value</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">alpha</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">beta</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">float</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">Dict</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">,</span> </span><span class="pre">bool</span><span class="p"><span class="pre">]</span></span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.alpha_lambda" title="Permalink to this definition">¶</a></dt>
+<dd><p>Calculate Alpha lambda metric for the prediction profile</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>ground_truth</strong> (<em>Dict</em><em>[</em><em>str</em><em>, </em><em>float</em><em>]</em>) – Ground Truth time of event for each event (e.g., {‘event1’: 748, ‘event2’, 2233, …})</p></li>
+<li><p><strong>lambda_value</strong> (<em>float</em>) – Prediction time at or after which metric is evaluated. Evaluation occurs at this time (if a prediction exists) or the next prediction following.</p></li>
+<li><p><strong>alpha</strong> (<em>float</em>) – percentage bounds around time to event (where 0.2 allows 20% error TtE)</p></li>
+<li><p><strong>beta</strong> (<em>float</em>) – portion of prediction that must be within those bounds</p></li>
+<li><p><strong>kwargs</strong> (<em>optional</em><em>, </em><em>keyword arguments</em>) – <p>configuration arguments. Accepted arge include:</p>
+<blockquote>
+<div><ul>
+<li><p>keys (list[string]): list of keys to use. If not provided, all keys are used.</p></li>
+<li><p>print (bool) : If True, print the results. Default is False.</p></li>
+</ul>
+</div></blockquote>
+</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>If alpha lambda was met for each key (e.g., {‘event1’: True, ‘event2’, False, …})</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p>Dict[str, bool]</p>
+</dd>
+</dl>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.clear">
+<span class="sig-name descname"><span class="pre">clear</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None.</span>&#160; <span class="pre">Remove</span> <span class="pre">all</span> <span class="pre">items</span> <span class="pre">from</span> <span class="pre">D.</span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.clear" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.get">
+<span class="sig-name descname"><span class="pre">get</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">k</span></em><span class="optional">[</span>, <em class="sig-param"><span class="pre">d</span></em><span class="optional">]</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">D[k]</span> <span class="pre">if</span> <span class="pre">k</span> <span class="pre">in</span> <span class="pre">D,</span> <span class="pre">else</span> <span class="pre">d.</span>&#160; <span class="pre">d</span> <span class="pre">defaults</span> <span class="pre">to</span> <span class="pre">None.</span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.get" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.items">
+<span class="sig-name descname"><span class="pre">items</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.items" title="Permalink to this definition">¶</a></dt>
+<dd><p>Get iterators for the items (time_of_prediction, toe_prediction) of the prediction profile</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.keys">
+<span class="sig-name descname"><span class="pre">keys</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.keys" title="Permalink to this definition">¶</a></dt>
+<dd><p>Get iterator for the keys (i.e., time_of_prediction) of the prediction profile</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.pop">
+<span class="sig-name descname"><span class="pre">pop</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">k</span></em><span class="optional">[</span>, <em class="sig-param"><span class="pre">d</span></em><span class="optional">]</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">v,</span> <span class="pre">remove</span> <span class="pre">specified</span> <span class="pre">key</span> <span class="pre">and</span> <span class="pre">return</span> <span class="pre">the</span> <span class="pre">corresponding</span> <span class="pre">value.</span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.pop" title="Permalink to this definition">¶</a></dt>
+<dd><p>If key is not found, d is returned if given, otherwise KeyError is raised.</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.popitem">
+<span class="sig-name descname"><span class="pre">popitem</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">(k,</span> <span class="pre">v),</span> <span class="pre">remove</span> <span class="pre">and</span> <span class="pre">return</span> <span class="pre">some</span> <span class="pre">(key,</span> <span class="pre">value)</span> <span class="pre">pair</span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.popitem" title="Permalink to this definition">¶</a></dt>
+<dd><p>as a 2-tuple; but raise KeyError if D is empty.</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.setdefault">
+<span class="sig-name descname"><span class="pre">setdefault</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">k</span></em><span class="optional">[</span>, <em class="sig-param"><span class="pre">d</span></em><span class="optional">]</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">D.get(k,d),</span> <span class="pre">also</span> <span class="pre">set</span> <span class="pre">D[k]=d</span> <span class="pre">if</span> <span class="pre">k</span> <span class="pre">not</span> <span class="pre">in</span> <span class="pre">D</span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.setdefault" title="Permalink to this definition">¶</a></dt>
+<dd></dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.update">
+<span class="sig-name descname"><span class="pre">update</span></span><span class="sig-paren">(</span><span class="optional">[</span><em class="sig-param"><span class="pre">E</span></em>, <span class="optional">]</span><em class="sig-param"><span class="pre">**F</span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None.</span>&#160; <span class="pre">Update</span> <span class="pre">D</span> <span class="pre">from</span> <span class="pre">mapping/iterable</span> <span class="pre">E</span> <span class="pre">and</span> <span class="pre">F.</span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.update" title="Permalink to this definition">¶</a></dt>
+<dd><p>If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
+If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
+In either case, this is followed by: for k, v in F.items(): D[k] = v</p>
+</dd></dl>
+
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.predictors.ToEPredictionProfile.values">
+<span class="sig-name descname"><span class="pre">values</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.values" title="Permalink to this definition">¶</a></dt>
+<dd><p>Get iterator for the values (i.e., toe_prediction) of the prediction profile</p>
+</dd></dl>
+
+</dd></dl>
+
+</section>
+</section>
+
+
+          </div>
+          
+        </div>
+      </div>
+      <div class="sphinxsidebar" role="navigation" aria-label="main navigation">
+        <div class="sphinxsidebarwrapper">
+<h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a></h1>
+
+
+
+
+
+
+
+
+<h3>Navigation</h3>
+<ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
+<li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
+<li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
+<li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
+<li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1 current"><a class="current reference internal" href="#">Prediction</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="#id1">Prediction</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#toe-prediction-profile">ToE Prediction Profile</a></li>
+</ul>
+</li>
+<li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
+<li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
+<li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+</ul>
+
+<div class="relations">
+<h3>Related Topics</h3>
+<ul>
+  <li><a href="index.html">Documentation overview</a><ul>
+      <li>Previous: <a href="uncertain_data.html" title="previous chapter">Uncertain Data</a></li>
+      <li>Next: <a href="metrics.html" title="next chapter">Metrics</a></li>
+  </ul></li>
+</ul>
+</div>
+<div id="searchbox" style="display: none" role="search">
+  <h3 id="searchlabel">Quick search</h3>
+    <div class="searchformwrapper">
+    <form class="search" action="search.html" method="get">
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
+      <input type="submit" value="Go" />
+    </form>
+    </div>
+</div>
+<script>$('#searchbox').show(0);</script>
+
+
+
+
+
+
+
+
+        </div>
+      </div>
+      <div class="clearer"></div>
+    </div>
+
+Copyright © 2020 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+  </body>
+</html>
\ No newline at end of file
diff --git a/docs/predictors.html b/docs/predictors.html
index efe316c6..e5997c03 100644
--- a/docs/predictors.html
+++ b/docs/predictors.html
@@ -6,10 +6,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Predictors &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Predictors &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -35,105 +35,128 @@
             
   <section id="predictors">
 <h1>Predictors<a class="headerlink" href="#predictors" title="Permalink to this headline">¶</a></h1>
+<p>The Predictor uses a state estimate (type UncertainData subclass, output of a state estimator), information about expected future loading, and a Prognostics Model (see: <a class="reference external" href="https://github.com/nasa/prog_models">prog_models package</a>) to predict both future states (also outputs, observables, event_states) at predefined points and the time that an event will occur (Time of Event, ToE) with uncertainty.</p>
+<p>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).</p>
+<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">prog_models.models</span> <span class="kn">import</span> <span class="n">ThrownObject</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">prog_algs.predictors</span> <span class="kn">import</span> <span class="n">MonteCarlo</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">prog_algs.uncertain_data</span> <span class="kn">import</span> <span class="n">ScalarData</span>
+<span class="go">&gt;&gt;&gt;</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">m</span> <span class="o">=</span> <span class="n">ThrownObject</span><span class="p">()</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">pred</span> <span class="o">=</span> <span class="n">MonteCarlo</span><span class="p">(</span><span class="n">m</span><span class="p">)</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">first_state</span> <span class="o">=</span> <span class="n">ScalarData</span><span class="p">({</span><span class="s1">&#39;x&#39;</span><span class="p">:</span> <span class="mf">1.7</span><span class="p">,</span> <span class="s1">&#39;v&#39;</span><span class="p">:</span> <span class="mi">20</span><span class="p">})</span>  <span class="c1"># Initial state for prediction</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="k">def</span> <span class="nf">future_loading</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
+<span class="gp">&gt;&gt;&gt; </span>   <span class="k">return</span> <span class="p">{}</span>  <span class="c1"># ThrownObject doesn&#39;t have a way of loading it</span>
+<span class="go">&gt;&gt;&gt;</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">u</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="n">es</span><span class="p">,</span> <span class="n">toe</span><span class="p">)</span> <span class="o">=</span> <span class="n">pred</span><span class="o">.</span><span class="n">predict</span><span class="p">(</span><span class="n">first_state</span><span class="p">,</span> <span class="n">future_loading</span><span class="p">,</span> <span class="n">save_freq</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">toe</span><span class="o">.</span><span class="n">plot_hist</span><span class="p">(</span><span class="n">events</span><span class="o">=</span><span class="s1">&#39;impact&#39;</span><span class="p">)</span>  <span class="c1"># Plot a histogram of when the impact event occurred</span>
+</pre></div>
+</div>
+<p>See tutorial and examples for more information and additional features.</p>
+<section id="included-predictors">
+<h2>Included Predictors<a class="headerlink" href="#included-predictors" title="Permalink to this headline">¶</a></h2>
+<p>The following predictors are included with this package. A new predictor can be created by subclassing <cite>prog_algs.predictors.Predictor</cite>. See also: <cite>predictor_template.py</cite></p>
+<section id="monte-carlo-predictor">
+<h3>Monte Carlo Predictor<a class="headerlink" href="#monte-carlo-predictor" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
-<dt id="prog_algs.predictors.predictor.Predictor">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.predictors.predictor.</span></code><code class="sig-name descname"><span class="pre">Predictor</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.predictor.Predictor" title="Permalink to this definition">¶</a></dt>
-<dd><p>Interface class for predictors</p>
-<p>Abstract base class for creating predictors that perform prediction. Predictor subclasses must implement this interface. Equivilant to “Observers” in NASA’s Matlab Prognostics Algorithm Library</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>model</strong> (<em>*</em>) – <p>See: Prognostics Model Package</p>
-<p>A prognostics model to be used in prediction</p>
-</p></li>
-<li><p><strong>options</strong><strong> (</strong><strong>optional</strong> (<em>*</em>) – Any additional configuration values. See documentation for specific predictor</p></li>
-<li><p><strong>kwargs</strong><strong>)</strong> (<em>configuration options</em>) – Any additional configuration values. See documentation for specific predictor</p></li>
-</ul>
+<dt class="sig sig-object py" id="prog_algs.predictors.MonteCarlo">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.predictors.</span></span><span class="sig-name descname"><span class="pre">MonteCarlo</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.MonteCarlo" title="Permalink to this definition">¶</a></dt>
+<dd><p>Class for performing a monte-carlo model-based prediction.</p>
+<p>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.</p>
+<p>The following configuration parameters are supported (as kwargs in constructor or as parameters in predict method):</p>
+<dl class="simple">
+<dt>dt<span class="classifier">float</span></dt><dd><p>Simulation step size (s), e.g., 0.1</p>
 </dd>
-</dl>
-<dl class="py method">
-<dt id="prog_algs.predictors.predictor.Predictor.predict">
-<em class="property"><span class="pre">abstract</span> </em><code class="sig-name descname"><span class="pre">predict</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">state_samples</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">future_loading_eqn</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.predictor.Predictor.predict" title="Permalink to this definition">¶</a></dt>
-<dd><p>Perform a single prediction</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>state_sampler</strong> (<em>function</em><em> (</em><em>n</em><em>) </em><em>-&gt;</em><em> [</em><em>x1</em><em>, </em><em>x2</em><em>, </em><em>.. xn</em><em>]</em>) – Function to generate n samples of the state.
-e.g., def f(n): return [x1, x2, x3, … xn]</p></li>
-<li><p><strong>future_loading_eqn</strong> (<em>function</em><em> (</em><em>t</em><em>) </em><em>-&gt; z</em>) – Function to generate an estimate of loading at future time t</p></li>
-<li><p><strong>options</strong> (<em>keyword arguments</em><em>, </em><em>optional</em>) – Any additional configuration values. See default parameters, above</p></li>
-</ul>
+<dt>events<span class="classifier">List[string]</span></dt><dd><p>Events to predict (subset of model.events) e.g., [‘event1’, ‘event2’]</p>
 </dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p><strong>result</strong></p>
+<dt>horizon<span class="classifier">float</span></dt><dd><p>Prediction horizon (s)</p>
+</dd>
+<dt>n_samples<span class="classifier">int</span></dt><dd><p>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.</p>
 </dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>recorded values for all samples</p>
+<dt>save_freq<span class="classifier">float</span></dt><dd><p>Frequency at which results are saved (s)</p>
+</dd>
+<dt>save_pts<span class="classifier">List[float]</span></dt><dd><p>Any additional savepoints (s) e.g., [10.1, 22.5]</p>
 </dd>
 </dl>
 </dd></dl>
 
+</section>
+<section id="unscented-transform-predictor">
+<h3>Unscented Transform Predictor<a class="headerlink" href="#unscented-transform-predictor" title="Permalink to this headline">¶</a></h3>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.predictors.UnscentedTransformPredictor">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.predictors.</span></span><span class="sig-name descname"><span class="pre">UnscentedTransformPredictor</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.UnscentedTransformPredictor" title="Permalink to this definition">¶</a></dt>
+<dd><p>Class for performing model-based prediction using an unscented transform.</p>
+<p>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.</p>
+<p>The following configuration parameters are supported (as kwargs in constructor or as parameters in predict method):</p>
+<dl class="simple">
+<dt>alpha, beta, kappa: float</dt><dd><p>UKF Scaling parameters. See: <a class="reference external" href="https://en.wikipedia.org/wiki/Kalman_filter#Unscented_Kalman_filter">https://en.wikipedia.org/wiki/Kalman_filter#Unscented_Kalman_filter</a></p>
+</dd>
+<dt>dt<span class="classifier">float</span></dt><dd><p>Simulation step size (s), e.g., 0.1</p>
+</dd>
+<dt>events<span class="classifier">List[string]</span></dt><dd><p>Events to predict (subset of model.events) e.g., [‘event1’, ‘event2’]</p>
+</dd>
+<dt>horizon<span class="classifier">float</span></dt><dd><p>Prediction horizon (s)</p>
+</dd>
+<dt>save_freq<span class="classifier">float</span></dt><dd><p>Frequency at which results are saved (s)</p>
+</dd>
+<dt>save_pts<span class="classifier">List[float]</span></dt><dd><p>Any additional savepoints (s) e.g., [10.1, 22.5]</p>
+</dd>
+</dl>
+<div class="admonition note">
+<p class="admonition-title">Note</p>
+<p>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.</p>
+</div>
 </dd></dl>
 
-<p>The predictor algorithms implemented in this package are described below:</p>
-<section id="mc-predictor">
-<h2>MC Predictor<a class="headerlink" href="#mc-predictor" title="Permalink to this headline">¶</a></h2>
+</section>
+</section>
+<section id="predictor-interface">
+<h2>Predictor Interface<a class="headerlink" href="#predictor-interface" title="Permalink to this headline">¶</a></h2>
 <dl class="py class">
-<dt id="prog_algs.predictors.monte_carlo.MonteCarlo">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.predictors.monte_carlo.</span></code><code class="sig-name descname"><span class="pre">MonteCarlo</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.monte_carlo.MonteCarlo" title="Permalink to this definition">¶</a></dt>
-<dd><p>Class for performing model-based prediction using sampling.</p>
-<p>This class defines logic for performing model-based state prediction using sampling methods. A Predictor is constructed using a PrognosticsModel object, (See Prognostics Model Package). The states are simulated until either a specified time horizon is met, or the threshold is reached for all samples, as defined by the threshold equation. A provided future loading equation is used to compute the inputs to the system at any given time point.</p>
+<dt class="sig sig-object py" id="prog_algs.predictors.Predictor">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.predictors.</span></span><span class="sig-name descname"><span class="pre">Predictor</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.Predictor" title="Permalink to this definition">¶</a></dt>
+<dd><p>Interface class for predictors</p>
+<p>Abstract base class for creating predictors that perform prediction. Predictor subclasses must implement this interface. Equivilant to “Observers” in NASA’s Matlab Prognostics Algorithm Library</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
 <dd class="field-odd"><ul class="simple">
-<li><p><strong>model</strong> (<em>*</em>) – <p>See: Prognostics Model Package</p>
+<li><p><strong>model</strong> (<em>PrognosticsModel</em>) – <p>See: Prognostics Model Package</p>
 <p>A prognostics model to be used in prediction</p>
 </p></li>
-<li><p><strong>options</strong><strong> (</strong><strong>optional</strong> (<em>*</em>) – <p>Any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported:</p>
-<ul>
-<li><p>dt : Step size (s)</p></li>
-<li><p>horizon : Prediction horizon (s)</p></li>
-<li><p>save_freq : Frequency at which results are saved (s)</p></li>
-<li><p>save_pts : Any additional savepoints (s) e.g., [10.1, 22.5]</p></li>
-<li><p>cores : Number of cores to use in multithreading</p></li>
-</ul>
-</p></li>
-<li><p><strong>kwargs</strong><strong>)</strong> (<em>configuration options</em>) – <p>Any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported:</p>
-<ul>
-<li><p>dt : Step size (s)</p></li>
-<li><p>horizon : Prediction horizon (s)</p></li>
-<li><p>save_freq : Frequency at which results are saved (s)</p></li>
-<li><p>save_pts : Any additional savepoints (s) e.g., [10.1, 22.5]</p></li>
-<li><p>cores : Number of cores to use in multithreading</p></li>
-</ul>
-</p></li>
+<li><p><strong>kwargs</strong> (<em>optional</em><em>, </em><em>keyword arguments</em>) – </p></li>
 </ul>
 </dd>
 </dl>
 <dl class="py method">
-<dt id="prog_algs.predictors.monte_carlo.MonteCarlo.predict">
-<code class="sig-name descname"><span class="pre">predict</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">state_samples</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">future_loading_eqn</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.monte_carlo.MonteCarlo.predict" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.predictors.Predictor.predict">
+<em class="property"><span class="pre">abstract</span> </em><span class="sig-name descname"><span class="pre">predict</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">state</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.uncertain_data.UncertainData"><span class="pre">prog_algs.uncertain_data.uncertain_data.UncertainData</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">future_loading_eqn</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.predictors.Predictor.predict" title="Permalink to this definition">¶</a></dt>
 <dd><p>Perform a single prediction</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
 <dd class="field-odd"><ul class="simple">
-<li><p><strong>state_samples</strong> (<em>collection of samples for the MonteCarlo</em>) – Function to generate n samples of the state.
-e.g., def f(n): return [x1, x2, x3, … xn]</p></li>
-<li><p><strong>future_loading_eqn</strong> (<em>function</em><em> (</em><em>t</em><em>, </em><em>x={}</em><em>) </em><em>-&gt; z</em>) – Function to generate an estimate of loading at future time t</p></li>
-<li><p><strong>config</strong> (<em>keyword arguments</em><em>, </em><em>optional</em>) – Any additional configuration values. See default parameters</p></li>
-<li><p><strong>(</strong><strong>tuple</strong><strong>)</strong> (<em>Returns</em>) – </p></li>
-<li><p><strong>-------</strong> – </p></li>
-<li><p><strong>times</strong> (<em>[</em><em>[</em><em>number</em><em>]</em><em>]</em>) – Times for each simulated point in format times[sample_id][index]</p></li>
-<li><p><strong>inputs</strong> (<em>[</em><em>[</em><em>dict</em><em>]</em><em>]</em>) – 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]</p></li>
-<li><p><strong>states</strong> (<em>[</em><em>[</em><em>dict</em><em>]</em><em>]</em>) – Estimated states for each sample and time in times
-where states[sample_id][index] corresponds to time times[sample_id][index]</p></li>
-<li><p><strong>outputs</strong> (<em>[</em><em>[</em><em>dict</em><em>]</em><em>]</em>) – Estimated outputs for each sample and time in times
-where outputs[sample_id][index] corresponds to time times[sample_id][index]</p></li>
-<li><p><strong>event_states</strong> (<em>[</em><em>[</em><em>dict</em><em>]</em><em>]</em>) – 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]</p></li>
-<li><p><strong>toe</strong> (<em>[</em><em>number</em><em>]</em>) – Estimated time where a predicted event will occur for each sample.</p></li>
+<li><p><strong>state</strong> (<a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData"><em>UncertainData</em></a>) – Distribution representing current state of the system</p></li>
+<li><p><strong>future_loading_eqn</strong> (<em>function</em><em> (</em><em>t</em><em>, </em><em>x</em><em>) </em><em>-&gt; z</em>) – Function to generate an estimate of loading at future time t, and state x</p></li>
+<li><p><strong>options</strong> (<em>optional</em><em>, </em><em>keyword arguments</em>) – <p>The following configuration parameters are supported:</p>
+<ul>
+<li><p>dt (float): Simulation step size (s), e.g., 0.1</p></li>
+<li><p>events (list[string]): Events to predict (subset of model.events) e.g., [‘event1’, ‘event2’]</p></li>
+<li><p>horizon (float): Prediction horizon (s)</p></li>
+<li><p>save_freq (float): Frequency at which results are saved (s)</p></li>
+<li><p>save_pts (list[float]): Any additional savepoints (s) e.g., [10.1, 22.5]</p></li>
+</ul>
+</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p><ul class="simple">
+<li><p><strong>times</strong> (<em>List[float]</em>) – 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]</p></li>
+<li><p><strong>inputs</strong> (<em>Prediction</em>) – Inputs at each savepoint such that inputs.snapshot(i) is the input distribution (type UncertainData) at times[i]</p></li>
+<li><p><strong>states</strong> (<em>Prediction</em>) – States at each savepoint such that states.snapshot(i) is the state distribution (type UncertainData) at times[i]</p></li>
+<li><p><strong>outputs</strong> (<em>Prediction</em>) – Outputs at each savepoint such that outputs.snapshot(i) is the output distribution (type UncertainData) at times[i]</p></li>
+<li><p><strong>event_states</strong> (<em>Prediction</em>) – Event states at each savepoint such that event_states.snapshot(i) is the event state distribution (type UncertainData) at times[i]</p></li>
+<li><p><strong>time_of_event</strong> (<em>UncertainData</em>) – Distribution of predicted Time of Event (ToE) for each predicted event, represented by some subclass of UncertaintData (e.g., MultivariateNormalDist)</p></li>
 </ul>
+</p>
 </dd>
 </dl>
 </dd></dl>
@@ -161,17 +184,21 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">Predictors</a><ul>
-<li class="toctree-l2"><a class="reference internal" href="#mc-predictor">MC Predictor</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#included-predictors">Included Predictors</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#predictor-interface">Predictor Interface</a></li>
 </ul>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -187,7 +214,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/py-modindex.html b/docs/py-modindex.html
index 58253184..c81bfff2 100644
--- a/docs/py-modindex.html
+++ b/docs/py-modindex.html
@@ -5,10 +5,10 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Python Module Index &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Python Module Index &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -61,16 +61,46 @@ <h1>Python Module Index</h1>
        <td>&#160;&#160;&#160;
        <a href="getting_started.html#module-examples.benchmarking_example"><code class="xref">examples.benchmarking_example</code></a></td><td>
        <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&#160;&#160;&#160;
+       <a href="getting_started.html#module-examples.eol_event"><code class="xref">examples.eol_event</code></a></td><td>
+       <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&#160;&#160;&#160;
+       <a href="getting_started.html#module-examples.measurement_eqn_example"><code class="xref">examples.measurement_eqn_example</code></a></td><td>
+       <em></em></td></tr>
      <tr class="cg-1">
        <td></td>
        <td>&#160;&#160;&#160;
        <a href="getting_started.html#module-examples.new_state_estimator_example"><code class="xref">examples.new_state_estimator_example</code></a></td><td>
        <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&#160;&#160;&#160;
+       <a href="getting_started.html#module-examples.particle_filter_battery_example"><code class="xref">examples.particle_filter_battery_example</code></a></td><td>
+       <em></em></td></tr>
      <tr class="cg-1">
        <td></td>
        <td>&#160;&#160;&#160;
        <a href="getting_started.html#module-examples.playback"><code class="xref">examples.playback</code></a></td><td>
        <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&#160;&#160;&#160;
+       <a href="getting_started.html#module-examples.predict_specific_event"><code class="xref">examples.predict_specific_event</code></a></td><td>
+       <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&#160;&#160;&#160;
+       <a href="getting_started.html#module-examples.thrown_object_example"><code class="xref">examples.thrown_object_example</code></a></td><td>
+       <em></em></td></tr>
+     <tr class="cg-1">
+       <td></td>
+       <td>&#160;&#160;&#160;
+       <a href="getting_started.html#module-examples.utpredictor"><code class="xref">examples.utpredictor</code></a></td><td>
+       <em></em></td></tr>
      <tr class="pcap"><td></td><td>&#160;</td><td></td></tr>
      <tr class="cap" id="cap-p"><td></td><td>
        <strong>p</strong></td><td></td></tr>
@@ -83,12 +113,7 @@ <h1>Python Module Index</h1>
      <tr class="cg-2">
        <td></td>
        <td>&#160;&#160;&#160;
-       <a href="metrics.html#module-prog_algs.metrics.samples"><code class="xref">prog_algs.metrics.samples</code></a></td><td>
-       <em></em></td></tr>
-     <tr class="cg-2">
-       <td></td>
-       <td>&#160;&#160;&#160;
-       <a href="visualize.html#module-prog_algs.visualize"><code class="xref">prog_algs.visualize</code></a></td><td>
+       <a href="metrics.html#module-prog_algs.metrics"><code class="xref">prog_algs.metrics</code></a></td><td>
        <em></em></td></tr>
    </table>
 
@@ -110,14 +135,17 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul>
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -131,7 +159,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/search.html b/docs/search.html
index ab75e430..aa53f31b 100644
--- a/docs/search.html
+++ b/docs/search.html
@@ -5,11 +5,11 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Search &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
+    <title>Search &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
     
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -37,26 +37,35 @@
           <div class="body" role="main">
             
   <h1 id="search-documentation">Search</h1>
-  <div id="fallback" class="admonition warning">
-  <script>$('#fallback').hide();</script>
+  
+  <noscript>
+  <div class="admonition warning">
   <p>
     Please activate JavaScript to enable the search
     functionality.
   </p>
   </div>
+  </noscript>
+  
+  
   <p>
     Searching for multiple words only shows matches that contain
     all words.
   </p>
+  
+  
   <form action="" method="get">
-    <input type="text" name="q" aria-labelledby="search-documentation" value="" />
+    <input type="text" name="q" aria-labelledby="search-documentation" value="" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
     <input type="submit" value="search" />
     <span id="search-progress" style="padding-left: 10px"></span>
   </form>
   
+  
+  
   <div id="search-results">
   
   </div>
+  
 
           </div>
           
@@ -75,14 +84,17 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul>
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
diff --git a/docs/searchindex.js b/docs/searchindex.js
index 9973ad1d..faaf64ca 100644
--- a/docs/searchindex.js
+++ b/docs/searchindex.js
@@ -1,1 +1,1 @@
-Search.setIndex({docnames:["dev_guide","exceptions","getting_started","index","metrics","predictors","state_estimators","uncertain_data","visualize"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["dev_guide.rst","exceptions.rst","getting_started.rst","index.rst","metrics.rst","predictors.rst","state_estimators.rst","uncertain_data.rst","visualize.rst"],objects:{"prog_algs.exceptions":{ProgAlgException:[1,1,1,""],ProgAlgInputException:[1,1,1,""],ProgAlgTypeError:[1,1,1,""]},"prog_algs.exceptions.ProgAlgException":{with_traceback:[1,2,1,""]},"prog_algs.exceptions.ProgAlgInputException":{with_traceback:[1,2,1,""]},"prog_algs.exceptions.ProgAlgTypeError":{with_traceback:[1,2,1,""]},"prog_algs.metrics":{samples:[4,0,0,"-"]},"prog_algs.metrics.samples":{alpha_lambda:[4,3,1,""],eol_metrics:[4,3,1,""],eol_profile_metrics:[4,3,1,""],mean_square_error:[4,3,1,""],percentage_in_bounds:[4,3,1,""],prob_success:[4,3,1,""]},"prog_algs.predictors.monte_carlo":{MonteCarlo:[5,4,1,""]},"prog_algs.predictors.monte_carlo.MonteCarlo":{predict:[5,2,1,""]},"prog_algs.predictors.predictor":{Predictor:[5,4,1,""]},"prog_algs.predictors.predictor.Predictor":{predict:[5,2,1,""]},"prog_algs.state_estimators.particle_filter":{ParticleFilter:[6,4,1,""]},"prog_algs.state_estimators.particle_filter.ParticleFilter":{estimate:[6,2,1,""],x:[6,2,1,""]},"prog_algs.state_estimators.state_estimator":{StateEstimator:[6,4,1,""]},"prog_algs.state_estimators.state_estimator.StateEstimator":{estimate:[6,2,1,""],x:[6,2,1,""]},"prog_algs.state_estimators.unscented_kalman_filter":{UnscentedKalmanFilter:[6,4,1,""]},"prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter":{estimate:[6,2,1,""],x:[6,2,1,""]},"prog_algs.uncertain_data":{MultivariateNormalDist:[7,4,1,""],ScalarData:[7,4,1,""],UncertainData:[7,4,1,""],UnweightedSamples:[7,4,1,""]},"prog_algs.uncertain_data.MultivariateNormalDist":{cov:[7,2,1,""],mean:[7,2,1,""],sample:[7,2,1,""]},"prog_algs.uncertain_data.ScalarData":{cov:[7,2,1,""],mean:[7,2,1,""],sample:[7,2,1,""]},"prog_algs.uncertain_data.UncertainData":{cov:[7,2,1,""],mean:[7,2,1,""],sample:[7,2,1,""]},"prog_algs.uncertain_data.UnweightedSamples":{append:[7,2,1,""],cov:[7,2,1,""],mean:[7,2,1,""],raw_samples:[7,2,1,""],sample:[7,2,1,""],size:[7,2,1,""]},examples:{basic_example:[2,0,0,"-"],benchmarking_example:[2,0,0,"-"],new_state_estimator_example:[2,0,0,"-"],playback:[2,0,0,"-"]},prog_algs:{visualize:[8,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","exception","Python exception"],"2":["py","method","Python method"],"3":["py","function","Python function"],"4":["py","class","Python class"]},objtypes:{"0":"py:module","1":"py:exception","2":"py:method","3":"py:function","4":"py:class"},terms:{"003":6,"013":0,"022":0,"023":0,"029":0,"033":0,"042":0,"100":[4,7],"121":0,"125":0,"139":0,"148":0,"156":0,"2020":3,"2020_nasa_prog_model":3,"205":0,"206":0,"221":6,"332":6,"7150":3,"abstract":[5,6,7],"catch":1,"class":[0,2,5,6,7],"default":[4,5,6,7],"float":[4,7],"function":[0,3,4,5,6],"int":7,"new":[0,2,3,7],"return":[1,4,5,7],"try":1,AND:3,BUT:3,FOR:3,For:[0,2],ITS:3,NOT:3,Not:0,SUCH:3,THAT:3,THE:3,The:[0,2,3,4,5,6,7],These:[0,4],USE:3,Use:3,WILL:3,Will:0,__traceback__:1,abc:6,abov:[0,5],accesss:0,acomplish:1,added:0,adding:0,addit:[5,6,7],addition:[5,6],against:3,agenc:3,agre:3,agreement:3,algorithm:[2,4,5,6],all:[0,2,3,4,5,7],alpha:[4,6],alpha_lambda:4,also:3,altern:1,analysi:[3,4],ani:[0,1,3,5,6,7],anoth:0,append:7,appli:2,applic:[0,2,3],approach:2,appropri:0,aquisiton:0,argument:5,aris:3,arrai:7,assign:0,assur:0,author:[0,3],autogen:0,base:[0,1,3,5,6,7],basic_exampl:2,batteri:2,been:0,befor:0,being:2,below:[0,1,2,4,5,6,8],benchmarking_exampl:2,best:3,beta:[4,6],between:5,bool:4,bound:4,branch:3,build:[0,2],calcul:4,call:2,can:[0,1,2,3],catolog:0,center:3,chang:0,check:0,chetan:3,christoph:3,claim:3,classif:0,code:0,collect:[4,5],com:3,command:2,commit:0,compar:3,compli:0,compliant:0,compon:3,comput:[2,3,4,5],concern:0,config:[0,5],configur:[5,6],conform:3,conjunct:2,constitut:3,construct:[1,5],constructor:6,contractor:3,control:2,corbetta:3,core:5,correspond:[0,3,4,5],could:1,cov:7,covar:7,covari:[6,7],creat:[2,5,6],cricial:0,critic:0,current:[2,6],custom:1,damag:3,data:[2,3,6],dec:3,def:[5,6],defin:[1,2,5,6],definit:2,demand:3,depend:3,describ:[0,4,5,6,8],descript:0,design:3,detail:0,dev:0,develop:[2,3],dict:[4,5,6,7],dictionari:6,differ:[3,6],directli:0,discharg:[2,4],dist:4,distribut:[3,7],doc:0,document:[0,3,5,6],doe:3,don:6,done:0,doubl:[4,6],drawn:2,dtype:7,each:[0,5],easili:3,effect:2,either:[2,3,5],electron:0,enabl:3,end:4,endors:3,engin:[2,3],ensur:0,enumer:2,environment:2,eol:4,eol_metr:4,eol_profile_metr:4,equat:[5,6],equivil:[5,6],error:[1,3,4],estim:[2,3,5,7],etc:6,evalu:3,event:[2,4,5],event_st:5,ever:6,everi:0,evid:0,exactli:6,exampl:[2,6,7],excel:3,except:3,exist:0,expens:3,express:3,extend:3,extent:3,failur:2,fall:1,featur:0,few:[1,2],file:2,filter:6,filterpi:6,fit:3,fload:4,float64:7,follow:[0,2,3,5,6],format:5,framework:[2,3],free:3,freedom:3,frequenc:5,frequent:2,from:[1,2,3,4,5,7],fulli:[0,2],further:3,futur:5,future_loading_eqn:5,gener:[0,5,6,7],get:[3,7],getter:6,getting_start:3,git:0,github:3,given:[3,4,5,6],govern:3,gradual:2,ground:4,ground_truth:4,guid:3,hand:3,hardwar:3,harmless:3,has:[0,2],have:2,health:2,healthi:2,here:0,hidden:2,hold:3,horizon:5,html:3,http:3,identifi:0,immedi:3,implement:[5,6,7],impli:3,includ:[0,2,3,4,7,8],incorrect:1,indemn:3,indemnifi:3,index:[3,5,6],indic:[1,2],infring:3,initi:6,input:[1,2,5,6],instal:3,interfac:[5,6],intern:2,kalman:6,kappa:6,kei:6,keyword:5,kind:3,kulkarni:3,kwarg:[5,6],label:7,lambda:4,lambda_valu:4,law:3,liabil:3,librari:[2,3,5,6],life:[2,3,4],like:[0,3],limit:3,load:5,logic:[5,6],look:3,loss:3,lower:4,maintain:0,manner:3,master:0,match:6,matlab:[5,6],matrix:[6,7],matteo:3,matter:3,mean:[4,7],mean_square_error:4,mean_valu:7,measur:[2,3,4,6],measurement_eqn:6,measurement_eqn_exampl:6,merchant:3,merg:0,met:[4,5],method:[1,5],metric:3,misalign:2,misc:3,mitig:0,model:[0,2,3,5,6],modul:[3,4],monte_carlo:[5,6],montecarlo:5,month:3,multithread:5,multivari:7,multivariatenormaldist:7,must:[0,5,6,7],name:2,nasa:[0,2,3,5,6],nears:2,need:[0,3],never:0,new_state_estimator_exampl:2,nois:[2,6],none:[4,6],normal:7,note:0,npr:3,nsampl:7,num_particl:6,num_sampl:7,number:[0,2,5,6,7],numpi:7,object:5,observ:[5,6],occur:[2,4,5],one:6,oper:2,option:[4,5,6,7],origin:3,other:[0,3],our:0,output:[2,4,5,6],packag:[0,2,5,6,7],page:3,paramet:[1,2,4,5,6,7],parti:3,partial:0,particl:6,particle_filt:6,particlefilt:6,particular:3,pcoe:3,percentag:4,percentage_in_bound:4,perform:[0,2,3,4,5,6],permit:3,pip:2,plan:0,playback:2,point:5,predict:[2,3,4,5],predictor:[2,3,4],present:3,prior:3,prob_success:4,probabl:4,process:[0,2,6],product:3,profil:4,prog_alg:[1,2,3,4,5,6,7,8],prog_model:1,progalgexcept:1,progalginputexcept:1,progalgtypeerror:1,progmodel:6,progmodelexcept:1,prognost:[0,2,4,5,6],prognosticsmodel:5,progress:2,project:0,propag:3,properti:[6,7],provid:[2,3,5],pull:0,purpos:3,python:[0,2],rapid:3,raw:7,raw_sampl:7,reach:5,rebuild:0,recipi:3,record:5,refer:3,regard:3,relev:0,remain:[2,3],remedi:3,repo:0,repres:[2,7],req:0,request:0,requir:[0,2],resampl:6,resample_fcn:6,research:[0,2,3],residual_resampl:6,respect:2,result:[3,4,5],review:0,run:[0,2],safeti:0,same:2,sampl:[4,5,7],sample_id:5,save:5,save_freq:5,save_pt:5,savepoint:5,scalardata:7,scale:6,search:3,second:6,section:0,see:[0,2,3,5,6],select:3,self:1,sensit:2,sensor:2,set:[1,2,3,7],shall:3,should:[0,3],signific:0,sim_exampl:2,simul:5,singl:[5,7],situat:6,size:[5,7],softwar:[0,3],soh:5,sole:3,solut:3,some:[0,2],someon:0,sourc:0,specif:[0,1,3,5,6],specifi:[4,5],sphinx:0,squar:4,start:[3,6],state:[2,3,5,7],state_estim:[2,6],state_sampl:5,stateestim:[2,6],statutori:3,step:[5,6],stochast:2,strategi:3,studi:3,subclass:[5,6],subcontractor:3,subject:3,success:4,suitabl:2,summar:2,support:[0,5,6],swap:3,swe:0,system:[2,3,5],tailor:0,tbd:[],temperatur:2,termin:3,test:[0,2],teubert:3,than:0,thi:[0,2,5,6,7],third:3,threshold:5,time:[4,5,6],timestamp:6,titl:3,toe:5,tool:[0,3,4,8],toward:2,transit:2,truth:4,tupl:5,txt:2,type:[1,4,5,7],typic:2,ukf:3,uncertain:3,uncertain_data:7,uncertaindata:7,uncertainti:[2,3,6,7],unilater:3,unit:[0,3,6],unscent:6,unscented_kalman_filt:6,unscentedkalmanfilt:6,until:5,unweight:7,unweightedsampl:7,updat:[0,6],upper:4,url:3,use:[2,3,5],used:[2,5,6],useful:[2,3],user:1,uses:6,using:[0,1,2,5,6],usual:6,valu:[2,4,5,6,7],variou:4,version:3,visual:3,voltag:2,waiv:3,waiver:3,warranti:3,weight:6,well:3,were:[0,1],what:6,where:[2,4,5,6],which:5,with_traceback:1,within:[2,4],without:[3,7],write:0,x0_uncertainti:6,year:3,you:[2,3]},titles:["Developers Guide","Exceptions","Getting Started","Prognostics Algorithms Python Package","Metrics","Predictors","State Estimators","Uncertain Data","Visualization"],titleterms:{"7150":0,Use:2,algorithm:3,aquisit:0,assess:0,branch:0,checklist:0,cite:3,complianc:0,content:0,cybersecur:0,data:7,depend:2,develop:0,disclaim:3,estim:6,except:1,extend:2,get:2,guid:0,indic:3,instal:2,legend:0,matrix:0,metric:4,model:1,notat:0,npr:0,option:0,packag:3,predictor:5,prognost:[1,3],python:3,releas:0,repositori:3,start:2,state:6,strategi:0,tabl:3,thi:3,ukf:6,uncertain:7,visual:8}})
\ No newline at end of file
+Search.setIndex({docnames:["dev_guide","exceptions","getting_started","index","metrics","prediction","predictors","state_estimators","uncertain_data"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"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:["dev_guide.rst","exceptions.rst","getting_started.rst","index.rst","metrics.rst","prediction.rst","predictors.rst","state_estimators.rst","uncertain_data.rst"],objects:{"prog_algs.exceptions":{ProgAlgException:[1,1,1,""],ProgAlgInputException:[1,1,1,""],ProgAlgTypeError:[1,1,1,""]},"prog_algs.exceptions.ProgAlgException":{with_traceback:[1,2,1,""]},"prog_algs.exceptions.ProgAlgInputException":{with_traceback:[1,2,1,""]},"prog_algs.exceptions.ProgAlgTypeError":{with_traceback:[1,2,1,""]},"prog_algs.metrics":{prob_success:[4,3,1,""]},"prog_algs.predictors":{MonteCarlo:[6,4,1,""],Prediction:[5,4,1,""],Predictor:[6,4,1,""],ToEPredictionProfile:[5,4,1,""],UnscentedTransformPredictor:[6,4,1,""],UnweightedSamplesPrediction:[5,4,1,""]},"prog_algs.predictors.Prediction":{mean:[5,5,1,""],snapshot:[5,2,1,""]},"prog_algs.predictors.Predictor":{predict:[6,2,1,""]},"prog_algs.predictors.ToEPredictionProfile":{add_prediction:[5,2,1,""],alpha_lambda:[5,2,1,""],clear:[5,2,1,""],get:[5,2,1,""],items:[5,2,1,""],keys:[5,2,1,""],pop:[5,2,1,""],popitem:[5,2,1,""],setdefault:[5,2,1,""],update:[5,2,1,""],values:[5,2,1,""]},"prog_algs.predictors.UnweightedSamplesPrediction":{count:[5,2,1,""],index:[5,2,1,""],mean:[5,5,1,""],snapshot:[5,2,1,""]},"prog_algs.state_estimators":{ParticleFilter:[7,4,1,""],StateEstimator:[7,4,1,""],UnscentedKalmanFilter:[7,4,1,""]},"prog_algs.state_estimators.StateEstimator":{estimate:[7,2,1,""],x:[7,5,1,""]},"prog_algs.uncertain_data":{MultivariateNormalDist:[8,4,1,""],ScalarData:[8,4,1,""],UncertainData:[8,4,1,""],UnweightedSamples:[8,4,1,""]},"prog_algs.uncertain_data.UncertainData":{cov:[8,5,1,""],keys:[8,2,1,""],mean:[8,5,1,""],median:[8,5,1,""],metrics:[8,2,1,""],percentage_in_bounds:[8,2,1,""],plot_hist:[8,2,1,""],plot_scatter:[8,2,1,""],sample:[8,2,1,""]},"prog_algs.uncertain_data.UnweightedSamples":{key:[8,2,1,""]},examples:{basic_example:[2,0,0,"-"],benchmarking_example:[2,0,0,"-"],eol_event:[2,0,0,"-"],measurement_eqn_example:[2,0,0,"-"],new_state_estimator_example:[2,0,0,"-"],particle_filter_battery_example:[2,0,0,"-"],playback:[2,0,0,"-"],predict_specific_event:[2,0,0,"-"],thrown_object_example:[2,0,0,"-"],utpredictor:[2,0,0,"-"]},prog_algs:{metrics:[4,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","exception","Python exception"],"2":["py","method","Python method"],"3":["py","function","Python function"],"4":["py","class","Python class"],"5":["py","property","Python property"]},objtypes:{"0":"py:module","1":"py:exception","2":"py:method","3":"py:function","4":"py:class","5":"py:property"},terms:{"0":[2,3,5,6,7,8],"000":4,"003":7,"013":0,"022":0,"023":0,"029":0,"033":0,"042":0,"1":[3,5,6,7,8],"10":[4,6],"100":8,"12":7,"121":0,"125":0,"139":0,"148":0,"156":0,"1s":6,"2":[0,2,3,5,7,8],"20":[5,6],"2020":[],"2020_nasa_prog_model":[],"2021":3,"2021_nasa_prog_alg":3,"205":0,"206":0,"22":6,"221":7,"2233":5,"23":8,"2rvf2estujordgwa":[],"3":[2,5,7,8],"332":7,"3da2pd5nhvsohjcrdc1g2r3ditpu7yic7w":[],"3v8pdw8":[],"4":[7,8],"49ukg7ulu61tglw1lq27ugqylclhc4bgv7vq":[],"5":[6,7,8],"50":8,"579aca":[],"7":6,"7150":3,"748":5,"7qgjvnjlrfr2ggsyx0eoy9upzn5spd86axggosekw2prz7du3vid3":[],"7rxnjc0kt5gc4djiv2sz3fb2iezg41":[],"8":7,"9":2,"95kx7":[],"9lspmxnylr3t5ao":[],"9xmsrmzyh1oljxmsr1oljxmsrmzyh1oljxmsr1oljxmsrmzyh1oll1oljxmsr1oljxmsrmzyh1oll1oljxmsrmzyh1oljxmsr1oll1oljxmsrmzyh1oll1oljxmsrmzyh1oll1oll0nff1oljxmsrmzyh1oljxmsq8dzb1oljxmsrmzyh1oljxmspxmspxmsr1oll1oljxmsrmzyh1oljxmsr1oll1oljxmsrmzyh1oll1olleaivxmsrmzyh1oll1oll1oljxmsrmzyh1ollna31xmsr1oljxmsr1oljxmsrmzyh1ollqovr1oljxmsr1oljxmsrmzyh1oll1olkkfapobxvvigabgadxmsqthkuofkhmz4dobnr1oljxmsr1oljxmspxmsr1oljxmsrfz4tuhwn1oll1oljxmsqbi7x1oljxmspzmslbmmhbmsdemsvfl8zgmsnim8jpk8f0m7r4m7f5nlb6jbh7jbidiroeibognkamhq":[],"abstract":[6,7,8],"case":[0,2,5],"catch":1,"class":[0,2,5,6,7,8],"default":[4,5,6,8],"do":[2,3],"float":[2,4,5,6,7,8],"function":[0,2,3,4,6,7,8],"import":[6,7],"int":[4,5,6,7,8],"new":[0,2,3,6,7,8],"return":[1,2,4,5,6,7,8],"true":[2,5],"try":1,A:[2,3,6,7],AND:3,AS:3,At:7,BE:3,BUT:3,BY:3,By:6,FOR:3,For:[0,2],IF:3,IN:3,IS:3,IT:3,ITS:3,If:[0,3,5,6,8],In:[2,5,6,7],Is:[2,5],It:2,NOT:3,No:[0,3,7],Not:0,OF:3,ON:3,OR:3,Of:6,SUCH:3,THAT:3,THE:3,TO:3,The:[0,2,3,4,5,6,7,8],There:2,These:[0,2,4,5],To:2,WILL:3,Will:0,With:8,__:[],__traceback__:1,abc:7,about:6,abov:0,accept:5,access:5,accesss:0,accord:7,account:2,achiev:2,acomplish:1,ad:[0,2,8],add:[2,5],add_predict:5,addit:[6,7,8],addition:[],adequ:2,adutnkaqjdscaaaaaelftksuqmcc:[],after:5,against:3,agenc:3,agre:3,agreement:3,algorithm:[2,4,6,7],all:[0,2,3,4,5,6,7,8],allow:5,along:6,alpha:[2,5,6,7],alpha_lambda:5,also:[2,3,5,6,7],altern:1,alwai:2,an:[0,2,3,5,6,7],analysi:[2,3,4],analyz:8,ani:[0,1,2,3,6,7,8],anoth:0,append:[],appli:[2,7],applic:[0,2,3],approach:2,appropri:0,aquisiton:0,ar:[0,2,3,4,5,6,7,8],arg:5,argument:[5,6,7,8],aris:3,around:5,arrai:[4,5,8],aspect:2,assign:0,assum:2,assumpt:6,assur:0,aug:3,author:[0,3],auto:[],autoclass:[],autogen:0,automat:[],avail:[2,7],avzyde4ipuk57osfnjm4fycqqoe0jhy2smylfoo56zyo6becos5uvddj7vih0zp:[],backward:[0,2],badg:[],base64:[],base:[0,1,2,3,6,7,8],basic_exampl:2,batteri:2,batterycircuit:2,becaus:2,been:0,befor:[0,2],being:2,below:[0,1,2,4,5,7,8],benchmark:2,benchmarking_exampl:2,best:[2,3],beta:[5,6,7],between:0,binder:[],bool:5,both:6,bound:[5,8],branch:[2,3],brbekr9ebw8zruckxwp73xkaliqqb:[],bugfix:2,build:[0,2],c3yb0tqzaml05:[],c:3,calc_metr:[],calcul:[2,4,5,8],call:[2,7],can:[0,1,2,3,5,6,7,8],cannot:6,catolog:0,cautiou:6,cd:2,center:3,chang:[0,2],characterist:2,check:0,checkout:2,chetan:3,choos:0,chosen:2,christoph:3,claim:3,classif:0,clear:5,clone:2,code:0,collect:[],com:[2,3],combin:[],command:2,commit:0,common:0,commonli:0,compar:3,compat:[0,2],compli:0,compliant:0,compon:3,comput:[2,3,6],concern:0,config:0,configur:[4,5,6,7],conform:3,conjunct:2,constitut:3,construct:[1,7],constructor:[6,7],contain:0,contractor:3,contribut:2,contributor:3,control:2,conveni:0,corbetta:3,core:[],correspond:[0,2,3,5],could:1,count:5,cov:8,covar:8,covari:[6,7,8],creat:[2,6,7,8],cricial:0,critic:0,current:[2,6,7],curv:6,custom:[1,2],d19pufyeo67znqtf2kh33id1psxofdw30spz1smvs2d060ahqws4fhejojlzqnw53cmfvg:[],d:5,damag:3,data:[3,5,7],ddsfdoyuyrikmfehz0hr2thpgqqmyqyb2otb0wxsz3beg3:[],dec:[],decis:0,def:[6,7],defin:[1,2,6,7],definit:2,demand:3,demonstr:[0,2],depend:0,depreci:0,describ:[0,4,5,7,8],descript:0,design:[0,3],desrib:2,detail:0,dev:[0,2],develop:[2,3],deviat:[7,8],diagnost:3,dict:[0,5,7,8],dictionari:[0,2,5,7,8],differ:[2,3,5,7,8],dimens:6,directli:[0,2,6],discharg:2,displai:2,dist:8,distinct:2,distribut:[2,3,5,6],doc:0,document:[0,3,7],doe:[3,5],doesn:[6,7],domain:6,don:7,done:0,dont:[],doubl:[],drawn:2,dt:[6,7],e:[0,2,4,5,6,7],each:[0,2,5,6,7],easili:3,effect:2,effici:0,either:[2,3,5,6,7],electron:0,element:2,els:5,empti:[5,8],en:6,enabl:3,end:[2,6],endors:3,engin:[2,3],ensur:0,enumer:[],environment:2,eol:[2,6],eol_ev:2,eol_metr:[],eol_profile_metr:[],equat:[6,7],equival:[],equivil:[6,7],error:[1,3,5],es:6,estim:[0,2,3,5,6],etc:[0,2,7],evalu:[3,5],event1:[2,5,6],event2:[5,6],event:[0,2,4,5,6],event_st:[0,2,6,7],ever:7,everi:[0,6],evid:0,exactli:7,exampl:[0,2,5,6,7,8],excel:3,except:[0,3],exist:[0,5,7,8],expect:6,expens:3,express:3,extend:[3,5],extent:3,extern:0,extract:2,extrem:0,f5:[],f:5,failur:2,fall:[1,2],fals:5,favor:[],fc:0,featur:[0,2,6,7],few:[0,1,2],fig:8,figur:[2,8],file:[],filt:7,filter:2,filterpi:7,final_st:2,first:[2,5,6],first_stat:6,fit:3,fload:[],follow:[0,2,3,5,6,7],form:[2,8],format:2,forward:[2,7],found:5,fr7:[],framework:[2,3],free:3,freedom:3,frequenc:6,frequent:2,from:[1,3,4,5,6,7,8],fulli:[0,2],further:[2,3],futur:[2,5,6],future_load:6,future_loading_eqn:6,fzqu4aeg8ze9ljgmztmqkxplnlwvnilqqtvxjf8ip7vgjzjyvprjw1te5otm7rmp7xm:[],g:[0,2,5,6,7,8],gaussian:6,gener:[0,6,7,8],get:[3,5,7,8],getter:[],getting_start:[],gh:[],git:[0,2],github:3,given:[2,3,4,5,6,7,8],govern:3,gradual:2,grl:[],ground:[5,8],ground_truth:[5,8],group:3,guid:3,guidanc:3,ha:[0,2,5],hand:3,hardwar:3,harmless:3,have:[0,2,6],health:2,healthi:2,here:[0,6,7],hidden:2,histogram:[2,6,8],hold:3,horizon:6,host:2,how:[2,7],html:[],http:[2,3,6],hypothesi:6,i2fnz:[],i:[0,2,4,5,6,7],id:[],identifi:0,illustr:2,imag:[],img:[],immedi:3,immut:5,impact:[2,6],implement:[3,6,7],impli:3,improv:2,includ:[0,2,3,4,5,8],incorrect:1,increas:5,indemn:3,indemnifi:3,independ:0,index:[3,5,7],indexerror:[],indic:[1,2],individu:2,inform:[2,6,7],infring:3,inidividu:8,inidividualev:2,initi:[6,7],initial_st:7,input:[0,1,2,5,6,7],insert:[],instal:3,instanc:2,instanti:2,instead:2,integ:5,intend:[],interchang:0,interest:2,interfac:3,intern:2,introduct:0,investig:2,io:[],ipynb:[],isn:2,item:5,iter:5,its:[6,7],itself:7,ivborw0kggoaaaansuheugaaafkaaabzcamaaabi1xidaaab8lbmvex:[],k:5,kalman:[],kalman_filt:6,kappa:[6,7],kei:[2,4,5,7,8],kept:[],key1:8,keyerror:5,keyword:[5,6,7,8],kgdujtnhg72zcw9lojgqqxpp3:[],kind:[3,8],kssxi8xpxc4v3t7:[],kulkarni:3,kw:[],kwarg:[4,5,6,7,8],kx9msjlr8mlltcw8hhgjc2r5hs219iif6pntusoqcml57gm0z8kankmaqg0qsyuzfn7zitsbgyo9qlnxy0ecud1xl2i:[],label:8,labpath:[],lack:5,lambda:[2,5],lambda_valu:5,last:[],latest:2,law:3,learn:2,least:0,legend:[],len:[],liabil:3,librari:[2,6,7],life:[2,3,6],like:[0,2,3,5,6,8],limit:[0,3],line:2,linear:6,link:2,list:[2,4,5,6,7,8],load:[2,6,7],logic:[6,7],logo:[],look:3,loss:3,lower:8,m3:[],m:[0,2,3,6,7],made:[0,5],mai:2,maintain:0,make:0,mani:2,manipul:2,manner:3,map:5,mark:0,master:0,match:7,matlab:[6,7],matplotlib:8,matrix:[7,8],matteo:3,matter:3,mb47ubirva4ryogjdaibeaixhowhmzyhobxvpchjqdhxrehlrovrsfg:[],mc:[],mean:[5,6,7,8],mean_square_error:[],mean_valu:[5,8],measur:[2,3,4,7],measurement_eqn:[2,7],measurement_eqn_exampl:[2,7],median:8,median_valu:8,member:2,merchant:3,merg:0,met:[2,5,6],method:[1,2,5,6,7],metric:[0,2,3,5,8],might:2,misalign:2,misc:3,miss:2,mitig:0,model:[0,2,3,6,7],modul:[3,4],monte_carlo:7,montecarlo:[2,5,6],month:3,more:[0,6,7],most:[0,2],msrqhlte7ug0ufozufjfe2pxbo:[],multipl:[2,4,5,7],multithread:[],multivari:[],multivariatenormaldist:[2,6,8],must:[0,5,6,7,8],mybind:[],n3vccppdcppgn3blb4:[],n:[2,5,8],n_sampl:[4,6,8],na:0,name:0,nasa:[0,2,3,6,7],nc:0,nears:2,necessari:2,need:[0,3],never:0,new_data:7,new_state_estimator_exampl:2,next:5,nois:[2,7],non:[2,6],none:[5,7,8],nonlinear:6,normal:[],note:0,notibl:2,now:2,npr:3,nsampl:8,nth:8,num_particl:7,num_sampl:8,number:[0,2,4,5,6,7,8],numpi:8,object:[0,2,5,8],observ:[2,5,6,7],obtain:6,occur:[0,2,4,5,6],occurr:5,one:[2,7],onli:[2,8],oper:[0,2,5,8],option:[4,5,6,7,8],order:[5,8],org:6,orhlksvo:[],origin:3,other:[0,2,3],otherwis:5,our:0,out:[],output:[0,2,4,5,6,7],overritten:8,packag:[0,2,5,6,7,8],package_structur:[],page:[2,3],pair:5,paramet:[1,2,4,5,6,7,8],parent:[],parti:3,partial:0,particl:2,particle_filt:[],particle_filter_battery_exampl:2,particlefilt:7,particular:3,pass:8,pc:0,pcoe:3,percentag:[5,8],percentage_in_bound:[2,8],perform:[0,2,3,4,6,7],permit:3,pf:7,pip:[],place:6,plan:0,playback:2,plot:[2,6,8],plot_hist:[6,8],plot_scatt:8,pnacvg6qwg6qegkaff6whnpkofkgtnomxezy3nog6dzi:[],png:[],point:[2,5,6],pop:5,popitem:5,portion:5,possibl:0,posterior:[2,7],pre:[],pred:6,predefin:6,prediciton:[],predict:[2,3,6,7],predict_specific_ev:2,predictor:[0,2,3,4,5,7],predictor_templ:6,prefer:[],present:[3,5],previou:2,previous:8,print:[5,7],prior:[2,3,7],prob_success:4,probabl:4,procedur:6,process:[0,2,7],produc:[2,7,8],product:3,profil:3,prog_alg:[1,2,3,4,5,6,7,8],prog_model:[1,2,6,7],progalgexcept:1,progalginputexcept:1,progalgtypeerror:1,progmodel:[3,7],progmodelexcept:1,prognost:[0,2,4,6,7],prognosticsmodel:6,progress:2,project:0,propag:[3,6],properti:[5,7,8],propog:2,provid:[2,3,5,6],pull:0,purpos:3,py:[6,7],pypi:2,python3:2,python:[0,2],pz9:[],q:7,r:7,rais:5,rang:[],rapid:3,reach:[2,6],real:6,rebuild:0,recipi:3,recommend:5,record:[],refer:[0,3],regard:3,regardless:2,releas:[],relev:0,remain:[2,3],remedi:3,remov:[0,2,5],repeat:6,replac:7,repo:[0,2],repres:[2,5,6,8],req:0,request:0,requir:0,resampl:7,resample_fcn:7,research:[0,2,3],residual_resampl:7,respect:2,respos:2,result:[2,3,4,5,6],revers:[],review:0,run:[0,2,7],rzl5zj137p136bisy44fkjxugn:[],s1mco54ymytwcn6a:[],s:[0,2,3,6,7],safeti:0,same:[2,8],sampl:[2,4,5,6],sample_id:[],satisfactori:6,satisfi:6,save:6,save_freq:6,save_pt:6,savepoint:[5,6],scalar:[6,7],scalardata:[2,6,8],scale:[6,7],scatter:[2,8],search:3,second:7,section:0,see:[0,2,3,6,7],select:3,self:1,sensit:2,sensor:[2,7],sequenc:[],set:[1,2,3,5,8],setdefault:5,shall:[0,3],shield:[],should:[0,3,6],show:2,shown:[],sigma:[2,6],signific:0,significantli:2,sim_exampl:[],simresult:[2,5],simul:[2,6],simulate_to:2,singl:[2,4,5,6,8],situat:7,size:[2,6],sk2gv9i8gi:[],snapshot:[2,5,6],so:[0,2],soc:2,softwar:[0,3],soh:[],sole:3,solut:3,some:[0,2,5,6],someon:[0,2],someth:2,sourc:0,space:6,specif:[0,1,2,3,5,7],specifi:[2,5,6,8],sphinx:0,squar:[],ssrs2w7ovvhktjrx2pd7zmltxayfbal9jidwflkq55tm7ifhmltgpycas7rfrhn47jnlcb9rm5t97asuzxicvnuudindpdbdsfrqsoppexl5i:[],stabl:2,standard:[7,8],stardard:7,start:[3,5,7],state1:[5,7,8],state2:[5,7,8],state:[0,2,3,5,6,8],state_estim:[2,7],state_estimator_templ:7,state_sampl:[],stateestim:[2,7],statutori:3,step:[2,6,7],stochast:2,stop:5,store:[2,5,7],str:[5,8],strategi:3,string:[4,5,6,8],strong:6,structur:5,studi:3,subclass:[2,6,7],subcontractor:3,subject:3,subpackag:0,subset:6,success:4,suitabl:2,summar:2,summari:3,suppli:0,support:[0,2,4,5,6,7],svg:[],swap:3,swe:0,system:[2,3,6,7],t09px29vb39:[],t0:7,t:[0,2,6,7],tailor:0,take:2,target:[],tayj5zc:[],tb:1,tcmhwrpbelyqtijljkaizsbi8sgsf3k0pa3mr5thuwpfoa7n7reoq2bqcsak1hqcu5uvi1n6jurxi:[],temperatur:2,termin:3,test:[0,2],teubert:3,th:6,than:0,thei:[0,5],therefor:6,thi:[0,2,5,6,7,8],third:3,those:[5,8],three:2,threshold:[2,6],through:2,throughout:2,thrown:2,thrown_object_exampl:2,thrownobject:[2,6,7],time:[2,4,5,6,7],time_index:5,time_of_ev:6,time_of_predict:5,timepoint:2,timestamp:7,timestep:5,titl:3,tj:[],toe:[2,3,4,6],toe_metr:[],toe_predict:5,toe_prediction_profil:[],toe_profil:[],toe_profile_metr:[],toepredictionprofil:[2,5],tool:[0,2,3,4],toward:2,transform:2,transit:2,treat:[2,5,8],trivial:2,truth:[5,8],tte:5,tupl:5,tutori:[0,2,3,6,7],two:5,txt:[],type:[0,1,3,4,5,6],typic:[2,7],tz:[],u:[6,7],uhgnuh2bwj2hxk17yl1vzmljzm1j0nlx1oll3ajxwaaaabxrstlmaebaqhx8gicauljawmdw9pubaqepqufbxv1hgygbkchbwcxl8gicagoiikjcqljicnj2gokcmqk:[],ujspvs1sd5cwrywobtvwt2epa4rtnww3jkpjggepbospr7f7eynewtpbisla7p43hcsnwooxtec3umpmcnn5lrqtjxy6nrmcavgzvt:[],ukf:[6,7],uncertain:[2,3],uncertain_data:[5,6,8],uncertaindata:[0,2,3,4,5,6],uncertaintdata:6,uncertainti:[2,3,5,6,7],uncertiantydata:8,under:6,unilater:3,unit:[0,3,7],unless:2,unlik:2,unscent:2,unscented_kalman_filt:6,unscentedkalmanfilt:7,unscentedtransformpredictor:6,unstabl:2,until:6,unweight:6,unweightedsampl:[2,4,5,6,8],up:2,updat:[0,3,5,7],upper:8,url:3,us:[0,1,3,4,5,6,7,8],usabl:0,user:[0,1,6],usual:7,utpredictor:2,v0:[],v1:3,v2:[],v7:[],v:[5,6,7],valu:[0,2,5,6,7,8],valueerror:5,variabl:2,varieti:2,variou:[2,4],vector:2,verifi:6,version:3,view:[],visual:[0,3],voltag:2,wa:[3,5],wai:[2,6],waiv:3,waiver:3,want:2,warranti:3,we:[0,2,6,7],weight:7,well:[3,6,7],were:[0,1,2],what:7,when:[0,2,6],whenev:0,where:[0,2,5,7,8],which:[2,5,6,7],who:2,wider:0,wiki:6,wikipedia:6,with_traceback:1,within:[2,4,5,8],without:[2,3,8],would:[2,6],wprb1vzl2uybog8ffghttfkjtaclnzyrro9ryg9ug:[],write:0,wslc4usdawmjp0ndq1nbw3nzg4odi5:[],x0:7,x0_uncertainti:7,x1:[],x2:[],x3:[],x:[6,7],xn:[],xnyf1gw3vkcez0pn5iufflqvgun4c0qnqys5yhpl:[],xr8mc0oejuxrxekx5ydevjlvilv0e10pxk5k7dyehu7cj1j:[],xvkdjfcth:[],yax8xppddwwn0mrdryizf0msmkcnhgaivfobbnolj7teqdkxgf0kclqimojczopv0oknoywccg9xmvai7arjzqdm2quh0gmbozjc3skg6dsbrqdgysuou66zg:[],year:3,you:[2,3],z8:[],z:[2,6,7],zczcxgaabc5jrefueahn1ul3k0uubvcb1ctvpmpaitagslspsukclwpbtknjfglcsmaff63iumrccng6glbuxkxu66jauef:[],zgavuj0d9zy05ppk3qzbamxdwttcrzyg:[]},titles:["Developers Guide","Exceptions","Getting Started","Prognostics Algorithms Python Package","Metrics","Prediction","Predictors","State Estimators","Uncertain Data"],titleterms:{"1":2,"7150":0,"class":[],algorithm:3,aquisit:0,assess:0,base:5,branch:0,carlo:6,checklist:0,cite:3,complianc:0,content:0,contributor:0,cybersecur:0,data:[2,8],depend:[],develop:0,disclaim:3,distribut:8,e:8,estim:7,except:1,exist:2,extend:2,filter:7,from:2,gener:2,get:2,github:2,guid:0,guidanc:0,i:8,implement:8,includ:[6,7],indic:3,instal:2,interfac:[6,7,8],kalman:7,legend:0,matrix:0,mc:[],metric:4,model:1,mont:6,multivari:8,multivariatenormaldist:[],normal:8,notat:0,note:2,npr:0,option:0,packag:3,particl:7,pf:[],pip:2,pre:2,predict:5,predictor:6,profil:5,prognost:[1,3],python:3,recommend:2,releas:[0,2],repositori:3,sampl:8,scalar:8,scalardata:[],start:2,state:7,strategi:0,structur:2,summari:2,tabl:3,thi:3,toe:5,transform:6,type:8,ukf:[],uncertain:8,uncertaindata:8,uncertainti:8,unscent:[6,7],unweight:8,unweightedsampl:[],unweightedsamplespredict:5,updat:2,us:2,user:2,v1:2,version:2,visual:[]}})
\ No newline at end of file
diff --git a/docs/state_estimators.html b/docs/state_estimators.html
index 18436d3c..5e3b28cf 100644
--- a/docs/state_estimators.html
+++ b/docs/state_estimators.html
@@ -6,10 +6,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>State Estimators &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>State Estimators &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -35,176 +35,114 @@
             
   <section id="state-estimators">
 <h1>State Estimators<a class="headerlink" href="#state-estimators" title="Permalink to this headline">¶</a></h1>
+<p>The State Estimator uses sensor information and a Prognostics Model (see: <a class="reference external" href="https://github.com/nasa/prog_models">prog_models package</a>) to produce an estimate of system state (which can be used to estimate outputs, event_states, and observables). This state estimate can either be used by itself or as input to a <a class="reference external" href="predictors.html">Predictor</a>. A state estimator is typically run multiple times as new information is available.</p>
+<p>Here’s an example of its use. In this example we use the unscented kalman filter state estimator and the ThrownObject model.</p>
+<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">prog_models.models</span> <span class="kn">import</span> <span class="n">ThrownObject</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">prog_algs.state_estimators</span> <span class="kn">import</span> <span class="n">UnscentedKalmanFilter</span>
+<span class="go">&gt;&gt;&gt;</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">m</span> <span class="o">=</span> <span class="n">ThrownObject</span><span class="p">()</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">initial_state</span> <span class="o">=</span> <span class="n">m</span><span class="o">.</span><span class="n">initialize</span><span class="p">()</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">filt</span> <span class="o">=</span> <span class="n">UnscentedKalmanFilter</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">initial_state</span><span class="p">)</span>
+<span class="go">&gt;&gt;&gt;</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">load</span> <span class="o">=</span> <span class="p">{}</span>  <span class="c1"># No load for ThrownObject</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">new_data</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;x&#39;</span><span class="p">:</span> <span class="mf">1.8</span><span class="p">}</span>  <span class="c1"># Observed state</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Prior: &#39;</span><span class="p">,</span> <span class="n">filt</span><span class="o">.</span><span class="n">x</span><span class="o">.</span><span class="n">mean</span><span class="p">)</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">filt</span><span class="o">.</span><span class="n">estimate</span><span class="p">(</span><span class="mf">0.1</span><span class="p">,</span> <span class="n">load</span><span class="p">,</span> <span class="n">new_data</span><span class="p">)</span>
+<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Posterior: &#39;</span><span class="p">,</span> <span class="n">filt</span><span class="o">.</span><span class="n">x</span><span class="o">.</span><span class="n">mean</span><span class="p">)</span>
+</pre></div>
+</div>
+<p>See tutorial and examples for more information and additional features.</p>
+<section id="included-state-estimators">
+<h2>Included State Estimators<a class="headerlink" href="#included-state-estimators" title="Permalink to this headline">¶</a></h2>
+<p>The following state estimators are included with this package. A new state estimator can be created by subclassing <cite>prog_algs.state_estimators.StateEstimator</cite>. See also: <cite>state_estimator_template.py</cite></p>
+<section id="unscented-kalman-filter">
+<h3>Unscented Kalman Filter<a class="headerlink" href="#unscented-kalman-filter" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
-<dt id="prog_algs.state_estimators.state_estimator.StateEstimator">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.state_estimator.</span></code><code class="sig-name descname"><span class="pre">StateEstimator</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">x0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">measurement_eqn</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.state_estimator.StateEstimator" title="Permalink to this definition">¶</a></dt>
-<dd><p>Interface class for state estimators</p>
-<p>Abstract base class for creating state estimators that perform state estimation. Subclasses must implement this interface. Equivilant to “Observers” in NASA’s Matlab Prognostics Algorithm Library</p>
-<dl>
-<dt>Constructor parameters:</dt><dd><ul>
-<li><p>model (ProgModel): Model to be used in state estimation</p>
-<blockquote>
-<div><p>See: Prognostics Model Package</p>
-<p>A prognostics model to be used in state estimation</p>
-</div></blockquote>
-</li>
-<li><p>x0 (dict): Initial State</p>
-<blockquote>
-<div><p>Initial (starting) state, with keys defined by model.states</p>
-<p>e.g., x = {‘abc’: 332.1, ‘def’: 221.003} given states = [‘abc’, ‘def’]</p>
-</div></blockquote>
-</li>
-<li><p>measurement_eqn (optional, function): Measurement equation (x)-&gt;z. Usually used in situations where what’s measured don’t exactly match the output (e.g., different unit, not ever output measured, etc.). see <cite>examples.measurement_eqn_example</cite></p></li>
-<li><p>options (optional, kwargs): configuration options</p>
-<blockquote>
-<div><p>Dictionary of any additional configuration values. See state-estimator specific documentation</p>
-</div></blockquote>
-</li>
-</ul>
+<dt class="sig sig-object py" id="prog_algs.state_estimators.UnscentedKalmanFilter">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.</span></span><span class="sig-name descname"><span class="pre">UnscentedKalmanFilter</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">x0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">measurement_eqn</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.UnscentedKalmanFilter" title="Permalink to this definition">¶</a></dt>
+<dd><p>An Unscented Kalman Filter (UKF) for state estimation</p>
+<p>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.</p>
+<p>The supported configuration parameters (keyword arguments) for UKF construction are described below:</p>
+<dl class="simple">
+<dt>Constructor Configuration Parameters:</dt><dd><dl class="simple">
+<dt>alpha, beta, kappa: float</dt><dd><p>UKF Scaling parameters</p>
+</dd>
+<dt>t0<span class="classifier">float</span></dt><dd><p>Starting time (s)</p>
+</dd>
+<dt>dt<span class="classifier">float</span></dt><dd><p>time step (s)</p>
+</dd>
+<dt>Q<span class="classifier">List[List[float]]</span></dt><dd><p>Process Noise Matrix</p>
+</dd>
+<dt>R<span class="classifier">List[List[float]]</span></dt><dd><p>Measurement Noise Matrix</p>
 </dd>
 </dl>
-<dl class="py method">
-<dt id="prog_algs.state_estimators.state_estimator.StateEstimator.estimate">
-<em class="property"><span class="pre">abstract</span> </em><code class="sig-name descname"><span class="pre">estimate</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">t</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">u</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">z</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.state_estimator.StateEstimator.estimate" title="Permalink to this definition">¶</a></dt>
-<dd><p>Perform one state estimation step (i.e., update the state estimate)</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>t</strong> (<em>double</em>) – Current timestamp in seconds (≥ 0.0)
-e.g., t = 3.4</p></li>
-<li><p><strong>u</strong> (<em>dict</em>) – Measured inputs, with keys defined by model.inputs.
-e.g., u = {‘i’:3.2} given inputs = [‘i’]</p></li>
-<li><p><strong>z</strong> (<em>dict</em>) – Measured outputs, with keys defined by model.outputs.
-e.g., z = {‘t’:12.4, ‘v’:3.3} given inputs = [‘t’, ‘v’]</p></li>
-</ul>
 </dd>
 </dl>
 </dd></dl>
 
-<dl class="py method">
-<dt id="prog_algs.state_estimators.state_estimator.StateEstimator.x">
-<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">x</span></code><a class="headerlink" href="#prog_algs.state_estimators.state_estimator.StateEstimator.x" title="Permalink to this definition">¶</a></dt>
-<dd><p>Getter for property ‘x’, the current estimated state.</p>
-<p class="rubric">Example</p>
-<p>state = observer.x</p>
-</dd></dl>
-
-</dd></dl>
-
-<p>The state estimator algorithms implemented in this package are described below:</p>
-<section id="ukf-state-estimator">
-<h2>UKF State Estimator<a class="headerlink" href="#ukf-state-estimator" title="Permalink to this headline">¶</a></h2>
+</section>
+<section id="particle-filter">
+<h3>Particle Filter<a class="headerlink" href="#particle-filter" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
-<dt id="prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.unscented_kalman_filter.</span></code><code class="sig-name descname"><span class="pre">UnscentedKalmanFilter</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">x0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">measurement_eqn</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter" title="Permalink to this definition">¶</a></dt>
-<dd><p>An Unscented Kalman Filter (UKF) for state estimation</p>
-<p>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.</p>
+<dt class="sig sig-object py" id="prog_algs.state_estimators.ParticleFilter">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.</span></span><span class="sig-name descname"><span class="pre">ParticleFilter</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">x0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">measurement_eqn</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.ParticleFilter" title="Permalink to this definition">¶</a></dt>
+<dd><p>Estimates state using a particle filter (PF) algorithm.</p>
+<p>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.</p>
+<p>The supported configuration parameters (keyword arguments) for UKF construction are described below:</p>
 <dl>
-<dt>Constructor parameters:</dt><dd><ul>
-<li><p>model (ProgModel): Model to be used in state estimation</p>
-<blockquote>
-<div><p>See: Prognostics Model Package</p>
-<p>A prognostics model to be used in state estimation</p>
-</div></blockquote>
-</li>
-<li><p>x0 (dict): Initial State</p>
-<blockquote>
-<div><p>Initial (starting) state, with keys defined by model.states</p>
-<p>e.g., x = {‘abc’: 332.1, ‘def’: 221.003} given states = [‘abc’, ‘def’]</p>
-</div></blockquote>
-</li>
-<li><p>measurement_eqn (optional, function): Measurement equation (x)-&gt;z. Usually used in situations where what’s measured don’t exactly match the output (e.g., different unit, not ever output measured, etc.). see <cite>examples.measurement_eqn_example</cite></p></li>
-<li><p>options (optional, kwargs): configuration options</p>
-<blockquote>
-<div><p>Dictionary of any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported:</p>
-<blockquote>
-<div><ul class="simple">
-<li><p>alpha, beta, kappa: UKF Scaling parameters</p></li>
-<li><p>t0 : Starting time</p></li>
-<li><p>dt : time step</p></li>
-<li><p>Q : Process Noise Matrix</p></li>
-<li><p>R : Measurement Noise Matrix</p></li>
-</ul>
-</div></blockquote>
-</div></blockquote>
-</li>
-</ul>
+<dt>Constructor Configuration Parameters:</dt><dd><dl>
+<dt>t0<span class="classifier">float</span></dt><dd><p>Starting time (s)</p>
+</dd>
+<dt>dt<span class="classifier">float</span></dt><dd><p>time step (s)</p>
+</dd>
+<dt>num_particles<span class="classifier">int</span></dt><dd><p>Number of particles in particle filter</p>
+</dd>
+<dt>resample_fcn<span class="classifier">function</span></dt><dd><p>Resampling function ([weights]) -&gt; [indexes] e.g., filterpy.monte_carlo.residual_resample</p>
+</dd>
+<dt>x0_uncertainty<span class="classifier">float or dict</span></dt><dd><p>Initial uncertainty in state. Can be 1. scalar (standard deviation applied to all), or 2. dict (stardard deviation for each)</p>
+<p>e.g., 0.5 or {‘state1’: 0.5, ‘state2’: 0.2}</p>
 </dd>
 </dl>
-<dl class="py method">
-<dt id="prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter.estimate">
-<code class="sig-name descname"><span class="pre">estimate</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">t</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">u</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">z</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter.estimate" title="Permalink to this definition">¶</a></dt>
-<dd><p>Perform one state estimation step (i.e., update the state estimate)</p>
-<dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><ul class="simple">
-<li><p><strong>t</strong> (<em>double</em>) – Current timestamp in seconds (≥ 0.0)
-e.g., t = 3.4</p></li>
-<li><p><strong>u</strong> (<em>dict</em>) – Measured inputs, with keys defined by model.inputs.
-e.g., u = {‘i’:3.2} given inputs = [‘i’]</p></li>
-<li><p><strong>z</strong> (<em>dict</em>) – Measured outputs, with keys defined by model.outputs.
-e.g., z = {‘t’:12.4, ‘v’:3.3} given inputs = [‘t’, ‘v’]</p></li>
-</ul>
 </dd>
 </dl>
 </dd></dl>
 
-<dl class="py method">
-<dt id="prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter.x">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">x</span></code><a class="headerlink" href="#prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter.x" title="Permalink to this definition">¶</a></dt>
-<dd><p>Getter for property ‘x’, the current estimated state.</p>
-<p class="rubric">Example</p>
-<p>state = observer.x</p>
-</dd></dl>
-
-</dd></dl>
-
 </section>
-<section id="pf-state-estimator">
-<h2>PF State Estimator<a class="headerlink" href="#pf-state-estimator" title="Permalink to this headline">¶</a></h2>
+</section>
+<section id="state-estimator-interface">
+<h2>State Estimator Interface<a class="headerlink" href="#state-estimator-interface" title="Permalink to this headline">¶</a></h2>
 <dl class="py class">
-<dt id="prog_algs.state_estimators.particle_filter.ParticleFilter">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.particle_filter.</span></code><code class="sig-name descname"><span class="pre">ParticleFilter</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">x0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">measurement_eqn</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.particle_filter.ParticleFilter" title="Permalink to this definition">¶</a></dt>
-<dd><p>Estimates state using a particle filter algorithm</p>
-<dl>
-<dt>Constructor parameters:</dt><dd><ul>
-<li><p>model (ProgModel): Model to be used in state estimation</p>
-<blockquote>
-<div><p>See: Prognostics Model Package</p>
-<p>A prognostics model to be used in state estimation</p>
-</div></blockquote>
-</li>
-<li><p>x0 (dict): Initial State</p>
-<blockquote>
-<div><p>Initial (starting) state, with keys defined by model.states</p>
+<dt class="sig sig-object py" id="prog_algs.state_estimators.StateEstimator">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.</span></span><span class="sig-name descname"><span class="pre">StateEstimator</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">model</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">x0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">measurement_eqn</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.StateEstimator" title="Permalink to this definition">¶</a></dt>
+<dd><p>Interface class for state estimators</p>
+<p>Abstract base class for creating state estimators that perform state estimation. Subclasses must implement this interface. Equivilant to “Observers” in NASA’s Matlab Prognostics Algorithm Library</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>model</strong> – ProgModel
+A prognostics model to be used in state estimation
+See: Prognostics Model Package</p></li>
+<li><p><strong>x0</strong> – <p>dict
+Initial (starting) state, with keys defined by model.states</p>
 <p>e.g., x = {‘abc’: 332.1, ‘def’: 221.003} given states = [‘abc’, ‘def’]</p>
-</div></blockquote>
-</li>
-<li><p>measurement_eqn (optional, function): Measurement equation (x)-&gt;z. Usually used in situations where what’s measured don’t exactly match the output (e.g., different unit, not ever output measured, etc.). see <cite>examples.measurement_eqn_example</cite></p></li>
-<li><p>options (optional, kwargs): configuration options</p>
-<blockquote>
-<div><p>Any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported:</p>
-<blockquote>
-<div><ul class="simple">
-<li><p>num_particles : Number of particles used in PF e.g., 20</p></li>
-<li><p>resample_fcn : Resampling function ([weights]) -&gt; [indexes] e.g., filterpy.monte_carlo.residual_resample</p></li>
-<li><p>x0_uncertainty : Initial uncertainty in state e.g., 0.5</p></li>
-<li><p>R (Number) : Measurement Noise. e.g., 0.1</p></li>
-</ul>
-</div></blockquote>
-</div></blockquote>
-</li>
+</p></li>
+<li><p><strong>measurement_eqn</strong> – optional, function
+Measurement equation (x)-&gt;z. Usually used in situations where what’s measured don’t exactly match the output (e.g., different unit, not ever output measured, etc.). see <cite>examples.measurement_eqn_example</cite></p></li>
+<li><p><strong>options</strong> – <p>optional, kwargs
+configuration options</p>
+<p>Dictionary of any additional configuration values. See state-estimator specific documentation</p>
+</p></li>
 </ul>
 </dd>
 </dl>
 <dl class="py method">
-<dt id="prog_algs.state_estimators.particle_filter.ParticleFilter.estimate">
-<code class="sig-name descname"><span class="pre">estimate</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">t</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">u</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">z</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.state_estimators.particle_filter.ParticleFilter.estimate" title="Permalink to this definition">¶</a></dt>
-<dd><p>Perform one state estimation step (i.e., update the state estimate)</p>
+<dt class="sig sig-object py" id="prog_algs.state_estimators.StateEstimator.estimate">
+<em class="property"><span class="pre">abstract</span> </em><span class="sig-name descname"><span class="pre">estimate</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">t</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">u</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">z</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None</span></span></span><a class="headerlink" href="#prog_algs.state_estimators.StateEstimator.estimate" title="Permalink to this definition">¶</a></dt>
+<dd><p>Perform one state estimation step (i.e., update the state estimate, filt.x)</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
 <dd class="field-odd"><ul class="simple">
-<li><p><strong>t</strong> (<em>double</em>) – Current timestamp in seconds (≥ 0.0)
+<li><p><strong>t</strong> (<em>float</em>) – Current timestamp in seconds (≥ 0.0)
 e.g., t = 3.4</p></li>
 <li><p><strong>u</strong> (<em>dict</em>) – Measured inputs, with keys defined by model.inputs.
 e.g., u = {‘i’:3.2} given inputs = [‘i’]</p></li>
@@ -213,14 +151,18 @@ <h2>PF State Estimator<a class="headerlink" href="#pf-state-estimator" title="Pe
 </ul>
 </dd>
 </dl>
+<div class="admonition note">
+<p class="admonition-title">Note</p>
+<p>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.</p>
+</div>
 </dd></dl>
 
-<dl class="py method">
-<dt id="prog_algs.state_estimators.particle_filter.ParticleFilter.x">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">x</span></code><a class="headerlink" href="#prog_algs.state_estimators.particle_filter.ParticleFilter.x" title="Permalink to this definition">¶</a></dt>
-<dd><p>Getter for property ‘x’, the current estimated state.</p>
+<dl class="py property">
+<dt class="sig sig-object py" id="prog_algs.state_estimators.StateEstimator.x">
+<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">x</span></span><a class="headerlink" href="#prog_algs.state_estimators.StateEstimator.x" title="Permalink to this definition">¶</a></dt>
+<dd><p>The current estimated state.</p>
 <p class="rubric">Example</p>
-<p>state = observer.x</p>
+<p>state = filt.x</p>
 </dd></dl>
 
 </dd></dl>
@@ -246,18 +188,21 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">State Estimators</a><ul>
-<li class="toctree-l2"><a class="reference internal" href="#ukf-state-estimator">UKF State Estimator</a></li>
-<li class="toctree-l2"><a class="reference internal" href="#pf-state-estimator">PF State Estimator</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#included-state-estimators">Included State Estimators</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#state-estimator-interface">State Estimator Interface</a></li>
 </ul>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -273,7 +218,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/uncertain_data.html b/docs/uncertain_data.html
index 0a4b93bc..cf8fafa9 100644
--- a/docs/uncertain_data.html
+++ b/docs/uncertain_data.html
@@ -6,16 +6,16 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Uncertain Data &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Uncertain Data &#8212; Prognostics Algorithms Python Package 1.2.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
     <link rel="index" title="Index" href="genindex.html" />
     <link rel="search" title="Search" href="search.html" />
-    <link rel="next" title="Metrics" href="metrics.html" />
+    <link rel="next" title="Prediction" href="prediction.html" />
     <link rel="prev" title="Predictors" href="predictors.html" />
    
   <link rel="stylesheet" href="_static/custom.css" type="text/css" />
@@ -35,169 +35,156 @@
             
   <section id="uncertain-data">
 <h1>Uncertain Data<a class="headerlink" href="#uncertain-data" title="Permalink to this headline">¶</a></h1>
-<p>The <cite>prog_algs.uncertain_data</cite> package includes classes for representing data with uncertainty.</p>
+<p>The <cite>prog_algs.uncertain_data</cite> package includes classes for representing data with uncertainty. All types of UncertainData can be operated on using <a class="reference external" href="#interface">the interface</a>. Inidividual classes for representing uncertain data of different kinds are described below, in <a class="reference external" href="#implemented-uncertaindata-types">Implemented UncertainData Types</a>.</p>
+<section id="interface">
+<h2>Interface<a class="headerlink" href="#interface" title="Permalink to this headline">¶</a></h2>
 <dl class="py class">
-<dt id="prog_algs.uncertain_data.UncertainData">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></code><code class="sig-name descname"><span class="pre">UncertainData</span></code><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></span><span class="sig-name descname"><span class="pre">UncertainData</span></span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData" title="Permalink to this definition">¶</a></dt>
 <dd><p>Abstract base class for data with uncertainty. Any new uncertainty type must implement this class</p>
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.UncertainData.cov">
-<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">cov</span></code><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.cov" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get the covariance matrix</p>
+<dl class="py property">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.cov">
+<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">cov</span></span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.cov" title="Permalink to this definition">¶</a></dt>
+<dd><p>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])</p>
 <dl class="field-list simple">
 <dt class="field-odd">Returns</dt>
-<dd class="field-odd"><p>covariance matrix</p>
+<dd class="field-odd"><p>Covariance matrix</p>
 </dd>
 <dt class="field-even">Return type</dt>
-<dd class="field-even"><p>[[float]]</p>
+<dd class="field-even"><p>array[array[float]]</p>
 </dd>
 </dl>
 </dd></dl>
 
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.UncertainData.mean">
-<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">mean</span></code><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.mean" title="Permalink to this definition">¶</a></dt>
-<dd><p>Mean estimate</p>
-<p class="rubric">Example</p>
-<p>mean_value = data.mean</p>
-</dd></dl>
-
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.UncertainData.sample">
-<em class="property"><span class="pre">abstract</span> </em><code class="sig-name descname"><span class="pre">sample</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">nSamples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.sample" title="Permalink to this definition">¶</a></dt>
-<dd><p>Generate samples from data</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.keys">
+<em class="property"><span class="pre">abstract</span> </em><span class="sig-name descname"><span class="pre">keys</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.keys" title="Permalink to this definition">¶</a></dt>
+<dd><p>Get the keys for the property represented</p>
 <dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>nSamples</strong> (<em>int</em><em>, </em><em>optional</em>) – Number of samples to generate. Defaults to 1.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>Array of nSamples samples</p>
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p>keys</p>
 </dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>samples (array)</p>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>[string]</p>
 </dd>
 </dl>
-<p class="rubric">Example</p>
-<p>samples = data.samples(100)</p>
 </dd></dl>
 
-</dd></dl>
-
-<dl class="py class">
-<dt id="prog_algs.uncertain_data.UnweightedSamples">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></code><code class="sig-name descname"><span class="pre">UnweightedSamples</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">samples</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">numpy.array</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">array([],</span> <span class="pre">dtype=float64)</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples" title="Permalink to this definition">¶</a></dt>
-<dd><p>Uncertain Data represented by a set of samples</p>
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.UnweightedSamples.append">
-<code class="sig-name descname"><span class="pre">append</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">value</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.append" title="Permalink to this definition">¶</a></dt>
-<dd><p>Append an additional sample to the unweighted samples</p>
+<dl class="py property">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.mean">
+<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">mean</span></span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.mean" title="Permalink to this definition">¶</a></dt>
+<dd><p>The mean of the UncertainData distribution or samples</p>
 <dl class="field-list simple">
-<dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>value</strong> (<em>dict</em>) – Value to be appended</p>
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p>Mean value. e.g., {‘key1’: 23.2, …}</p>
+</dd>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>Dict[str, float]</p>
 </dd>
 </dl>
+<p class="rubric">Example</p>
+<p>mean_value = data.mean</p>
 </dd></dl>
 
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.UnweightedSamples.cov">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">cov</span></code><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.cov" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get the covariance matrix</p>
+<dl class="py property">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.median">
+<em class="property"><span class="pre">abstract</span> <span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">median</span></span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.median" title="Permalink to this definition">¶</a></dt>
+<dd><p>The median of the UncertainData distribution or samples</p>
 <dl class="field-list simple">
 <dt class="field-odd">Returns</dt>
-<dd class="field-odd"><p>covariance matrix</p>
+<dd class="field-odd"><p>Mean value. e.g., {‘key1’: 23.2, …}</p>
 </dd>
 <dt class="field-even">Return type</dt>
-<dd class="field-even"><p>[[float]]</p>
+<dd class="field-even"><p>Dict[str, float]</p>
 </dd>
 </dl>
-</dd></dl>
-
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.UnweightedSamples.mean">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">mean</span></code><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.mean" title="Permalink to this definition">¶</a></dt>
-<dd><p>Mean estimate</p>
 <p class="rubric">Example</p>
-<p>mean_value = data.mean</p>
+<p>median_value = data.median</p>
 </dd></dl>
 
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.UnweightedSamples.raw_samples">
-<code class="sig-name descname"><span class="pre">raw_samples</span></code><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.raw_samples" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get raw samples</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.metrics">
+<span class="sig-name descname"><span class="pre">metrics</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.metrics" title="Permalink to this definition">¶</a></dt>
+<dd><p>Calculate Metrics for this dist</p>
 <dl class="field-list simple">
-<dt class="field-odd">Returns</dt>
-<dd class="field-odd"><p>all the samples</p>
+<dt class="field-odd">Keyword Arguments</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>ground_truth</strong> (<em>int</em><em> or </em><em>dict</em><em>, </em><em>optional</em>) – Ground truth value. Defaults to None.</p></li>
+<li><p><strong>n_samples</strong> (<em>int</em><em>, </em><em>optional</em>) – Number of samples to use for calculating metrics (if not UnweightedSamples)</p></li>
+<li><p><strong>keys</strong> (<em>List</em><em>[</em><em>str</em><em>]</em><em>, </em><em>optional</em>) – Keys to calculate metrics for. Defaults to all keys.</p></li>
+</ul>
 </dd>
-<dt class="field-even">Return type</dt>
-<dd class="field-even"><p>np.array(dict)</p>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Dictionary of metrics</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p>dict</p>
 </dd>
 </dl>
 </dd></dl>
 
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.UnweightedSamples.sample">
-<code class="sig-name descname"><span class="pre">sample</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">num_samples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.sample" title="Permalink to this definition">¶</a></dt>
-<dd><p>Generate samples from data</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.percentage_in_bounds">
+<span class="sig-name descname"><span class="pre">percentage_in_bounds</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">bounds</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">keys</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.percentage_in_bounds" title="Permalink to this definition">¶</a></dt>
+<dd><p>Calculate percentage of dist is within specified bounds</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>nSamples</strong> (<em>int</em><em>, </em><em>optional</em>) – Number of samples to generate. Defaults to 1.</p>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>bounds</strong> (<em>[</em><em>float</em><em>, </em><em>float</em><em>]</em>) – Lower and upper bounds</p></li>
+<li><p><strong>keys</strong> (<em>list of strings</em><em>, </em><em>optional</em>) – Keys to analyze. Defaults to all keys.</p></li>
+</ul>
 </dd>
 <dt class="field-even">Returns</dt>
-<dd class="field-even"><p>Array of nSamples samples</p>
+<dd class="field-even"><p>Percentage within bounds (where 0.5 = 50%)</p>
 </dd>
 <dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>samples (array)</p>
+<dd class="field-odd"><p>float</p>
 </dd>
 </dl>
-<p class="rubric">Example</p>
-<p>samples = data.samples(100)</p>
 </dd></dl>
 
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.UnweightedSamples.size">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">size</span></code><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.size" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get the number of samples</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.plot_hist">
+<span class="sig-name descname"><span class="pre">plot_hist</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">fig</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">keys</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">num_samples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">100</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.plot_hist" title="Permalink to this definition">¶</a></dt>
+<dd><p>Create a histogram</p>
 <dl class="field-list simple">
-<dt class="field-odd">Returns</dt>
-<dd class="field-odd"><p>Number of samples</p>
-</dd>
-<dt class="field-even">Return type</dt>
-<dd class="field-even"><p>int</p>
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>fig</strong> (<em>MatPlotLib Figure</em><em>, </em><em>optional</em>) – Existing histogram figure to be overritten. Defaults to create new figure.</p></li>
+<li><p><strong>num_samples</strong> (<em>int</em><em>, </em><em>optional</em>) – Number of samples to plot. Defaults to 100</p></li>
+<li><p><strong>keys</strong> (<em>List</em><em>(</em><em>String</em><em>)</em><em>, </em><em>optional</em>) – Keys to be plotted. Defaults to None.</p></li>
+</ul>
 </dd>
 </dl>
 </dd></dl>
 
-</dd></dl>
-
-<dl class="py class">
-<dt id="prog_algs.uncertain_data.MultivariateNormalDist">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></code><code class="sig-name descname"><span class="pre">MultivariateNormalDist</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">labels</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">mean</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">numpy.array</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">covar</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">numpy.array</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.MultivariateNormalDist" title="Permalink to this definition">¶</a></dt>
-<dd><p>Data represented by a multivariate normal distribution with mean and covariance matrix</p>
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.MultivariateNormalDist.cov">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">cov</span></code><a class="headerlink" href="#prog_algs.uncertain_data.MultivariateNormalDist.cov" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get the covariance matrix</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.plot_scatter">
+<span class="sig-name descname"><span class="pre">plot_scatter</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">fig</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">keys</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">num_samples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">100</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.plot_scatter" title="Permalink to this definition">¶</a></dt>
+<dd><p>Produce a scatter plot</p>
 <dl class="field-list simple">
-<dt class="field-odd">Returns</dt>
-<dd class="field-odd"><p>covariance matrix</p>
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>fig</strong> (<em>Figure</em><em>, </em><em>optional</em>) – 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</p></li>
+<li><p><strong>keys</strong> (<em>list of strings</em><em>, </em><em>optional</em>) – Keys to plot. Defaults to all keys.</p></li>
+<li><p><strong>num_samples</strong> (<em>int</em><em>, </em><em>optional</em>) – Number of samples to plot. Defaults to 100</p></li>
+<li><p><strong>**kwargs</strong> (<em>optional</em>) – Additional keyword arguments passed to scatter function.</p></li>
+</ul>
 </dd>
-<dt class="field-even">Return type</dt>
-<dd class="field-even"><p>[[float]]</p>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Figure</p>
 </dd>
 </dl>
-</dd></dl>
-
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.MultivariateNormalDist.mean">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">mean</span></code><a class="headerlink" href="#prog_algs.uncertain_data.MultivariateNormalDist.mean" title="Permalink to this definition">¶</a></dt>
-<dd><p>Mean estimate</p>
 <p class="rubric">Example</p>
-<p>mean_value = data.mean</p>
+<p>states = UnweightedSamples([1, 2, 3, 4, 5])</p>
+<p>states.plot_scatter() # With 100 samples</p>
+<p>states.plot_scatter(num_samples=5) # Specifying the number of samples to plot</p>
+<p>states.plot_scatter(keys=[‘state1’, ‘state2’]) # only plot those keys</p>
 </dd></dl>
 
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.MultivariateNormalDist.sample">
-<code class="sig-name descname"><span class="pre">sample</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">num_samples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.MultivariateNormalDist.sample" title="Permalink to this definition">¶</a></dt>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.sample">
+<em class="property"><span class="pre">abstract</span> </em><span class="sig-name descname"><span class="pre">sample</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">nSamples</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">int</span></span> <span class="o"><span class="pre">=</span></span> <span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.sample" title="Permalink to this definition">¶</a></dt>
 <dd><p>Generate samples from data</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
@@ -207,7 +194,7 @@ <h1>Uncertain Data<a class="headerlink" href="#uncertain-data" title="Permalink
 <dd class="field-even"><p>Array of nSamples samples</p>
 </dd>
 <dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>samples (array)</p>
+<dd class="field-odd"><p>samples (<a class="reference internal" href="#prog_algs.uncertain_data.UnweightedSamples" title="prog_algs.uncertain_data.UnweightedSamples">UnweightedSamples</a>)</p>
 </dd>
 </dl>
 <p class="rubric">Example</p>
@@ -216,53 +203,76 @@ <h1>Uncertain Data<a class="headerlink" href="#uncertain-data" title="Permalink
 
 </dd></dl>
 
+</section>
+<section id="implemented-uncertaindata-types">
+<h2>Implemented UncertainData Types<a class="headerlink" href="#implemented-uncertaindata-types" title="Permalink to this headline">¶</a></h2>
+<section id="unweighted-samples">
+<h3>Unweighted Samples<a class="headerlink" href="#unweighted-samples" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
-<dt id="prog_algs.uncertain_data.ScalarData">
-<em class="property"><span class="pre">class</span> </em><code class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></code><code class="sig-name descname"><span class="pre">ScalarData</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">state</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.ScalarData" title="Permalink to this definition">¶</a></dt>
-<dd><p>Data without uncertainty- single value</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UnweightedSamples">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></span><span class="sig-name descname"><span class="pre">UnweightedSamples</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">samples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">[]</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples" title="Permalink to this definition">¶</a></dt>
+<dd><p>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).</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>samples</strong> (<em>array</em><em> or </em><em>dict</em><em>, </em><em>optional</em>) – <p>array of samples. Defaults to empty array.</p>
+<p>If dict, must be of the form of {key: [value, …], …}</p>
+<p>If list, must be of the form of [{key: value, …}, …]</p>
+</p>
+</dd>
+</dl>
 <dl class="py method">
-<dt id="prog_algs.uncertain_data.ScalarData.cov">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">cov</span></code><a class="headerlink" href="#prog_algs.uncertain_data.ScalarData.cov" title="Permalink to this definition">¶</a></dt>
-<dd><p>Get the covariance matrix</p>
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UnweightedSamples.key">
+<span class="sig-name descname"><span class="pre">key</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">key</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.UnweightedSamples.key" title="Permalink to this definition">¶</a></dt>
+<dd><p>Return samples for given key</p>
 <dl class="field-list simple">
-<dt class="field-odd">Returns</dt>
-<dd class="field-odd"><p>covariance matrix</p>
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>key</strong> (<em>str</em>) – key</p>
 </dd>
-<dt class="field-even">Return type</dt>
-<dd class="field-even"><p>[[float]]</p>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>list of values for given key</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p>list</p>
 </dd>
 </dl>
 </dd></dl>
 
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.ScalarData.mean">
-<em class="property"><span class="pre">property</span> </em><code class="sig-name descname"><span class="pre">mean</span></code><a class="headerlink" href="#prog_algs.uncertain_data.ScalarData.mean" title="Permalink to this definition">¶</a></dt>
-<dd><p>Mean estimate</p>
-<p class="rubric">Example</p>
-<p>mean_value = data.mean</p>
 </dd></dl>
 
-<dl class="py method">
-<dt id="prog_algs.uncertain_data.ScalarData.sample">
-<code class="sig-name descname"><span class="pre">sample</span></code><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">num_samples</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.ScalarData.sample" title="Permalink to this definition">¶</a></dt>
-<dd><p>Generate samples from data</p>
+</section>
+<section id="multivariate-normal-distribution">
+<h3>Multivariate Normal Distribution<a class="headerlink" href="#multivariate-normal-distribution" title="Permalink to this headline">¶</a></h3>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.MultivariateNormalDist">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></span><span class="sig-name descname"><span class="pre">MultivariateNormalDist</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">labels</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">mean</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">numpy.array</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">covar</span></span><span class="p"><span class="pre">:</span></span> <span class="n"><span class="pre">numpy.array</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.MultivariateNormalDist" title="Permalink to this definition">¶</a></dt>
+<dd><p>Data represented by a multivariate normal distribution with mean and covariance matrix</p>
 <dl class="field-list simple">
 <dt class="field-odd">Parameters</dt>
-<dd class="field-odd"><p><strong>nSamples</strong> (<em>int</em><em>, </em><em>optional</em>) – Number of samples to generate. Defaults to 1.</p>
-</dd>
-<dt class="field-even">Returns</dt>
-<dd class="field-even"><p>Array of nSamples samples</p>
-</dd>
-<dt class="field-odd">Return type</dt>
-<dd class="field-odd"><p>samples (array)</p>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>labels</strong> (<em>list</em><em>[</em><em>str</em><em>]</em>) – Labels for states, in order of mean values</p></li>
+<li><p><strong>mean</strong> (<em>array</em><em>[</em><em>float</em><em>]</em>) – Mean values for state in the same order as labels</p></li>
+<li><p><strong>covar</strong> (<em>array</em><em>[</em><em>array</em><em>[</em><em>float</em><em>]</em><em>]</em>) – Covariance matrix for state</p></li>
+</ul>
 </dd>
 </dl>
-<p class="rubric">Example</p>
-<p>samples = data.samples(100)</p>
 </dd></dl>
 
+</section>
+<section id="scalar-data-i-e-no-uncertainty">
+<h3>Scalar Data (i.e., no uncertainty)<a class="headerlink" href="#scalar-data-i-e-no-uncertainty" title="Permalink to this headline">¶</a></h3>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.ScalarData">
+<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">prog_algs.uncertain_data.</span></span><span class="sig-name descname"><span class="pre">ScalarData</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">state</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.uncertain_data.ScalarData" title="Permalink to this definition">¶</a></dt>
+<dd><p>Data without uncertainty- single value</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>state</strong> (<em>dict</em>) – Single state</p>
+</dd>
+</dl>
 </dd></dl>
 
+</section>
+</section>
 </section>
 
 
@@ -283,14 +293,21 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
-<li class="toctree-l1 current"><a class="current reference internal" href="#">Uncertain Data</a></li>
+<li class="toctree-l1 current"><a class="current reference internal" href="#">Uncertain Data</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="#interface">Interface</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#implemented-uncertaindata-types">Implemented UncertainData Types</a></li>
+</ul>
+</li>
+<li class="toctree-l1"><a class="reference internal" href="prediction.html">Prediction</a></li>
 <li class="toctree-l1"><a class="reference internal" href="metrics.html">Metrics</a></li>
-<li class="toctree-l1"><a class="reference internal" href="visualize.html">Visualization</a></li>
 <li class="toctree-l1"><a class="reference internal" href="exceptions.html">Exceptions</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models">ProgModels</a></li>
 <li class="toctree-l1"><a class="reference internal" href="dev_guide.html">Developers Guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
 <div class="relations">
@@ -298,7 +315,7 @@ <h3>Related Topics</h3>
 <ul>
   <li><a href="index.html">Documentation overview</a><ul>
       <li>Previous: <a href="predictors.html" title="previous chapter">Predictors</a></li>
-      <li>Next: <a href="metrics.html" title="next chapter">Metrics</a></li>
+      <li>Next: <a href="prediction.html" title="next chapter">Prediction</a></li>
   </ul></li>
 </ul>
 </div>
@@ -306,7 +323,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/docs/visualize.html b/docs/visualize.html
index f277dc81..3494401d 100644
--- a/docs/visualize.html
+++ b/docs/visualize.html
@@ -6,10 +6,10 @@
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
 
-    <title>Visualization &#8212; Prognostics Algorithms Python Package 1.0.0 documentation</title>
-    <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
-    <link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
-    <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
+    <title>Visualization &#8212; Prognostics Algorithms Python Package 1.1.0 documentation</title>
+    <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
+    <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
+    <script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
     <script src="_static/jquery.js"></script>
     <script src="_static/underscore.js"></script>
     <script src="_static/doctools.js"></script>
@@ -36,7 +36,34 @@
   <section id="visualization">
 <h1>Visualization<a class="headerlink" href="#visualization" title="Permalink to this headline">¶</a></h1>
 <p>Visualization tools are included in <cite>prog_algs.visualize</cite>, described below:</p>
-<span class="target" id="module-prog_algs.visualize"></span></section>
+<span class="target" id="module-prog_algs.visualize"></span><dl class="py function">
+<dt class="sig sig-object py" id="prog_algs.visualize.plot_scatter">
+<span class="sig-prename descclassname"><span class="pre">prog_algs.visualize.</span></span><span class="sig-name descname"><span class="pre">plot_scatter</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">samples</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">fig</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">keys</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">legend</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">'auto'</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#prog_algs.visualize.plot_scatter" title="Permalink to this definition">¶</a></dt>
+<dd><p>Produce a scatter plot for a given list of states</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>samples</strong> (<em>[</em><em>dict</em><em>]</em>) – Non-empty list of states where each element is a dictionary containing a single sample</p></li>
+<li><p><strong>fig</strong> (<em>Figure</em><em>, </em><em>optional</em>) – 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</p></li>
+<li><p><strong>keys</strong> (<em>list of strings</em><em>, </em><em>optional</em>) – Keys to plot. Defaults to all keys.</p></li>
+<li><p><strong>legend</strong> (<em>optional</em>) – 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</p></li>
+<li><p><strong>**kwargs</strong> (<em>optional</em>) – Additional keyword arguments passed to scatter function. Includes those supported by scatter</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Figure</p>
+</dd>
+</dl>
+<p class="rubric">Example</p>
+<p>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</p>
+</dd></dl>
+
+</section>
 
 
           </div>
@@ -56,7 +83,10 @@ <h1 class="logo"><a href="index.html">Prognostics Algorithms Python Package</a><
 
 <h3>Navigation</h3>
 <ul class="current">
+<li class="toctree-l1"><a class="reference external" href="https://mybinder.org/v2/gh/nasa/prog_algs/dev?labpath=tutorial.ipynb">Tutorial</a></li>
 <li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
+<li class="toctree-l1"><a class="reference internal" href="getting_started.html#installing-from-pip-recommended">Installing from pip (recommended)</a></li>
+<li class="toctree-l1"><a class="reference internal" href="getting_started.html#installing-pre-release-versions-with-github">Installing pre-release versions with GitHub</a></li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a></li>
 <li class="toctree-l1"><a class="reference internal" href="predictors.html">Predictors</a></li>
 <li class="toctree-l1"><a class="reference internal" href="uncertain_data.html">Uncertain Data</a></li>
@@ -79,7 +109,7 @@ <h3>Related Topics</h3>
   <h3 id="searchlabel">Quick search</h3>
     <div class="searchformwrapper">
     <form class="search" action="search.html" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel" />
+      <input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
       <input type="submit" value="Go" />
     </form>
     </div>
diff --git a/examples/__init__.py b/examples/__init__.py
index 572d2868..32cfa83a 100644
--- a/examples/__init__.py
+++ b/examples/__init__.py
@@ -1,3 +1,3 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
-__all__ = ['basic_example', 'benchmarking_example', 'new_state_estimator_example', 'measurement_eqn_example', 'playback']
\ No newline at end of file
+__all__ = ['basic_example', 'benchmarking_example', 'eol_event', 'utpredictor', 'new_state_estimator_example', 'measurement_eqn_example', 'playback', 'predict_specific_event', 'thrown_object_example']
diff --git a/examples/basic_example.py b/examples/basic_example.py
index c61048c6..07e4a46f 100644
--- a/examples/basic_example.py
+++ b/examples/basic_example.py
@@ -1,10 +1,27 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
-from prog_models.models import BatteryCircuit
+"""
+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 BatteryElectroChem as Battery
+
 from prog_algs import *
 
 def run_example():
-    ## Setup
+    # Step 1: Setup model & future loading
     def future_loading(t, x = None):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -18,33 +35,89 @@ def future_loading(t, x = None):
         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'])
-    filt = state_estimators.ParticleFilter(batt, batt.parameters['x0'])
+    # 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
-    load = future_loading(t)
-    filt.estimate(t, load, {'t': 32.2, 'v': 3.915})
-    print("Posterior State:", filt.x.mean)
+    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'])
-    ## Prediction - Predict EOD given current state
-    # Setup prediction
+    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)
-    if isinstance(filt, state_estimators.UnscentedKalmanFilter):
-        samples = filt.x.sample(20)
-    else: # Particle Filter
-        samples = filt.x.raw_samples()
-    # Predict with a step size of 0.1
-    (times, inputs, states, outputs, event_states, eol) = mc.predict(samples, future_loading, dt=0.1)
-    ## Print Metrics
-    print("\nEOD Predictions (s):")
-    from prog_algs.metrics import samples as metrics 
-    print('\tPercentage between 3005.2 and 3005.6: ', metrics.percentage_in_bounds(eol, [3005.2, 3005.6])*100.0, '%')
-    print('\tAssuming ground truth 3002.25: ', metrics.eol_metrics(eol, 3005.25))
-    print('\tP(Success) if mission ends at 3002.25: ', metrics.prob_success(eol, 3005.25))
+
+    # Step 3b: Perform a prediction
+    NUM_SAMPLES = 100
+    STEP_SIZE = 0.1
+    (times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE)
+    print('ToE', toe.mean)
+
+    # Step 3c: Analyze the results
+
+    # Note: The results of a sample-based prediction can be accessed by sample, e.g.,
+    states_sample_1 = 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 = 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 = toe.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('\Portion between 3005.2 and 3005.6: ', toe.percentage_in_bounds([3005.2, 3005.6]))
+    print('\tAssuming ground truth 3002.25: ', toe.metrics(ground_truth=3005.25))
+    print('\tP(Success) if mission ends at 3002.25: ', prob_success(toe, 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 = states.snapshot(0).plot_scatter(label = "t={} s".format(int(times[0])))  # 0
+    quarter_index = int(len(times)/4)
+    states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(times[quarter_index])))  # 25%
+    states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(times[quarter_index*2])))  # 50%
+    states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(times[quarter_index*3])))  # 75%
+    states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(times[-1])))  # 100%
+
+    toe.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()
\ No newline at end of file
+    run_example()
diff --git a/examples/benchmarking_example.py b/examples/benchmarking_example.py
index 329c8922..73bddf8a 100644
--- a/examples/benchmarking_example.py
+++ b/examples/benchmarking_example.py
@@ -1,10 +1,26 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from prog_models.models import BatteryCircuit
+"""
+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():
-    ## Setup
+    # Step 1: Setup Model and Future Loading
     def future_loading(t, x={}):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -19,34 +35,38 @@ def future_loading(t, x={}):
             i = 3
         return {'i': i}
 
-    batt = BatteryCircuit()
+    batt = Battery()   
 
-    ##  Setup State Estimation 
-    filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])
+    # Step 2: Setup Predictor 
+    pred = predictors.MonteCarlo(batt, dt= 0.05)
 
-    ## Setup Prediction
-    mc = 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})
 
-    # Playback 
+    # 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 
-    print('Run 1 (2 samples)')
-    (t, u, x, z, es, eol) = run_prog_playback(filt, mc, future_loading, [(0.1, {'t': 32.2, 'v': 3.915}), (0.1, {'t': 32.3, 'v': 3.91})], num_samples= 2)
-    print('MSE: ', metrics.mean_square_error(eol, 3005.4))
-    prediction_times = [0.1, 0.2]
-    print('Alpha-lambda met: ', metrics.alpha_lambda(prediction_times, eol, 3005.4, 0.2, 1e-4, 0.65))
-
-    print('Run 2 (5 samples)')
-    (t, u, x, z, es, eol) = run_prog_playback(filt, mc, future_loading, [(0.1, {'t': 32.2, 'v': 3.915}), (0.1, {'t': 32.3, 'v': 3.91})], num_samples= 5)
-    print('MSE: ', metrics.mean_square_error(eol, 3005.4))
-    prediction_times = [0.1, 0.2]
-    print('Alpha-lambda met: ', metrics.alpha_lambda(prediction_times, eol, 3005.4, 0.2, 1e-4, 0.65))
-
-    print('Run 3 (10 samples)')
-    (t, u, x, z, es, eol) = run_prog_playback(filt, mc, future_loading, [(0.1, {'t': 32.2, 'v': 3.915}), (0.1, {'t': 32.3, 'v': 3.91})], num_samples= 10)
-    print('MSE: ', metrics.mean_square_error(eol, 3005.4))
-    prediction_times = [0.1, 0.2]
-    print('Alpha-lambda met: ', metrics.alpha_lambda(prediction_times, eol, 3005.4, 0.2, 1e-4, 0.65))
+
+    # 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()
+        (t, u, x, z, es, toe) = pred.predict(state_estimator.x, future_loading, n_samples = sample_count)
+        toe = toe.key("EOD")  # Looking at EOD event
+        end = time.perf_counter()
+        print('\tMSE:     {:4.2f}s'.format(metrics.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()
\ No newline at end of file
+    run_example()
diff --git a/examples/eol_event.py b/examples/eol_event.py
new file mode 100644
index 00000000..fb25ecb4
--- /dev/null
+++ b/examples/eol_event.py
@@ -0,0 +1,54 @@
+# 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., 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
+    (times, inputs, states, outputs, event_states, toe) = pred.predict(state, future_loading, events=['EOL'], dt=0.025)
+
+    # Step 5: Plot results
+    toe.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/examples/measurement_eqn_example.py b/examples/measurement_eqn_example.py
index 0c899dd0..d56086a6 100644
--- a/examples/measurement_eqn_example.py
+++ b/examples/measurement_eqn_example.py
@@ -1,10 +1,24 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from prog_models.models import BatteryCircuit
+"""
+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 measurement_eqn to remove the other output value.  
+        Estimation of the current state is performed at various time steps, using the defined state_estimator. The state_estimator takes the measurement_eqn as input, to account for the missing output information. 
+
+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():
-    ## Setup
+    # Step 1: Setup model & future loading
     def future_loading(t, x={}):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -19,34 +33,48 @@ def future_loading(t, x={}):
             i = 3
         return {'i': i}
 
-    batt = BatteryCircuit()
+    batt = Battery()
+    x0 = batt.parameters['x0']
 
-    ## State Estimation - perform a single ukf state estimate step
+    # Step 2: Define Measurement Equation
     # This example is a little different. Here we are saying that we dont have the complete output. Instead we're only measuring voltage. We use the measurement eqn to remove temp
-
     def measure(x):
         output = batt.output(x)
         del output['t'] # Not measuring temperature
         return output
 
-    # Setup particle filter to use measurement eqn
-    filt = state_estimators.ParticleFilter(batt, batt.parameters['x0'], measurement_eqn = measure)
+    # Step 3: Setup particle filter to use measurement eqn
+    filt = state_estimators.ParticleFilter(batt, x0, measurement_eqn = measure)
+
+    # Step 4: Run step and print results
+    print('Running state estimation step with only one of 2 outputs measured')
 
-    # Simulate results
-    print("Prior State:", filt.x.mean)
+    # Print Prior
+    print("\nPrior State:", filt.x.mean)
     print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
+
+    # Estimate Step
     t = 0.1
     load = future_loading(t)
     filt.estimate(t, load, {'v': 3.915})
-    print("Posterior State:", filt.x.mean)
+
+    # 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 State (t={}):".format(t), filt.x.mean)
+
+    # 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 measurement_eqn 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
+
 # This allows the module to be executed directly 
 if __name__ == '__main__':
-    run_example()
\ No newline at end of file
+    run_example()
diff --git a/examples/particle_filter_battery_example.py b/examples/particle_filter_battery_example.py
new file mode 100644
index 00000000..4a759da3
--- /dev/null
+++ b/examples/particle_filter_battery_example.py
@@ -0,0 +1,69 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from prog_models.models.battery_electrochem 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)
+    times, inputs, states, outputs, event_states = 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(times)*.25,len(times)*.45,1)))# Random current time
+
+    for i in range(n_times):
+        if i == 0:
+            batt_pf = state_estimators.ParticleFilter(model = battery,  x0 = states[i], num_particles = 250)
+        else:
+            batt_pf.estimate(t = times[i], u = inputs[i], z = outputs[i])
+        all_particles.append(batt_pf.particles)
+
+    # Mean of the particles
+    alpha = 0.05
+    states_vsn = [s['tb'] for s in 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(times[:n_times],[p['tb'] for p in pf_mean],linewidth=0.7,color="blue")
+    plt.plot(times[:n_times], states_vsn[:n_times],"--",linewidth=0.7,color="red")
+    plt.fill_between(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
index bc09c91e..d2972d6d 100644
--- a/examples/playback.py
+++ b/examples/playback.py
@@ -1,42 +1,73 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
 """
-Example using playback data. Builds a Battery Model, runs prognostics using the playback data. 
+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
 """
 
-# Constants
-num_samples = 10
-time_step = 0.1
-prediction_update_freq = 5 # Number of steps between prediction update
+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
 
-def run_example():
-    from prog_models.models import BatteryCircuit
-    from prog_algs.metrics import samples as metrics
-    from prog_algs import state_estimators, predictors
+from prog_algs.state_estimators import ParticleFilter as StateEstimator
+# VVV Uncomment this to use UnscentedKalmanFilter instead VVV
+# from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator
+
+from prog_algs.predictors import MonteCarlo, ToEPredictionProfile
+from prog_algs.metrics import samples as metrics
 
-    import csv
-    import matplotlib.pyplot as plt 
+import csv
+import matplotlib.pyplot as plt 
+
+# Constants
+NUM_SAMPLES = 10
+TIME_STEP = 1
+PREDICTION_UPDATE_FREQ = 5 # Number of steps between prediction update
 
+def run_example():
     # Setup Model
-    batt = BatteryCircuit()
+    batt = Battery()
 
     # Setup State Estimation
-    filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])
+    filt = StateEstimator(batt, batt.parameters['x0'])
 
     # Setup Prediction
     def future_loading(t, x=None):
         return {'i': 2.35}
-    mc = predictors.MonteCarlo(batt)
+    mc = MonteCarlo(batt)
 
-    # Prepare Plot
+    # Prepare SOC Plot
     fig, ax = plt.subplots()
     line, = ax.plot([], [])
     ax.grid()
+    ax.set_ylim(-0.05, 1.05)
+    ax.set_xlabel('Time (s)')
+    ax.set_ylabel('SOC')
     xdata, ydata = [], []
-    plt.show()
+    fig.show()
+
+    # Prepare RUL Plot
+    rul_fig, rulax = plt.subplots()
+    rul_line, = rulax.plot([], [])
+    rulax.grid()
+    rulax.set_xlabel('Time (s)')
+    rulax.set_ylabel('RUL (s)')
+    rul_x, rul_y = [], []
+    rul_fig.show()
 
     # Run Playback
     step = 0
+    profile = ToEPredictionProfile()
     with open('examples/data_const_load.csv', 'r') as f:
         reader = csv.reader(f)
         next(reader) # Skip header
@@ -59,15 +90,27 @@ def future_loading(t, x=None):
 
             if t >= xmax:
                 ax.set_xlim(xmin, 2*xmax)
-                ax.figure.canvas.draw()
+                rulax.set_xlim(xmin, 2*xmax)
             line.set_data(xdata, ydata)
+            fig.canvas.draw()
+
+            # Prediction Step (every PREDICTION_UPDATE_FREQ steps)
+            if (step%PREDICTION_UPDATE_FREQ == 0):
+                (times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, n_samples=NUM_SAMPLES, dt=TIME_STEP)
+                m = toe.metrics()
+                print('  - ToE: {} (sigma: {})'.format(m['EOD']['mean'], m['EOD']['std']))
+
+                # Update Plot
+                rul_x.append(t)
+                rul_y.append(m['EOD']['mean']-t)
+                ymin, ymax = rulax.get_ylim()
+                if m['EOD']['mean']-t > ymax:
+                    rulax.set_ylim(0, (m['EOD']['mean']-t)*1.1)
+                rul_line.set_data(rul_x, rul_y)
+                rul_fig.canvas.draw()
+                profile.add_prediction(t, toe)
 
-            # Prediction Step (every prediction_update_freq steps)
-            if (step%prediction_update_freq == 0):
-                samples = filt.x.sample(10)
-                (times, inputs, states, outputs, event_states, eol) = mc.predict(samples, future_loading, dt=1.0)
-                m = metrics.eol_metrics(eol)
-                print('  - RUL: {} (sigma: {})'.format(m['mean'], m['std']))
+    input('Press any key to exit')
 
 # This allows the module to be executed directly 
 if __name__ == '__main__':
diff --git a/examples/predict_specific_event.py b/examples/predict_specific_event.py
new file mode 100644
index 00000000..e2f10dbe
--- /dev/null
+++ b/examples/predict_specific_event.py
@@ -0,0 +1,43 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from prog_models.models.thrown_object import ThrownObject
+from prog_algs import *
+from prog_algs.uncertain_data import UnweightedSamples
+
+"""
+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.
+"""
+
+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
+    (times, inputs, states, outputs, event_states, toe) = pred.predict(filt.x, future_loading, dt=0.1, save_freq= 1, events=['impact'])
+
+    # Print Results
+    for i, time in enumerate(times):
+        print('\nt = {}'.format(time))
+        print('\tu = {}'.format(inputs.snapshot(i).mean))
+        print('\tx = {}'.format(states.snapshot(i).mean))
+        print('\tz = {}'.format(outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(event_states.snapshot(i).mean))
+
+    # Note only impact event is shown here
+    print('\nToE:', toe.mean)
+
+# This allows the module to be executed directly 
+if __name__ == '__main__':
+    run_example()
diff --git a/examples/thrown_object_example.py b/examples/thrown_object_example.py
new file mode 100644
index 00000000..eaefc6fd
--- /dev/null
+++ b/examples/thrown_object_example.py
@@ -0,0 +1,65 @@
+# 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.1
+    (_, _, _, _, _, toe) = mc.predict(samples, future_loading, dt=STEP_SIZE)
+    print("\nPredicted Time of Event:")
+    pprint(toe.metrics())
+    toe.plot_hist(keys = 'impact')
+    toe.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/utpredictor.py b/examples/utpredictor.py
new file mode 100644
index 00000000..abd7c426
--- /dev/null
+++ b/examples/utpredictor.py
@@ -0,0 +1,63 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+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
+    (times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, dt=0.1, save_freq= 100)
+
+    # Print Results
+    for i, time in enumerate(times):
+        print('\nt = {}'.format(time))
+        print('\tu = {}'.format(inputs.snapshot(i).mean))
+        print('\tx = {}'.format(states.snapshot(i).mean))
+        print('\tz = {}'.format(outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(event_states.snapshot(i).mean))
+
+    print('\nToE:', toe.mean)
+
+    # You can also access the final state (of type UncertainData), like so:
+    final_state = toe.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/forms/Corporate CLA.pdf b/forms/Corporate CLA.pdf
index 3c2ec876..5175701e 100644
Binary files a/forms/Corporate CLA.pdf and b/forms/Corporate CLA.pdf differ
diff --git a/forms/Individual CLA.pdf b/forms/Individual CLA.pdf
index f15b1a98..86fb7d73 100644
Binary files a/forms/Individual CLA.pdf and b/forms/Individual CLA.pdf differ
diff --git a/predictor_template.py b/predictor_template.py
index 4b652db1..f7eec136 100644
--- a/predictor_template.py
+++ b/predictor_template.py
@@ -1,8 +1,12 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from prog_algs import predictors
+from copy import deepcopy
+from prog_algs.predictors import Predictor, Prediction
+# 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(predictors.Predictor):
+
+class TemplatePredictor(Predictor):
     """
     Template class for performing model-based prediction
     """
@@ -14,7 +18,7 @@ class TemplatePredictor(predictors.Predictor):
 
     def __init__(self, model, **kwargs):
         """
-        Constructor
+        Constructor (optional)
         """
         super().__init__(model, **kwargs)
         # ADD PARAMETER CHECKS HERE
@@ -22,48 +26,50 @@ def __init__(self, model, **kwargs):
 
         # INITIALIZE PREDICTOR
 
-    def predict(self, state_samples, future_loading_eqn, options = {}):
+    def predict(self, state, future_loading_eqn, **kwargs):
         """
         Perform a single prediction
 
         Parameters
         ----------
-        state_samples : 
-            Function to generate n samples of the state. 
-            e.g., def f(n): return [x1, x2, x3, ... xn]
-        future_loading_eqn : function (t) -> z
-            Function to generate an estimate of loading at future time t
+        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 (tuple)
         -------
-        times: [[number]]
-            Times for each simulated point in format times[sample_id][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]
-        toe: [number]
-            Estimated time where a predicted event will occur for each sample.
+        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 = self.parameters # copy default parameters
-        params.update(options)
+        params = deepcopy(self.parameters)  # copy default parameters
+        params.update(kwargs)
 
         # PERFORM PREDICTION HERE, REPLACE THE FOLLOWING LISTS
-        times = [] # array of double (e.g., [0.0, 0.5, 1.0, ...])
-        inputs = [] # array of dict (e.g., [{'input 1': 1.2, ...}, ...])
-        states = [] # array of dict (e.g., [{'state 1': 1.2, ...}, ...])
-        outputs = [] # array of dict (e.g., [{'output 1': 1.2, ...}, ...])
-        event_states = [] # array of dict (e.g., [{'event_state 1': 1.2, ...}, ...])
-        time_of_event = [] # array of double, time for each event prediction
+
+        # 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})
 
         return (times, inputs, states, outputs, event_states, time_of_event)
diff --git a/requirements.txt b/requirements.txt
index 058d3748..22ec70ff 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,4 +2,4 @@ filterpy==1.4.5
 matplotlib==3.3.2
 numpy==1.19.2
 scipy==1.5.3
-prog_models==1.0.1
+prog_models==1.2.0
diff --git a/setup.py b/setup.py
index c05ce6a7..edaaa3d5 100644
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@
 
 setup(
     name = 'prog_algs',
-    version = '1.0.0',
+    version = '1.2.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',
@@ -29,12 +29,13 @@
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3 :: Only'
     ],
     keywords = ['prognostics', 'diagnostics', 'fault detection', 'fdir', 'prognostics and health management', 'PHM', 'health management'],
     package_dir = {"":"src"},
     packages = find_packages(where = 'src'),
-    python_requires='>=3.6, <3.9',
+    python_requires='>=3.6, <3.10',
     install_requires = [
         'numpy',
         'scipy',
diff --git a/sphinx_config/dev_guide.rst b/sphinx_config/dev_guide.rst
index 6fb0c301..0e1e3a34 100644
--- a/sphinx_config/dev_guide.rst
+++ b/sphinx_config/dev_guide.rst
@@ -6,13 +6,33 @@ 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 <https://nvie.com/posts/a-successful-git-branching-model/>`_. Release branches are not required. Details specific to each branch are described below. 
+Our project is following the git strategy described `here <https://nvie.com/posts/a-successful-git-branching-model/>`_. 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 commit on the dev branch should be functional. All unit tests must function before commiting to dev or merging another branch. 
+`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.
 
@@ -28,6 +48,8 @@ Release Checklist
 
 NPR 7150
 --------
+This section describes this project's compliance with the NASA's NPR 7150 requirements, documented `here <https://nodis3.gsfc.nasa.gov/displayDir.cfm?t=NPR&c=7150&s=2B>`_.
+
 * Software Classification: Class-E (Research Software)
 * Safety Criticality: Not Safety Critical 
 
diff --git a/sphinx_config/getting_started.rst b/sphinx_config/getting_started.rst
index aef28158..2d99a4d6 100644
--- a/sphinx_config/getting_started.rst
+++ b/sphinx_config/getting_started.rst
@@ -1,48 +1,133 @@
 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 for the Prognostics Models Library to perform research in prognostics with prognostics systems. 
+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 for the Prognostics Models Library to perform research in prognostics with prognostics systems.
 
-A few definitions:
+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`), the version on PyPi 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 <https://github.com/nasa/prog_algs>`__. This isn't recommended for most users as this version may be unstable. To use this version, use the following commands:
+
+.. code-block:: console
 
-* **events**: some state that can be predicted (e.g., system failure). An event has either occured or not. 
+    $ git clone https://github.com/nasa/prog_algs
+    $ cd prog_algs
+    $ git checkout dev 
+    $ pip install -e .
 
-* **event state**: progress towards event occuring. Defined as a number where an event state of 0 indicates the event has occured and 1 indicates no progress towards the event (i.e., fully healthy operation for a failure event). For gradually occuring 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"
+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)
 
+* **observables**: 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**: stochastic process representing uncertainty in the model transition. 
 
 * **measurement noise**: stochastic process representing uncertainty in the measurement process; e.g., sensor sensitivity, sensor misalignements, environmental effects 
 
-Installing Dependencies
------------------------
-You can install dependencies using the included `requirements.txt` file. This file enumerates all the dependencies of this package. Use the following command to install dependencies:
-    `pip install -r requirements.txt`
+The structure of the packages is illustrated below:
+
+.. image:: images/package_structure.png
+
+Prognostics is performed using `State Estimators <state_estimators.html>`__ and `Predictors <predictors.html>`__. State Estimators are resposible for estimating the current state of the modeled system using sensor data and a prognostics model (see: `prog_models package <https://github.com/nasa/prog_models>`__). The state estimator then produces an estimate of the system state with uncertainty in the form of an `uncertain data object <uncertain_data.html>`__. This state estimate is used by the predictor to predict when events will occur (Time of Event, ToE - returned as an `uncertain data object <uncertain_data.html>`__), 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) <https://nasa.github.io/prog_models/sim_result.html>`__ : 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 <uncertain_data.html>`__ : 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.html#id1>`__ : 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 <prediction.html#toe-prediction-profile>`__ : 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 
 ----
-See the examples for examples of use. Run examples using the command `python -m examples.[Example name]` command (e.g., `python -m examples.sim_example`). The examples are summarized below:
+The best way to learn how to use `prog_algs` is through the `tutorial <https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb>`__. 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.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
+    |
 
 Extending
 ---------
-New State Estimators and Predictors are created by extending the :class:`prog_algs.state_estimators.state_estimator.StateEstimator` and :class:`prog_algs.predictors.predictor.Predictor` class, respectively. 
+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.
+
+Updates in v1.1
+---------------
+
+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 event (e.g., toe.final_state['event1'])
 
-See :download:`examples.new_state_estimator_example <../examples/new_state_estimator_example.py>` for an example of this approach.
\ No newline at end of file
+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/sphinx_config/images/package_structure.png b/sphinx_config/images/package_structure.png
new file mode 100644
index 00000000..e5b56af1
Binary files /dev/null and b/sphinx_config/images/package_structure.png differ
diff --git a/sphinx_config/index.rst b/sphinx_config/index.rst
index e5981f3a..4cd8de20 100644
--- a/sphinx_config/index.rst
+++ b/sphinx_config/index.rst
@@ -1,40 +1,45 @@
 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 library 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 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 Models Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE).
+The Prognostics Algorithms Package was developed by researchers of the NASA Prognostics Center of Excellence (PCoE) and `Diagnostics & Prognostics Group <https://www.nasa.gov/content/diagnostics-prognostics>`__.
 
-If you are new to this package, see `<getting_started.html>`__.
+If you are new to this package, see `getting started <getting_started.html>`__.
 
 .. toctree::
    :maxdepth: 2
    
+   Tutorial <https://mybinder.org/v2/gh/nasa/prog_algs/master?labpath=tutorial.ipynb>
    getting_started
    state_estimators
    predictors
    uncertain_data
+   prediction
    metrics
-   visualize
    exceptions
+   ProgModels <https://nasa.github.io/prog_models>
    dev_guide
+   GitHub <https://github.com/nasa/prog_algs>
 
 Citing this repository
 -----------------------
 Use the following to cite this repository:
 
-@misc{2020_nasa_prog_model,
-    author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
-    title     = {Prognostics Algorithms Python Package},
-    month     = Dec,
-    year      = 2020,
-    version   = {0.3.4},
-    url       = {https://github.com/nasa/prog_algs}
-    }
+@misc{2021_nasa_prog_algs,
+  | author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
+  | title     = {Prognostics Algorithms Python Package},
+  | month     = Aug,
+  | year      = 2021,
+  | version   = {1.2.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, v0.2.0, Dec. 2020. URL https://github.com/nasa/prog_algs.
+C. Teubert, C. Kulkarni, M. Corbetta Prognostics Algorithms Python Package, v1.2.0, Aug. 2021. URL https://github.com/nasa/prog_algs.
 
 Indices and tables
 -----------------------
diff --git a/sphinx_config/metrics.rst b/sphinx_config/metrics.rst
index 8825bd1b..04d59aa0 100644
--- a/sphinx_config/metrics.rst
+++ b/sphinx_config/metrics.rst
@@ -3,6 +3,7 @@ Metrics
 
 The metrics module includes tools for calculating various metrics for prognostics results. These metrics are a measurement of the performance of a prognostics algorithms. The analysis functions are described below:
 
-.. automodule:: prog_algs.metrics.samples
+.. automodule:: prog_algs.metrics
    :members:
    :inherited-members:
+   :exclude-members: calc_metrics, alpha_lambda
diff --git a/sphinx_config/prediction.rst b/sphinx_config/prediction.rst
new file mode 100644
index 00000000..34ad13d7
--- /dev/null
+++ b/sphinx_config/prediction.rst
@@ -0,0 +1,27 @@
+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, observables, 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:
\ No newline at end of file
diff --git a/sphinx_config/predictors.rst b/sphinx_config/predictors.rst
index fbc2f20f..f91569b0 100644
--- a/sphinx_config/predictors.rst
+++ b/sphinx_config/predictors.rst
@@ -1,15 +1,41 @@
 Predictors
 =======================
 
-.. autoclass:: prog_algs.predictors.predictor.Predictor
-   :members:
-   :inherited-members:
+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 <https://github.com/nasa/prog_models>`__) to predict both future states (also outputs, observables, 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
 
-The predictor algorithms implemented in this package are described below:
+   >>> 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
+   >>>
+   >>> (t, u, x, z, es, toe) = pred.predict(first_state, future_loading, save_freq=1)
+   >>> toe.plot_hist(events='impact')  # Plot a histogram of when the impact event occurred
 
-MC Predictor
+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
 
-.. autoclass:: prog_algs.predictors.monte_carlo.MonteCarlo
+Unscented Transform Predictor
+*****************************
+.. autoclass:: prog_algs.predictors.UnscentedTransformPredictor
+
+Predictor Interface
+-----------------------
+.. autoclass:: prog_algs.predictors.Predictor
    :members:
    :inherited-members:
diff --git a/sphinx_config/state_estimators.rst b/sphinx_config/state_estimators.rst
index 7b183c7a..af7b1429 100644
--- a/sphinx_config/state_estimators.rst
+++ b/sphinx_config/state_estimators.rst
@@ -1,23 +1,40 @@
 State Estimators
 =======================
+The State Estimator uses sensor information and a Prognostics Model (see: `prog_models package <https://github.com/nasa/prog_models>`__) to produce an estimate of system state (which can be used to estimate outputs, event_states, and observables). This state estimate can either be used by itself or as input to a `Predictor <predictors.html>`__. A state estimator is typically run multiple times as new information is available.
 
-.. autoclass:: prog_algs.state_estimators.state_estimator.StateEstimator
-   :members:
-   :inherited-members:
+Here's an example of its use. In this example we use the unscented kalman filter state estimator and the ThrownObject model. 
 
-The state estimator algorithms implemented in this package are described below:
+.. code-block:: python
 
-UKF State Estimator
------------------------
+   >>> 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)
 
-.. autoclass:: prog_algs.state_estimators.unscented_kalman_filter.UnscentedKalmanFilter
-   :members:
-   :inherited-members:
+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`
 
+Unscented Kalman Filter
+*************************
+.. autoclass:: prog_algs.state_estimators.UnscentedKalmanFilter
 
-PF State Estimator
------------------------
+Particle Filter
+*************************
+.. autoclass:: prog_algs.state_estimators.ParticleFilter
 
-.. autoclass:: prog_algs.state_estimators.particle_filter.ParticleFilter
+State Estimator Interface
+-------------------------
+.. autoclass:: prog_algs.state_estimators.StateEstimator
    :members:
    :inherited-members:
diff --git a/sphinx_config/uncertain_data.rst b/sphinx_config/uncertain_data.rst
index e01bd17d..049b987c 100644
--- a/sphinx_config/uncertain_data.rst
+++ b/sphinx_config/uncertain_data.rst
@@ -1,20 +1,26 @@
 Uncertain Data
 =======================
 
-The `prog_algs.uncertain_data` package includes classes for representing data with uncertainty. 
+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:
-   :inherited-members:
+   :members: key
 
+Multivariate Normal Distribution
+********************************
 .. autoclass:: prog_algs.uncertain_data.MultivariateNormalDist
-   :members:
-   :inherited-members:
 
+Scalar Data (i.e., no uncertainty)
+**********************************
 .. autoclass:: prog_algs.uncertain_data.ScalarData
-   :members:
-   :inherited-members:
diff --git a/sphinx_config/visualize.rst b/sphinx_config/visualize.rst
deleted file mode 100644
index 47393412..00000000
--- a/sphinx_config/visualize.rst
+++ /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/src/prog_algs/__init__.py b/src/prog_algs/__init__.py
index 16d98db7..b3935025 100644
--- a/src/prog_algs/__init__.py
+++ b/src/prog_algs/__init__.py
@@ -1,13 +1,14 @@
 # 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']
+__all__ = ['predictors', 'uncertain_data', 'state_estimators', 'run_prog_playback', 'metrics']
 from . import predictors, state_estimators, uncertain_data
 
-import numpy as np
+import warnings
 
-__version__ = '1.0.0'
+__version__ = '1.2.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,
@@ -16,23 +17,23 @@ def run_prog_playback(obs, pred, future_loading, output_measurements, **kwargs):
     config.update(kwargs)
 
     next_predict = output_measurements[0][0] + config['predict_rate']
-    times = np.empty((len(output_measurements), config['num_samples']), dtype=object)
-    inputs = np.empty((len(output_measurements), config['num_samples']), dtype=object)
-    states = np.empty((len(output_measurements), config['num_samples']), dtype=object)
-    outputs = np.empty((len(output_measurements), config['num_samples']), dtype=object)
-    event_states = np.empty((len(output_measurements), config['num_samples']), dtype=object)
-    eols = np.empty((len(output_measurements), config['num_samples']), dtype=object)
+    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:
-            (t, u, x, z, es, eol) = pred.predict(obs.x.sample(config['num_samples']), future_loading, **config['predict_config'])
-            times[index, :] = t
-            inputs[index, :]  = u
-            states[index, :]  = x
-            outputs[index, :]  = z
-            event_states[index, :]  =  es
-            eols[index, :] = eol
+            (t, u, x, z, es, toe) = pred.predict(obs.x.sample(config['num_samples']), future_loading, **config['predict_config'])
+            times.append(t)
+            inputs.append(u)
+            states.append(x)
+            outputs.append(z)
+            event_states.append(es)
+            toes.append(toe)
             index += 1
             next_predict += config['predict_rate']
-    return (times, inputs, states, outputs, event_states, eols) 
+    return (times, inputs, states, outputs, event_states, toes) 
diff --git a/src/prog_algs/exceptions.py b/src/prog_algs/exceptions.py
index cc672777..0563e340 100644
--- a/src/prog_algs/exceptions.py
+++ b/src/prog_algs/exceptions.py
@@ -1,19 +1,19 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
+
 class ProgAlgException(Exception):
     """
     Base Prognostics Model Exception
     """
-    pass
+
 
 class ProgAlgInputException(ProgAlgException):
     """
     Prognostics Input Exception - indicates the method input parameters were incorrect
     """
-    pass
+
 
 class ProgAlgTypeError(ProgAlgException, TypeError):
     """
     Prognostics Type Error - indicates the model could not be constructed
     """
-    pass
\ No newline at end of file
diff --git a/src/prog_algs/metrics/__init__.py b/src/prog_algs/metrics/__init__.py
index decbbd9d..90b082b4 100644
--- a/src/prog_algs/metrics/__init__.py
+++ b/src/prog_algs/metrics/__init__.py
@@ -1,3 +1,8 @@
 # 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
+
+__all__ = ['alpha_lambda', 'calc_metrics', 'prob_success']
diff --git a/src/prog_algs/metrics/samples.py b/src/prog_algs/metrics/samples.py
index 84305023..6ca17b3b 100644
--- a/src/prog_algs/metrics/samples.py
+++ b/src/prog_algs/metrics/samples.py
@@ -1,118 +1,32 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from numpy import mean, std
-from scipy import stats
-
-def eol_metrics(eol, ground_truth = None):
-    """Calculate all end of life metrics
-
-    Args:
-        eol ([double]): End of life times, output from predictor
-        ground_truth (float, optional): Ground truth end of discharge time. Defaults to None.
-
-    Returns:
-        dict: collection of metrics
-    """
-    eol.sort()
-    m = mean(eol)
-    median = eol[int(len(eol)/2)]
-    metrics = {
-        'min': eol[0],
-        'percentiles': {
-            '0.01': eol[int(len(eol)/10000)] if len(eol) >= 10000 else None,
-            '0.1': eol[int(len(eol)/1000)] if len(eol) >= 1000 else None,
-            '1': eol[int(len(eol)/100)] if len(eol) >= 100 else None,
-            '10': eol[int(len(eol)/10)] if len(eol) >= 10 else None,
-            '25': eol[int(len(eol)/4)] if len(eol) >= 4 else None,
-            '50': median,
-            '75': eol[int(3*len(eol)/4)] if len(eol) >= 4 else None,
-        },
-        'median': median,
-        'mean': m,
-        'std': std(eol),
-        'max': eol[-1],
-        'median absolute deviation': sum([abs(x - median) for x in eol])/len(eol),
-        'mean absolute deviation':   sum([abs(x - m)   for x in eol])/len(eol),
-        'number of samples': len(eol)
-    }
-
-    if ground_truth is not None:
-        # Metrics comparing to ground truth
-        metrics['mean absolute error'] = sum([abs(x - ground_truth) for x in eol])/len(eol)
-        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(eol, ground_truth)
-
-    return metrics
-
-def prob_success(eol, time):
-    """Calculate probability of success - i.e., probability that event will not occur within a given time (i.e., success)
-
-    Args:
-        eol ([float]): End of life times, output from predictor
-        time ([type]): time for calculation
-
-    Returns:
-        fload: Probability of success
-    """
-    return sum([e > time for e in eol])/len(eol)
-
-def alpha_lambda(times, eols, ground_truth, lambda_value, alpha, beta): 
-    """Compute alpha lambda metrics
-
-    Args:
-        times ([float]): Times corresponding to eols (output from predictors)
-        eols ([float]): End of life times, output from predictor
-        ground_truth (float): Ground Truth end of life time
-        lambda_value (fload): lambda
-        alpha (float): alpha
-        beta (float): beta
-
-    Returns:
-        bool: if alpha-lambda met
-    """
-    for (t, eol) in zip(times, eols):
-        if (t >= lambda_value):
-            upper_bound = ground_truth + alpha*(ground_truth-t)
-            lower_bound = ground_truth - alpha*(ground_truth-t)
-            return percentage_in_bounds(eol, [lower_bound, upper_bound]) >= beta 
+# This file is kept for backwards compatability
+from .uncertain_data_metrics import calc_metrics as eol_metrics
+from .toe_metrics import prob_success
+from .toe_profile_metrics import alpha_lambda
 
+from numpy import mean
+from warnings import warn
 
 def mean_square_error(values, ground_truth):
     """Mean Square Error
-
     Args:
-        values ([float]): End of life times, output from predictor
-        ground_truth (float): Ground truth EOL
-
-    Returns:
-        float: mean square error of eol predictions
-    """
-    return sum([(x.mean() - ground_truth)**2 for x in values])/len(values)
-
-def eol_profile_metrics(eol, ground_truth):
-    """Calculate eol profile metrics
-
-    Args:
-        eol ([float]): End of life times, output from predictor
-        ground_truth (float): Ground truth EOL
-
+        values ([float]): time of event for a single event, output from predictor
+        ground_truth (float): Ground truth ToE
     Returns:
-        dict: EOL Profile Metrics
+        float: mean square error of toe predictions
     """
-    # TODO(CT): Consider ground truth optional
-    return {
-        'mean square error': mean_square_error(eol, ground_truth)
-    }
+    return sum([(mean(x) - ground_truth)**2 for x in values])/len(values)
 
-def percentage_in_bounds(eol, bounds):
-    """Calculate percentage of EOL dist is within specified bounds
+def percentage_in_bounds(toe, bounds):
+    """Calculate percentage of ToE dist is within specified bounds
 
     Args:
-        eol ([float]): End of life times, output from predictor
+        toe ([float]): Times of event for a single event, output from predictor
         bounds ([float, float]): Lower and upper bounds
 
     Returns:
         float: Percentage within bounds (where 1 = 100%)
     """
-    return sum([x < bounds[1] and x > bounds[0] for x in eol])/ len(eol)
\ No newline at end of file
+    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
new file mode 100644
index 00000000..b6890ee1
--- /dev/null
+++ b/src/prog_algs/metrics/toe_metrics.py
@@ -0,0 +1,60 @@
+# 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, time, **kwargs):
+    """Calculate probability of success - i.e., probability that event will not occur within a given time (i.e., success)
+
+    Args:
+        toe (UncertainData or array[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
+    """
+    params = {
+        'n_samples': 10000,  # Default 
+    }
+    params.update(kwargs)
+
+    if isinstance(toe, UncertainData):
+        # Default to all keys
+        keys = params.setdefault('keys', toe.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]):
+            # 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 > 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
new file mode 100644
index 00000000..1d7cc095
--- /dev/null
+++ b/src/prog_algs/metrics/toe_profile_metrics.py
@@ -0,0 +1,43 @@
+# 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 ..predictors import ToEPredictionProfile
+
+def alpha_lambda(toe_profile : ToEPredictionProfile, ground_truth : dict, lambda_value : float, alpha : float, beta : float, **kwargs): 
+    """
+    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 arge include:
+            * keys (list[string], optional): list of keys to use. If not provided, all keys are used.
+
+    Returns:
+        bool: if alpha-lambda 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())
+
+            result = {}
+            for key in keys:
+                upper_bound = ground_truth[key] + alpha*(ground_truth[key]-t_prediction)
+                lower_bound = ground_truth[key] - alpha*(ground_truth[key]-t_prediction)
+                result[key] = toe.percentage_in_bounds([lower_bound, upper_bound])[key] >= beta 
+                if params['print']:
+                    print('\n', key)
+                    print('\ttoe:', toe.key(key))
+                    print('\tBounds: [{} - {}]({}%)'.format(lower_bound, upper_bound, toe.percentage_in_bounds([lower_bound, upper_bound])[key]))
+            return result
diff --git a/src/prog_algs/metrics/uncertain_data_metrics.py b/src/prog_algs/metrics/uncertain_data_metrics.py
new file mode 100644
index 00000000..7d58906e
--- /dev/null
+++ b/src/prog_algs/metrics/uncertain_data_metrics.py
@@ -0,0 +1,104 @@
+# 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
+from numpy import isscalar, mean, std, array
+from scipy import stats
+from ..uncertain_data import UncertainData, UnweightedSamples
+
+def calc_metrics(data, ground_truth = None, **kwargs):
+    """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.
+        **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 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]):
+            # 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 = array(data)  # Must be array
+    data.sort()
+    m = mean(data)
+    median = data[int(len(data)/2)]
+    metrics = {
+        'min': data[0],
+        'percentiles': {
+            '0.01': data[int(len(data)/10000)] if len(data) >= 10000 else None,
+            '0.1': data[int(len(data)/1000)] if len(data) >= 1000 else None,
+            '1': data[int(len(data)/100)] if len(data) >= 100 else None,
+            '10': data[int(len(data)/10)] if len(data) >= 10 else None,
+            '25': data[int(len(data)/4)] if len(data) >= 4 else None,
+            '50': median,
+            '75': data[int(3*len(data)/4)] if len(data) >= 4 else None,
+        },
+        'median': median,
+        'mean': m,
+        'std': std(data),
+        'max': data[-1],
+        'median absolute deviation': sum([abs(x - median) for x in data])/len(data),
+        'mean absolute deviation':   sum([abs(x - m)   for x in data])/len(data),
+        'number of samples': len(data)
+    }
+
+    if ground_truth is not None:
+        # Metrics comparing to ground truth
+        metrics['mean absolute error'] = sum([abs(x - ground_truth) for x in data])/len(data)
+        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, ground_truth)
+
+    return metrics
diff --git a/src/prog_algs/predictors/__init__.py b/src/prog_algs/predictors/__init__.py
index b9de4ba0..6b7cbebb 100644
--- a/src/prog_algs/predictors/__init__.py
+++ b/src/prog_algs/predictors/__init__.py
@@ -1,5 +1,8 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-__all__ = ['predictor', 'monte_carlo']
 from .monte_carlo import MonteCarlo
-from .predictor import Predictor
\ No newline at end of file
+from .predictor import Predictor
+from .prediction import Prediction, UnweightedSamplesPrediction
+from .toe_prediction_profile import ToEPredictionProfile
+from .unscented_transform import UnscentedTransformPredictor
+__all__ = ['predictor', 'monte_carlo', 'unscented_transform', 'MonteCarlo', 'Predictor', 'Prediction', 'UnweightedSamplesPrediction', 'ToEPredictionProfile', 'UnscentedTransformPredictor']
diff --git a/src/prog_algs/predictors/monte_carlo.py b/src/prog_algs/predictors/monte_carlo.py
index 03f8d213..522d1659 100644
--- a/src/prog_algs/predictors/monte_carlo.py
+++ b/src/prog_algs/predictors/monte_carlo.py
@@ -1,104 +1,124 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from . import predictor
-from numpy import empty
-from ..exceptions import ProgAlgTypeError
+from .prediction import UnweightedSamplesPrediction
+from .predictor import Predictor
 from copy import deepcopy
 from functools import partial
+from prog_models.sim_result import SimResult, LazySimResult
+from prog_algs.uncertain_data import UnweightedSamples, UncertainData
 
-def prediction_fcn(x, model, params, loading):
+def prediction_fcn(x, model, params, events, loading):
     # This is the main prediction function for the multi-threading
+    events_remaining = deepcopy(events)
     first_output = model.output(x)
+    time_of_event = {}
+    last_state = {}
+    times = []
+    # inputs will be the same as states unless we explicitly deepcopy
+    inputs = SimResult()
+    states = SimResult()
+    outputs = LazySimResult(fcn = model.output)
+    event_states = LazySimResult(fcn = model.event_state)
     params['x'] = x
-    (times, inputs, states, outputs, event_states) = model.simulate_to_threshold(loading, first_output, **params)
-    if (model.threshold_met(states[-1])):
-        time_of_event = times[-1]
-    else:
-        time_of_event = None
-    return (times, inputs, states, outputs, event_states, time_of_event)
+    params['t'] = 0
+    while len(events_remaining) > 0:  # Still events to predict
+        (t, u, xi, z, es) = model.simulate_to_threshold(loading, first_output, **params, threshold_keys=events_remaining, print=False)
 
+        # Add results
+        times.extend(t)
+        inputs.extend(u)
+        states.extend(xi)
+        outputs.extend(z)
+        event_states.extend(es)
 
-class MonteCarlo(predictor.Predictor):
-    """
-    Class for performing model-based prediction using sampling. 
+        # Get which event occurs
+        t_met = model.threshold_met(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
+            for event in events_remaining:
+                time_of_event[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] = deepcopy(params['x'])
+        outputs.pop()
+        event_states.pop()
+        
+    return (times, inputs, states, outputs, event_states, time_of_event, last_state)
 
-    This class defines logic for performing model-based state prediction using sampling methods. A Predictor is constructed using a PrognosticsModel object, (See Prognostics Model Package). The states are simulated until either a specified time horizon is met, or the threshold is reached for all samples, as defined by the threshold equation. A provided future loading equation is used to compute the inputs to the system at any given time point. 
 
-    Parameters
-    ----------
-    * model : prog_models.prognostics_model.PrognosticsModel\n
-        See: Prognostics Model Package\n
-        A prognostics model to be used in prediction
-    * options (optional, kwargs): configuration options\n
-        Any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported: \n
-        * dt : Step size (s)
-        * horizon : Prediction horizon (s)
-        * save_freq : Frequency at which results are saved (s)
-        * save_pts : Any additional savepoints (s) e.g., [10.1, 22.5]
-        * cores : Number of cores to use in multithreading
+class MonteCarlo(Predictor):
     """
-    default_parameters = { # Default Parameters
-        'dt': 0.5,          # Timestep, seconds
-        'horizon': 4000,    # Prediction horizon, seconds
-        'save_freq': 10,    # Frequency at which results are saved
-        'cores': 6          # Number of cores to use in parallelization
-    }
+    Class for performing a monte-carlo model-based prediction.
 
-    def predict(self, state_samples, future_loading_eqn, **kwargs):
-        """
-        Perform a single 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. 
 
-        Parameters
-        ----------
-        state_samples : collection of samples for the MonteCarlo
-            Function to generate n samples of the state. 
-            e.g., def f(n): return [x1, x2, x3, ... xn]
-        future_loading_eqn : function (t, x={}) -> z
-            Function to generate an estimate of loading at future time t
-        config : keyword arguments, optional
-            Any additional configuration values. See default parameters
+    The following configuration parameters are supported (as kwargs in constructor or as parameters in predict method):
+    
+    Configuration Parameters
+    ------------------------------
+    dt : float
+        Simulation step size (s), e.g., 0.1
+    events : List[string]
+        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_N_SAMPLES = 100  # Default number of samples to use, if none specified
 
-        Returns (tuple)
-        -------
-        times: [[number]]
-            Times for each simulated point in format times[sample_id][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]
-        toe: [number]
-            Estimated time where a predicted event will occur for each sample.
-        """
+    def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
         params = deepcopy(self.parameters) # copy parameters
         params.update(kwargs) # update for specific run
 
-        times_all = empty(state_samples.size, dtype=object)
-        inputs_all = empty(state_samples.size, dtype=object)
-        states_all = empty(state_samples.size, dtype=object)
-        outputs_all = empty(state_samples.size, dtype=object)
-        event_states_all = empty(state_samples.size, dtype=object)
-        time_of_event = empty(state_samples.size)
+        # Sample from state if n_samples specified or state is not UnweightedSamples
+        if 'n_samples' in params:
+            # If n_samples is specified, sample
+            state = state.sample(params['n_samples'])
+        elif not isinstance(state, UnweightedSamples):
+            # If no n_samples specified, but state is not UnweightedSamples, then sample with default
+            state = state.sample(self.DEFAULT_N_SAMPLES)
 
         # Perform prediction
         pred_fcn = partial(
             prediction_fcn, 
             model = self.model, 
             params = params,
+            events = params['events'],
             loading = future_loading_eqn)
         
-        result = [pred_fcn(sample) for sample in state_samples]
-        times_all = [tmp[0] for tmp in result]
-        inputs_all = [tmp[1] for tmp in result]
-        states_all = [tmp[2] for tmp in result]
-        outputs_all = [tmp[3] for tmp in result]
-        event_states_all = [tmp[4] for tmp in result]
-        time_of_event = [tmp[5] for tmp in result]
-        return (times_all, inputs_all, states_all, outputs_all, event_states_all, time_of_event)
+        result = [pred_fcn(sample) for sample in state]
+        times_all, inputs_all, states_all, outputs_all, event_states_all, time_of_event, last_states = map(list, zip(*result))
+        
+        # Return longest time array
+        times_length = [len(t) for t in times_all]
+        times_max_len = max(times_length)
+        times = times_all[times_length.index(times_max_len)] 
+        
+        inputs_all = UnweightedSamplesPrediction(times, inputs_all)
+        states_all = UnweightedSamplesPrediction(times, states_all)
+        outputs_all = UnweightedSamplesPrediction(times, outputs_all)
+        event_states_all = UnweightedSamplesPrediction(times, event_states_all)
+        time_of_event = UnweightedSamples(time_of_event)
+
+        # Transform final states:
+        last_states = {
+            key: UnweightedSamples([sample[key] for sample in last_states]) for key in time_of_event.keys()
+        }
+        time_of_event.final_state = last_states
+        return (times, 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
new file mode 100644
index 00000000..16fd73aa
--- /dev/null
+++ b/src/prog_algs/predictors/prediction.py
@@ -0,0 +1,136 @@
+# 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 warnings import warn
+
+from ..uncertain_data import UnweightedSamples
+
+
+class Prediction():
+    """
+    Class for the result of a prediction. Is returned by the predict method of a predictor.
+
+    Args:
+        times (array(float)):
+            Times for each data point where times[n] corresponds to data[n]
+        data (array(UncertainData)):
+            Data points for each time in times 
+    """
+
+    def __init__(self, times, data):
+        self.times = times
+        self.data = data
+
+    def __eq__(self, other):
+        """Compare 2 Predictions
+
+        Args:
+            other (Precition):
+
+        Returns:
+            bool: If the two Predictions are equal
+        """
+        return self.times == other.times and self.data == other.data
+
+    def snapshot(self, time_index):
+        """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):
+        """Estimate of the mean value of the prediction at each time
+
+        Returns:
+            array(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):
+        warn("Deprecated. Please use prediction.times[index] instead.")
+        return self.times[index]
+
+
+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 : array(float)
+            Times for each data point where times[n] corresponds to data[n]
+        data : array(dict)
+            Data points where data[n] is a SimResult for sample n
+    """
+
+    def __init__(self, times, data):
+        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):
+        return "UnweightedSamplesPrediction with {} savepoints".format(len(self.times))
+
+    @property
+    def mean(self):
+        if not self.__transformed:
+            self.__calculate_tranform()
+        return [dist.mean for dist in self.__transform]
+
+    def sample(self, sample_id):
+        warn("Deprecated. Please use prediction[sample_id] instead.")
+        return self[sample_id]
+
+    def snapshot(self, time_index):
+        """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
index 3932b7e7..10caef34 100644
--- a/src/prog_algs/predictors/predictor.py
+++ b/src/prog_algs/predictors/predictor.py
@@ -3,6 +3,8 @@
 from abc import ABC, abstractmethod
 from copy import deepcopy
 from ..exceptions import ProgAlgTypeError
+from ..uncertain_data import UncertainData
+
 
 class Predictor(ABC):
     """
@@ -12,11 +14,10 @@ class Predictor(ABC):
 
     Parameters
     ----------
-    * model : prog_models.prognostics_model.PrognosticsModel\n
+    model : PrognosticsModel
         See: Prognostics Model Package\n
         A prognostics model to be used in prediction
-    * options (optional, kwargs): configuration options\n
-        Any additional configuration values. See documentation for specific predictor \n
+    kwargs : optional, keyword arguments
     """
     default_parameters = {}
 
@@ -36,25 +37,40 @@ def __init__(self, model, **kwargs):
         self.model = model
 
         self.parameters = deepcopy(self.default_parameters)
+        self.parameters['events'] = deepcopy(self.model.events)  # Events to predict to
         self.parameters.update(kwargs)
 
     @abstractmethod
-    def predict(self, state_samples, future_loading_eqn, **kwargs):
+    def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
         """
         Perform a single prediction
 
         Parameters
         ----------
-        state_sampler : function (n) -> [x1, x2, ... xn]
-            Function to generate n samples of the state. 
-            e.g., def f(n): return [x1, x2, x3, ... xn]
-        future_loading_eqn : function (t) -> z
-            Function to generate an estimate of loading at future time t
-        options : keyword arguments, optional
-            Any additional configuration values. See default parameters, above
+        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
+        options : optional, keyword arguments
+            The following configuration parameters are supported: \n
+            * dt (float): Simulation step size (s), e.g., 0.1
+            * events (list[string]): 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]
 
         Return
-        ______
-        result : recorded values for all samples
+        ----------
+        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
new file mode 100644
index 00000000..33b42e56
--- /dev/null
+++ b/src/prog_algs/predictors/toe_prediction_profile.py
@@ -0,0 +1,63 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+from collections import UserDict
+from typing import Dict
+
+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 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
+            kwargs (optional, keyword arguments):
+                configuration arguments. Accepted arge include: \n
+                 * keys (list[string]): list of keys to use. If not provided, all keys are used.
+                 * print (bool) : If True, print the results. 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)
diff --git a/src/prog_algs/predictors/unscented_transform.py b/src/prog_algs/predictors/unscented_transform.py
new file mode 100644
index 00000000..fbb0957e
--- /dev/null
+++ b/src/prog_algs/predictors/unscented_transform.py
@@ -0,0 +1,259 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+from .prediction import Prediction, UnweightedSamplesPrediction
+from .predictor import Predictor
+from numpy import diag, array, transpose
+from copy import deepcopy
+from math import isnan
+from filterpy import kalman
+from prog_algs.uncertain_data import MultivariateNormalDist
+
+
+class LazyUTPrediction(Prediction):
+    def __init__(self, state_prediction, sigma_fcn, ut_fcn, transform_fcn):
+        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
+    dt : float
+        Simulation step size (s), e.g., 0.1
+    events : List[string]
+        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 = len(model.states)
+        num_measurements = len(model.outputs)
+
+        if 'Q' not in self.parameters:
+            # Default 
+            self.parameters['Q'] = diag([1.0e-1 for i in range(num_states)])
+        if 'R' not in self.parameters:
+            self.parameters['R'] = diag([1.0e-1 for i in range(num_measurements)])
+        
+        def measure(x):
+            x = {key: value for (key, value) in zip(self.__state_keys, x)}
+            z = model.output(x)
+            return {array(list(z.values()))}
+
+        def state_transition(x, dt):
+            x = {key: value for (key, value) in zip(self.__state_keys, 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']
+        self.filter.R = self.parameters['R']
+
+    def predict(self, state, future_loading_eqn, **kwargs):
+        """
+        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 (tuple)
+        -------
+        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]
+        toe: 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
+        """
+        params = deepcopy(self.parameters) # copy parameters
+        params.update(kwargs) # update for specific run
+        events_to_predict = params['events']
+
+        # Optimizations 
+        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
+        events = model.events
+
+        # 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: [float('nan') 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)
+            states.append(x_dict)  # Avoid optimization where x is not copied
+
+        # Optimization
+        state_keys = self.__state_keys
+
+        # Simulation
+        update_all()  # First State
+        while t < params['horizon']:
+            # Iterate through time
+            t += dt
+            mean_state = {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 = {key: x for (key, x) in zip(state_keys, 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] = deepcopy(x)
+                    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():
+            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)
+
+        # 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 (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
index 4092c5df..d776766e 100644
--- a/src/prog_algs/state_estimators/__init__.py
+++ b/src/prog_algs/state_estimators/__init__.py
@@ -1,6 +1,6 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-__all__ = ['state_estimator', 'unscented_kalman_filter', 'particle_filter']
 from .particle_filter import ParticleFilter
 from .state_estimator import StateEstimator
-from .unscented_kalman_filter import UnscentedKalmanFilter
\ No newline at end of file
+from .unscented_kalman_filter import UnscentedKalmanFilter
+__all__ = ['state_estimator', 'StateEstimator', 'unscented_kalman_filter', 'UnscentedKalmanFilter', 'particle_filter', 'ParticleFilter']
diff --git a/src/prog_algs/state_estimators/particle_filter.py b/src/prog_algs/state_estimators/particle_filter.py
index 4f120e41..452e9ec4 100644
--- a/src/prog_algs/state_estimators/particle_filter.py
+++ b/src/prog_algs/state_estimators/particle_filter.py
@@ -1,95 +1,141 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
 from . import state_estimator
-from numpy import array, empty, random
-import filterpy.monte_carlo
+from numpy import array, empty, random, take, exp, max, take
+from filterpy.monte_carlo import residual_resample
 from numbers import Number
 from scipy.stats import norm
 from ..uncertain_data import UnweightedSamples
 from ..exceptions import ProgAlgTypeError
-from copy import deepcopy
+from warnings import warn
+
 
 class ParticleFilter(state_estimator.StateEstimator):
     """
-    Estimates state using a particle filter algorithm
-
-    Constructor parameters:
-     * model (ProgModel): Model to be used in state estimation \n
-        See: Prognostics Model Package \n
-        A prognostics model to be used in state estimation
-     * x0 (dict): Initial State \n
-        Initial (starting) state, with keys defined by model.states \n
-        e.g., x = {'abc': 332.1, 'def': 221.003} given states = ['abc', 'def']
-     * measurement_eqn (optional, function): Measurement equation (x)->z. Usually used in situations where what's measured don't exactly match the output (e.g., different unit, not ever output measured, etc.). see `examples.measurement_eqn_example`
-     * options (optional, kwargs): configuration options\n
-        Any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported: \n
-         * num_particles : Number of particles used in PF e.g., 20
-         * resample_fcn : Resampling function ([weights]) -> [indexes] e.g., filterpy.monte_carlo.residual_resample
-         * x0_uncertainty : Initial uncertainty in state e.g., 0.5
-         * R (Number) : Measurement Noise. e.g., 0.1
-    """
-    t = 0 # last timestep
+    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:
+
+    Constructor Configuration Parameters:
+        t0 : float
+            Starting time (s)
+        dt : float 
+            time step (s)
+        num_particles : int
+            Number of particles in particle filter
+        resample_fcn : function 
+            Resampling function ([weights]) -> [indexes] e.g., filterpy.monte_carlo.residual_resample
+        x0_uncertainty : float or dict
+            Initial uncertainty in state. Can be 1. scalar (standard deviation applied to all), or 2. dict (stardard deviation for each)\n
+            e.g., 0.5 or {'state1': 0.5, 'state2': 0.2}
+    """
     default_parameters = {
-            'n': 0.1, # Sensor Noise
+            't0': -1,
             'num_particles': 20, 
-            'resample_fcn': filterpy.monte_carlo.residual_resample, # Resampling function ([weights]) -> [indexes]
-            'x0_uncertainty': 0.5   # Initial State Uncertainty
-                                    # Can be:
-                                    #   1. scalar (standard deviation applied to all),
-                                    #   2. dict (stardard deviation for each)
-                                    #   Todo(CT): covar, function
+            'resample_fcn': residual_resample,
+            'x0_uncertainty': 0.5
         }
 
     def __init__(self, model, x0, measurement_eqn = None, **kwargs):
         super().__init__(model, x0, measurement_eqn = measurement_eqn, **kwargs)
+        
+        self.__measure = measurement_eqn if measurement_eqn else model.output
 
-        if measurement_eqn is None:
-            self.__measure = model.output
-        else:
-            self.__measure = measurement_eqn
-
-        # State-estimator specific logic
-        if isinstance(self.parameters['n'], Number):
-            self.parameters['n'] = {key : self.parameters['n'] for key in self.__measure(x0).keys()}
-        # todo(CT): Check fields on n
+        # Build array inplace
+        x = array(list(x0.values()))
 
-        if isinstance(self.parameters['x0_uncertainty'], Number):
-            # Build array inplace
-            x = array(list(x0.values()))
+        if isinstance(self.parameters['x0_uncertainty'], dict):
+            sd = array([self.parameters['x0_uncertainty'][key] for key in x0.keys()])
+        elif isinstance(self.parameters['x0_uncertainty'], Number):
             sd = array([self.parameters['x0_uncertainty']] * len(x0))
-            samples = array([random.normal(x, sd) for i in range(self.parameters['num_particles'])])
-            self.particles = array([{key: value for (key, value) in zip(x0.keys(), x)} for x in samples])
         else:
             raise ProgAlgTypeError
+
+        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 x0.keys()}
+
+        samples = [random.normal(
+            x[i], sd[i], self.parameters['num_particles']) for i in range(len(x))]
+        self.particles = dict(zip(x0.keys(), samples))
     
     def __str__(self):
         return "{} State Estimator".format(self.__class__)
         
     def estimate(self, t, u, z):
-        # todo(CT): assert t > self.t?
+        assert t > self.t, "New time must be greater than previous"
         dt = t - self.t
-        weights = empty(len(self.particles))
-        
+        self.t = t
+
         # Optimization
         particles = self.particles
         next_state = self.model.next_state
+        apply_process_noise = self.model.apply_process_noise
         output = self.__measure
-        noise_params = self.parameters['n']
+        apply_measurement_noise = self.model.apply_measurement_noise
+        noise_params = self.model.parameters['measurement_noise']
+        num_particles = self.parameters['num_particles']
+        # Check which output keys are present (i.e., output of measurement function)
+        measurement_keys = output({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
+            particles = apply_process_noise(next_state(particles, u, dt))
+
+            # Get particle measurements
+            zPredicted = apply_measurement_noise(output(particles))
+        else:
+            # Propogate and calculate weights
+            for i in range(num_particles):
+                x = {key: particles[key][i] for key in particles.keys()}
+                x = next_state(x, u, dt) 
+                x = apply_process_noise(x)
+                for key in particles.keys():
+                    self.particles[key][i] = x[key]
+                z = output(x)
+                z = apply_measurement_noise(z)
+                for key in measurement_keys:
+                    zPredicted[key][i] = z[key]
+
+        # 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)
 
-        # Propogate and calculate weights
-        for i in range(len(particles)):
-            self.particles[i] = next_state(particles[i], u, dt) 
-            zPredicted = output(self.particles[i])
-            weights[i] = sum([norm(zPredicted[key], noise_params[key]).pdf(z[key]) for key in zPredicted.keys()])
-        
         # Normalize
-        total_weight = sum(weights)
-        weights = array([weight/total_weight for weight in weights])
+        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()]
 
-        # Resample
-        indexes = self.parameters['resample_fcn'](weights)
-        self.particles = array([self.particles[i] for i in indexes])
+        # Particles as a dictionary
+        self.particles = dict(zip(self.particles.keys(), samples))
 
     @property
     def x(self):
diff --git a/src/prog_algs/state_estimators/state_estimator.py b/src/prog_algs/state_estimators/state_estimator.py
index bbae8219..fe9a473e 100644
--- a/src/prog_algs/state_estimators/state_estimator.py
+++ b/src/prog_algs/state_estimators/state_estimator.py
@@ -11,19 +11,23 @@ class StateEstimator(ABC):
 
     Abstract base class for creating state estimators that perform state estimation. Subclasses must implement this interface. Equivilant to "Observers" in NASA's Matlab Prognostics Algorithm Library
 
-    Constructor parameters:
-     * model (ProgModel): Model to be used in state estimation \n
-        See: Prognostics Model Package \n
+    parameters:
+     model : ProgModel
         A prognostics model to be used in state estimation
-     * x0 (dict): Initial State \n
+        See: Prognostics Model Package
+     x0 : dict
         Initial (starting) state, with keys defined by model.states \n
         e.g., x = {'abc': 332.1, 'def': 221.003} given states = ['abc', 'def']
-     * measurement_eqn (optional, function): Measurement equation (x)->z. Usually used in situations where what's measured don't exactly match the output (e.g., different unit, not ever output measured, etc.). see `examples.measurement_eqn_example`
-     * options (optional, kwargs): configuration options\n
+     measurement_eqn : optional, function
+        Measurement equation (x)->z. Usually used in situations where what's measured don't exactly match the output (e.g., different unit, not ever output measured, etc.). see `examples.measurement_eqn_example`
+     options : optional, kwargs
+        configuration options\n
         Dictionary of any additional configuration values. See state-estimator specific documentation
     """
 
-    default_parameters = {}
+    default_parameters = {
+        't0': 0
+    }
 
     def __init__(self, model, x0, measurement_eqn = None, **kwargs):
         # Check model
@@ -50,14 +54,16 @@ def __init__(self, model, x0, measurement_eqn = None, **kwargs):
         self.parameters = deepcopy(self.default_parameters)
         self.parameters.update(kwargs)
 
+        self.t = self.parameters['t0']  # Initial Time
+
     @abstractmethod
-    def estimate(self, t, u, z):
+    def estimate(self, t, u, z) -> None:
         """
-        Perform one state estimation step (i.e., update the state estimate)
+        Perform one state estimation step (i.e., update the state estimate, filt.x)
 
         Parameters
         ----------
-        t : double
+        t : float
             Current timestamp in seconds (≥ 0.0)
             e.g., t = 3.4
         u : dict
@@ -66,17 +72,19 @@ def estimate(self, t, u, z):
         z : dict
             Measured outputs, with keys defined by model.outputs.
             e.g., z = {'t':12.4, 'v':3.3} given inputs = ['t', 'v']
+
+        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.
         """
-        pass
 
     @property
     @abstractproperty
     def x(self) -> UncertainData:
         """
-        Getter for property 'x', the current estimated state. 
+        The current estimated state. 
 
         Example
         -------
-        state = observer.x
+        state = filt.x
         """
-        pass
diff --git a/src/prog_algs/state_estimators/unscented_kalman_filter.py b/src/prog_algs/state_estimators/unscented_kalman_filter.py
index 30d3a6e7..e36e7656 100644
--- a/src/prog_algs/state_estimators/unscented_kalman_filter.py
+++ b/src/prog_algs/state_estimators/unscented_kalman_filter.py
@@ -4,59 +4,57 @@
 from filterpy import kalman
 from numpy import diag, array
 from ..uncertain_data import MultivariateNormalDist
-from ..exceptions import ProgAlgTypeError
-from copy import deepcopy
 
 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. 
-    
-    Constructor parameters:
-     * model (ProgModel): Model to be used in state estimation \n
-        See: Prognostics Model Package \n
-        A prognostics model to be used in state estimation
-     * x0 (dict): Initial State \n
-        Initial (starting) state, with keys defined by model.states \n
-        e.g., x = {'abc': 332.1, 'def': 221.003} given states = ['abc', 'def']
-     * measurement_eqn (optional, function): Measurement equation (x)->z. Usually used in situations where what's measured don't exactly match the output (e.g., different unit, not ever output measured, etc.). see `examples.measurement_eqn_example`
-     * options (optional, kwargs): configuration options\n
-        Dictionary of any additional configuration values. See default parameters. Additionally, the following configuration parameters are supported: \n
-         * alpha, beta, kappa: UKF Scaling parameters
-         * t0 : Starting time
-         * dt : time step
-         * Q : Process Noise Matrix 
-         * R : Measurement Noise Matrix 
+
+    The supported configuration parameters (keyword arguments) for UKF construction are described below:
+
+    Constructor Configuration Parameters:
+        alpha, beta, kappa: float
+            UKF Scaling parameters
+        t0 : float
+            Starting time (s)
+        dt : float 
+            time step (s)
+        Q : List[List[float]]
+            Process Noise Matrix 
+        R : List[List[float]]
+            Measurement Noise Matrix 
     """
-    t = 0 # Last timestep
     default_parameters = {
-        'alpha': 1,     # UKF scaling param
-        'beta': 0,      # UKF scaling param
-        'kappa': -1,    # UKF scaling param
-        't0': 0,        # Starting time
-        'dt': 1         # Time step
+        'alpha': 1, 
+        'beta': 0, 
+        'kappa': -1,
+        't0': -1,
+        'dt': 1
     } 
 
     def __init__(self, model, x0, measurement_eqn = None, **kwargs):
         super().__init__(model, x0, measurement_eqn, **kwargs)
 
-        self._input = None
-        self.t = self.parameters['t0']
+        self.__input = None
+        self.x0 = x0
 
         if measurement_eqn is None: 
             def measure(x):
-                x = {key: value for (key, value) in zip(model.states, x)}
+                x = {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)
-                return array(list(z.values()))
+                model.parameters['measurement_noise'] = R_err
+                return array(list(z.values())).ravel()
         else:
             def measure(x):
-                x = {key: value for (key, value) in zip(model.states, x)}
+                x = {key: value for (key, value) in zip(x0.keys(), x)}
                 z = measurement_eqn(x)
-                return array(list(z.values()))
+                return array(list(z.values())).ravel()
 
         if 'Q' not in self.parameters:
-            self.parameters['Q'] = diag([1.0e-1 for i in model.states])
+            self.parameters['Q'] = diag([1.0e-1 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
@@ -64,14 +62,19 @@ def measure(x):
 
         def state_transition(x, dt):
             x = {key: value for (key, value) in zip(x0.keys(), x)}
-            x = model.next_state(x, self._input, dt)
-            return array(list(x.values()))
+            Q_err = model.parameters['process_noise'].copy()
+            model.parameters['process_noise'] = dict.fromkeys(Q_err, 0)
+            z = model.output(x)
+            model.parameters['process_noise'] = Q_err
+            x = model.next_state(x, self.__input, dt)
+            return array(list(x.values())).ravel()
 
-        num_states = len(model.states)
+        num_states = len(x0.keys())
         num_measurements = len(model.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)
-        self.filter.x = array(list(x0.values()))
+        self.filter.x = array(list(x0.values())).ravel()
+        self.filter.P = self.parameters['Q'] / 10
         self.filter.Q = self.parameters['Q']
         self.filter.R = self.parameters['R']
 
@@ -91,8 +94,9 @@ def estimate(self, t, u, z):
             Measured outputs, with keys defined by model.outputs.
             e.g., z = {'t':12.4, 'v':3.3} given inputs = ['t', 'v']
         """
+        assert t > self.t, "New time must be greater than previous"
         dt = t - self.t
-        self._input = u
+        self.__input = u
         self.t = t
         self.filter.predict(dt=dt)
         self.filter.update(array(list(z.values())))
@@ -106,4 +110,4 @@ def x(self):
         -------
         state = observer.x
         """
-        return MultivariateNormalDist(self.model.states, self.filter.x, self.filter.Q)
+        return MultivariateNormalDist(self.x0.keys(), self.filter.x, self.filter.P)
diff --git a/src/prog_algs/uncertain_data/__init__.py b/src/prog_algs/uncertain_data/__init__.py
index 574cbd38..42b58be8 100644
--- a/src/prog_algs/uncertain_data/__init__.py
+++ b/src/prog_algs/uncertain_data/__init__.py
@@ -1,204 +1,8 @@
 # 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 numpy.random import choice, multivariate_normal
-from numpy import array, append, delete, cov
+from .uncertain_data import UncertainData
+from .unweighted_samples import UnweightedSamples
+from .scalar_data import ScalarData
+from .multivariate_normal_dist import MultivariateNormalDist
 
-class UncertainData(ABC):
-    """
-    Abstract base class for data with uncertainty. Any new uncertainty type must implement this class
-    """
-    @abstractmethod
-    def sample(self, nSamples = 1):
-        """Generate samples from data
-
-        Args:
-            nSamples (int, optional): Number of samples to generate. Defaults to 1.
-
-        Returns:
-            samples (array): Array of nSamples samples
-
-        Example:
-            samples = data.samples(100)
-        """
-        pass
-
-    @property
-    @abstractproperty
-    def mean(self):
-        """Mean estimate
-
-        Example:
-            mean_value = data.mean
-        """
-        pass
-
-    @property
-    @abstractproperty
-    def cov(self):
-        """Get the covariance matrix
-
-        Returns:
-            [[float]]: covariance matrix
-        """
-
-    # TODO(CT): Consider median
-
-class ScalarData(UncertainData):
-    """
-    Data without uncertainty- single value
-    """
-    def __init__(self, state): 
-        """Initialize Scalar Data
-
-        Args:
-            state (dict): Single state
-        """
-        self.__state = state
-
-    @property
-    def mean(self):
-        return self.__state
-
-    @property
-    def cov(self):
-        return [[0 for i in self.__state] for j in self.__state]
-
-    def sample(self, num_samples = 1):
-        return array([self.__state] * num_samples)
-
-    def __str__(self):
-        return 'ScalarData({})'.format(self.__state)
-
-
-class UnweightedSamples(UncertainData):
-    """
-    Uncertain Data represented by a set of samples
-    """
-    def __init__(self, samples : array = array([])):
-        """Initialize Unweighted Samples
-
-        Args:
-            samples (array, optional): array of samples. Defaults to empty array.
-        """
-        self.__samples = samples
-
-    def sample(self, num_samples = 1):
-        # Completely random resample
-        return choice(self.__samples, num_samples)
-
-    @property
-    def mean(self):
-        mean = {}
-        for key in self.__samples[0].keys():
-            mean[key] = array([x[key] for x in self.__samples]).mean()
-        return mean
-
-    @property
-    def cov(self):
-        if len(self.__samples) == 0:
-            return [[]]
-        unlabeled_samples = array([[x[key] for x in self.__samples] for key in self.__samples[0].keys()])
-        return cov(unlabeled_samples)
-
-    def __str__(self):
-        return 'UnweightedSamples({})'.format(self.__samples)
-
-    # Sample-specific methods
-    def append(self, value):
-        """Append an additional sample to the unweighted samples
-
-        Args:
-            value (dict): Value to be appended
-        """
-        self.__samples = append(self.__samples, value)
-
-    @property
-    def size(self):
-        """Get the number of samples
-
-        Returns:
-            int: Number of samples
-        """
-        return self.__samples.size
-
-    def __getitem__(self, index):
-        """Get a specific item by index
-
-        Args:
-            index (int): Sample index requested
-
-        Returns:
-            dict: item requested
-
-        Example:
-            sample = samples[index]
-        """
-        return self.__samples[index]
-
-    def __setitem__(self, index, value):
-        """Set a specific item by index
-
-        Args:
-            index (int): index to be set
-            value (dict): new value
-
-        Example:
-            samples[index] = new_value
-        """
-        self.__samples[index] = value
-
-    def __delitem__(self, index):
-        """Delete a specific utem
-
-        Args:
-            index (int): Index of item to be deleted
-
-        Example:
-            del samples[index]
-        """
-        self.__samples = delete(self.__samples, index)
-
-    def raw_samples(self):
-        """Get raw samples
-
-        Returns:
-            np.array(dict): all the samples
-        """
-        return self.__samples
-
-class MultivariateNormalDist(UncertainData):
-    """
-    Data represented by a multivariate normal distribution with mean and covariance matrix
-    """
-    def __init__(self, labels, mean: array, covar : array):
-        """Initialize distribution
-
-        Args:
-            labels ([str]): Labels for states, in order of mean values
-            mean ([float]): Mean values for state in the same order as labels
-            covar ([[float]]): Covariance matrix for state
-        """
-        self.__labels = labels
-        self.__mean = mean
-        self.__covar = covar
-
-    def sample(self, num_samples = 1):
-        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 = array([{key: value for (key, value) in zip(self.__labels, x)} for x in samples])
-        return samples
-
-    @property
-    def mean(self):
-        return {key: value for (key, value) in zip(self.__labels, self.__mean)}
-
-    def __str__(self):
-        return 'MultivariateNormalDist(mean: {}, covar: {})'.format(self.__mean, self.__covar)     
-
-    @property
-    def cov(self):
-        return self.__covar
-    
\ No newline at end of file
+__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
new file mode 100644
index 00000000..58a87c39
--- /dev/null
+++ b/src/prog_algs/uncertain_data/multivariate_normal_dist.py
@@ -0,0 +1,47 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from . import UncertainData, UnweightedSamples
+from numpy import array
+from numpy.random import multivariate_normal
+
+
+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):
+        self.__labels = labels
+        self.__mean = mean
+        self.__covar = covar
+
+    def sample(self, num_samples = 1):
+        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)
+
+    def keys(self):
+        return self.__labels
+
+    @property
+    def median(self):
+        # For normal distribution medain = mean
+        return self.mean
+
+    @property
+    def mean(self):
+        return {key: value for (key, value) in zip(self.__labels, self.__mean)}
+
+    def __str__(self):
+        return 'MultivariateNormalDist(mean: {}, covar: {})'.format(self.__mean, self.__covar)     
+
+    @property
+    def cov(self):
+        return self.__covar
diff --git a/src/prog_algs/uncertain_data/scalar_data.py b/src/prog_algs/uncertain_data/scalar_data.py
new file mode 100644
index 00000000..65aac5c7
--- /dev/null
+++ b/src/prog_algs/uncertain_data/scalar_data.py
@@ -0,0 +1,45 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from . import UncertainData, UnweightedSamples
+
+
+class ScalarData(UncertainData):
+    """
+    Data without uncertainty- single value
+
+    Args:
+            state (dict): Single state
+    """
+    def __init__(self, state): 
+        self.__state = state
+
+    def __eq__(self, other):
+        return isinstance(other, ScalarData) and other.mean == self.__state
+
+    @property
+    def median(self):
+        return self.mean
+        
+    @property
+    def mean(self):
+        return self.__state
+
+    @property
+    def cov(self):
+        return [[0]]
+
+    def keys(self):
+        return self.__state.keys()
+        
+    def sample(self, num_samples = 1):
+        return UnweightedSamples([self.__state] * num_samples)
+
+    def __str__(self):
+        return 'ScalarData({})'.format(self.__state)
+
+    def percentage_in_bounds(self, bounds):
+        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
new file mode 100644
index 00000000..d7699537
--- /dev/null
+++ b/src/prog_algs/uncertain_data/uncertain_data.py
@@ -0,0 +1,127 @@
+# 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 ..visualize import plot_scatter, plot_hist
+
+
+class UncertainData(ABC):
+    """
+    Abstract base class for data with uncertainty. Any new uncertainty type must implement this class
+    """
+    @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):
+        """The median of the UncertainData distribution or samples 
+
+        Returns:
+            Dict[str, float]: Mean value. e.g., {'key1': 23.2, ...}
+
+        Example:
+            median_value = data.median
+        """
+
+    @property
+    @abstractproperty
+    def mean(self):
+        """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):
+        """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:
+            array[array[float]]: Covariance matrix
+        """
+
+    @abstractmethod
+    def keys(self):
+        """Get the keys for the property represented
+
+        Returns:
+            [string]: keys
+        """
+
+    def percentage_in_bounds(self, bounds, keys = None):
+        """Calculate percentage of dist is within specified bounds
+
+        Args:
+            bounds ([float, float]): Lower and upper bounds
+            keys (list of strings, optional): Keys to analyze. Defaults to all keys.
+
+        Returns:
+            float: Percentage within bounds (where 0.5 = 50%)
+        """
+        return self.sample(1000).percentage_in_bounds(bounds)
+
+    def metrics(self, **kwargs):
+        """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
+        """
+        from ..metrics import calc_metrics
+        return calc_metrics(self, **kwargs)
+
+    def plot_scatter(self, fig = None, keys = None, num_samples = 100, **kwargs):
+        """
+        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 of strings, 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:
+            states = UnweightedSamples([1, 2, 3, 4, 5])\n
+            states.plot_scatter() # With 100 samples\n
+            states.plot_scatter(num_samples=5) # Specifying the number of samples to plot\n
+            states.plot_scatter(keys=['state1', 'state2']) # 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.
+        """
+        if keys is None:
+            keys = self.keys()
+        samples = self.sample(num_samples)
+        return plot_hist(samples, fig=fig, keys=keys, **kwargs)
diff --git a/src/prog_algs/uncertain_data/unweighted_samples.py b/src/prog_algs/uncertain_data/unweighted_samples.py
new file mode 100644
index 00000000..1546e0ea
--- /dev/null
+++ b/src/prog_algs/uncertain_data/unweighted_samples.py
@@ -0,0 +1,109 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+from . import UncertainData
+from collections import UserList
+from collections.abc import Iterable
+from numpy import array, cov, random
+import warnings
+
+
+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 or dict, 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, ...}, ...]
+    """
+    def __init__(self, samples = []):
+        if isinstance(samples, dict):
+            # 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 sample(self, num_samples = 1):
+        # Completely random resample
+        return UnweightedSamples(random.choice(self.data, num_samples, replace = True))
+
+    def keys(self):
+        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):
+        """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]
+
+    @property
+    def median(self):
+        # Calculate Geometric median of all samples
+        min_value = float('inf')
+        for i, datem in enumerate(self.data):
+            p1 = array(list(datem.values()))
+            total_dist = sum(
+                sum((p1 - array(list(d.values())))**2)  # Distance between 2 points
+                for d in self.data)  # For each point
+            if total_dist < min_value:
+                min_index = i
+                min_value = total_dist
+        return self[min_index]
+
+    @property
+    def mean(self):
+        mean = {}
+        for key in self.keys():
+            mean[key] = array([x[key] for x in self.data if x is not None]).mean()
+        return mean
+
+    @property
+    def cov(self):
+        if len(self.data) == 0:
+            return [[]]
+        unlabeled_samples = array([[x[key] for x in self.data if x is not None] for key in self.keys()])
+        return cov(unlabeled_samples)
+
+    def __str__(self):
+        return 'UnweightedSamples({})'.format(self.data)
+
+    @property
+    def size(self):
+        """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 = None):
+        if not keys:
+            keys = self.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 < bounds[key][1] and x > bounds[key][0] for x in self.key(key)])/n_elements for key in keys}
+
+    def raw_samples(self):
+        warnings.warn("raw_samples is deprecated and will be removed in the future")
+        return self.data
diff --git a/src/prog_algs/visualize.py b/src/prog_algs/visualize.py
deleted file mode 100644
index 826cc501..00000000
--- a/src/prog_algs/visualize.py
+++ /dev/null
@@ -1,1 +0,0 @@
-# Copyright © 2020 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
diff --git a/src/prog_algs/visualize/__init__.py b/src/prog_algs/visualize/__init__.py
new file mode 100644
index 00000000..d37a5535
--- /dev/null
+++ b/src/prog_algs/visualize/__init__.py
@@ -0,0 +1,5 @@
+# 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
new file mode 100644
index 00000000..d7c21ed6
--- /dev/null
+++ b/src/prog_algs/visualize/plot_hist.py
@@ -0,0 +1,55 @@
+# 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
+
+def plot_hist(samples, fig = None, keys = None, **kwargs):
+    """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], 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
new file mode 100644
index 00000000..02af545b
--- /dev/null
+++ b/src/prog_algs/visualize/plot_scatter.py
@@ -0,0 +1,92 @@
+# 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
+import matplotlib.pyplot as plt
+from math import sqrt
+
+def plot_scatter(samples, fig = None, keys = None, legend = 'auto', **kwargs):
+    """
+    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
index 5f5096aa..1514ee78 100644
--- a/state_estimator_template.py
+++ b/state_estimator_template.py
@@ -1,21 +1,23 @@
 # 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
     """
-    t = 0 # Last timestep
-
     # REPLACE THE FOLLOWING LIST WITH CONFIGURED PARAMETERS
     default_parameters = { # Default Parameters, used as config
-        'Example Parameter': 0.0
+        'Example Parameter': 0.0,
+        't0' : 0.0  # Initial timestamp
     } 
 
     def __init__(self, model, x0, measurement_eqn=None, **kwargs):
         """
-        Constructor
+        Constructor (optional)
         """
         super().__init__(model, x0, measurement_eqn, **kwargs)
         # ADD PARAMETER CHECKS HERE
@@ -23,7 +25,7 @@ def __init__(self, model, x0, measurement_eqn=None, **kwargs):
 
         # ADD ANY STATE ESTIMATOR INITIALIZATION LOGIC
 
-    def estimate(self, t, u, z):
+    def estimate(self, t, u, z) -> None:
         """
         Perform one state estimation step (i.e., update the state estimate)
 
@@ -33,17 +35,18 @@ def estimate(self, t, u, z):
             Current timestamp in seconds (≥ 0.0)
             e.g., t = 3.4
         u : dict
-            Measured inputs, with keys defined by model.inputs.
+            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
-        pass
+
+        # Note, returns none. State is accessed using the property state_estimator.x
 
     @property
-    def x(self):
+    def x(self) -> UncertainData:
         """
         Getter for property 'x', the current estimated state. 
 
@@ -52,6 +55,8 @@ def x(self):
         state = observer.x
         """
         # REPLACE THE FOLLOWING WITH THE LOGIC TO CONSTRUCT/RETURN THE STATE
-        x = {key: 0.0 for key in self.model.states}
+
+        # Here we're using ScalarData, but the state could be represented by any other type of UncertainData (e.g., MultivariateNormalDist)
+        x = ScalarData({key: 0.0 for key in self.model.states})
 
         return x
diff --git a/tutorial.ipynb b/tutorial.ipynb
index 4273a128..fc083d00 100644
--- a/tutorial.ipynb
+++ b/tutorial.ipynb
@@ -1,39 +1,15 @@
 {
- "metadata": {
-  "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.6.12-final"
-  },
-  "orig_nbformat": 2,
-  "kernelspec": {
-   "name": "python3",
-   "display_name": "Python 3.6.12 64-bit",
-   "metadata": {
-    "interpreter": {
-     "hash": "d7f94b8b1e41b02170d45ac71ce2d6b011e7cd56207b4c480f5292088bcfab93"
-    }
-   }
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2,
  "cells": [
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "# Welcome to the Prognostics Algorithms Package Tutorial"
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
+   "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",
@@ -43,49 +19,51 @@
     "* 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 occurance of events. \n",
     "\n",
-    "The `prog_algs` package has the following structure\n",
-    "* `prog_algs/state_estimators/` - Tools for performing state estimation\n",
-    "* `prog_algs/predictors/` - Tools for performing prediction\n",
-    "* `prog_algs/metrics/` - Tools for analyzing the performance of prognostics algorithms\n",
-    "* `prog_algs/samplers/` - Tools for sampling from a distribution\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 (<https://nasa.github.io/prog_algs>), and this tutorial (`tutorial.ipynb`).\n",
+    "\n",
+    "Before you start, install `prog_algs` using pip:\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 (`docs/`), and this tutorial (`tutorial.ipynb`).\n",
+    "      `pip install prog_algs`\n",
     "\n",
-    "Before you start, make sure that all the required packages are installed (defined in `requirements.txt`)\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 lets get started with some examples"
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
-   "source": [
-    "## Prediction Example \n",
-    "First thing to do is to import the prog_algs and the model you intend to use"
-   ],
    "cell_type": "markdown",
-   "metadata": {}
+   "metadata": {},
+   "source": [
+    "## Prognostics Example \n",
+    "First, import the prog_algs and the model you intend to use"
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "from prog_models.models.battery_circuit import BatteryCircuit\n",
+    "from prog_models.models import BatteryCircuit\n",
     "from prog_algs import *"
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "Next, prepare the model like you did for simulation"
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -107,134 +85,178 @@
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "Now that we have our model ready, we can construct our state estimator:"
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "filt = state_estimators.unscented_kalman_filter.UnscentedKalmanFilter(batt, batt.parameters['x0'])"
+    "filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])"
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "The filter estimate function can then be run when there is updated data. Each iteration it will produce a new estimate of the system state (with uncertainty). For example:"
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "output_type": "stream",
-     "name": "stdout",
-     "text": [
-      "Prior State: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0.0, 'qcs': 0.0}\n\tSOC:  1.0\nPosterior State: {'tb': 30.992955874995502, 'qb': 7856.162476433427, 'qcp': 0.2777038820843582, 'qcs': 0.26019743822781277}\n\tSOC:  0.9999790505893568\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
+    "import matplotlib.pyplot as plt  # For plotting\n",
+    "\n",
     "print(\"Prior State:\", filt.x.mean)\n",
     "print('\\tSOC: ', batt.event_state(filt.x.mean)['EOD'])\n",
+    "fig = filt.x.plot_scatter(label='prior')\n",
     "example_measurements = {'t': 32.2, 'v': 3.915}\n",
     "t = 0.1\n",
     "filt.estimate(t, future_loading(t), example_measurements)\n",
     "print(\"Posterior State:\", filt.x.mean)\n",
-    "print('\\tSOC: ', batt.event_state(filt.x.mean)['EOD'])"
+    "print('\\tSOC: ', batt.event_state(filt.x.mean)['EOD'])\n",
+    "filt.x.plot_scatter(fig= fig, label='posterior')\n",
+    "\n",
+    "fig.show()"
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
+    "Typically in a prognostics application the state estimation step is run whenever there is new data.\n",
+    "\n",
     "That's the state estimation step- now lets prepare for prediction. "
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
     "# Create predictor \n",
-    "mc = predictors.monte_carlo.MonteCarlo(batt)\n",
-    "\n",
-    "# Generate Samples\n",
-    "samples = filt.x.sample(20)"
+    "mc = predictors.MonteCarlo(batt)"
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "Now lets 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": {
+    "tags": [
+     "outputPrepend"
+    ]
+   },
+   "outputs": [],
+   "source": [
+    "(times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, dt=0.05, n_samples=20)"
+   ]
+  },
+  {
    "cell_type": "markdown",
-   "metadata": {}
+   "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 `states[m][n]` corresponds to the state for sample `m` at time `times[m][n]`. Alternately, use the method `snapshot` to get a  single point in time. e.g., \n",
+    "\n",
+    "    `state = states.snapshot(3)`\n",
+    "\n",
+    "In this case the state snapshot corresponds to time `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 analyse the ToE"
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
-    "(times, inputs, states, outputs, event_states, eol) = mc.predict(samples, future_loading, dt=0.025)\n"
+    "print(\"\\nEOD Predictions (s):\")\n",
+    "print('\\tPortion between 3005.2 and 3005.6: ', toe.percentage_in_bounds([3005.2, 3005.6]))\n",
+    "print('\\tAssuming ground truth 3005.25: ', toe.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(toe, 3005.25))"
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
-    "The prediction returns the data corresponding to each sample and each save point. The format is `[sample #][time]` so that `states[m][n]` corresponds to the state for sample `m` at time `times[m][n]`\n",
+    "These analysis methods applied to ToE can also be applied to anything of type UncertainData (e.g., state snapshot). \n",
     "\n",
-    "Next, let's use the metrics package to analyse the results"
-   ],
+    "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 = states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(times[0])))\n",
+    "for i in range(1, 4):\n",
+    "    index = int(len(times)/4*i)\n",
+    "    states.snapshot(index).plot_scatter(fig=fig, label = \"t={:.0f}\".format(times[index]))\n",
+    "states.snapshot(-1).plot_scatter(fig = fig, label = \"t={:.0f}\".format(int(times[-1])))\n",
+    "fig.show()"
+   ]
+  },
+  {
    "cell_type": "markdown",
-   "metadata": {}
+   "metadata": {},
+   "source": [
+    "Or time of event"
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": null,
    "metadata": {},
-   "outputs": [
-    {
-     "output_type": "stream",
-     "name": "stdout",
-     "text": [
-      "\n",
-      "EOD Predictions (s):\n",
-      "\tPercentage between 3005.2 and 3005.6:  95.0 %\n",
-      "\tAssuming ground truth 3005.25:  {'min': 3005.2750000065503, 'percentiles': {'0.01': None, '0.1': None, '1': None, '10': 3005.3000000065504, '25': 3005.3250000065505, '50': 3005.3750000065506, '75': 3005.5500000065513}, 'median': 3005.3750000065506, 'mean': 3005.4112500065507, 'std': 0.10967993207550562, 'max': 3005.6250000065515, 'median absolute deviation': 0.09125000000033197, 'mean absolute deviation': 0.0960000000003447, 'number of samples': 20, 'mean absolute error': 0.16125000655076746, 'mean absolute percentage error': 5.365610400158638e-05, 'relative accuracy': 0.9999463438959985, 'ground truth percentile': 0.0}\n",
-      "\tP(Success) if mission ends at 3005.25:  1.0\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
-    "print(\"\\nEOD Predictions (s):\")\n",
-    "from prog_algs.metrics import samples as metrics \n",
-    "print('\\tPercentage between 3005.2 and 3005.6: ', metrics.percentage_in_bounds(eol, [3005.2, 3005.6])*100.0, '%')\n",
-    "print('\\tAssuming ground truth 3005.25: ', metrics.eol_metrics(eol, 3005.25))\n",
-    "print('\\tP(Success) if mission ends at 3005.25: ', metrics.prob_success(eol, 3005.25))"
+    "fig = toe.plot_hist()\n",
+    "fig.show()"
    ]
   },
   {
+   "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": [
     "## Extending - Adding a new state estimator\n",
     "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": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -242,15 +264,15 @@
    ]
   },
   {
+   "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": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -258,15 +280,15 @@
    ]
   },
   {
+   "cell_type": "markdown",
+   "metadata": {},
    "source": [
     "Now we construct the class, implementing the functions of the state estimator template (`state_estimator_template.py`)"
-   ],
-   "cell_type": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": null,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -299,27 +321,19 @@
    ]
   },
   {
+   "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": "markdown",
-   "metadata": {}
+   ]
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": null,
    "metadata": {
     "tags": []
    },
-   "outputs": [
-    {
-     "output_type": "stream",
-     "name": "stdout",
-     "text": [
-      "{'tb': 0, 'qb': 0, 'qcp': 0, 'qcs': 0}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 1.0411987997959682, 'qb': 1.1987180490088467, 'qcp': 1.1444771729534409, 'qcs': 4.323389888820035}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 1.0411987997959682, 'qb': 1.1987180490088467, 'qcp': 1.1444771729534409, 'qcs': 4.323389888820035}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 5.594233428235789, 'qb': 1.1781629778079783, 'qcp': 3.1696967528521416, 'qcs': 5.511062816581412}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 7.07778733969836, 'qb': -3.3737234395580016, 'qcp': 3.5297993481567893, 'qcs': 6.073162464421083}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 9.446372100181849, 'qb': -5.158688218976241, 'qcp': 5.651216643148805, 'qcs': 4.353977508352162}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 9.446372100181849, 'qb': -5.158688218976241, 'qcp': 5.651216643148805, 'qcs': 4.353977508352162}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 9.446372100181849, 'qb': -5.158688218976241, 'qcp': 5.651216643148805, 'qcs': 4.353977508352162}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 9.783138988829059, 'qb': -2.7435648829786983, 'qcp': 3.744497695396511, 'qcs': 3.7955776686740785}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 11.207302750257691, 'qb': -4.921702203729505, 'qcp': -0.6148101252154419, 'qcs': 4.016087721985571}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 11.708619807275147, 'qb': -1.93345778486762, 'qcp': -3.1023409556650785, 'qcs': 3.864824851718849}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 13.513663993758913, 'qb': -5.275390906390555, 'qcp': -2.439838155457245, 'qcs': 7.845896867887361}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 17.05277355944687, 'qb': -3.6874726308706403, 'qcp': 1.2600115345529015, 'qcs': 5.406299967693404}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 17.05277355944687, 'qb': -3.6874726308706403, 'qcp': 1.2600115345529015, 'qcs': 5.406299967693404}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 17.05277355944687, 'qb': -3.6874726308706403, 'qcp': 1.2600115345529015, 'qcs': 5.406299967693404}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.060989401879887, 'qb': -7.1114030104904735, 'qcp': 3.2673867470083193, 'qcs': 1.4558284172625093}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.281675631491986, 'qb': -4.258614751542815, 'qcp': 7.83438235380312, 'qcs': 3.451237577386477}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.281675631491986, 'qb': -4.258614751542815, 'qcp': 7.83438235380312, 'qcs': 3.451237577386477}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n{'tb': 19.281675631491986, 'qb': -4.258614751542815, 'qcp': 7.83438235380312, 'qcs': 3.451237577386477}\n\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "x0 = {key: 0 for key in batt.states}\n",
     "se = BlindlyStumbleEstimator(batt, x0)\n",
@@ -333,19 +347,59 @@
    ]
   },
   {
-   "source": [
-    "## Conclusion\n",
-    "Thank you for trying out this tutorial. See the examples in the `examples/` folder for more details on how to use the package. Any questions can be directed to Chris Teubert (christopher.a.teubert@nasa.gov)"
-   ],
    "cell_type": "markdown",
-   "metadata": {}
+   "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": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
-   "source": []
+   "source": [
+    "## Conclusion\n",
+    "This is just the basics, there's much more to learn. Please see the documentation at <https://nasa.github.io/prog_algs> 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 Prognsotics \n",
+    "* `examples.benchmarking_example` : An example benchmarking the performance of prognsotis algorihtms\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 (<https://github.com/nasa/prog_algs/issues>) or email Chris Teubert (christopher.a.teubert@nasa.gov) with any questions or issues."
+   ]
   }
- ]
-}
\ No newline at end of file
+ ],
+ "metadata": {
+  "interpreter": {
+   "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2"
+  },
+  "kernelspec": {
+   "display_name": "Python 3.8.12 64-bit",
+   "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.12"
+  },
+  "metadata": {
+   "interpreter": {
+    "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2"
+   }
+  },
+  "orig_nbformat": 2
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}


Test patch:
diff --git a/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py b/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py
new file mode 100644
index 00000000..abd7c426
--- /dev/null
+++ b/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py
@@ -0,0 +1,63 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+
+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
+    (times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, dt=0.1, save_freq= 100)
+
+    # Print Results
+    for i, time in enumerate(times):
+        print('\nt = {}'.format(time))
+        print('\tu = {}'.format(inputs.snapshot(i).mean))
+        print('\tx = {}'.format(states.snapshot(i).mean))
+        print('\tz = {}'.format(outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(event_states.snapshot(i).mean))
+
+    print('\nToE:', toe.mean)
+
+    # You can also access the final state (of type UncertainData), like so:
+    final_state = toe.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/tests/__init__.py b/tests/__init__.py
index e46abfaf..0585f535 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,3 +1,3 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
-__all__ = ['test_state_estimators', 'test_predictors', 'test_examples', 'test_integration', 'test_uncertain_data']
+__all__ = ['test_examples', 'test_integration', 'test_metrics', 'test_predictors', 'test_state_estimators', 'test_uncertain_data', 'test_visualize']
diff --git a/tests/__main__.py b/tests/__main__.py
index 791ec4e9..e95d2ba0 100644
--- a/tests/__main__.py
+++ b/tests/__main__.py
@@ -6,17 +6,20 @@
 from .test_uncertain_data import TestUncertainData
 from .test_examples import TestExamples
 from .test_misc import TestMisc
+from .test_metrics import TestMetrics
+from .test_visualize import TestVisualize
 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()
-
-    basic_example.run_example()
+    with patch('matplotlib.pyplot') as p:
+        basic_example.run_example()
 
     # Reset stdout 
     sys.stdout = _stdout
@@ -35,11 +38,17 @@ def run_basic_ex():
     print('\n\nUncertain Data Tests')
     unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestUncertainData))
 
+    print('\n\nVisualize Tests')
+    unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestVisualize))
+
     print('\n\nExamples Tests')
     unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestExamples))
 
     print('\n\nIntegration Tests')
     unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestIntegration))
 
+    print('\n\nMetrics Tests')
+    unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestMetrics))
+
     print('\n\nMisc Tests')
-    unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestMisc))
\ No newline at end of file
+    unittest.TextTestRunner().run(l.loadTestsFromTestCase(TestMisc))
diff --git a/tests/test_examples.py b/tests/test_examples.py
index ac7f0714..c2816189 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -4,6 +4,8 @@
 import sys
 from io import StringIO 
 from examples import *
+from unittest.mock import patch
+
 
 class TestExamples(unittest.TestCase):
     def test_main_ex(self):
@@ -12,7 +14,8 @@ def test_main_ex(self):
         sys.stdout = StringIO()
 
         from examples import basic_example
-        basic_example.run_example()
+        with patch('matplotlib.pyplot') as p:
+            basic_example.run_example()
 
         # Reset stdout 
         sys.stdout = _stdout
@@ -23,7 +26,20 @@ def test_benchmarking_ex(self):
         sys.stdout = StringIO()
 
         from examples import benchmarking_example
-        benchmarking_example.run_example()
+        with patch('matplotlib.pyplot') as p:
+            benchmarking_example.run_example()
+
+        # Reset stdout 
+        sys.stdout = _stdout
+
+    def test_eol_ex(self):
+        # set stdout (so it wont print)
+        _stdout = sys.stdout
+        sys.stdout = StringIO()
+
+        from examples import eol_event
+        with patch('matplotlib.pyplot') as p:
+            eol_event.run_example()
 
         # Reset stdout 
         sys.stdout = _stdout
@@ -34,7 +50,30 @@ def test_new_state_est_ex(self):
         sys.stdout = StringIO()
 
         from examples import new_state_estimator_example
-        new_state_estimator_example.run_example()
+        with patch('matplotlib.pyplot') as p:
+            new_state_estimator_example.run_example()
+
+        # Reset stdout 
+        sys.stdout = _stdout
+
+    def test_thrown_obj_ex(self):
+        # set stdout (so it wont print)
+        _stdout = sys.stdout
+        sys.stdout = StringIO()
+
+        from examples import thrown_object_example
+        thrown_object_example.run_example()
+
+        # Reset stdout 
+        sys.stdout = _stdout
+
+    def test_thrown_obj_ex(self):
+        # set stdout (so it wont print)
+        _stdout = sys.stdout
+        sys.stdout = StringIO()
+
+        from examples import predict_specific_event
+        thrown_object_example.run_example()
 
         # Reset stdout 
         sys.stdout = _stdout
@@ -45,7 +84,37 @@ def test_measurement_ex(self):
         sys.stdout = StringIO()
 
         from examples import measurement_eqn_example
-        measurement_eqn_example.run_example()
+        with patch('matplotlib.pyplot') as p:
+            measurement_eqn_example.run_example()
+
+        # Reset stdout 
+        sys.stdout = _stdout
+
+    def test_utpredictor_ex(self):
+        # set stdout (so it wont print)
+        _stdout = sys.stdout
+        sys.stdout = StringIO()
+
+        from examples import utpredictor
+        utpredictor.run_example()
 
         # Reset stdout 
         sys.stdout = _stdout
+
+# This allows the module to be executed directly
+def main():
+    # This ensures that the directory containing ProgModelTemplate 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 Examples")
+    result = runner.run(l.loadTestsFromTestCase(TestExamples)).wasSuccessful()
+
+    if not result:
+        raise Exception("Failed test")
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test_integration.py b/tests/test_integration.py
index d45dac53..9e5cff66 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -2,5 +2,24 @@
 
 import unittest
 
+
 class TestIntegration(unittest.TestCase):
-    pass
\ No newline at end of file
+    pass
+
+# This allows the module to be executed directly
+def main():
+    # This ensures that the directory containing ProgModelTemplate 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 Integration")
+    result = runner.run(l.loadTestsFromTestCase(TestIntegration)).wasSuccessful()
+
+    if not result:
+        raise Exception("Failed test")
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test_metrics.py b/tests/test_metrics.py
new file mode 100644
index 00000000..8e001ca5
--- /dev/null
+++ b/tests/test_metrics.py
@@ -0,0 +1,373 @@
+# 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.metrics import prob_success
+from prog_algs.metrics import calc_metrics as toe_metrics
+from prog_algs.uncertain_data import 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)
+
+# 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_misc.py b/tests/test_misc.py
index 7b13cb73..8c94dbb7 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -3,5 +3,24 @@
 import unittest
 from . import *
 
+
 class TestMisc(unittest.TestCase):
     pass
+
+# This allows the module to be executed directly
+def main():
+    # This ensures that the directory containing ProgModelTemplate 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 Misc")
+    result = runner.run(l.loadTestsFromTestCase(TestMisc)).wasSuccessful()
+
+    if not result:
+        raise Exception("Failed test")
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test_predictors.py b/tests/test_predictors.py
index 3bb149e6..0fe45c29 100644
--- a/tests/test_predictors.py
+++ b/tests/test_predictors.py
@@ -3,6 +3,7 @@
 
 from prog_models import PrognosticsModel
 
+
 class MockProgModel(PrognosticsModel):
     states = ['a', 'b', 'c', 't']
     inputs = ['i1', 'i2']
@@ -40,6 +41,79 @@ def test_pred_template(self):
         m = MockProgModel()
         pred = TemplatePredictor(m)
 
+    def test_UKP_ThrownObject(self):
+        from prog_algs.predictors import UnscentedTransformPredictor
+        from prog_algs.uncertain_data import MultivariateNormalDist
+        from prog_models.models.thrown_object import ThrownObject
+        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 {}
+
+        (times, inputs, states, outputs, event_states, toe) = pred.predict(samples, future_loading, dt=0.01, save_freq=1)
+        self.assertAlmostEqual(toe.mean['impact'], 8.21, 0)
+        self.assertAlmostEqual(toe.mean['falling'], 4.15, 0)
+        self.assertAlmostEqual(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_UKP_ThrownObject_One_Event(self):
+        # Test thrown object, similar to test_UKP_ThrownObject, but with only the 'falling' event
+        from prog_algs.predictors import UnscentedTransformPredictor
+        from prog_algs.uncertain_data import MultivariateNormalDist
+        from prog_models.models.thrown_object import ThrownObject
+        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 {}
+
+        (times, inputs, states, outputs, event_states, toe) = pred.predict(samples, future_loading, dt=0.01, events=['falling'], save_freq=1)
+        self.assertAlmostEqual(toe.mean['falling'], 4.15, 0)
+        self.assertTrue('impact' not in toe.mean)
+        self.assertAlmostEqual(times[-1], 4, 1)  # Saving every second, last time should be around the nearest 1s before falling event
+
+    def test_UKP_Battery(self):
+        from prog_algs.predictors import UnscentedTransformPredictor
+        from prog_algs.uncertain_data import MultivariateNormalDist
+        from prog_models.models import BatteryCircuit
+        from prog_algs.state_estimators import UnscentedKalmanFilter
+
+        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
+        (times, inputs, states, outputs, event_states, toe) = ut.predict(filt.x, future_loading, dt=0.1)
+        self.assertAlmostEqual(toe.mean['EOD'], 3004, -2)
+
+        # Test Metrics
+        from prog_algs.metrics import samples
+        s = toe.sample(100).key('EOD')
+        samples.eol_metrics(s)  # Kept for backwards compatibility
+
     def test_MC(self):
         from prog_algs.predictors import MonteCarlo
         m = MockProgModel()
@@ -59,5 +133,112 @@ def future_loading(t, x={}):
             else:
                 return {'i1': -4, 'i2': 2.5}
 
-        
-        
+    def test_prediction_mvnormaldist(self):
+        from prog_algs.predictors import Prediction as MultivariateNormalDistPrediction
+        from prog_algs.uncertain_data import MultivariateNormalDist
+        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 = MultivariateNormalDistPrediction(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):
+        from prog_algs.predictors.prediction import UnweightedSamplesPrediction
+        from prog_algs.uncertain_data import UnweightedSamples
+        times = list(range(10))
+        states = [list(range(10)), list(range(1, 11)), list(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([0, 1, -1]))
+        self.assertEqual(p.snapshot(-1), UnweightedSamples([9, 10, 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):
+        from prog_algs.predictors import ToEPredictionProfile
+        from prog_algs.uncertain_data import ScalarData
+        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
+
+# This allows the module to be executed directly    
+def run_tests():
+    l = unittest.TestLoader()
+    runner = unittest.TextTestRunner()
+    print("\n\nTesting Predictor")
+    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
index 69bbffb4..6e1b4e03 100644
--- a/tests/test_state_estimators.py
+++ b/tests/test_state_estimators.py
@@ -1,8 +1,10 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 import unittest
+from prog_models import PrognosticsModel
 from prog_algs.exceptions import ProgAlgTypeError
 
-class MockProgModel():
+
+class MockProgModel(PrognosticsModel):
     states = ['a', 'b', 'c', 't']
     inputs = ['i1', 'i2']
     outputs = ['o1']
@@ -228,3 +230,16 @@ def measurement_eqn(x):
     def test_PF_incorrect_input(self):
         from prog_algs.state_estimators import ParticleFilter
         self.__incorrect_input_tests(ParticleFilter)
+
+# This allows the module to be executed directly    
+def run_tests():
+    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
index 3d42bcc2..72d2d3d8 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -1,1 +1,20 @@
-# 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
+# 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_uncertain_data.py b/tests/test_uncertain_data.py
index 90a25298..4d44c663 100644
--- a/tests/test_uncertain_data.py
+++ b/tests/test_uncertain_data.py
@@ -4,6 +4,7 @@
 from prog_algs.uncertain_data import UnweightedSamples, MultivariateNormalDist, ScalarData
 from numpy import array
 
+
 class TestUncertainData(unittest.TestCase):
     def test_unweightedsamples(self):
         empty_samples = UnweightedSamples()
@@ -21,14 +22,15 @@ def test_unweightedsamples(self):
         self.assertDictEqual(samples[0], {'a': 1, 'b': 2})
         self.assertEqual(samples.size, 1)
 
-        s = UnweightedSamples(array([{'a': 1, 'b':2}, {'a': 3, 'b':-2}]))
+        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)
-        self.assertEqual(s.raw_samples().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):
@@ -37,6 +39,17 @@ def test_unweightedsamples(self):
         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()
@@ -46,11 +59,32 @@ def test_multivariatenormaldist(self):
     
         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):
-        d = ScalarData(12)
-        self.assertEqual(d.mean, 12)
-        self.assertListEqual(list(d.sample(10)), [12]*10)
\ No newline at end of file
+        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})
+
+
+# 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
new file mode 100644
index 00000000..9f756452
--- /dev/null
+++ b/tests/test_visualize.py
@@ -0,0 +1,53 @@
+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