Repository: nasa/prog_algs (language: python)

Issue:
Release Review Fixes



Discussion / hints:

Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..8364cc4f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,34 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**Relevant Requirements**
+Indicate any enhancement issues (i.e., requirements) that are impacted by this issue.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Python Version [e.g. 3.7]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/documentation-update.md b/.github/ISSUE_TEMPLATE/documentation-update.md
new file mode 100644
index 00000000..6eb7c869
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation-update.md
@@ -0,0 +1,14 @@
+---
+name: Documentation update
+about: Update to the documentation
+title: ''
+labels: documentation
+assignees: ''
+
+---
+
+** Location where change is recommended **
+
+** Recommended change **
+
+** Reason **
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..b786a387
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,25 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Requirement Text**
+What must this feature do, specifically - this is the thing to test to. E.g., Ability to simulate model until a specified event has been met. 
+
+**Background Information**
+Optional
+
+**Suggested Solution**
+Optional, Solution that is proposed. Requirement can be met with other solutions 
+
+**DoD**
+What need to be completed for this feature to be complete. E.g., 
+ - [ ] Implement feature
+ - [ ] Add to example 
+ - [ ] Add to tutorial
+ - [ ] Add tests 
+ - [ ] Add to change notes for next release
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index ffb1d03e..f26b70e8 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -10,12 +10,11 @@ on:
       - prog_algs
 
 jobs:
-  analysis:
+  test-prog_models-dev:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.8']
-
+        python-version: ['3.7', '3.8', '3.9', '3.10']
     steps:
     - uses: actions/checkout@v2
     - name: Set up Python ${{ matrix.python-version }}
@@ -25,62 +24,47 @@ jobs:
     - name: Install prog_models
       run: |
         python -m pip install git+https://github.com/nasa/prog_models.git@dev
-    - name: Lint with flake8
+    - name: Install
       run: |
-        python -m pip install flake8
-        # stop the build if there are Python syntax errors or undefined names
-        flake8 src/prog_algs --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        flake8 src/prog_algs --count --benchmark --exit-zero --show-source --max-complexity=10 --max-line-length=127 --statistics --tee --output-file=lint_results_${{ matrix.python-version }}.txt
-    - name: Upload Lint Results
-      uses: actions/upload-artifact@v2
-      with:
-        name: lint_results_${{matrix.python-version}}
-        path: lint_results_${{matrix.python-version}}.txt
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
-      with:
-        languages: 'python'
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
-  test-prog_models-dev:
+        python -m pip install --upgrade pip
+        python -m pip install -e .
+        python -m pip install notebook
+        python -m pip install testbook
+    - name: Run tests
+      run: python -m tests
+  test-prog_models-released:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        python-version: ['3.7', '3.8', '3.9', '3.10']
     steps:
     - uses: actions/checkout@v2
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
-    - name: Install prog_models
-      run: |
-        python -m pip install git+https://github.com/nasa/prog_models.git@dev
     - name: Install
       run: |
         python -m pip install --upgrade pip
         python -m pip install -e .
+        python -m pip install notebook
+        python -m pip install testbook
     - name: Run tests
       run: python -m tests
-  test-prog_models-released:
+  copyright:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
+        python-version: ['3.9']
     steps:
     - uses: actions/checkout@v2
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
-    - name: Install
+    - name: Run copyright check
       run: |
-        python -m pip install --upgrade pip
-        python -m pip install -e .
-    - name: Run tests
-      run: python -m tests
-
+        python scripts/test_copyright.py
   coverage:
     runs-on: ubuntu-latest
     strategy:
@@ -92,11 +76,16 @@ jobs:
       uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
+    - name: Install prog_models from dev
+      run: |
+        python -m pip install git+https://github.com/nasa/prog_models.git@dev
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
         python -m pip install -e .
         pip install coverage
+        pip install notebook
+        pip install testbook
     - name: Run coverage
       run: |
         coverage run -m tests
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..381fd315
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,41 @@
+name: Release
+
+on: 
+  push:
+    branches:
+      - 'release/**'
+      - 'master'
+
+jobs:
+  analysis:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ['3.9']
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install prog_models
+      run: |
+        python -m pip install git+https://github.com/nasa/prog_models.git@dev
+    - name: Lint with flake8
+      run: |
+        python -m pip install flake8
+        # stop the build if there are Python syntax errors or undefined names
+        flake8 src/prog_algs --count --select=E9,F63,F7,F82 --show-source --statistics
+        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+        flake8 src/prog_algs --count --benchmark --exit-zero --show-source --max-complexity=10 --max-line-length=127 --statistics --tee --output-file=lint_results_${{ matrix.python-version }}.txt
+    - name: Upload Lint Results
+      uses: actions/upload-artifact@v2
+      with:
+        name: lint_results_${{matrix.python-version}}
+        path: lint_results_${{matrix.python-version}}.txt
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: 'python'
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/.gitignore b/.gitignore
index 9bba824e..3c50e3f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -99,3 +99,6 @@ cython_debug/
 # VS Code
 .vscode/
 .pypirc
+.DS_Store
+data_test.pkl
+predictor_test.pkl
diff --git a/README.md b/README.md
index 7a020491..9c7a14be 100644
--- a/README.md
+++ b/README.md
@@ -35,22 +35,21 @@ Use the following to cite this repository:
 @misc{2022_nasa_prog_algs,
     author    = {Christopher Teubert and Matteo Corbetta and Chetan Kulkarni},
     title     = {Prognostics Algorithm Python Package},
-    month     = Dec,
-    year      = 2021,
-    version   = {1.2.3},
+    month     = May,
+    year      = 2022,
+    version   = {1.3.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, v1.2.3, Dec 2021. URL https://github.com/nasa/prog_algs.
+C. Teubert, M. Corbetta, C. Kulkarni, Prognostics Algorithm Python Package, v1.3.0, May 2022. 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
 
 ## Notices
-
 Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
 ## Disclaimers
diff --git a/docs/.buildinfo b/docs/.buildinfo
index 0596ccb0..46b3f0d7 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: d169039d98198512da4bb212eadd1180
+config: 84a069a180fbdb8b47010e7f2620a345
 tags: 645f666f9bcd5a90fca523b33c5a78b7
diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle
index d74cb4e2..3a173175 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 a86d22e7..8969685b 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 f9572c29..cbc7cb60 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 bb7886b9..e5fa879d 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 258f721d..e58bc54a 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
index 17be829e..9b32e6df 100644
Binary files a/docs/.doctrees/prediction.doctree and b/docs/.doctrees/prediction.doctree differ
diff --git a/docs/.doctrees/predictors.doctree b/docs/.doctrees/predictors.doctree
index a4dd1b27..2f2a6e64 100644
Binary files a/docs/.doctrees/predictors.doctree and b/docs/.doctrees/predictors.doctree differ
diff --git a/docs/.doctrees/release.doctree b/docs/.doctrees/release.doctree
new file mode 100644
index 00000000..386ff299
Binary files /dev/null and b/docs/.doctrees/release.doctree differ
diff --git a/docs/.doctrees/state_estimators.doctree b/docs/.doctrees/state_estimators.doctree
index c2919080..6a7b406b 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 1862e740..360d50b2 100644
Binary files a/docs/.doctrees/uncertain_data.doctree and b/docs/.doctrees/uncertain_data.doctree differ
diff --git a/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py b/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py
index b5a4c621..cc409ce0 100644
--- a/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py
+++ b/docs/_downloads/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py
@@ -1,5 +1,11 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
+"""
+An example illustrating the creation of a new state estimator.
+
+In this example a basic state estimator is constructed by subclassing the StateEstimator class. This StateEstimator is then demonstrated with a ThrownObject model
+"""
+
 from prog_algs.state_estimators import StateEstimator
 from prog_algs.uncertain_data import ScalarData
 import random
diff --git a/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb b/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb
index fc083d00..b20c4b48 100644
--- a/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb
+++ b/docs/_downloads/200bc74cf412da163fa7fd0ac3a7bb56/tutorial.ipynb
@@ -17,7 +17,7 @@
     "\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",
+    "* prediction: The process of predicting the evolution of a system state with time and the occurrence of events. \n",
     "\n",
     "The `prog_algs` package has the following primary subpackages\n",
     "* `prog_algs.state_estimators` - Tools for performing state estimation\n",
@@ -33,15 +33,28 @@
     "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"
+    "Now let's get started with some examples"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Prognostics Example \n",
-    "First, import the prog_algs and the model you intend to use"
+    "## UncertainData - Representing a Distribution"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Uncertainty is sometimes present in data used for performing state estimations or making predictions.\n",
+    "\n",
+    "In `prog_algs`, data with uncertainty is represented using classes inheriting from `UncertainData`:\n",
+    "* `prog_algs.uncertain_data.MultivariateNormalDist` - Data represented by a multivariate normal distribution with mean and covariance matrix\n",
+    "* `prog_algs.uncertain_data.ScalarData` - Data without uncertainty, a single value\n",
+    "* `prog_algs.uncertain_data.UnweightedSamples` - Data represented by a set of unweighted samples. Objects of this class can be treated like a list where samples[n] returns the nth sample (Dict)\n",
+    "\n",
+    "To begin using `UncertainData`, import the type that best portrays the data. In this simple demonstration, we import the `UnweightedSamples` data type. See <https://nasa.github.io/prog_algs> for full details on the available `UncertainData` types."
    ]
   },
   {
@@ -50,15 +63,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from prog_models.models import BatteryCircuit\n",
-    "from prog_algs import *"
+    "from prog_algs.uncertain_data import UnweightedSamples"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Next, prepare the model like you did for simulation"
+    "With `UnweightedSamples` imported, construct an object with samples. This object can be initialized using either a dictionary, list, or model.*Container type from prog_models (e.g., StateContainer). Let's try creating an object using a dictionary. "
    ]
   },
   {
@@ -67,28 +79,139 @@
    "metadata": {},
    "outputs": [],
    "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",
+    "samples = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])\n",
+    "print(samples)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Given an integer value, addition and subtraction can be performed on the `UncertainData` classes to adjust the distribution by a scalar amount."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "samples = samples + 5\n",
+    "print(samples)\n",
+    "samples -= 3\n",
+    "print(samples)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can also sample from any `UncertainData` distribution using the `sample` method. In this case it resamples from the existing samples"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(samples.sample()) # A single sample\n",
+    "print(samples.sample(10)) # 10 samples"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can see the keys present using the `.keys()` method:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(samples.keys())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "and the data corresponding to a specific key can be retrieved using `.key()`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(samples.key('x'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Various properties are available to quantify the `UncertainData` distribution"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('mean', samples.mean)\n",
+    "print('median', samples.median)\n",
+    "print('covariance', samples.cov)\n",
+    "print('size', samples.size)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "These `UncertainData` classes are used throughout the prog_algs package to represent data with uncertainty, as described in the following sections."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## State Estimation"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "State estimation is the process of estimating the system state given sensor data and a model. Typically, this is done repeatedly as new sensor data is available.\n",
     "\n",
-    "batt = BatteryCircuit()"
+    "In `prog_algs` a State Estimator is used to estimate the system state. \n",
+    "\n",
+    "To start, import the needed packages. Here we will import the `BatteryCircuit` model and the `UnscentedKalmanFilter` state estimator. See <https://nasa.github.io/prog_algs> for more details on the available state estimators.\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from prog_models.models import BatteryCircuit\n",
+    "from prog_algs.state_estimators import UnscentedKalmanFilter"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Now that we have our model ready, we can construct our state estimator:"
+    "Next we construct and initialize the model. \n",
+    "\n",
+    "We use the resulting model and initial state to construct the state estimator. "
    ]
   },
   {
@@ -97,14 +220,26 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "filt = state_estimators.UnscentedKalmanFilter(batt, batt.parameters['x0'])"
+    "m = BatteryCircuit()\n",
+    "x0 = m.initialize()\n",
+    "\n",
+    "# Turn into a distribution - this represents uncertainty in the initial state\n",
+    "from prog_algs.uncertain_data import MultivariateNormalDist\n",
+    "from numpy import diag\n",
+    "INITIAL_UNCERT = 0.05  # Uncertainty in initial state (%)\n",
+    "# Construct covariance matrix (making sure each value is positive)\n",
+    "cov = diag([max(abs(INITIAL_UNCERT * value), 1e-9) for value in x0.values()])\n",
+    "x0 = MultivariateNormalDist(x0.keys(), x0.values(), cov)\n",
+    "\n",
+    "# Construct State estimator\n",
+    "est = UnscentedKalmanFilter(m, x0)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "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:"
+    "Now we can use the estimator to estimate the system state."
    ]
   },
   {
@@ -113,28 +248,41 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "import matplotlib.pyplot as plt  # For plotting\n",
+    "print(\"Prior State:\", est.x.mean)\n",
+    "print('\\tSOC: ', m.event_state(est.x.mean)['EOD'])\n",
+    "fig = est.x.plot_scatter(label='prior')\n",
     "\n",
-    "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",
+    "u = {'i': 2}\n",
+    "example_measurements = {'t': 32.2, 'v': 3.915}\n",
+    "est.estimate(t, u, example_measurements)\n",
     "\n",
-    "fig.show()"
+    "print(\"Posterior State:\", est.x.mean)\n",
+    "print('\\tSOC: ', m.event_state(est.x.mean)['EOD'])\n",
+    "est.x.plot_scatter(fig= fig, label='posterior')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Typically in a prognostics application the state estimation step is run whenever there is new data.\n",
+    "As mentioned previously, this step is typically repeated when there's new data. filt.x may not be accessed every time the estimate is updated, only when it's needed."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Prediction Example"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Prediction is the practice of using a state estimation, a model, and estimates of future loading to predict future states and when an event will occur.\n",
     "\n",
-    "That's the state estimation step- now lets prepare for prediction. "
+    "First we will import a predictor. In this case, we will use the MonteCarlo Predictor, but see documentation <https://nasa.github.io/prog_algs> for a full list of predictors and their configuration parameters."
    ]
   },
   {
@@ -143,43 +291,84 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# Create predictor \n",
-    "mc = predictors.MonteCarlo(batt)"
+    "from prog_algs.predictors import MonteCarlo"
    ]
   },
   {
    "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"
+    "Next we initialize it using the model from the above example"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "metadata": {
-    "tags": [
-     "outputPrepend"
-    ]
-   },
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mc = MonteCarlo(m)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Next, let's define future loading and the first state. The first state is the output of the state estimator, and the future loading scheme is a simple piecewise function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
    "outputs": [],
    "source": [
-    "(times, inputs, states, outputs, event_states, toe) = mc.predict(filt.x, future_loading, dt=0.05, n_samples=20)"
+    "x = est.x  # The state estimate\n",
+    "\n",
+    "def future_loading(t, x={}):\n",
+    "    # Variable (piece-wise) future loading scheme \n",
+    "    if (t < 600):\n",
+    "        i = 2\n",
+    "    elif (t < 900):\n",
+    "        i = 1\n",
+    "    elif (t < 1800):\n",
+    "        i = 4\n",
+    "    elif (t < 3000):\n",
+    "        i = 2\n",
+    "    else:\n",
+    "        i = 3\n",
+    "    return {'i': i}"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format `[sample #][time]` so that `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",
+    "Now let's use the constructed mc predictor to perform a single prediction. Here we're setting dt to 0.25. Note this may take up to a minute"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "mc_results = mc.predict(x, future_loading, dt=0.25, n_samples=20)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format `[sample #][time]` so that `mc_results.states[m][n]` corresponds to the state for sample `m` at time `mc_results.times[m][n]`. Alternately, use the method `snapshot` to get a  single point in time. e.g., \n",
     "\n",
-    "    `state = states.snapshot(3)`\n",
+    "    `state = mc_results.states.snapshot(3)`\n",
     "\n",
-    "In this case the state snapshot corresponds to time `times[3]`. The snapshot method returns type UncertainData. \n",
+    "In this case the state snapshot corresponds to time `mc_results.times[3]`. The snapshot method returns type UncertainData. \n",
     "\n",
     "The `predict` method also returns Time of Event (ToE) as a type UncertainData, representing the predicted time of event (for each event predicted), with uncertainty.\n",
     "\n",
-    "Next, let's use the metrics package to analyse the ToE"
+    "Next, let's use the metrics package to analyze the ToE"
    ]
   },
   {
@@ -189,10 +378,10 @@
    "outputs": [],
    "source": [
     "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",
+    "print('\\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))\n",
+    "print('\\tAssuming ground truth 3005.25: ', mc_results.time_of_event.metrics(ground_truth = 3005.25))\n",
     "from prog_algs.metrics import prob_success \n",
-    "print('\\tP(Success) if mission ends at 3005.25: ', prob_success(toe, 3005.25))"
+    "print('\\tP(Success) if mission ends at 3005.25: ', prob_success(mc_results.time_of_event, 3005.25))"
    ]
   },
   {
@@ -210,19 +399,18 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "fig = states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(times[0])))\n",
+    "fig = mc_results.states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(mc_results.times[0])))\n",
     "for i in range(1, 4):\n",
-    "    index = int(len(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()"
+    "    index = int(len(mc_results.times)/4*i)\n",
+    "    mc_results.states.snapshot(index).plot_scatter(fig=fig, label = \"t={:.0f}\".format(mc_results.times[index]))\n",
+    "mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = \"t={:.0f}\".format(int(mc_results.times[-1])))"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Or time of event"
+    "Or time of event (ToE)"
    ]
   },
   {
@@ -231,15 +419,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "fig = toe.plot_hist()\n",
-    "fig.show()"
+    "fig = mc_results.time_of_event.plot_hist()"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Note, for this event, there is only one event (End of Discharge). Many models have multiple events that can be predicted. For these models, ToE for each event is returned and can be analyzed. \n",
+    "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."
    ]
@@ -248,7 +435,20 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Extending - Adding a new state estimator\n",
+    "Frequently the prediction step is run periodically, less often than the state estimator step"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Extending - Adding a new state estimator"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "New state estimators can be created by extending the state_estimator interface. As an example lets use a really dumb state estimator that adds random noise each step - and accepts the state that is closest. \n",
     "\n",
     "First thing we need to do is import the StateEstimator parent class"
@@ -335,8 +535,8 @@
    },
    "outputs": [],
    "source": [
-    "x0 = {key: 0 for key in batt.states}\n",
-    "se = BlindlyStumbleEstimator(batt, x0)\n",
+    "x0 = {key: 0 for key in m.states}\n",
+    "se = BlindlyStumbleEstimator(m, x0)\n",
     "\n",
     "for i in range(25):\n",
     "    u = {'i': 0}\n",
@@ -346,6 +546,13 @@
     "    print(\"\\tcorrect: {'tb': 18.95, 'qb': 7856.3254, 'qcp': 0, 'qcs': 0}\")"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Extending - Adding a new Predictor"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -359,8 +566,8 @@
    "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.basic_example` : A basic Example using prog_algs for Prognostics \n",
+    "* `examples.benchmarking_example` : An example benchmarking the performance of prognostics algorithms\n",
     "* `examples.eol_event` : An example where a model has multiple events, but the user is only interested in predicting the time when the first event occurs (whatever it is).\n",
     "* `examples.measurement_eqn_example` : An example where not every output is measured or measurements are not in the same format as outputs, so a measurement equation is defined to translate between outputs and what's measured. \n",
     "* `examples.new_state_estimator_example` : An example of extending StateEstimator to create a new state estimator class\n",
@@ -371,14 +578,22 @@
     "\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."
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved."
+   ]
   }
  ],
  "metadata": {
   "interpreter": {
-   "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2"
+   "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
   },
   "kernelspec": {
-   "display_name": "Python 3.8.12 64-bit",
+   "display_name": "Python 3.9.1 64-bit",
+   "language": "python",
    "name": "python3"
   },
   "language_info": {
@@ -391,7 +606,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.12"
+   "version": "3.8.13"
   },
   "metadata": {
    "interpreter": {
diff --git a/docs/_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py b/docs/_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py
new file mode 100644
index 00000000..eab72e2d
--- /dev/null
+++ b/docs/_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py
@@ -0,0 +1,133 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+"""
+This example demonstrates use of the Kalman Filter State Estimator with a LinearModel.
+
+First, a linear model is defined. Then the KF State estimator is used with fake data to estimate state.
+"""
+
+import numpy as np
+
+from prog_models import LinearModel
+from prog_algs.state_estimators import KalmanFilter
+
+# Linear Model for an object thrown into the air
+class ThrownObject(LinearModel):
+    """
+    Model that similates an object thrown into the air without air resistance
+
+    Events (2)
+        | falling: The object is falling
+        | impact: The object has hit the ground
+
+    Inputs/Loading: (0)
+
+    States: (2)
+        | x: Position in space (m)
+        | v: Velocity in space (m/s)
+
+    Outputs/Measurements: (1)
+        | x: Position in space (m)
+
+    Keyword Args
+    ------------
+        process_noise : Optional, float or Dict[Srt, float]
+          Process noise (applied at dx/next_state). 
+          Can be number (e.g., .2) applied to every state, a dictionary of values for each 
+          state (e.g., {'x1': 0.2, 'x2': 0.3}), or a function (x) -> x
+        process_noise_dist : Optional, String
+          distribution for process noise (e.g., normal, uniform, triangular)
+        measurement_noise : Optional, float or Dict[Srt, float]
+          Measurement noise (applied in output eqn).
+          Can be number (e.g., .2) applied to every output, a dictionary of values for each
+          output (e.g., {'z1': 0.2, 'z2': 0.3}), or a function (z) -> z
+        measurement_noise_dist : Optional, String
+          distribution for measurement noise (e.g., normal, uniform, triangular)
+        g : Optional, float
+            Acceleration due to gravity (m/s^2). Default is 9.81 m/s^2 (standard gravity)
+        thrower_height : Optional, float
+            Height of the thrower (m). Default is 1.83 m
+        throwing_speed : Optional, float
+            Speed at which the ball is thrown (m/s). Default is 40 m/s
+    """
+
+    inputs = []  # no inputs, no way to control
+    states = [
+        'x',     # Position (m) 
+        'v'      # Velocity (m/s)
+        ]
+    outputs = [
+        'x'      # Position (m)
+    ]
+    events = [
+        'impact' # Event- object has impacted ground
+    ]
+
+    A = np.array([[0, 1], [0, 0]])
+    E = np.array([[0], [-9.81]])
+    C = np.array([[1, 0]])
+    F = None # Will override method
+
+    # The Default parameters. Overwritten by passing parameters dictionary into constructor
+    default_parameters = {
+        'thrower_height': 1.83,  # m
+        'throwing_speed': 40,  # m/s
+        'g': -9.81  # Acceleration due to gravity in m/s^2
+    }
+
+    def initialize(self, u=None, z=None):
+        return self.StateContainer({
+            'x': self.parameters['thrower_height'],  # Thrown, so initial altitude is height of thrower
+            'v': self.parameters['throwing_speed']  # Velocity at which the ball is thrown - this guy is a professional baseball pitcher
+            })
+    
+    # This is actually optional. Leaving thresholds_met empty will use the event state to define thresholds.
+    #  Threshold = Event State == 0. However, this implementation is more efficient, so we included it
+    def threshold_met(self, x):
+        return {
+            'falling': x['v'] < 0,
+            'impact': x['x'] <= 0
+        }
+
+    def event_state(self, x): 
+        x_max = x['x'] + np.square(x['v'])/(-self.parameters['g']*2) # Use speed and position to estimate maximum height
+        return {
+            'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0),  # Throwing speed is max speed
+            'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1  # 1 until falling begins, then it's fraction of height
+        }
+
+def run_example():
+    # Step 1: Instantiate the model
+    m = ThrownObject(process_noise = 0, measurement_noise = 0)
+
+    # Step 2: Instantiate the Kalman Filter State Estimator
+    # Define the initial state to be slightly off of actual
+    x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40}
+    kf = KalmanFilter(m, x_guess)
+
+    # Step 3: Run the Kalman Filter State Estimator
+    # Here we're using simulated data from the thrown_object. In a real application you would be using sensor data from the system
+    dt = 0.01  # Time step (s)
+    print_freq = 50  # Print every print_freq'th iteration
+    x = m.initialize()
+    u = m.InputContainer({})  # No input for this model
+    
+    for i in range(500):
+        # Get simulated output (would be measured in a real application)
+        z = m.output(x)
+
+        # Estimate New State
+        kf.estimate(i*dt, u, z)
+        x_est = kf.x.mean
+
+        # Print Results
+        if i%print_freq == 0:  # Print every print_freq'th iteration
+            print(f"t: {i*dt:.2f}\n\tEstimate: {x_est}\n\tTruth: {x}")
+            diff = {key: x_est[key] - x[key] for key in x.keys()}
+            print(f"\t Diff: {diff}")
+
+        # Update Real state for next step
+        x = m.next_state(x, u, dt)
+
+if __name__ == '__main__':
+    run_example()
diff --git a/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py b/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py
index f5de4827..54c8ac22 100644
--- a/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py
+++ b/docs/_downloads/42767bcae008de45e317b0df375c7ab9/thrown_object_example.py
@@ -49,12 +49,12 @@ def future_loading(t, x = None):
 
     # 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, horizon=8)
+    STEP_SIZE = 0.01
+    mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon=8)
     print("\nPredicted Time of Event:")
-    pprint(toe.metrics())  # Note this takes some time
-    toe.plot_hist(keys = 'impact')
-    toe.plot_hist(keys = 'falling')
+    pprint(mc_results.time_of_event.metrics())  # Note this takes some time
+    mc_results.time_of_event.plot_hist(keys = 'impact')
+    mc_results.time_of_event.plot_hist(keys = 'falling')
     
     # Step 4: Show all plots
     import matplotlib.pyplot as plt  # For plotting
diff --git a/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py b/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py
index c6476840..1442a22c 100644
--- a/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py
+++ b/docs/_downloads/44de953994033adbab05302cdc8c05e4/horizon.py
@@ -53,13 +53,13 @@ def future_loading(t, x = None):
     # We're saying we are not interested in any events that occur after this time
     PREDICTION_HORIZON = 7.75
     samples = filt.x  # Since we're using a particle filter, which is also sample-based, we can directly use the samples, without changes
-    STEP_SIZE = 0.1
-    (_, _, _, _, _, toe) = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon = PREDICTION_HORIZON)
+    STEP_SIZE = 0.01
+    mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon = PREDICTION_HORIZON)
     print("\nPredicted Time of Event:")
-    metrics = toe.metrics()
+    metrics = mc_results.time_of_event.metrics()
     pprint(metrics)  # Note this takes some time
-    toe.plot_hist(keys = 'impact')
-    toe.plot_hist(keys = 'falling')
+    mc_results.time_of_event.plot_hist(keys = 'impact')
+    mc_results.time_of_event.plot_hist(keys = 'falling')
 
     print("\nSamples where impact occurs before horizon: {:.2f}%".format(metrics['impact']['number of samples']/NUM_SAMPLES*100))
     
diff --git a/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py b/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py
index d2972d6d..bc982487 100644
--- a/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py
+++ b/docs/_downloads/847c5bdc6f78e586d5a56a90de4f1d95/playback.py
@@ -8,6 +8,7 @@
         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)
@@ -19,55 +20,61 @@
 # VVV Uncomment this to use Electro Chemistry Model VVV
 # from prog_models.models import BatteryElectroChem as Battery
 
-from prog_algs.state_estimators import ParticleFilter as StateEstimator
+from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator
 # VVV Uncomment this to use UnscentedKalmanFilter instead VVV
-# from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator
+# from prog_algs.state_estimators import ParticleFilter as StateEstimator
 
-from prog_algs.predictors import MonteCarlo, ToEPredictionProfile
-from prog_algs.metrics import samples as metrics
+from prog_algs.predictors import ToEPredictionProfile
+
+from prog_algs.predictors import UnscentedTransformPredictor as Predictor
+# VVV Uncomment this to use MonteCarloPredictor instead
+# from prog_algs.predictors import MonteCarlo as Predictor
+
+from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist
 
 import csv
-import matplotlib.pyplot as plt 
+import matplotlib.pyplot as plt
+import numpy as np
 
 # Constants
-NUM_SAMPLES = 10
+NUM_SAMPLES = 20
+NUM_PARTICLES = 1000 # For state estimator (if using ParticleFilter)
 TIME_STEP = 1
-PREDICTION_UPDATE_FREQ = 5 # Number of steps between prediction update
+PREDICTION_UPDATE_FREQ = 50 # Number of steps between prediction update
+PLOT = True
+PROCESS_NOISE = 1e-4 # Percentage process noise
+MEASUREMENT_NOISE = 1e-4 # Percentage measurement noise
+X0_COV = 1 # Covariance percentage with initial state
+GROUND_TRUTH = {'EOD':2780}
+ALPHA = 0.05
+BETA = 0.90
+LAMBDA_VALUE = 1500
 
 def run_example():
     # Setup Model
     batt = Battery()
 
+    # Initial state
+    x0 = batt.initialize()
+    batt.parameters['process_noise'] = {key: PROCESS_NOISE * value for key, value in x0.items()}
+    z0 = batt.output(x0)
+    batt.parameters['measurement_noise'] = {key: MEASUREMENT_NOISE * value for key, value in z0.items()}
+    x0 = MultivariateNormalDist(x0.keys(), list(x0.values()), np.diag([max(1e-9, X0_COV * abs(x)) for x in x0.values()]))
+
     # Setup State Estimation
-    filt = StateEstimator(batt, batt.parameters['x0'])
+    filt = StateEstimator(batt, x0, num_particles = NUM_PARTICLES)
 
     # Setup Prediction
     def future_loading(t, x=None):
         return {'i': 2.35}
-    mc = MonteCarlo(batt)
-
-    # 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 = [], []
-    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()
+    Q = np.diag([batt.parameters['process_noise'][key] for key in batt.states])
+    R = np.diag([batt.parameters['measurement_noise'][key] for key in batt.outputs])
+    mc = Predictor(batt, Q = Q, R = R)
 
     # Run Playback
     step = 0
     profile = ToEPredictionProfile()
+    
     with open('examples/data_const_load.csv', 'r') as f:
         reader = csv.reader(f)
         next(reader) # Skip header
@@ -83,32 +90,54 @@ def future_loading(t, x=None):
             eod = batt.event_state(filt.x.mean)['EOD']
             print("  - Event State: ", eod)
 
-            # Update Plot
-            xdata.append(t)
-            ydata.append(eod)
-            xmin, xmax = ax.get_xlim()
-
-            if t >= xmax:
-                ax.set_xlim(xmin, 2*xmax)
-                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)
+                mc_results = mc.predict(filt.x, future_loading, t0 = t, n_samples=NUM_SAMPLES, dt=TIME_STEP)
+                metrics = mc_results.time_of_event.metrics()
+                print('  - ToE: {} (sigma: {})'.format(metrics['EOD']['mean'], metrics['EOD']['std']))
+                profile.add_prediction(t, mc_results.time_of_event)
+
+        # Calculating Prognostic Horizon once the loop completes
+        from prog_algs.uncertain_data.uncertain_data import UncertainData
+        from prog_algs.metrics import samples as metrics
+
+        def criteria_eqn(tte : UncertainData, ground_truth_tte : dict) -> dict:
+            """
+            Sample criteria equation for playback. 
+            # UPDATE THIS CRITERIA EQN AND WHAT IS CALCULATED
+
+            Args:
+                tte : UncertainData
+                    Time to event in UncertainData format.
+                ground_truth_tte : dict
+                    Dictionary of ground truth of time to event.
+            """
+            
+            # Set an alpha value
+            bounds = {}
+            for key, value in ground_truth_tte.items():
+                # Set bounds for precentage_in_bounds by adding/subtracting to the ground_truth
+                alpha_calc = value * ALPHA
+                bounds[key] = [value - alpha_calc, value + alpha_calc] # Construct bounds for all events
+            percentage_in_bounds = tte.percentage_in_bounds(bounds)
+            
+            # Verify if percentage in bounds for this ground truth meets beta distribution percentage limit
+            return {key: percentage_in_bounds[key] > BETA for key in percentage_in_bounds.keys()}
+
+        # Generate plots for playback example
+        playback_plots = profile.plot(GROUND_TRUTH, ALPHA, True)
+
+        # Calculate prognostic horizon with ground truth, and print
+        ph = profile.prognostic_horizon(criteria_eqn, GROUND_TRUTH)
+        print(f"Prognostic Horizon for 'EOD': {ph['EOD']}")
+
+        # Calculate alpha lambda with ground truth, lambda, alpha, and beta, and print
+        al = profile.alpha_lambda(GROUND_TRUTH, LAMBDA_VALUE, ALPHA, BETA)
+        print(f"Alpha Lambda for 'EOD': {al['EOD']}")
+
+        # Calculate cumulative relative accuracy with ground truth, and print
+        cra = profile.cumulative_relative_accuracy(GROUND_TRUTH)
+        print(f"Cumulative Relative Accuracy for 'EOD': {cra['EOD']}")
 
     input('Press any key to exit')
 
diff --git a/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py b/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py
index 4a759da3..4b9a9856 100644
--- a/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py
+++ b/docs/_downloads/a61393d56728af1a85621dfbed0a6ee1/particle_filter_battery_example.py
@@ -1,6 +1,10 @@
 # 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
+"""
+In this example the BatteryElectroChemEOD model is used with a particle filter to estimate the state of the battery
+"""
+
+from prog_models.models import BatteryElectroChemEOD
 from prog_algs import *
 import numpy as np
 import matplotlib.pyplot as plt
@@ -37,30 +41,30 @@ def future_loading(t, x=None):
     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)
+    sim_results = battery.simulate_to_threshold(future_loading, start_y, save_freq = 1)
 
     # Run particle filter
     all_particles = []
-    n_times = int(np.round(np.random.uniform(len(times)*.25,len(times)*.45,1)))# Random current time
+    n_times = int(np.round(np.random.uniform(len(sim_results.times)*.25,len(sim_results.times)*.45,1)))# Random current time
 
     for i in range(n_times):
         if i == 0:
-            batt_pf = state_estimators.ParticleFilter(model = battery,  x0 = states[i], num_particles = 250)
+            batt_pf = state_estimators.ParticleFilter(model = battery,  x0 = sim_results.states[i], num_particles = 250)
         else:
-            batt_pf.estimate(t = times[i], u = inputs[i], z = outputs[i])
+            batt_pf.estimate(t = sim_results.times[i], u = sim_results.inputs[i], z = sim_results.outputs[i])
         all_particles.append(batt_pf.particles)
 
     # Mean of the particles
     alpha = 0.05
-    states_vsn = [s['tb'] for s in states]
+    states_vsn = [s['tb'] for s in sim_results.states]
     pf_mean = [{key: np.mean(ps[key]) for key in battery.states} for ps in all_particles]
     pf_low = [{key: np.quantile(ps[key], alpha / 2.0) for key in battery.states} for ps in all_particles]
     pf_upp = [{key: np.quantile(ps[key], 1.0 - alpha / 2.0) for key in battery.states} for ps in all_particles]
     print("First State:", pf_mean[0])
     print("Current State:", pf_mean[-1])
-    plt.plot(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.plot(sim_results.times[:n_times],[p['tb'] for p in pf_mean],linewidth=0.7,color="blue")
+    plt.plot(sim_results.times[:n_times], states_vsn[:n_times],"--",linewidth=0.7,color="red")
+    plt.fill_between(sim_results.times[:n_times],[p['tb'] for p in pf_low],[p['tb'] for p in pf_upp],alpha=0.5,color="blue")
     plt.show()
     
 
diff --git a/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py b/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py
index 2bd5b9ea..0cbe2fcd 100644
--- a/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py
+++ b/docs/_downloads/b5b078b0fe440e9fa4d922ec9f4f1eea/basic_example.py
@@ -16,7 +16,7 @@
 
 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_models.models import BatteryElectroChemEOD as Battery
 
 from prog_algs import *
 
@@ -78,41 +78,41 @@ def future_loading(t, x = None):
     # 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)
+    mc_results = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE)
+    print('ToE', mc_results.time_of_event.mean)
 
     # Step 3c: Analyze the results
 
     # Note: The results of a sample-based prediction can be accessed by sample, e.g.,
-    states_sample_1 = states[1]
+    states_sample_1 = mc_results.states[1]
     # now states_sample_1[n] corresponds to times[n] for the first sample
 
     # You can also access a state distribution at a specific time using the .snapshot function
-    states_time_1 = states.snapshot(1)
+    states_time_1 = mc_results.states.snapshot(1)
     # now you have all the samples corresponding to times[1]
 
     # You can also access the final state (of type UncertainData), like so:
-    final_state = toe.final_state
+    final_state = mc_results.time_of_event.final_state
     print('Final state @EOD: ', final_state['EOD'].mean)
     
     # You can also use the metrics package to generate some useful metrics on the result of a prediction
     print("\nEOD Prediction Metrics")
 
     from prog_algs.metrics import prob_success
-    print('\tPortion between 3005.2 and 3005.6: ', 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))
+    print('\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))
+    print('\tAssuming ground truth 3002.25: ', mc_results.time_of_event.metrics(ground_truth=3005.25))
+    print('\tP(Success) if mission ends at 3002.25: ', prob_success(mc_results.time_of_event, 3005.25))
 
     # Plot state transition 
     # Here we will plot the states at t0, 25% to ToE, 50% to ToE, 75% to ToE, and ToE
-    fig = 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()
+    fig = mc_results.states.snapshot(0).plot_scatter(label = "t={} s".format(int(mc_results.times[0])))  # 0
+    quarter_index = int(len(mc_results.times)/4)
+    mc_results.states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index])))  # 25%
+    mc_results.states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*2])))  # 50%
+    mc_results.states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*3])))  # 75%
+    mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[-1])))  # 100%
+
+    mc_results.time_of_event.plot_hist()
     
     # Step 4: Show all plots
     import matplotlib.pyplot as plt  # For plotting
diff --git a/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py b/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py
index fb25ecb4..82b1eaf6 100644
--- a/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.py
+++ b/docs/_downloads/d8bf57631d1133f1784a65d1b777032f/eol_event.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.
 """
-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.
+This example demonstrates a use case where someone wants to predict the first event (i.e., End Of Life (EOL)) of a system. Many system models have multiple events that can occur. In some prognostics applications, users are not interested in predicting a specific event, and are instead interested in when the first event occurs, regardless of the event. This example demonstrates how to predict the first event of a system.
 
 Method: An instance of ThrownObject is used for this example. In this case it is trivial because the event 'falling' will always occur before 'impact', but for some other models that might not be true. The ThrownObject class is subclassed to add a new event 'EOL' which occurs if any other event occurs. The new model is then instantiated and used for prognostics like in basic_example. Prediction specifically specifies EOL as the event to be predicted.
 Results: 
@@ -41,10 +41,10 @@ def future_loading(t=None, x=None):
     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)
+    pred_results = pred.predict(state, future_loading, events=['EOL'], dt=0.025)
 
     # Step 5: Plot results
-    toe.plot_hist()
+    pred_results.time_of_event.plot_hist()
 
     import matplotlib.pyplot as plt
     plt.show()
diff --git a/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py b/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py
index d56086a6..cd2fdca4 100644
--- a/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py
+++ b/docs/_downloads/daee0732ebc9103bad35edc2271b7f8d/measurement_eqn_example.py
@@ -3,8 +3,8 @@
 """
 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. 
+Method: An instance of the BatteryCircuit model in prog_models is created. We assume that we are only measuring one of the output values, and we define a subclass to remove the other output value.  
+        Estimation of the current state is performed at various time steps, using the defined state_estimator.
 
 Results: 
     i) Estimate of the current state given various times
@@ -18,7 +18,14 @@
 from prog_algs import *
 
 def run_example():
-    # Step 1: Setup model & future loading
+    # Step 1: Subclass model with measurement equation
+    # In this case we're only measuring 'v' (i.e., removing temperature)
+    # To do this we're creating a new class that's subclassed from the complete model.
+    # To change the outputs we just have to override outputs (the list of keys)
+    class MyBattery(Battery):
+        outputs = ['v']
+
+    # Step 2: Setup model & future loading
     def future_loading(t, x={}):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -33,18 +40,11 @@ def future_loading(t, x={}):
             i = 3
         return {'i': i}
 
-    batt = Battery()
+    batt = MyBattery()
     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 3: Use the updated model
+    filt = state_estimators.ParticleFilter(batt, x0)
 
     # Step 4: Run step and print results
     print('Running state estimation step with only one of 2 outputs measured')
@@ -54,6 +54,7 @@ def measure(x):
     print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
 
     # Estimate Step
+    # Note, only voltage was needed in the measurement step, since that is the only output we're measuring
     t = 0.1
     load = future_loading(t)
     filt.estimate(t, load, {'v': 3.915})
@@ -72,8 +73,34 @@ def measure(x):
     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
+    # The updated outputs can be used for any case where the measurement doesn't match the model outputs
     # For example, when units are different, or when the measurement is some combination of the outputs
+    # These are a little more complicated, since they require an instance of the parent class. For example:
+
+    parent = Battery()
+
+
+    class MyBattery(Battery):
+        outputs = ['tv']  # output is temperature * voltage (for some reason)
+
+        def output(self, x):
+            parent.parameters = self.parameters  # only needed if you expect to change parameters
+            z = parent.output(x)
+            return self.OutputContainer({'tv': z['v'] * z['t']})
+
+    batt = MyBattery()
+    filt = state_estimators.ParticleFilter(batt, x0)
+
+    print('-----------------\n\nExample 2')
+    print("\nPrior State:", filt.x.mean)
+    print("\toutput: ", batt.output(filt.x.mean))
+    print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
+    t = 0.1
+    load = future_loading(t)
+    filt.estimate(t, load, {'tv': 80})
+    print("\nPosterior State:", filt.x.mean)
+    print("\toutput: ", batt.output(filt.x.mean))
+    print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
 
 # This allows the module to be executed directly 
 if __name__ == '__main__':
diff --git a/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py b/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py
index e2f10dbe..053a179f 100644
--- a/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py
+++ b/docs/_downloads/ea97230ef09e27d10dd5f70f57cc729a/predict_specific_event.py
@@ -1,13 +1,12 @@
 # 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.
 """
 
+from prog_models.models.thrown_object import ThrownObject
+from prog_algs import *
+
 def run_example():
     ## Setup
     def future_loading(t, x = None):
@@ -25,18 +24,18 @@ def future_loading(t, x = None):
     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'])
+    mc_results = pred.predict(filt.x, future_loading, dt=0.1, save_freq= 1, events=['impact'])
 
     # Print Results
-    for i, time in enumerate(times):
+    for i, time in enumerate(mc_results.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('\tu = {}'.format(mc_results.inputs.snapshot(i).mean))
+        print('\tx = {}'.format(mc_results.states.snapshot(i).mean))
+        print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(mc_results.states.snapshot(i).mean))
 
     # Note only impact event is shown here
-    print('\nToE:', toe.mean)
+    print('\nToE:', mc_results.time_of_event.mean)
 
 # This allows the module to be executed directly 
 if __name__ == '__main__':
diff --git a/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py b/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py
index 73bddf8a..1f1679fb 100644
--- a/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py
+++ b/docs/_downloads/f970960da825ceb736fa070c31c0b0e8/benchmarking_example.py
@@ -58,10 +58,11 @@ def future_loading(t, x={}):
     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
+        pred_results = pred.predict(state_estimator.x, future_loading, n_samples = sample_count)
+        toe = pred_results.time_of_event.key("EOD")  # Looking at EOD event
         end = time.perf_counter()
         print('\tMSE:     {:4.2f}s'.format(metrics.mean_square_error(toe, 3005.4)))
+        print('\tRMSE:     {:4.2f}s'.format(metrics.root_mean_square_error(toe, 3005.4)))
         print('\tRuntime: {:4.2f}s'.format(end - start))
 
     # This same approach can be applied for benchmarking and comparing other changes 
diff --git a/docs/_sources/getting_started.rst.txt b/docs/_sources/getting_started.rst.txt
index e199ce4e..ef2cb76c 100644
--- a/docs/_sources/getting_started.rst.txt
+++ b/docs/_sources/getting_started.rst.txt
@@ -3,20 +3,20 @@ 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 with the Prognostics Models Package (`prog_models`) to perform research in prognostics with prognostics systems.
 
 Installing
 -----------------------
 
 Installing from pip (recommended)
 ********************************************
-The latest stable release of `prog_algs` is hosted on PyPi. For most users (unless you want to contribute to the development of `prog_algs`), the version on PyPi will be adequate. To install from the command line, use the following command:
+The latest stable release of `prog_algs` is hosted on PyPi. For most users (unless you want to contribute to the development of `prog_algs`), this version will be adequate. To install from the command line, use the following command:
 
 .. code-block:: console
 
     $ pip install prog_algs
 
-Installing pre-release versions with GitHub
+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:
 
@@ -33,31 +33,31 @@ 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"
+* **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)
+* **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)
+* **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.
+* **performance metrics**: performance characteristics of a system that are a function of system state, but are not directly measured.
 
 * **states**: Internal parameters (typically hidden states) used to represent the state of the system- can be same as inputs/outputs but do not have to be. 
 
-* **process noise**: stochastic process representing uncertainty in the model transition. 
+* **process noise**: representing uncertainty in the model transition (e.g., model uncertainty). 
 
-* **measurement noise**: stochastic process representing uncertainty in the measurement process; e.g., sensor sensitivity, sensor misalignements, environmental effects 
+* **measurement noise**: representing uncertainty in the measurement process (e.g., sensor sensitivity, sensor misalignements, environmental effects).
 
 The structure of the packages is illustrated below:
 
 .. image:: images/package_structure.png
 
-Prognostics is performed using `State Estimators <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).
+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 <prediction.html#id1>`__).
 
 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.
+ * `SimResult (from prog_models) <https://nasa.github.io/prog_models/simresult.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. 
@@ -84,6 +84,9 @@ The best way to learn how to use `prog_algs` is through the `tutorial <https://m
 * :download:`examples.horizon <../examples/horizon.py>`
     .. automodule:: examples.horizon
     |
+* :download:`examples.kalman_filter <../examples/kalman_filter.py>`
+    .. automodule:: examples.kalman_filter
+    |
 * :download:`examples.measurement_eqn_example <../examples/measurement_eqn_example.py>`
     .. automodule:: examples.measurement_eqn_example
     |
@@ -107,32 +110,3 @@ Extending
 New State Estimators and Predictors are created by extending the :class:`prog_algs.state_estimators.StateEstimator` and :class:`prog_algs.predictors.Predictor` class, respectively. 
 
 See :download:`examples.new_state_estimator_example <../examples/new_state_estimator_example.py>` for an example of this approach.
-
-Updates in v1.2
----------------
-
-Note for existing users
-***********************
-This release includes changes to the return format of the MonteCarlo Predictor's `predict` method. These changes were necessary to support non-sample based predictors. The non backwards-compatible changes are listed below:
-* times: 
-    * previous ```List[List[float]]``` where times[n][m] corresponds to timepoint m of sample n. 
-    * new ```List[float]``` where times[m] corresponds to timepoint m for all samples
-* End of Life (EOL)/ Time of Event (ToE) estimates:
-    * previous ```List[float]``` where the times correspond to the time that the first event occurs.
-    * new ```UnweightedSamples``` where keys correspond to the inidividualevents predicted.
-* State at time of event (ToE)
-   * previous: element in states 
-   * new: member of toe event (e.g., toe.final_state['event1'])
-
-General Updates
-***************
-* New Feature: Histogram and Scatter Plot of UncertainData
-* New Feature: Vectorized particle filter
-    * Particle Filter State Estimator is now vectorized for vectorized models - this significantly improves performance.
-* New Feature: Unscented Transform Predictor
-    * New predictor that propogates sigma points forward to estimate time of event and future states
-* New Feature: `Prediction` class to represent predicted future values
-* New Feature: `ToEPredictionProfile` class to represent and operate on the result of multiple predictions generated at different prediction times.
-* Added metrics `percentage_in_bounds` and `metrics` and plots to UncertainData 
-* Add support for Python3.9
-* General Bugfixes
diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt
index 4cd8de20..20f484b2 100644
--- a/docs/_sources/index.rst.txt
+++ b/docs/_sources/index.rst.txt
@@ -18,11 +18,11 @@ If you are new to this package, see `getting started <getting_started.html>`__.
    predictors
    uncertain_data
    prediction
-   metrics
-   exceptions
    ProgModels <https://nasa.github.io/prog_models>
-   dev_guide
+   ProgServer <https://nasa.github.io/prog_server>
+   dev_guide <https://nasa.github.io/prog_models/dev_guide.html>
    GitHub <https://github.com/nasa/prog_algs>
+   release
 
 Citing this repository
 -----------------------
@@ -31,15 +31,15 @@ Use the following to cite this repository:
 @misc{2021_nasa_prog_algs,
   | author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
   | title     = {Prognostics Algorithms Python Package},
-  | month     = Aug,
-  | year      = 2021,
-  | version   = {1.2.0},
+  | month     = May,
+  | year      = 2022,
+  | version   = {1.3.0},
   | url       = {https://github.com/nasa/prog_algs}
   | }
 
 The corresponding reference should look like this:
 
-C. Teubert, C. Kulkarni, M. Corbetta Prognostics Algorithms Python Package, v1.2.0, Aug. 2021. URL https://github.com/nasa/prog_algs.
+C. Teubert, C. Kulkarni, M. Corbetta. Prognostics Algorithms Python Package, v1.3.0, May 2022. URL https://github.com/nasa/prog_algs.
 
 Indices and tables
 -----------------------
diff --git a/docs/_sources/prediction.rst.txt b/docs/_sources/prediction.rst.txt
index 34ad13d7..de7739ea 100644
--- a/docs/_sources/prediction.rst.txt
+++ b/docs/_sources/prediction.rst.txt
@@ -3,7 +3,7 @@ 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.
+Predictions store the result of a prediction (i.e., returned by the predict method of a predictor). They store values (with uncertainty) at different future times. These are used to store states, inputs, outputs, perfomance metrics, and event states with uncertainty at savepoints.
 
 Two types of predictions are distributed with this package: `Prediction` and `UnweightedSamplesPrediction`, described below. `UnweightedSamplesPrediction` extends `Prediction` to allow some operations specific to cases where each prediction is represented by an UnweightedSamples object (e.g., accessing SimResult for a single sample).
 
diff --git a/docs/_sources/predictors.rst.txt b/docs/_sources/predictors.rst.txt
index f91569b0..3e09496a 100644
--- a/docs/_sources/predictors.rst.txt
+++ b/docs/_sources/predictors.rst.txt
@@ -1,7 +1,7 @@
 Predictors
 =======================
 
-The Predictor uses a state estimate (type UncertainData subclass, output of a state estimator), information about expected future loading, and a Prognostics Model (see: `prog_models package <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.
+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, performance metrics, event_states) at predefined points and the time that an event will occur (Time of Event, ToE) with uncertainty.
 
 Here's an example of its use. In this example we use the ThrownObject model and the MonteCarlo predictor, and we save the state every 1s. We also use a scalar first state (i.e., no uncertainty).
 
@@ -17,8 +17,8 @@ Here's an example of its use. In this example we use the ThrownObject model and
    >>> 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
+   >>> pred_results = pred.predict(first_state, future_loading, save_freq=1)
+   >>> pred_results.time_of_event.plot_hist(events='impact')  # Plot a histogram of when the impact event occurred
 
 See tutorial and examples for more information and additional features.
 
diff --git a/docs/_sources/release.rst.txt b/docs/_sources/release.rst.txt
new file mode 100644
index 00000000..05538bd6
--- /dev/null
+++ b/docs/_sources/release.rst.txt
@@ -0,0 +1,48 @@
+Release Notes
+=======================
+
+..  contents:: 
+    :backlinks: top
+
+Updates in V1.3
+-----------------------
+* **New State Estimator Added** :class:`prog_algs.state_estimators.KalmanFilter`. Works with models derived from :class:`prog_models.LinearModel`. See :download:`examples.kalman_filter <../examples/kalman_filter.py>`
+* **New Predictor Added** :class:`prog_algs.predictors.UnscentedTransformPredictor`. See :download:`examples.utpredictor <../examples/utpredictor.py>`
+* Initial state estimate (x0) can now be passed as `UncertainData` to represent initial state uncertainty. See :download:`examples.playback <../examples/playback.py>`
+* Added new metrics for :class:`prog_algs.predictors.ToEPredictionProfile`: Prognostics horizon, Cumulative Relative Accuracy (CRA). See :download:`examples.playback <../examples/playback.py>`
+* Added ability to plot :class:`prog_algs.predictors.ToEPredictionProfile`: profile.plot(). See :download:`examples.playback <../examples/playback.py>`
+* Added new metric for :class:`prog_algs.predictors.Prediction`: Monotonicity, Relative Accuracy (RA)
+* Added new metric for :class:`prog_algs.uncertain_data.UncertainData` (and subclasses): Root Mean Square Error (RMSE)
+* Added new describe method for :class:`prog_algs.uncertain_data.UncertainData` (and subclasses)
+* Add support for python 3.10
+* Various performance improvements and bugfixes
+
+
+Updates in v1.2
+---------------
+
+Note for Existing Users
+***********************
+This release includes changes to the return format of the MonteCarlo Predictor's `predict` method. These changes were necessary to support non-sample based predictors. The non backwards-compatible changes are listed below:
+* times: 
+    * previous ```List[List[float]]``` where times[n][m] corresponds to timepoint m of sample n. 
+    * new ```List[float]``` where times[m] corresponds to timepoint m for all samples.
+* End of Life (EOL)/ Time of Event (ToE) estimates:
+    * previous ```List[float]``` where the times correspond to the time that the first event occurs.
+    * new ```UnweightedSamples``` where keys correspond to the inidividualevents predicted.
+* State at time of event (ToE).
+   * previous: element in states.
+   * new: member of ToE structure (e.g., ToE.final_state['event1']).
+
+General Updates
+***************
+* New Feature: Histogram and Scatter Plot of UncertainData.
+* New Feature: Vectorized particle filter.
+    * Particle Filter State Estimator is now vectorized for vectorized models - this significantly improves performance.
+* New Feature: Unscented Transform Predictor.
+    * New predictor that propogates sigma points forward to estimate time of event and future states.
+* New Feature: `Prediction` class to represent predicted future values.
+* New Feature: `ToEPredictionProfile` class to represent and operate on the result of multiple predictions generated at different prediction times.
+* Added metrics `percentage_in_bounds` and `metrics` and plots to UncertainData .
+* Add support for Python3.9.
+* General Bugfixes.
diff --git a/docs/_sources/state_estimators.rst.txt b/docs/_sources/state_estimators.rst.txt
index af7b1429..ee41ad5e 100644
--- a/docs/_sources/state_estimators.rst.txt
+++ b/docs/_sources/state_estimators.rst.txt
@@ -1,6 +1,6 @@
 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.
+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 performance metrics). This state estimate can either be used by itself or as input to a `Predictor <predictors.html>`__. A state estimator is typically run each time new information is available.
 
 Here's an example of its use. In this example we use the unscented kalman filter state estimator and the ThrownObject model. 
 
@@ -25,6 +25,10 @@ Included State Estimators
 -------------------------
 The following state estimators are included with this package. A new state estimator can be created by subclassing `prog_algs.state_estimators.StateEstimator`. See also: `state_estimator_template.py`
 
+Kalman Filter
+*************************
+.. autoclass:: prog_algs.state_estimators.KalmanFilter
+
 Unscented Kalman Filter
 *************************
 .. autoclass:: prog_algs.state_estimators.UnscentedKalmanFilter
diff --git a/docs/_static/basic.css b/docs/_static/basic.css
index 912859b5..603f6a87 100644
--- a/docs/_static/basic.css
+++ b/docs/_static/basic.css
@@ -731,8 +731,9 @@ dl.glossary dt {
 
 .classifier:before {
     font-style: normal;
-    margin: 0.5em;
+    margin: 0 0.5em;
     content: ":";
+    display: inline-block;
 }
 
 abbr, acronym {
diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js
index 120e3eec..7cf0629b 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.2.1',
+    VERSION: '1.3.0',
     LANGUAGE: 'None',
     COLLAPSE_INDEX: false,
     BUILDER: 'html',
diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js
index 58ff35c4..002e9c4a 100644
--- a/docs/_static/searchtools.js
+++ b/docs/_static/searchtools.js
@@ -328,7 +328,9 @@ var Search = {
     var results = [];
 
     for (var prefix in objects) {
-      for (var name in objects[prefix]) {
+      for (var iMatch = 0; iMatch != objects[prefix].length; ++iMatch) {
+        var match = objects[prefix][iMatch];
+        var name = match[4];
         var fullname = (prefix ? prefix + '.' : '') + name;
         var fullnameLower = fullname.toLowerCase()
         if (fullnameLower.indexOf(object) > -1) {
@@ -342,7 +344,6 @@ var Search = {
           } else if (parts[parts.length - 1].indexOf(object) > -1) {
             score += Scorer.objPartialMatch;
           }
-          var match = objects[prefix][name];
           var objname = objnames[match[1]][2];
           var title = titles[match[0]];
           // If more than one term searched for, we require other words to be
diff --git a/docs/exceptions.html b/docs/exceptions.html
index 551d0ee6..d038fcd2 100644
--- a/docs/exceptions.html
+++ b/docs/exceptions.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>Exceptions &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -15,7 +15,6 @@
     <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="Metrics" href="metrics.html" />
    
   <link rel="stylesheet" href="_static/custom.css" type="text/css" />
@@ -40,7 +39,7 @@ <h1>Exceptions<a class="headerlink" href="#exceptions" title="Permalink to this
 <h2>Prognostics Model Exceptions<a class="headerlink" href="#prognostics-model-exceptions" title="Permalink to this headline">¶</a></h2>
 <dl class="py exception">
 <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>
+<em class="property"><span class="pre">exception</span><span class="w"> </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 class="sig sig-object py" id="prog_algs.exceptions.ProgAlgException.with_traceback">
@@ -53,7 +52,7 @@ <h2>Prognostics Model Exceptions<a class="headerlink" href="#prognostics-model-e
 
 <dl class="py exception">
 <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>
+<em class="property"><span class="pre">exception</span><span class="w"> </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 class="sig sig-object py" id="prog_algs.exceptions.ProgAlgInputException.with_traceback">
@@ -66,7 +65,7 @@ <h2>Prognostics Model Exceptions<a class="headerlink" href="#prognostics-model-e
 
 <dl class="py exception">
 <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>
+<em class="property"><span class="pre">exception</span><span class="w"> </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 class="sig sig-object py" id="prog_algs.exceptions.ProgAlgTypeError.with_traceback">
@@ -110,7 +109,8 @@ <h3>Navigation</h3>
 </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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
@@ -119,7 +119,6 @@ <h3>Related Topics</h3>
 <ul>
   <li><a href="index.html">Documentation overview</a><ul>
       <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>
 </div>
diff --git a/docs/genindex.html b/docs/genindex.html
index 500d9360..aff3360c 100644
--- a/docs/genindex.html
+++ b/docs/genindex.html
@@ -5,7 +5,7 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Index &#8212; Prognostics Algorithms Python Package 1.2.1 documentation</title>
+    <title>Index &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -36,17 +36,18 @@ <h1 id="index">Index</h1>
 <div class="genindex-jumpbox">
  <a href="#A"><strong>A</strong></a>
  | <a href="#C"><strong>C</strong></a>
+ | <a href="#D"><strong>D</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>
  
 </div>
@@ -67,11 +68,21 @@ <h2 id="C">C</h2>
   <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.clear">clear() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
-  </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>
+  </ul></td>
+  <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.cov">cov (prog_algs.uncertain_data.UncertainData property)</a>
+</li>
+      <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.cumulative_relative_accuracy">cumulative_relative_accuracy() (prog_algs.predictors.ToEPredictionProfile method)</a>
+</li>
+  </ul></td>
+</tr></table>
+
+<h2 id="D">D</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.describe">describe() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -107,6 +118,13 @@ <h2 id="E">E</h2>
 
       <ul>
         <li><a href="getting_started.html#module-examples.horizon">module</a>
+</li>
+      </ul></li>
+      <li>
+    examples.kalman_filter
+
+      <ul>
+        <li><a href="getting_started.html#module-examples.kalman_filter">module</a>
 </li>
       </ul></li>
       <li>
@@ -186,6 +204,8 @@ <h2 id="I">I</h2>
 <h2 id="K">K</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.KalmanFilter">KalmanFilter (class in prog_algs.state_estimators)</a>
+</li>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples.key">key() (prog_algs.uncertain_data.UnweightedSamples method)</a>
 </li>
   </ul></td>
@@ -225,6 +245,8 @@ <h2 id="M">M</h2>
         <li><a href="getting_started.html#module-examples.eol_event">examples.eol_event</a>
 </li>
         <li><a href="getting_started.html#module-examples.horizon">examples.horizon</a>
+</li>
+        <li><a href="getting_started.html#module-examples.kalman_filter">examples.kalman_filter</a>
 </li>
         <li><a href="getting_started.html#module-examples.measurement_eqn_example">examples.measurement_eqn_example</a>
 </li>
@@ -239,12 +261,16 @@ <h2 id="M">M</h2>
         <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="prediction.html#prog_algs.predictors.Prediction.monotonicity">monotonicity() (prog_algs.predictors.Prediction method)</a>
+
+      <ul>
+        <li><a href="prediction.html#prog_algs.predictors.UnweightedSamplesPrediction.monotonicity">(prog_algs.predictors.UnweightedSamplesPrediction method)</a>
+</li>
+      </ul></li>
       <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>
@@ -258,37 +284,34 @@ <h2 id="P">P</h2>
       <li><a href="state_estimators.html#prog_algs.state_estimators.ParticleFilter">ParticleFilter (class in prog_algs.state_estimators)</a>
 </li>
       <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="prediction.html#prog_algs.predictors.ToEPredictionProfile.plot">plot() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.plot_hist">plot_hist() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
       <li><a href="uncertain_data.html#prog_algs.uncertain_data.UncertainData.plot_scatter">plot_scatter() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
+  </ul></td>
+  <td style="width: 33%; vertical-align: top;"><ul>
       <li><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.pop">pop() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
       <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><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><a href="prediction.html#prog_algs.predictors.ToEPredictionProfile.prognostic_horizon">prognostic_horizon() (prog_algs.predictors.ToEPredictionProfile method)</a>
 </li>
-      <li>
-    prog_algs.metrics
+  </ul></td>
+</tr></table>
 
-      <ul>
-        <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>
-</li>
-      <li><a href="exceptions.html#prog_algs.exceptions.ProgAlgInputException">ProgAlgInputException</a>
-</li>
-      <li><a href="exceptions.html#prog_algs.exceptions.ProgAlgTypeError">ProgAlgTypeError</a>
+<h2 id="R">R</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.relative_accuracy">relative_accuracy() (prog_algs.uncertain_data.UncertainData method)</a>
 </li>
   </ul></td>
 </tr></table>
@@ -351,20 +374,6 @@ <h2 id="V">V</h2>
   </ul></td>
 </tr></table>
 
-<h2 id="W">W</h2>
-<table style="width: 100%" class="indextable genindextable"><tr>
-  <td style="width: 33%; vertical-align: top;"><ul>
-      <li><a href="exceptions.html#prog_algs.exceptions.ProgAlgException.with_traceback">with_traceback() (prog_algs.exceptions.ProgAlgException method)</a>
-
-      <ul>
-        <li><a href="exceptions.html#prog_algs.exceptions.ProgAlgInputException.with_traceback">(prog_algs.exceptions.ProgAlgInputException method)</a>
-</li>
-        <li><a href="exceptions.html#prog_algs.exceptions.ProgAlgTypeError.with_traceback">(prog_algs.exceptions.ProgAlgTypeError method)</a>
-</li>
-      </ul></li>
-  </ul></td>
-</tr></table>
-
 <h2 id="X">X</h2>
 <table style="width: 100%" class="indextable genindextable"><tr>
   <td style="width: 33%; vertical-align: top;"><ul>
@@ -398,11 +407,11 @@ <h3>Navigation</h3>
 <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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -435,7 +444,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/getting_started.html b/docs/getting_started.html
index db3b42c7..2f2666e7 100644
--- a/docs/getting_started.html
+++ b/docs/getting_started.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>Getting Started &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -36,18 +36,18 @@
   <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>The NASA Prognostics Algorithms Package is a Python framework for defining, building, using, and testing Algorithms for prognostics (computation of remaining useful life) of engineering systems, and provides a set of prognostics algorithms developed within this framework, suitable for use in prognostics applications. It can be used in conjunction with the Prognostics Models Package (<cite>prog_models</cite>) to perform research in prognostics with prognostics systems.</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>
+<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>), this version 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>
+<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
@@ -62,22 +62,22 @@ <h2>Summary<a class="headerlink" href="#summary" title="Permalink to this headli
 <p>A few definitions to get started:</p>
 <ul class="simple">
 <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>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>performance metrics</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>
+<li><p><strong>process noise</strong>: representing uncertainty in the model transition (e.g., model uncertainty).</p></li>
+<li><p><strong>measurement noise</strong>: representing uncertainty in the measurement process (e.g., sensor sensitivity, sensor misalignements, environmental effects).</p></li>
 </ul>
 <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>
+<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 <a class="reference external" href="prediction.html#id1">Prediction object</a>).</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>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="https://nasa.github.io/prog_models/simresult.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>
@@ -134,7 +134,8 @@ <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/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">
+<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><p>An example using the UnscentedTransformPredictor class to predict the degredation of a battery.</p>
+<div class="line-block">
 <div class="line"><br /></div>
 </div>
 </dd>
@@ -160,7 +161,7 @@ <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/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>
+<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., End Of Life (EOL)) of a system. Many system models have multiple events that can occur. In some prognostics applications, users are not interested in predicting a specific event, and are instead interested in when the first event occurs, regardless of the event. This example demonstrates how to predict the first event of a system.</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>
@@ -197,9 +198,18 @@ <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/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.kalman_filter</span></code></a></dt><dd><span class="target" id="module-examples.kalman_filter"></span><p>This example demonstrates use of the Kalman Filter State Estimator with a LinearModel.</p>
+<p>First, a linear model is defined. Then the KF State estimator is used with fake data to estimate state.</p>
+<div class="line-block">
+<div class="line"><br /></div>
+</div>
+</dd>
+</dl>
+</li>
+<li><dl>
 <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>
+<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 subclass 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.</p>
 </dd>
 <dt>Results:</dt><dd><ol class="lowerroman simple">
 <li><p>Estimate of the current state given various times</p></li>
@@ -214,7 +224,9 @@ <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/067e6c82fdc9573fb921219d62002181/new_state_estimator_example.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.new_state_estimator_example</span></code></a></dt><dd><span class="target" id="module-examples.new_state_estimator_example"></span><div class="line-block">
+<dt><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></dt><dd><span class="target" id="module-examples.new_state_estimator_example"></span><p>An example illustrating the creation of a new state estimator.</p>
+<p>In this example a basic state estimator is constructed by subclassing the StateEstimator class. This StateEstimator is then demonstrated with a ThrownObject model</p>
+<div class="line-block">
 <div class="line"><br /></div>
 </div>
 </dd>
@@ -243,14 +255,16 @@ <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/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">
+<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><p>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.</p>
+<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">
+<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><p>In this example the BatteryElectroChemEOD model is used with a particle filter to estimate the state of the battery</p>
+<div class="line-block">
 <div class="line"><br /></div>
 </div>
 </dd>
@@ -270,63 +284,6 @@ <h2>Extending<a class="headerlink" href="#extending" title="Permalink to this he
 <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-2">
-<h2>Updates in v1.2<a class="headerlink" href="#updates-in-v1-2" 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>
 
 
@@ -353,18 +310,17 @@ <h3>Navigation</h3>
 <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-2">Updates in v1.2</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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -399,7 +355,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/index.html b/docs/index.html
index 65490233..384749b9 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>Prognostics Algorithms Python Package &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -46,7 +46,6 @@ <h1>Prognostics Algorithms Python Package<a class="headerlink" href="#prognostic
 <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-2">Updates in v1.2</a></li>
 </ul>
 </li>
 <li class="toctree-l1"><a class="reference internal" href="state_estimators.html">State Estimators</a><ul>
@@ -69,19 +68,15 @@ <h1>Prognostics Algorithms Python Package<a class="headerlink" href="#prognostic
 <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="metrics.html">Metrics</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>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="release.html#updates-in-v1-3">Updates in V1.3</a></li>
+<li class="toctree-l2"><a class="reference internal" href="release.html#updates-in-v1-2">Updates in v1.2</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">
@@ -91,9 +86,9 @@ <h2>Citing this repository<a class="headerlink" href="#citing-this-repository" t
 <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">month     = May,</div>
+<div class="line">year      = 2022,</div>
+<div class="line">version   = {1.3.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>
@@ -101,7 +96,7 @@ <h2>Citing this repository<a class="headerlink" href="#citing-this-repository" t
 </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, 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>
+<li><p>Teubert, C. Kulkarni, M. Corbetta. Prognostics Algorithms Python Package, v1.3.0, May 2022. 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">
@@ -143,11 +138,11 @@ <h3>Navigation</h3>
 <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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -181,7 +176,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/metrics.html b/docs/metrics.html
index f3278697..44c68d87 100644
--- a/docs/metrics.html
+++ b/docs/metrics.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>Metrics &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -37,16 +37,38 @@
 <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"></span><dl class="py function">
+<dt class="sig sig-object py" id="prog_algs.metrics.cumulative_relative_accuracy">
+<span class="sig-prename descclassname"><span class="pre">prog_algs.metrics.</span></span><span class="sig-name descname"><span class="pre">cumulative_relative_accuracy</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">toe_profile</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="prediction.html#prog_algs.predictors.ToEPredictionProfile" title="prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile"><span class="pre">prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">dict</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="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span></span><a class="headerlink" href="#prog_algs.metrics.cumulative_relative_accuracy" title="Permalink to this definition">¶</a></dt>
+<dd><p>Compute cumulative relative accuracy for a given profile, defined as the normalized sum of relative prediction accuracies at specific time instances.</p>
+<p>CRA = Σ(RA)/N for each event
+Where Σ is summation of all relative accuracies for a given profile and N is the total count of profiles (Journal Prognostics Health Management, Saxena et al.)
+:param toe_profile: A profile of predictions, the combination of multiple predictions
+:type toe_profile: ToEPredictionProfile
+:param ground_truth: Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {‘event1’: 47.3, ‘event2’: 52.1, ‘event3’: 46.1}
+:type ground_truth: dict
+:param kwargs: configuration arguments. Accepted args include:
+:type kwargs: optional</p>
+<dl class="field-list simple">
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p>Dictionary containing cumulative relative accuracy (value) for each event (key). e.g., {‘event1’: 12.3, ‘event2’: 15.1}</p>
+</dd>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>dict</p>
+</dd>
+</dl>
+</dd></dl>
+
+<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>
+<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><span class="p"><span class="pre">:</span></span><span class="w"> </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">time</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </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">float</span></span></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>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>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>list</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.
+* 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>
@@ -59,6 +81,44 @@ <h1>Metrics<a class="headerlink" href="#metrics" title="Permalink to this headli
 </dl>
 </dd></dl>
 
+<dl class="py function">
+<dt class="sig sig-object py" id="prog_algs.metrics.prognostic_horizon">
+<span class="sig-prename descclassname"><span class="pre">prog_algs.metrics.</span></span><span class="sig-name descname"><span class="pre">prognostic_horizon</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">toe_profile</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="prediction.html#prog_algs.predictors.ToEPredictionProfile" title="prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile"><span class="pre">prog_algs.predictors.toe_prediction_profile.ToEPredictionProfile</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">criteria_eqn</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Callable</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">dict</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></span><a class="headerlink" href="#prog_algs.metrics.prognostic_horizon" title="Permalink to this definition">¶</a></dt>
+<dd><p>Compute prognostic horizon metric, defined as the difference between a time ti, when the predictions meet specified performance criteria, and the time corresponding to the true Time of Event (ToE), for each event.
+PH = ToE - ti
+:param toe_profile: A profile of predictions, the combination of multiple predictions
+:type toe_profile: ToEPredictionProfile
+:param criteria_eqn: A function (tte: UncertainData, ground_truth_tte: dict[str, float]) -&gt; dict[str, bool] calculating whether a prediction in ToEPredictionProfile meets some criteria.</p>
+<blockquote>
+<div><div class="line-block">
+<div class="line">Args:</div>
+<div class="line-block">
+<div class="line">* tte (UncertainData): A single prediction of Time of Event (ToE)</div>
+<div class="line">* ground truth tte (dict[str, float]): Ground truth passed into prognostics_horizon</div>
+</div>
+<div class="line">Returns: Map of event names to boolean representing if the event has been met.</div>
+<div class="line-block">
+<div class="line">e.g., {‘event1’: True, ‘event2’: False}</div>
+</div>
+</div>
+</div></blockquote>
+<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>) – Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {‘event1’: 47.3, ‘event2’: 52.1, ‘event3’: 46.1}</p></li>
+<li><p><strong>kwargs</strong> (<em>optional</em>) – configuration arguments. Accepted args include:
+* print (bool): Boolean specifying whether the prognostic horizon metric should be printed.</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Dictionary containing prognostic horizon calculations (value) for each event (key). e.g., {‘event1’: 12.3, ‘event2’: 15.1}</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p>dict</p>
+</dd>
+</dl>
+</dd></dl>
+
 </section>
 
 
@@ -88,7 +148,8 @@ <h3>Navigation</h3>
 <li class="toctree-l1 current"><a class="current reference internal" href="#">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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
 </ul>
 
diff --git a/docs/objects.inv b/docs/objects.inv
index b9f900e4..52f9fed5 100644
Binary files a/docs/objects.inv and b/docs/objects.inv differ
diff --git a/docs/prediction.html b/docs/prediction.html
index 9ea05649..4d9cb8fd 100644
--- a/docs/prediction.html
+++ b/docs/prediction.html
@@ -6,7 +6,7 @@
     <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.1 documentation</title>
+    <title>Prediction &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -15,7 +15,7 @@
     <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="Release Notes" href="release.html" />
     <link rel="prev" title="Uncertain Data" href="uncertain_data.html" />
    
   <link rel="stylesheet" href="_static/custom.css" type="text/css" />
@@ -37,25 +37,25 @@
 <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>Predictions store the result of a prediction (i.e., returned by the predict method of a predictor). They store values (with uncertainty) at different future times. These are used to store states, inputs, outputs, perfomance metrics, and event states with uncertainty at savepoints.</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>
+<em class="property"><span class="pre">class</span><span class="w"> </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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</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>
+<li><p><strong>times</strong> (<em>list</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>list</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>
+<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">mean</span></span><em class="property"><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="pre">list</span></em><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>
@@ -65,16 +65,41 @@ <h3>Base Prediction<a class="headerlink" href="#base-prediction" title="Permalin
 </p>
 </dd>
 <dt class="field-even">Return type</dt>
-<dd class="field-even"><p>array(dict)</p>
+<dd class="field-even"><p>list[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.monotonicity">
+<span class="sig-name descname"><span class="pre">monotonicity</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">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="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span></span><a class="headerlink" href="#prog_algs.predictors.Prediction.monotonicity" title="Permalink to this definition">¶</a></dt>
+<dd><p>Calculate monotonicty for a single prediction.
+Given a single prediction, for each event: go through all predicted states and compare those to the next one.
+Calculates monotonicity for each event key using its associated mean value in UncertainData.</p>
+<p>monotonoicity = <a href="#id2"><span class="problematic" id="id3">|Σsign(i+1 - i) / N-1|</span></a>
+Where N is number of measurements and sign indicates sign of calculation.
+Coble, J., et. al. (2021). Identifying Optimal Prognostic Parameters from Data: A Genetic Algorithms Approach. Annual Conference of the PHM Society.
+<a class="reference external" href="http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404">http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404</a>
+Baptistia, M., et. al. (2022). Relation between prognostics predictor evaluation metrics and local interpretability SHAP values. Aritifical Intelligence, Volume 306.
+<a class="reference external" href="https://www.sciencedirect.com/science/article/pii/S0004370222000078">https://www.sciencedirect.com/science/article/pii/S0004370222000078</a></p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>None</strong> – </p>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Value between [0, 1] indicating monotonicity of a given event for the Prediction.</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p>float</p>
+</dd>
+</dl>
+</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>
+<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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><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></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>
@@ -96,7 +121,7 @@ <h3>Base Prediction<a class="headerlink" href="#base-prediction" title="Permalin
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</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>
@@ -115,7 +140,7 @@ <h3>UnweightedSamplesPrediction<a class="headerlink" href="#unweightedsamplespre
 
 <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>
+<span class="sig-name descname"><span class="pre">index</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">value</span></span></em><span class="optional">[</span>, <em class="sig-param"><span class="n"><span class="pre">start</span></span></em><span class="optional">[</span>, <em class="sig-param"><span class="n"><span class="pre">stop</span></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>
@@ -123,7 +148,7 @@ <h3>UnweightedSamplesPrediction<a class="headerlink" href="#unweightedsamplespre
 
 <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>
+<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">mean</span></span><em class="property"><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="pre">list</span></em><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>
@@ -133,16 +158,41 @@ <h3>UnweightedSamplesPrediction<a class="headerlink" href="#unweightedsamplespre
 </p>
 </dd>
 <dt class="field-even">Return type</dt>
-<dd class="field-even"><p>array(dict)</p>
+<dd class="field-even"><p>list[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.monotonicity">
+<span class="sig-name descname"><span class="pre">monotonicity</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">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="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span></span><a class="headerlink" href="#prog_algs.predictors.UnweightedSamplesPrediction.monotonicity" title="Permalink to this definition">¶</a></dt>
+<dd><p>Calculate monotonicty for a single prediction.
+Given a single prediction, for each event: go through all predicted states and compare those to the next one.
+Calculates monotonicity for each event key using its associated mean value in UncertainData.</p>
+<p>monotonoicity = <a href="#id4"><span class="problematic" id="id5">|Σsign(i+1 - i) / N-1|</span></a>
+Where N is number of measurements and sign indicates sign of calculation.
+Coble, J., et. al. (2021). Identifying Optimal Prognostic Parameters from Data: A Genetic Algorithms Approach. Annual Conference of the PHM Society.
+<a class="reference external" href="http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404">http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404</a>
+Baptistia, M., et. al. (2022). Relation between prognostics predictor evaluation metrics and local interpretability SHAP values. Aritifical Intelligence, Volume 306.
+<a class="reference external" href="https://www.sciencedirect.com/science/article/pii/S0004370222000078">https://www.sciencedirect.com/science/article/pii/S0004370222000078</a></p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><p><strong>None</strong> – </p>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Value between [0, 1] indicating monotonicity of a given event for the Prediction.</p>
+</dd>
+<dt class="field-odd">Return type</dt>
+<dd class="field-odd"><p>float</p>
+</dd>
+</dl>
+</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>
+<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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UnweightedSamples" title="prog_algs.uncertain_data.unweighted_samples.UnweightedSamples"><span class="pre">prog_algs.uncertain_data.unweighted_samples.UnweightedSamples</span></a></span></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>
@@ -165,11 +215,11 @@ <h3>UnweightedSamplesPrediction<a class="headerlink" href="#unweightedsamplespre
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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 Time of Event (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>
+<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="w"> </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="w"> </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>
@@ -183,7 +233,7 @@ <h2>ToE Prediction Profile<a class="headerlink" href="#toe-prediction-profile" t
 
 <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>
+<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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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="w"> </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>
@@ -216,9 +266,29 @@ <h2>ToE Prediction Profile<a class="headerlink" href="#toe-prediction-profile" t
 <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.cumulative_relative_accuracy">
+<span class="sig-name descname"><span class="pre">cumulative_relative_accuracy</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ground_truth</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="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.cumulative_relative_accuracy" title="Permalink to this definition">¶</a></dt>
+<dd><p>Compute cumulative relative accuracy for a given profile, defined as the normalized sum of relative prediction accuracies at specific time instances.</p>
+<p>CRA = Σ(RA)/N for each event
+Where Σ is summation of all relative accuracies for a given profile and N is the total count of profiles (Journal Prognostics Health Management, Saxena et al.)
+:param ground_truth: Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {‘event1’: 47.3, ‘event2’: 52.1, ‘event3’: 46.1}
+:type ground_truth: dict
+:param kwargs: configuration arguments. Accepted args include:
+:type kwargs: optional</p>
+<dl class="field-list simple">
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p>Dictionary containing cumulative relative accuracy (value) for each event (key). e.g., {‘event1’: 12.3, ‘event2’: 15.1}</p>
+</dd>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>dict</p>
+</dd>
+</dl>
+</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>
+<span class="sig-name descname"><span class="pre">get</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">k</span></span></em><span class="optional">[</span>, <em class="sig-param"><span class="n"><span class="pre">d</span></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">
@@ -233,9 +303,34 @@ <h2>ToE Prediction Profile<a class="headerlink" href="#toe-prediction-profile" t
 <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.plot">
+<span class="sig-name descname"><span class="pre">plot</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="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">dict</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</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="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">show</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">True</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></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.plot" title="Permalink to this definition">¶</a></dt>
+<dd><p>Produce an alpha-beta plot depicting the TtE distribution by time of prediction.</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> – dict = None
+Optional dictionary argument containing event and its respective ground truth value; none by default and plotted if specified</p></li>
+<li><p><strong>alpha</strong> – float
+Optional alpha value; none by default and plotted if specified</p></li>
+<li><p><strong>show</strong> – bool = True
+Optional bool value; specify whether to display generated plots or not</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p><dl class="simple">
+<dt>dict</dt><dd><p>Collection of generated matplotlib figures for each event in profile</p>
+</dd>
+</dl>
+</p>
+</dd>
+</dl>
+</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>
+<span class="sig-name descname"><span class="pre">pop</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">k</span></span></em><span class="optional">[</span>, <em class="sig-param"><span class="n"><span class="pre">d</span></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>
 
@@ -245,14 +340,52 @@ <h2>ToE Prediction Profile<a class="headerlink" href="#toe-prediction-profile" t
 <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.prognostic_horizon">
+<span class="sig-name descname"><span class="pre">prognostic_horizon</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">criteria_eqn</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ground_truth</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="w"> </span><span class="pre">float</span><span class="p"><span class="pre">]</span></span></span></span><a class="headerlink" href="#prog_algs.predictors.ToEPredictionProfile.prognostic_horizon" title="Permalink to this definition">¶</a></dt>
+<dd><p>Compute prognostic horizon metric, defined as the difference between a time ti, when the predictions meet specified performance criteria, and the time corresponding to the true Time of Event (ToE), for each event.
+PH = ToE - ti
+:param toe_profile: A profile of predictions, the combination of multiple predictions
+:type toe_profile: ToEPredictionProfile
+:param criteria_eqn: A function (toe: UncertainData, ground_truth: dict[str, float]) -&gt; dict[str, bool] calculating whether a prediction in ToEPredictionProfile meets some criteria.</p>
+<blockquote>
+<div><div class="line-block">
+<div class="line">Args:</div>
+<div class="line-block">
+<div class="line">* toe (UncertainData): A single prediction of Time of Event (ToE)</div>
+<div class="line">* ground truth (dict[str, float]): Ground truth passed into prognostics_horizon</div>
+</div>
+<div class="line">Returns: Map of event names to boolean representing if the event has been met.</div>
+<div class="line-block">
+<div class="line">e.g., {‘event1’: True, ‘event2’: False}</div>
+</div>
+</div>
+</div></blockquote>
+<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>) – Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {‘event1’: 47.3, ‘event2’: 52.1, ‘event3’: 46.1}</p></li>
+<li><p><strong>kwargs</strong> (<em>optional</em>) – configuration arguments. Accepted args include:
+* print (bool): Boolean specifying whether the prognostic horizon metric should be printed.</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p>Dictionary containing prognostic horizon calculations (value) for each event (key). e.g., {‘event1’: 12.3, ‘event2’: 15.1}</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 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>
+<span class="sig-name descname"><span class="pre">setdefault</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">k</span></span></em><span class="optional">[</span>, <em class="sig-param"><span class="n"><span class="pre">d</span></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>
+<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="n"><span class="pre">E</span></span></em>, <span class="optional">]</span><em class="sig-param"><span class="n"><span class="pre">**F</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>&#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>
@@ -297,11 +430,11 @@ <h3>Navigation</h3>
 <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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -309,7 +442,7 @@ <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>
+      <li>Next: <a href="release.html" title="next chapter">Release Notes</a></li>
   </ul></li>
 </ul>
 </div>
@@ -336,7 +469,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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 b88fafc7..0a8f4f73 100644
--- a/docs/predictors.html
+++ b/docs/predictors.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>Predictors &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -35,7 +35,7 @@
             
   <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>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, performance metrics, 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>
@@ -47,8 +47,8 @@ <h1>Predictors<a class="headerlink" href="#predictors" title="Permalink to this
 <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>
+<span class="gp">&gt;&gt;&gt; </span><span class="n">pred_results</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">pred_results</span><span class="o">.</span><span class="n">time_of_event</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>
@@ -59,11 +59,13 @@ <h2>Included Predictors<a class="headerlink" href="#included-predictors" title="
 <h3>Monte Carlo Predictor<a class="headerlink" href="#monte-carlo-predictor" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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>t0<span class="classifier">float</span></dt><dd><p>Initial time at which prediction begins, e.g., 0</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>
@@ -84,13 +86,15 @@ <h3>Monte Carlo Predictor<a class="headerlink" href="#monte-carlo-predictor" tit
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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>t0<span class="classifier">float</span></dt><dd><p>Initial time at which prediction begins, e.g., 0</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>
@@ -114,7 +118,7 @@ <h3>Unscented Transform Predictor<a class="headerlink" href="#unscented-transfor
 <h2>Predictor Interface<a class="headerlink" href="#predictor-interface" title="Permalink to this headline">¶</a></h2>
 <dl class="py class">
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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">
@@ -129,7 +133,7 @@ <h2>Predictor Interface<a class="headerlink" href="#predictor-interface" title="
 </dl>
 <dl class="py method">
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </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="w"> </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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Callable</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">prog_algs.predictors.prediction.PredictionResults</span></span></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>
@@ -145,19 +149,16 @@ <h2>Predictor Interface<a class="headerlink" href="#predictor-interface" title="
 <li><p>save_pts (list[float]): Any additional savepoints (s) e.g., [10.1, 22.5]</p></li>
 </ul>
 </p></li>
+<li><p><strong>namedtuple</strong> (<em>Return PredictionResults</em>) – </p></li>
+<li><p><strong>----------</strong> – </p></li>
+<li><p><strong>times</strong> (<em>List</em><em>[</em><em>float</em><em>]</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> (<a class="reference internal" href="prediction.html#prog_algs.predictors.Prediction" title="prog_algs.predictors.Prediction"><em>Prediction</em></a>) – 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> (<a class="reference internal" href="prediction.html#prog_algs.predictors.Prediction" title="prog_algs.predictors.Prediction"><em>Prediction</em></a>) – 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> (<a class="reference internal" href="prediction.html#prog_algs.predictors.Prediction" title="prog_algs.predictors.Prediction"><em>Prediction</em></a>) – 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> (<a class="reference internal" href="prediction.html#prog_algs.predictors.Prediction" title="prog_algs.predictors.Prediction"><em>Prediction</em></a>) – 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> (<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 Time of Event (ToE) for each predicted event, represented by some subclass of UncertaintData (e.g., MultivariateNormalDist)</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>
 
@@ -194,11 +195,11 @@ <h3>Navigation</h3>
 </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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -233,7 +234,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/py-modindex.html b/docs/py-modindex.html
index e812d045..0205ff59 100644
--- a/docs/py-modindex.html
+++ b/docs/py-modindex.html
@@ -5,7 +5,7 @@
   <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.2.1 documentation</title>
+    <title>Python Module Index &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -37,8 +37,7 @@
    <h1>Python Module Index</h1>
 
    <div class="modindex-jumpbox">
-   <a href="#cap-e"><strong>e</strong></a> | 
-   <a href="#cap-p"><strong>p</strong></a>
+   <a href="#cap-e"><strong>e</strong></a>
    </div>
 
    <table class="indextable modindextable">
@@ -71,6 +70,11 @@ <h1>Python Module Index</h1>
        <td>&#160;&#160;&#160;
        <a href="getting_started.html#module-examples.horizon"><code class="xref">examples.horizon</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.kalman_filter"><code class="xref">examples.kalman_filter</code></a></td><td>
+       <em></em></td></tr>
      <tr class="cg-1">
        <td></td>
        <td>&#160;&#160;&#160;
@@ -106,20 +110,6 @@ <h1>Python Module Index</h1>
        <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>
-     <tr>
-       <td><img src="_static/minus.png" class="toggler"
-              id="toggle-2" style="display: none" alt="-" /></td>
-       <td>
-       <code class="xref">prog_algs</code></td><td>
-       <em></em></td></tr>
-     <tr class="cg-2">
-       <td></td>
-       <td>&#160;&#160;&#160;
-       <a href="metrics.html#module-prog_algs.metrics"><code class="xref">prog_algs.metrics</code></a></td><td>
-       <em></em></td></tr>
    </table>
 
 
@@ -146,11 +136,11 @@ <h3>Navigation</h3>
 <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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -183,7 +173,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/release.html b/docs/release.html
new file mode 100644
index 00000000..0be4cbb1
--- /dev/null
+++ b/docs/release.html
@@ -0,0 +1,196 @@
+
+<!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>Release Notes &#8212; Prognostics Algorithms Python Package 1.3.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="prev" title="Prediction" href="prediction.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="release-notes">
+<h1><a class="toc-backref" href="#contents">Release Notes</a><a class="headerlink" href="#release-notes" title="Permalink to this headline">¶</a></h1>
+<div class="contents topic" id="contents">
+<p class="topic-title">Contents</p>
+<ul class="simple">
+<li><p><a class="reference internal" href="#release-notes" id="id1">Release Notes</a></p>
+<ul>
+<li><p><a class="reference internal" href="#updates-in-v1-3" id="id2">Updates in V1.3</a></p></li>
+<li><p><a class="reference internal" href="#updates-in-v1-2" id="id3">Updates in v1.2</a></p>
+<ul>
+<li><p><a class="reference internal" href="#note-for-existing-users" id="id4">Note for Existing Users</a></p></li>
+<li><p><a class="reference internal" href="#general-updates" id="id5">General Updates</a></p></li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+</div>
+<section id="updates-in-v1-3">
+<h2><a class="toc-backref" href="#contents">Updates in V1.3</a><a class="headerlink" href="#updates-in-v1-3" title="Permalink to this headline">¶</a></h2>
+<ul class="simple">
+<li><p><strong>New State Estimator Added</strong> <a class="reference internal" href="state_estimators.html#prog_algs.state_estimators.KalmanFilter" title="prog_algs.state_estimators.KalmanFilter"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.state_estimators.KalmanFilter</span></code></a>. Works with models derived from <code class="xref py py-class docutils literal notranslate"><span class="pre">prog_models.LinearModel</span></code>. See <a class="reference download internal" download="" href="_downloads/2c94710bd8ef995f1d885fb2ba73f158/kalman_filter.py"><code class="xref download docutils literal notranslate"><span class="pre">examples.kalman_filter</span></code></a></p></li>
+<li><p><strong>New Predictor Added</strong> <a class="reference internal" href="predictors.html#prog_algs.predictors.UnscentedTransformPredictor" title="prog_algs.predictors.UnscentedTransformPredictor"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.predictors.UnscentedTransformPredictor</span></code></a>. See <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></p></li>
+<li><p>Initial state estimate (x0) can now be passed as <cite>UncertainData</cite> to represent initial state uncertainty. See <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></p></li>
+<li><p>Added new metrics for <a class="reference internal" href="prediction.html#prog_algs.predictors.ToEPredictionProfile" title="prog_algs.predictors.ToEPredictionProfile"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.predictors.ToEPredictionProfile</span></code></a>: Prognostics horizon, Cumulative Relative Accuracy (CRA). See <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></p></li>
+<li><p>Added ability to plot <a class="reference internal" href="prediction.html#prog_algs.predictors.ToEPredictionProfile" title="prog_algs.predictors.ToEPredictionProfile"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.predictors.ToEPredictionProfile</span></code></a>: profile.plot(). See <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></p></li>
+<li><p>Added new metric for <a class="reference internal" href="prediction.html#prog_algs.predictors.Prediction" title="prog_algs.predictors.Prediction"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.predictors.Prediction</span></code></a>: Monotonicity, Relative Accuracy (RA)</p></li>
+<li><p>Added new metric for <a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.uncertain_data.UncertainData</span></code></a> (and subclasses): Root Mean Square Error (RMSE)</p></li>
+<li><p>Added new describe method for <a class="reference internal" href="uncertain_data.html#prog_algs.uncertain_data.UncertainData" title="prog_algs.uncertain_data.UncertainData"><code class="xref py py-class docutils literal notranslate"><span class="pre">prog_algs.uncertain_data.UncertainData</span></code></a> (and subclasses)</p></li>
+<li><p>Add support for python 3.10</p></li>
+<li><p>Various performance improvements and bugfixes</p></li>
+</ul>
+</section>
+<section id="updates-in-v1-2">
+<h2><a class="toc-backref" href="#contents">Updates in v1.2</a><a class="headerlink" href="#updates-in-v1-2" title="Permalink to this headline">¶</a></h2>
+<section id="note-for-existing-users">
+<h3><a class="toc-backref" href="#contents">Note for Existing Users</a><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 structure (e.g., ToE.final_state[‘event1’]).</p></li>
+</ul>
+</dd>
+</dl>
+</li>
+</ul>
+</section>
+<section id="general-updates">
+<h3><a class="toc-backref" href="#contents">General Updates</a><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>
+
+
+          </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"><a class="reference internal" href="prediction.html">Prediction</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 external" href="https://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1 current"><a class="current reference internal" href="#">Release Notes</a><ul>
+<li class="toctree-l2"><a class="reference internal" href="#updates-in-v1-3">Updates in V1.3</a></li>
+<li class="toctree-l2"><a class="reference internal" href="#updates-in-v1-2">Updates in v1.2</a></li>
+</ul>
+</li>
+</ul>
+
+<div class="relations">
+<h3>Related Topics</h3>
+<ul>
+  <li><a href="index.html">Documentation overview</a><ul>
+      <li>Previous: <a href="prediction.html" title="previous chapter">Prediction</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 © 2021 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/search.html b/docs/search.html
index e783a3ec..ac9bfa23 100644
--- a/docs/search.html
+++ b/docs/search.html
@@ -5,7 +5,7 @@
   <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Search &#8212; Prognostics Algorithms Python Package 1.2.1 documentation</title>
+    <title>Search &#8212; Prognostics Algorithms Python Package 1.3.0 documentation</title>
     <link rel="stylesheet" type="text/css" href="_static/pygments.css" />
     <link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
     
@@ -90,11 +90,11 @@ <h3>Navigation</h3>
 <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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -117,7 +117,7 @@ <h3>Related Topics</h3>
       <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.
+Copyright © 2021 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/searchindex.js b/docs/searchindex.js
index 74b723c0..678c07bd 100644
--- a/docs/searchindex.js
+++ b/docs/searchindex.js
@@ -1,1 +1,1 @@
-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,"-"],horizon:[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":[2,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,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:[],back:2,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:[],come:2,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:[2,6],host:2,how:[2,7],html:[],http:[2,3,6],hypothesi:6,i2fnz:[],i:[0,2,4,5,6,7],id:[],identifi:0,ignor:2,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,2],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,mission: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:[2,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,often:2,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],outsid:2,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,suffici:2,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,unconcern:2,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":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
+Search.setIndex({docnames:["getting_started","index","prediction","predictors","release","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:["getting_started.rst","index.rst","prediction.rst","predictors.rst","release.rst","state_estimators.rst","uncertain_data.rst"],objects:{"prog_algs.predictors":[[3,1,1,"","MonteCarlo"],[2,1,1,"","Prediction"],[3,1,1,"","Predictor"],[2,1,1,"","ToEPredictionProfile"],[3,1,1,"","UnscentedTransformPredictor"],[2,1,1,"","UnweightedSamplesPrediction"]],"prog_algs.predictors.Prediction":[[2,2,1,"","mean"],[2,3,1,"","monotonicity"],[2,3,1,"","snapshot"]],"prog_algs.predictors.Predictor":[[3,3,1,"","predict"]],"prog_algs.predictors.ToEPredictionProfile":[[2,3,1,"","add_prediction"],[2,3,1,"","alpha_lambda"],[2,3,1,"","clear"],[2,3,1,"","cumulative_relative_accuracy"],[2,3,1,"","get"],[2,3,1,"","items"],[2,3,1,"","keys"],[2,3,1,"","plot"],[2,3,1,"","pop"],[2,3,1,"","popitem"],[2,3,1,"","prognostic_horizon"],[2,3,1,"","setdefault"],[2,3,1,"","update"],[2,3,1,"","values"]],"prog_algs.predictors.UnweightedSamplesPrediction":[[2,3,1,"","count"],[2,3,1,"","index"],[2,2,1,"","mean"],[2,3,1,"","monotonicity"],[2,3,1,"","snapshot"]],"prog_algs.state_estimators":[[5,1,1,"","KalmanFilter"],[5,1,1,"","ParticleFilter"],[5,1,1,"","StateEstimator"],[5,1,1,"","UnscentedKalmanFilter"]],"prog_algs.state_estimators.StateEstimator":[[5,3,1,"","estimate"],[5,2,1,"","x"]],"prog_algs.uncertain_data":[[6,1,1,"","MultivariateNormalDist"],[6,1,1,"","ScalarData"],[6,1,1,"","UncertainData"],[6,1,1,"","UnweightedSamples"]],"prog_algs.uncertain_data.UncertainData":[[6,2,1,"","cov"],[6,3,1,"","describe"],[6,3,1,"","keys"],[6,2,1,"","mean"],[6,2,1,"","median"],[6,3,1,"","metrics"],[6,3,1,"","percentage_in_bounds"],[6,3,1,"","plot_hist"],[6,3,1,"","plot_scatter"],[6,3,1,"","relative_accuracy"],[6,3,1,"","sample"]],"prog_algs.uncertain_data.UnweightedSamples":[[6,3,1,"","key"]],examples:[[0,0,0,"-","basic_example"],[0,0,0,"-","benchmarking_example"],[0,0,0,"-","eol_event"],[0,0,0,"-","horizon"],[0,0,0,"-","kalman_filter"],[0,0,0,"-","measurement_eqn_example"],[0,0,0,"-","new_state_estimator_example"],[0,0,0,"-","particle_filter_battery_example"],[0,0,0,"-","playback"],[0,0,0,"-","predict_specific_event"],[0,0,0,"-","thrown_object_example"],[0,0,0,"-","utpredictor"]]},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","property","Python property"],"3":["py","method","Python method"]},objtypes:{"0":"py:module","1":"py:class","2":"py:property","3":"py:method"},terms:{"0":[0,1,2,3,5,6],"000":[],"003":5,"013":[],"022":[],"023":[],"029":[],"033":[],"042":[],"1":[0,1,2,3,5,6],"10":[3,4],"100":6,"12":[2,5],"121":[],"125":[],"139":[],"1404":2,"148":[],"15":2,"156":[],"1s":3,"2":[0,1,2,5,6],"20":[2,3],"2020":[],"2020_nasa_prog_model":[],"2021":2,"2021_nasa_prog_alg":1,"2022":[1,2],"205":[],"206":[],"22":3,"221":5,"2233":2,"23":6,"239":6,"2rvf2estujordgwa":[],"3":[0,1,2,5,6],"306":2,"332":5,"3da2pd5nhvsohjcrdc1g2r3ditpu7yic7w":[],"3v8pdw8":[],"4":[5,6],"46":2,"47":2,"49ukg7ulu61tglw1lq27ugqylclhc4bgv7vq":[],"5":[3,6],"50":6,"52":2,"579aca":[],"7":3,"7150":[],"748":2,"7qgjvnjlrfr2ggsyx0eoy9upzn5spd86axggosekw2prz7du3vid3":[],"7rxnjc0kt5gc4djiv2sz3fb2iezg41":[],"8":5,"9":4,"95kx7":[],"9lspmxnylr3t5ao":[],"9xmsrmzyh1oljxmsr1oljxmsrmzyh1oljxmsr1oljxmsrmzyh1oll1oljxmsr1oljxmsrmzyh1oll1oljxmsrmzyh1oljxmsr1oll1oljxmsrmzyh1oll1oljxmsrmzyh1oll1oll0nff1oljxmsrmzyh1oljxmsq8dzb1oljxmsrmzyh1oljxmspxmspxmsr1oll1oljxmsrmzyh1oljxmsr1oll1oljxmsrmzyh1oll1olleaivxmsrmzyh1oll1oll1oljxmsrmzyh1ollna31xmsr1oljxmsr1oljxmsrmzyh1ollqovr1oljxmsr1oljxmsrmzyh1oll1olkkfapobxvvigabgadxmsqthkuofkhmz4dobnr1oljxmsr1oljxmspxmsr1oljxmsrfz4tuhwn1oll1oljxmsqbi7x1oljxmspzmslbmmhbmsdemsvfl8zgmsnim8jpk8f0m7r4m7f5nlb6jbh7jbidiroeibognkamhq":[],"\u03c3":2,"\u03c3sign":2,"abstract":[3,5,6],"boolean":2,"case":[0,2],"catch":[],"class":[0,2,3,4,5,6],"default":[2,3,6],"do":[0,1],"float":[2,3,4,5,6],"function":[0,1,2,3,5,6],"import":[3,5],"int":[2,3,5,6],"new":[0,1,3,4,5,6],"return":[0,2,3,4,5,6],"true":[0,2,6],"try":[],A:[0,1,2,3,5],AND:1,AS:1,At:5,BE:1,BUT:1,BY:1,By:3,FOR:1,For:0,IF:1,IN:1,IS:1,IT:1,ITS:1,If:[1,2,3,6],In:[0,2,3,5],Is:[0,2],It:0,NOT:1,No:[1,5],Not:[],OF:1,ON:1,OR:1,Of:[0,3],SUCH:1,THAT:1,THE:1,TO:1,The:[0,1,2,3,4,5,6],Then:0,There:0,These:[0,2,4],To:0,WILL:1,Will:[],With:6,__:[],__traceback__:[],abc:5,abil:4,about:[3,6],abov:[],accept:2,access:2,accesss:[],accord:5,account:[],accuraci:[2,4,6],achiev:0,acomplish:[],ad:[4,6],add:[0,2,4],add_predict:2,addit:[3,5,6],addition:[],adequ:0,adutnkaqjdscaaaaaelftksuqmcc:[],after:2,against:1,agenc:1,agre:1,agreement:1,al:[2,6],algorithm:[0,2,3,5],all:[0,1,2,3,4,6],allow:2,along:3,alpha:[0,2,3,5],alpha_lambda:2,also:[0,1,2,3,5],altern:[],alwai:0,an:[0,1,2,3,5],analysi:[0,1],analyz:6,ani:[0,1,3,5,6],annual:2,anoth:[],append:[],appli:0,applic:[0,1],approach:[0,2],appropri:[],aquisiton:[],ar:[0,1,2,3,4,5,6],arg:[2,5],argument:[2,3,5,6],aris:1,aritif:2,around:2,arrai:[2,6],articl:2,aspect:0,assign:[],associ:2,assum:0,assumpt:3,assur:[],aug:[],author:1,auto:[],autoclass:[],autogen:[],automat:[],avail:[0,5],avzyde4ipuk57osfnjm4fycqqoe0jhy2smylfoo56zyo6becos5uvddj7vih0zp:[],back:0,backward:4,badg:[],baptistia:2,base64:[],base:[1,3,4,5,6],basic:[0,6],basic_exampl:0,batteri:0,batterycircuit:0,batteryelectrochemeod:0,becaus:0,been:2,befor:[0,6],begin:3,being:0,below:[0,2,4,5,6],benchmark:0,benchmarking_exampl:0,best:[0,1],beta:[2,3,5],between:[2,6],binder:[],bool:[2,6],both:3,bound:[2,6],branch:0,brbekr9ebw8zruckxwp73xkaliqqb:[],bugfix:4,build:0,c3yb0tqzaml05:[],c:1,calc_metr:[],calcul:[0,2,6],call:[0,5],callabl:[3,5],can:[0,1,2,3,4,5,6],cannot:3,catolog:[],cautiou:3,cd:0,center:1,chang:4,characterist:0,check:[],checkout:0,chetan:1,choos:[],chosen:0,christoph:1,claim:1,classif:[],clear:2,clone:0,cobl:2,code:[],collect:[2,6],com:[0,1,2],combin:2,come:0,command:0,commit:[],common:[],commonli:[],compar:[1,2],compat:4,compli:[],compliant:[],compon:1,comput:[0,1,2,3],concern:[],confer:2,config:[],configur:[2,3,5],conform:1,conjunct:0,constitut:1,construct:[0,5],constructor:3,contain:[2,6],contractor:1,contribut:0,contributor:[],control:0,conveni:[],corbetta:1,core:[],correspond:[0,1,2,4],could:[],count:2,cov:6,covar:6,covari:[3,5,6],cra:[2,4],creat:[0,3,5,6],creation:0,cricial:[],criteria:2,criteria_eqn:2,critic:[],cumul:[2,4],cumulative_relative_accuraci:2,current:[0,3,5],curv:3,custom:[0,5],d19pufyeo67znqtf2kh33id1psxofdw30spz1smvs2d060ahqws4fhejojlzqnw53cmfvg:[],d:2,damag:1,data:[1,2,5],ddsfdoyuyrikmfehz0hr2thpgqqmyqyb2otb0wxsz3beg3:[],dec:[],decis:[],def:[3,5],defaultdict:6,defin:[0,2,3,5],definit:0,degred:0,demand:1,demonstr:0,depend:[],depict:2,depreci:[],deriv:4,describ:[2,4,5,6],descript:[],design:1,desrib:0,detail:[],dev:0,dev_guid:1,develop:[0,1],deviat:6,diagnost:1,dict:[2,5,6],dictionari:[0,2,5,6],differ:[0,1,2,4,6],dimens:3,directli:[0,3],discharg:0,displai:[0,2],dist:6,distinct:0,distribut:[0,1,2,3],doc:[],document:[1,5],doe:[1,2,5],doesn:[3,5],domain:3,don:[],done:[],dont:[],doubl:[],download:[],drawn:0,dt:[3,5],e:[0,2,3,4,5],each:[0,2,3,5,6],easili:1,effect:0,effici:[],either:[0,1,2,3,5],electron:[],element:4,els:2,empti:[2,6],en:3,enabl:1,end:[0,3,4],endors:1,engin:[0,1],ensur:[],enumer:[],environment:0,eol:[0,3,4],eol_ev:0,eol_metr:[],eol_profile_metr:[],equat:3,equival:[],equivil:[3,5],error:[1,2,4],es:[],estim:[0,1,2,3,4],et:[2,6],etc:0,evalu:[1,2],event1:[2,3,4],event2:[2,3],event3:2,event:[0,2,3,4,6],event_st:[0,3,5],ever:[],everi:3,evid:[],exactli:[],exampl:[0,2,3,4,5,6],excel:1,except:[],exist:[2,5,6],expect:3,expens:1,express:1,extend:[1,2],extent:1,extern:[],extract:0,extrem:[],f5:[],f:2,fade:5,failur:0,fake:0,fall:0,fals:2,favor:[],fc:[],featur:[0,3,4,5],few:0,fig:6,figur:[0,2,6],file:[],filt:5,filter:[0,4],filterpi:5,final_st:4,first:[0,2,3,4],first_stat:3,fit:1,fload:[],follow:[0,1,2,3,5],form:[0,6],format:4,forward:[4,5],found:2,fr7:[],framework:[0,1],free:1,freedom:1,frequenc:3,frequent:0,from:[1,2,3,4,5,6],fulli:0,further:[0,1],futur:[0,2,3,4],future_load:3,future_loading_eqn:3,fzqu4aeg8ze9ljgmztmqkxplnlwvnilqqtvxjf8ip7vgjzjyvprjw1te5otm7rmp7xm:[],g:[0,2,3,4,5,6],gaussian:3,gener:[2,3,5,6],genet:2,get:[1,2,5,6],getter:[],getting_start:[],gh:[],git:0,github:1,given:[0,1,2,3,5,6],go:2,goebel:6,govern:1,gradual:0,grl:[],ground:[2,6],ground_truth:[2,6],ground_truth_tt:[],group:1,guid:[],guidanc:[],ha:[0,2],hand:1,hardwar:1,harmless:1,have:[0,3],health:[0,2],healthi:0,here:[3,5],hidden:0,histogram:[0,3,4,6],hold:1,horizon:[0,2,3,4],host:0,how:[0,5],html:[],http:[0,1,2,3],hypothesi:3,i2fnz:[],i:[0,2,3,5],id:[],identifi:2,ignor:0,illustr:0,imag:[],img:[],immedi:1,immut:2,impact:[0,3],implement:[1,3,5],impli:1,improv:4,includ:[0,1,2,4,6],incorrect:[],increas:2,indemn:1,indemnifi:1,independ:[],index:[1,2,5],indexerror:[],indic:[0,2],individu:0,inform:[3,5,6],infring:1,inidividu:6,inidividualev:4,initi:[3,4,5],initial_st:5,input:[0,2,3,5],inputcontain:6,insert:[],instal:1,instanc:[0,2],instanti:0,instead:0,integ:2,intellig:2,intend:[],interchang:[],interest:0,interfac:1,intern:0,interpret:2,introduct:[],investig:0,io:[],ipynb:[],isn:0,item:2,iter:2,its:[2,3,5],itself:5,ivborw0kggoaaaansuheugaaafkaaabzcamaaabi1xidaaab8lbmvex:[],j:2,journal:2,k:2,kalman:0,kalman_filt:[0,3,4],kalmanfilt:[4,5],kappa:[3,5],kei:[2,4,5,6],kept:[],key1:6,keyerror:2,keyword:[2,3,5,6],kf:[0,5],kgdujtnhg72zcw9lojgqqxpp3:[],kind:[1,6],kssxi8xpxc4v3t7:[],kulkarni:1,kw:[],kwarg:[2,3,5,6],kx9msjlr8mlltcw8hhgjc2r5hs219iif6pntusoqcml57gm0z8kankmaqg0qsyuzfn7zitsbgyo9qlnxy0ecud1xl2i:[],label:6,labpath:[],lack:2,lambda:[0,2],lambda_valu:2,last:[],latest:0,law:1,learn:0,least:[],legend:[],len:[],liabil:1,librari:[3,5],life:[0,1,3,4],like:[0,1,2,3,6],limit:1,line:0,linear:[0,3],linearmodel:[0,4,5],link:0,list:[0,2,3,4,5,6],load:[0,3,5],local:2,logic:[3,5],logo:[],look:1,loss:1,lower:6,m3:[],m:[1,2,3,4,5],made:2,mai:[0,1],maintain:[],make:6,manag:2,mani:0,manipul:0,manner:1,map:2,mark:0,master:[],match:5,matlab:[3,5],matplotlib:[2,6],matrix:[5,6],matteo:1,matter:1,mb47ubirva4ryogjdaibeaixhowhmzyhobxvpchjqdhxrehlrovrsfg:[],mc:[],mean:[2,3,4,5,6],mean_square_error:[],mean_valu:[2,6],measur:[0,1,2,5],measurement_eqn:5,measurement_eqn_exampl:0,median:6,median_valu:6,meet:2,member:4,memori:5,merchant:1,merg:[],met:[0,2,3],method:[0,2,3,4,5],metric:[0,1,2,3,4,5,6],might:0,misalign:0,misc:1,miss:[],mission:0,mitig:[],model:[0,1,3,4,5,6],modul:1,monoton:[2,4],monotonicti:2,monotono:2,monte_carlo:5,montecarlo:[2,3,4],month:1,more:[3,5],most:0,msrqhlte7ug0ufozufjfe2pxbo:[],multipl:[0,2,4],multithread:[],multivari:[],multivariatenormaldist:[0,3,6],must:[2,3,5,6],mybind:[],n3vccppdcppgn3blb4:[],n:[2,4,6],n_sampl:[3,6],na:[],name:2,namedtupl:3,nasa:[0,1,3,5],nc:[],nears:0,necessari:[0,4],need:1,never:[],new_data:5,new_state_estimator_exampl:0,next:2,nois:[0,5],non:[3,4],none:[0,2,5,6],nonlinear:3,normal:2,note:1,notibl:0,now:4,npr:[],nsampl:6,nth:6,num_particl:5,num_sampl:6,number:[0,2,3,5,6],numpi:6,object:[0,2,6],observ:[0,3,5],obtain:3,occur:[0,2,3,4],occurr:2,often:0,one:[0,2,5],onli:[0,6],oper:[0,2,4,6],optim:2,option:[2,3,5,6],order:[2,6],org:[2,3],orhlksvo:[],origin:1,other:[0,1],otherwis:2,our:[],out:[],output:[0,2,3,5],outputcontain:6,outsid:0,overritten:6,p:6,packag:[0,2,3,5,6],package_structur:[],page:[0,1],pair:2,paper:2,param:2,paramet:[0,2,3,5,6],parent:[],parti:1,partial:[],particl:[0,4],particle_filt:[],particle_filter_battery_exampl:0,particlefilt:5,particular:1,pass:[2,4,6],pc:[],pcoe:1,percentag:[2,6],percentage_in_bound:[4,6],perfom:2,perform:[0,1,2,3,4,5],permit:1,pf:5,ph:2,phm:2,phmconf:2,phmsocieti:2,php:2,pii:2,pip:[],place:3,plan:[],playback:[0,4],plot:[2,3,4,6],plot_hist:[3,6],plot_scatt:6,pnacvg6qwg6qegkaff6whnpkofkgtnomxezy3nog6dzi:[],png:[],point:[0,2,3,4],pop:2,popitem:2,portion:2,possibl:[],posterior:[0,5],pre:[],pred:3,pred_result:3,predefin:3,prediciton:[],predict:[0,1,3,4,5,6],predict_specific_ev:0,predictionresult:3,predictor:[0,1,2,4,5],predictor_templ:3,prefer:[],present:[1,2],previou:4,previous:6,print:[2,5,6],prior:[0,1,5],prob_success:[],probabl:[],procedur:3,process:[0,5],produc:[0,2,5,6],product:1,profil:[1,4],prog_alg:[0,1,2,3,4,5,6],prog_model:[0,3,4,5],progalgexcept:[],progalginputexcept:[],progalgtypeerror:[],progmodel:[1,5],progmodelexcept:[],prognost:[0,2,3,4,5,6],prognostic_horizon:2,prognostics_horizon:2,prognosticsmodel:3,progress:0,progserv:1,project:[],propag:[1,3],properti:[2,5,6],propog:4,provid:[0,1,2,3],pull:[],purpos:1,py:[3,5],pypi:0,python3:4,python:[0,4],pz9:[],q:5,r:[5,6],ra:[2,4,6],rais:2,rang:[],rapid:1,reach:[0,3],real:3,rebuild:[],recipi:1,recommend:2,record:[],refer:1,regard:1,regardless:0,rel:[2,4,6],relat:2,relative_accuraci:6,releas:1,relev:[],remain:[0,1],remedi:1,remov:[0,2],repeat:3,replac:5,repo:0,repres:[0,2,3,4,6],req:[],request:[],requir:[],resampl:5,resample_fcn:5,research:[0,1],residual_resampl:5,respect:[0,2,6],respos:0,result:[0,1,2,3,4],revers:[],review:[],rmse:4,root:4,row:6,run:[0,5],rzl5zj137p136bisy44fkjxugn:[],s0004370222000078:2,s1mco54ymytwcn6a:[],s:[0,1,3,4,5],safeti:[],same:[0,6],sampl:[0,2,3,4],sample_id:[],satisfactori:3,satisfi:3,save:3,save_freq:3,save_pt:3,savepoint:[2,3],saxena:2,scalar:3,scalardata:[0,3,5,6],scale:[3,5],scatter:[4,6],scienc:[2,6],sciencedirect:2,search:1,second:5,section:[],see:[0,1,3,4,5],select:1,self:[],sensit:0,sensor:[0,5],sequenc:[],set:[0,1,2,6],setdefault:2,shall:1,shap:2,shield:[],should:[1,2,3],show:[0,2],shown:[],sigma:[3,4],sign:2,signific:[],significantli:4,sim_exampl:[],simresult:[0,2],simul:[0,3],simulate_to:0,singl:[0,2,3,6],situat:[],size:[0,3],sk2gv9i8gi:[],snapshot:[0,2,3],so:0,soc:0,societi:2,softwar:1,soh:[],sole:1,solut:1,some:[0,2,3],someon:0,someth:0,sourc:[],space:3,specif:[0,1,2,5],specifi:[0,2,3,6],sphinx:[],squar:4,ssrs2w7ovvhktjrx2pd7zmltxayfbal9jidwflkq55tm7ifhmltgpycas7rfrhn47jnlcb9rm5t97asuzxicvnuudindpdbdsfrqsoppexl5i:[],stabl:0,standard:6,stardard:[],start:[1,2,5],state1:[2,6],state2:[2,6],state:[0,1,2,3,4,6],state_estim:[0,4,5],state_estimator_templ:5,state_sampl:[],statecontain:[5,6],stateestim:[0,5],statist:6,statutori:1,step:[0,3,5],stochast:[],stop:2,store:[0,2,5],str:[2,6],strategi:[],string:[2,3,6],strong:3,structur:[2,4],studi:1,subclass:[0,3,4,5],subcontractor:1,subject:1,subpackag:[],subset:3,success:[],suffici:0,suitabl:0,sum:2,summar:0,summari:1,summat:2,suppli:[],support:[2,3,4,5],svg:[],swap:1,swe:[],system:[0,1,3,5],t09px29vb39:[],t0:[3,5],t:[0,3,5],tabl:6,tailor:[],take:[],target:[],tayj5zc:[],tb:[],tcmhwrpbelyqtijljkaizsbi8sgsf3k0pa3mr5thuwpfoa7n7reoq2bqcsak1hqcu5uvi1n6jurxi:[],temperatur:0,termin:1,test:0,teubert:1,text:6,th:3,than:[],thei:2,therefor:3,thi:[0,2,3,4,5,6],third:1,those:[2,6],three:0,threshold:[0,3],through:[0,2],throughout:0,thrown:0,thrown_object_exampl:0,thrownobject:[0,3,5],ti:2,time:[0,2,3,4,5],time_index:2,time_of_ev:3,time_of_predict:2,timepoint:4,timestamp:5,timestep:2,titl:[1,6],tj:[],toe:[0,1,3,4],toe_metr:[],toe_predict:2,toe_prediction_profil:[],toe_profil:2,toe_profile_metr:[],toepredictionprofil:[0,2,4],tool:[0,1],total:2,toward:0,transform:4,transit:0,treat:[0,2,6],trivial:0,truth:[2,6],tte:2,tupl:[2,6],turn:5,tutori:[0,1,3,5],two:2,txt:[],type:[1,2,3],typic:[0,5],tz:[],u:5,uhgnuh2bwj2hxk17yl1vzmljzm1j0nlx1oll3ajxwaaaabxrstlmaebaqhx8gicauljawmdw9pubaqepqufbxv1hgygbkchbwcxl8gicagoiikjcqljicnj2gokcmqk:[],ujspvs1sd5cwrywobtvwt2epa4rtnww3jkpjggepbospr7f7eynewtpbisla7p43hcsnwooxtec3umpmcnn5lrqtjxy6nrmcavgzvt:[],ukf:[3,5],uncertain:[0,1],uncertain_data:[2,3,4,6],uncertaindata:[0,1,2,3,4,5],uncertaintdata:3,uncertainti:[0,1,2,3,4],uncertiantydata:6,unconcern:0,under:3,unilater:1,unit:1,unless:0,unlik:0,unscent:4,unscented_kalman_filt:3,unscentedkalmanfilt:5,unscentedtransformpredictor:[0,3,4],unstabl:0,until:3,unweight:3,unweighted_sampl:2,unweightedsampl:[0,2,3,4,6],up:0,updat:[1,2,5],upper:6,url:1,us:[1,2,3,5,6],usabl:[],user:[0,3],usual:[],utpredictor:[0,4],v0:[],v1:1,v2:[],v7:[],v:[2,3,5],valu:[0,2,3,4,5,6],valueerror:2,variabl:0,varieti:0,variou:[0,4],vector:4,verifi:3,version:1,view:[2,6],visual:1,voltag:0,volum:2,wa:[1,2],wai:[0,3],waiv:1,waiver:1,want:0,warranti:1,we:[0,3,5],weight:5,well:[1,3,5],were:4,what:[],when:[0,2,3],whenev:[],where:[0,2,4,6],whether:[2,6],which:[0,2,3,5],who:0,wider:[],wiki:3,wikipedia:3,with_traceback:[],within:[0,2,6],without:[0,1,6],work:4,would:[0,3],wprb1vzl2uybog8ffghttfkjtaclnzyrro9ryg9ug:[],write:[],wslc4usdawmjp0ndq1nbw3nzg4odi5:[],www:2,x0:[4,5],x0_uncertainti:[],x1:[],x2:[],x3:[],x:[3,5],xn:[],xnyf1gw3vkcez0pn5iufflqvgun4c0qnqys5yhpl:[],xr8mc0oejuxrxekx5ydevjlvilv0e10pxk5k7dyehu7cj1j:[],xvkdjfcth:[],yax8xppddwwn0mrdryizf0msmkcnhgaivfobbnolj7teqdkxgf0kclqimojczopv0oknoywccg9xmvai7arjzqdm2quh0gmbozjc3skg6dsbrqdgysuou66zg:[],year:1,you:[0,1],z8:[],z:[0,3,5],zczcxgaabc5jrefueahn1ul3k0uubvcb1ctvpmpaitagslspsukclwpbtknjfglcsmaff63iumrccng6glbuxkxu66jauef:[],zgavuj0d9zy05ppk3qzbamxdwttcrzyg:[]},titles:["Getting Started","Prognostics Algorithms Python Package","Prediction","Predictors","Release Notes","State Estimators","Uncertain Data"],titleterms:{"1":[],"2":4,"3":4,"7150":[],"class":[],algorithm:1,aquisit:[],assess:[],base:2,branch:[],carlo:3,checklist:[],cite:1,complianc:[],content:4,contributor:[],cybersecur:[],data:[0,6],depend:[],develop:[],disclaim:1,distribut:6,e:6,estim:5,except:[],exist:4,extend:0,filter:5,from:0,gener:4,get:0,github:0,guid:[],guidanc:[],i:6,implement:6,includ:[3,5],indic:1,instal:0,interfac:[3,5,6],kalman:5,legend:[],matrix:[],mc:[],metric:[],model:[],mont:3,multivari:6,multivariatenormaldist:[],normal:6,notat:[],note:4,npr:[],option:[],packag:1,particl:5,pf:[],pip:0,pre:0,predict:2,predictor:3,profil:2,prognost:1,python:1,recommend:0,releas:[0,4],repositori:1,sampl:6,scalar:6,scalardata:[],start:0,state:5,strategi:[],structur:0,summari:0,tabl:1,thi:1,toe:2,transform:3,type:6,ukf:[],uncertain:6,uncertaindata:6,uncertainti:6,unscent:[3,5],unweight:6,unweightedsampl:[],unweightedsamplespredict:2,updat:4,us:0,user:4,v1:4,version:0,visual:[]}})
\ No newline at end of file
diff --git a/docs/state_estimators.html b/docs/state_estimators.html
index 7866e139..7e791a98 100644
--- a/docs/state_estimators.html
+++ b/docs/state_estimators.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>State Estimators &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -35,7 +35,7 @@
             
   <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>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 performance metrics). 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 each time 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>
@@ -55,16 +55,71 @@ <h1>State Estimators<a class="headerlink" href="#state-estimators" title="Permal
 <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="kalman-filter">
+<h3>Kalman Filter<a class="headerlink" href="#kalman-filter" title="Permalink to this headline">¶</a></h3>
+<dl class="py class">
+<dt class="sig sig-object py" id="prog_algs.state_estimators.KalmanFilter">
+<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">prog_algs.state_estimators.</span></span><span class="sig-name descname"><span class="pre">KalmanFilter</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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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.KalmanFilter" title="Permalink to this definition">¶</a></dt>
+<dd><p>A Kalman Filter (KF) for state estimation</p>
+<p>This class defines the logic for performing a kalman filter with a LinearModel (see Prognostics Model Package). This filter uses measurement data with noise to generate a state estimate and covariance matrix.</p>
+<p>The supported configuration parameters (keyword arguments) for UKF construction are described below:</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>UncertainData, model.StateContainer, or dict
+Initial (starting) state, with keys defined by model.states</p>
+<p>e.g., x = ScalarData({‘abc’: 332.1, ‘def’: 221.003}) given states = [‘abc’, ‘def’]</p>
+</p></li>
+</ul>
+</dd>
+<dt class="field-even">Keyword Arguments</dt>
+<dd class="field-even"><ul class="simple">
+<li><p><strong>alpha</strong> – float
+KF Scaling parameter. An alpha &gt; 1 turns this into a fading memory filter.</p></li>
+<li><p><strong>t0</strong> – float
+Starting time (s)</p></li>
+<li><p><strong>dt</strong> – float
+time step (s)</p></li>
+<li><p><strong>Q</strong> – List[List[float]]
+Process Noise Matrix</p></li>
+<li><p><strong>R</strong> – List[List[float]]
+Measurement Noise Matrix</p></li>
+</ul>
+</dd>
+</dl>
+<div class="admonition note">
+<p class="admonition-title">Note</p>
+<p>The Kalman Filter does not support a custom measurement function</p>
+</div>
+</dd></dl>
+
+</section>
 <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 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>
+<em class="property"><span class="pre">class</span><span class="w"> </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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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="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>UncertainData, model.StateContainer, or dict
+Initial (starting) state, with keys defined by model.states</p>
+<p>e.g., x = ScalarData({‘abc’: 332.1, ‘def’: 221.003}) given states = [‘abc’, ‘def’]</p>
+</p></li>
+</ul>
+</dd>
+</dl>
 <dl class="simple">
-<dt>Constructor Configuration Parameters:</dt><dd><dl class="simple">
+<dt>Keyword Args</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>
@@ -85,24 +140,33 @@ <h3>Unscented Kalman Filter<a class="headerlink" href="#unscented-kalman-filter"
 <h3>Particle Filter<a class="headerlink" href="#particle-filter" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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 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>
+<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>UncertainData, model.StateContainer, or dict
+Initial (starting) state, with keys defined by model.states</p>
+<p>e.g., x = ScalarData({‘abc’: 332.1, ‘def’: 221.003}) given states = [‘abc’, ‘def’]</p>
+</p></li>
+</ul>
 </dd>
-</dl>
+<dt class="field-even">Keyword Arguments</dt>
+<dd class="field-even"><ul class="simple">
+<li><p><strong>t0</strong> – float
+Starting time (s)</p></li>
+<li><p><strong>dt</strong> – float
+time step (s)</p></li>
+<li><p><strong>num_particles</strong> – int
+Number of particles in particle filter</p></li>
+<li><p><strong>resample_fcn</strong> – function
+Resampling function ([weights]) -&gt; [indexes] e.g., filterpy.monte_carlo.residual_resample</p></li>
+</ul>
 </dd>
 </dl>
 </dd></dl>
@@ -113,7 +177,7 @@ <h3>Particle Filter<a class="headerlink" href="#particle-filter" title="Permalin
 <h2>State Estimator Interface<a class="headerlink" href="#state-estimator-interface" title="Permalink to this headline">¶</a></h2>
 <dl class="py class">
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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">
@@ -122,12 +186,10 @@ <h2>State Estimator Interface<a class="headerlink" href="#state-estimator-interf
 <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
+<li><p><strong>x0</strong> – <p>UncertainData, model.StateContainer, or 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>
+<p>e.g., x = ScalarData({‘abc’: 332.1, ‘def’: 221.003}) given states = [‘abc’, ‘def’]</p>
 </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>
@@ -137,7 +199,7 @@ <h2>State Estimator Interface<a class="headerlink" href="#state-estimator-interf
 </dl>
 <dl class="py method">
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">float</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>, <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">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>
@@ -159,7 +221,7 @@ <h2>State Estimator Interface<a class="headerlink" href="#state-estimator-interf
 
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </span><span class="pre">property</span><span class="w"> </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 = filt.x</p>
@@ -198,11 +260,11 @@ <h3>Navigation</h3>
 <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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -237,7 +299,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/uncertain_data.html b/docs/uncertain_data.html
index 44858eae..ebde95fc 100644
--- a/docs/uncertain_data.html
+++ b/docs/uncertain_data.html
@@ -6,7 +6,7 @@
     <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.2.1 documentation</title>
+    <title>Uncertain Data &#8212; Prognostics Algorithms Python Package 1.3.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>
@@ -40,11 +40,11 @@ <h1>Uncertain Data<a class="headerlink" href="#uncertain-data" title="Permalink
 <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this headline">¶</a></h2>
 <dl class="py class">
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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 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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </span><span class="pre">property</span><span class="w"> </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>
@@ -56,9 +56,32 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 </dl>
 </dd></dl>
 
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.describe">
+<span class="sig-name descname"><span class="pre">describe</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">title</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'UncertainData</span> <span class="pre">Metrics'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">print</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bool</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">True</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">collections.defaultdict</span></span></span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.describe" title="Permalink to this definition">¶</a></dt>
+<dd><p>Print and view basic statistical information about this UncertainData object in a text-based printed table.</p>
+<dl class="field-list simple">
+<dt class="field-odd">Parameters</dt>
+<dd class="field-odd"><ul class="simple">
+<li><p><strong>title</strong> – str
+Title of the table, printed before data rows.</p></li>
+<li><p><strong>print</strong> – bool = True
+Optional argument specifying whether to print or not; default true.</p></li>
+</ul>
+</dd>
+<dt class="field-even">Returns</dt>
+<dd class="field-even"><p><dl class="simple">
+<dt>defaultdict</dt><dd><p>Dictionary of lists used to print metrics.</p>
+</dd>
+</dl>
+</p>
+</dd>
+</dl>
+</dd></dl>
+
 <dl class="py method">
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </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">Returns</dt>
@@ -72,7 +95,7 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </span><span class="pre">property</span><span class="w"> </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">Returns</dt>
@@ -88,11 +111,11 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </span><span class="pre">property</span><span class="w"> </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>Mean value. e.g., {‘key1’: 23.2, …}</p>
+<dd class="field-odd"><p>Median value. e.g., {‘key1’: 23.2, …}</p>
 </dd>
 <dt class="field-even">Return type</dt>
 <dd class="field-even"><p>Dict[str, float]</p>
@@ -104,7 +127,7 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 
 <dl class="py method">
 <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>
+<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> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">dict</span></span></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">Keyword Arguments</dt>
@@ -125,12 +148,12 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 
 <dl class="py method">
 <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>
+<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><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">tuple</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">keys</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">list</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</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">float</span></span></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"><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>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>
@@ -160,7 +183,7 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 
 <dl class="py method">
 <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>
+<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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">matplotlib.figure.Figure</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">list</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">matplotlib.figure.Figure</span></span></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">Parameters</dt>
@@ -182,9 +205,26 @@ <h2>Interface<a class="headerlink" href="#interface" title="Permalink to this he
 <p>states.plot_scatter(keys=[‘state1’, ‘state2’]) # only plot those keys</p>
 </dd></dl>
 
+<dl class="py method">
+<dt class="sig sig-object py" id="prog_algs.uncertain_data.UncertainData.relative_accuracy">
+<span class="sig-name descname"><span class="pre">relative_accuracy</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="w"> </span><span class="n"><span class="pre">dict</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></span><a class="headerlink" href="#prog_algs.uncertain_data.UncertainData.relative_accuracy" title="Permalink to this definition">¶</a></dt>
+<dd><p>The relative accuracy of the mean of the distribution.</p>
+<p>RA = 1 - <a href="#id3"><span class="problematic" id="id4">|r-p|</span></a>/r
+Where r is ground truth and p is mean of predicted distribution
+Prognostics: The Science of Making Predictions (Goebel et al, 239)</p>
+<dl class="field-list simple">
+<dt class="field-odd">Returns</dt>
+<dd class="field-odd"><p>float): Relative accuracy for each event where value is relative accuracy between [0,1]</p>
+</dd>
+<dt class="field-even">Return type</dt>
+<dd class="field-even"><p>dict(str</p>
+</dd>
+</dl>
+</dd></dl>
+
 <dl class="py method">
 <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>
+<em class="property"><span class="pre">abstract</span><span class="w"> </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="w"> </span><span class="n"><span class="pre">int</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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>
@@ -210,19 +250,20 @@ <h2>Implemented UncertainData Types<a class="headerlink" href="#implemented-unce
 <h3>Unweighted Samples<a class="headerlink" href="#unweighted-samples" title="Permalink to this headline">¶</a></h3>
 <dl class="py class">
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">list</span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </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>
+<dd class="field-odd"><p><strong>samples</strong> (<em>array</em><em>, </em><em>dict</em><em>, or </em><em>model.*Container</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>If InputContainer, OutputContainer, or StateContainer, must be of the form of <a href="#id1"><span class="problematic" id="id2">*</span></a>Container({‘key’: value, …})</p>
 </p>
 </dd>
 </dl>
 <dl class="py method">
 <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>
+<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> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">list</span></span></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">Parameters</dt>
@@ -244,7 +285,7 @@ <h3>Unweighted Samples<a class="headerlink" href="#unweighted-samples" title="Pe
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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="w"> </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="w"> </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>
@@ -262,11 +303,11 @@ <h3>Multivariate Normal Distribution<a class="headerlink" href="#multivariate-no
 <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>
+<em class="property"><span class="pre">class</span><span class="w"> </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 class="field-odd"><p><strong>state</strong> (<em>dict</em><em> or </em><em>Container</em>) – Single state in the form of dict or model.*Container (InputContainer, OutputContainer, Statecontainer) representing states and respective values.</p>
 </dd>
 </dl>
 </dd></dl>
@@ -303,11 +344,11 @@ <h3>Navigation</h3>
 </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="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://nasa.github.io/prog_server">ProgServer</a></li>
+<li class="toctree-l1"><a class="reference external" href="https://nasa.github.io/prog_models/dev_guide.html">dev_guide</a></li>
 <li class="toctree-l1"><a class="reference external" href="https://github.com/nasa/prog_algs">GitHub</a></li>
+<li class="toctree-l1"><a class="reference internal" href="release.html">Release Notes</a></li>
 </ul>
 
 <div class="relations">
@@ -342,7 +383,7 @@ <h3 id="searchlabel">Quick search</h3>
       <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.
+Copyright © 2021 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/examples/__init__.py b/examples/__init__.py
index cc2a0c1e..2bd4c7c0 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', 'eol_event', 'horizon', 'utpredictor', 'new_state_estimator_example', 'measurement_eqn_example', 'playback', 'predict_specific_event', 'thrown_object_example']
+__all__ = ['basic_example', 'benchmarking_example', 'eol_event', 'horizon', 'kalman_filter', '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 2bd5b9ea..0cbe2fcd 100644
--- a/examples/basic_example.py
+++ b/examples/basic_example.py
@@ -16,7 +16,7 @@
 
 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_models.models import BatteryElectroChemEOD as Battery
 
 from prog_algs import *
 
@@ -78,41 +78,41 @@ def future_loading(t, x = None):
     # 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)
+    mc_results = mc.predict(filt.x, future_loading, n_samples = NUM_SAMPLES, dt=STEP_SIZE)
+    print('ToE', mc_results.time_of_event.mean)
 
     # Step 3c: Analyze the results
 
     # Note: The results of a sample-based prediction can be accessed by sample, e.g.,
-    states_sample_1 = states[1]
+    states_sample_1 = mc_results.states[1]
     # now states_sample_1[n] corresponds to times[n] for the first sample
 
     # You can also access a state distribution at a specific time using the .snapshot function
-    states_time_1 = states.snapshot(1)
+    states_time_1 = mc_results.states.snapshot(1)
     # now you have all the samples corresponding to times[1]
 
     # You can also access the final state (of type UncertainData), like so:
-    final_state = toe.final_state
+    final_state = mc_results.time_of_event.final_state
     print('Final state @EOD: ', final_state['EOD'].mean)
     
     # You can also use the metrics package to generate some useful metrics on the result of a prediction
     print("\nEOD Prediction Metrics")
 
     from prog_algs.metrics import prob_success
-    print('\tPortion between 3005.2 and 3005.6: ', 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))
+    print('\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))
+    print('\tAssuming ground truth 3002.25: ', mc_results.time_of_event.metrics(ground_truth=3005.25))
+    print('\tP(Success) if mission ends at 3002.25: ', prob_success(mc_results.time_of_event, 3005.25))
 
     # Plot state transition 
     # Here we will plot the states at t0, 25% to ToE, 50% to ToE, 75% to ToE, and ToE
-    fig = 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()
+    fig = mc_results.states.snapshot(0).plot_scatter(label = "t={} s".format(int(mc_results.times[0])))  # 0
+    quarter_index = int(len(mc_results.times)/4)
+    mc_results.states.snapshot(quarter_index).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index])))  # 25%
+    mc_results.states.snapshot(quarter_index*2).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*2])))  # 50%
+    mc_results.states.snapshot(quarter_index*3).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[quarter_index*3])))  # 75%
+    mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = "t={} s".format(int(mc_results.times[-1])))  # 100%
+
+    mc_results.time_of_event.plot_hist()
     
     # Step 4: Show all plots
     import matplotlib.pyplot as plt  # For plotting
diff --git a/examples/benchmarking_example.py b/examples/benchmarking_example.py
index 73bddf8a..1f1679fb 100644
--- a/examples/benchmarking_example.py
+++ b/examples/benchmarking_example.py
@@ -58,10 +58,11 @@ def future_loading(t, x={}):
     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
+        pred_results = pred.predict(state_estimator.x, future_loading, n_samples = sample_count)
+        toe = pred_results.time_of_event.key("EOD")  # Looking at EOD event
         end = time.perf_counter()
         print('\tMSE:     {:4.2f}s'.format(metrics.mean_square_error(toe, 3005.4)))
+        print('\tRMSE:     {:4.2f}s'.format(metrics.root_mean_square_error(toe, 3005.4)))
         print('\tRuntime: {:4.2f}s'.format(end - start))
 
     # This same approach can be applied for benchmarking and comparing other changes 
diff --git a/examples/eol_event.py b/examples/eol_event.py
index fb25ecb4..82b1eaf6 100644
--- a/examples/eol_event.py
+++ b/examples/eol_event.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.
 """
-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.
+This example demonstrates a use case where someone wants to predict the first event (i.e., End Of Life (EOL)) of a system. Many system models have multiple events that can occur. In some prognostics applications, users are not interested in predicting a specific event, and are instead interested in when the first event occurs, regardless of the event. This example demonstrates how to predict the first event of a system.
 
 Method: An instance of ThrownObject is used for this example. In this case it is trivial because the event 'falling' will always occur before 'impact', but for some other models that might not be true. The ThrownObject class is subclassed to add a new event 'EOL' which occurs if any other event occurs. The new model is then instantiated and used for prognostics like in basic_example. Prediction specifically specifies EOL as the event to be predicted.
 Results: 
@@ -41,10 +41,10 @@ def future_loading(t=None, x=None):
     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)
+    pred_results = pred.predict(state, future_loading, events=['EOL'], dt=0.025)
 
     # Step 5: Plot results
-    toe.plot_hist()
+    pred_results.time_of_event.plot_hist()
 
     import matplotlib.pyplot as plt
     plt.show()
diff --git a/examples/horizon.py b/examples/horizon.py
index c6476840..1442a22c 100644
--- a/examples/horizon.py
+++ b/examples/horizon.py
@@ -53,13 +53,13 @@ def future_loading(t, x = None):
     # We're saying we are not interested in any events that occur after this time
     PREDICTION_HORIZON = 7.75
     samples = filt.x  # Since we're using a particle filter, which is also sample-based, we can directly use the samples, without changes
-    STEP_SIZE = 0.1
-    (_, _, _, _, _, toe) = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon = PREDICTION_HORIZON)
+    STEP_SIZE = 0.01
+    mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon = PREDICTION_HORIZON)
     print("\nPredicted Time of Event:")
-    metrics = toe.metrics()
+    metrics = mc_results.time_of_event.metrics()
     pprint(metrics)  # Note this takes some time
-    toe.plot_hist(keys = 'impact')
-    toe.plot_hist(keys = 'falling')
+    mc_results.time_of_event.plot_hist(keys = 'impact')
+    mc_results.time_of_event.plot_hist(keys = 'falling')
 
     print("\nSamples where impact occurs before horizon: {:.2f}%".format(metrics['impact']['number of samples']/NUM_SAMPLES*100))
     
diff --git a/examples/kalman_filter.py b/examples/kalman_filter.py
new file mode 100644
index 00000000..eab72e2d
--- /dev/null
+++ b/examples/kalman_filter.py
@@ -0,0 +1,133 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+"""
+This example demonstrates use of the Kalman Filter State Estimator with a LinearModel.
+
+First, a linear model is defined. Then the KF State estimator is used with fake data to estimate state.
+"""
+
+import numpy as np
+
+from prog_models import LinearModel
+from prog_algs.state_estimators import KalmanFilter
+
+# Linear Model for an object thrown into the air
+class ThrownObject(LinearModel):
+    """
+    Model that similates an object thrown into the air without air resistance
+
+    Events (2)
+        | falling: The object is falling
+        | impact: The object has hit the ground
+
+    Inputs/Loading: (0)
+
+    States: (2)
+        | x: Position in space (m)
+        | v: Velocity in space (m/s)
+
+    Outputs/Measurements: (1)
+        | x: Position in space (m)
+
+    Keyword Args
+    ------------
+        process_noise : Optional, float or Dict[Srt, float]
+          Process noise (applied at dx/next_state). 
+          Can be number (e.g., .2) applied to every state, a dictionary of values for each 
+          state (e.g., {'x1': 0.2, 'x2': 0.3}), or a function (x) -> x
+        process_noise_dist : Optional, String
+          distribution for process noise (e.g., normal, uniform, triangular)
+        measurement_noise : Optional, float or Dict[Srt, float]
+          Measurement noise (applied in output eqn).
+          Can be number (e.g., .2) applied to every output, a dictionary of values for each
+          output (e.g., {'z1': 0.2, 'z2': 0.3}), or a function (z) -> z
+        measurement_noise_dist : Optional, String
+          distribution for measurement noise (e.g., normal, uniform, triangular)
+        g : Optional, float
+            Acceleration due to gravity (m/s^2). Default is 9.81 m/s^2 (standard gravity)
+        thrower_height : Optional, float
+            Height of the thrower (m). Default is 1.83 m
+        throwing_speed : Optional, float
+            Speed at which the ball is thrown (m/s). Default is 40 m/s
+    """
+
+    inputs = []  # no inputs, no way to control
+    states = [
+        'x',     # Position (m) 
+        'v'      # Velocity (m/s)
+        ]
+    outputs = [
+        'x'      # Position (m)
+    ]
+    events = [
+        'impact' # Event- object has impacted ground
+    ]
+
+    A = np.array([[0, 1], [0, 0]])
+    E = np.array([[0], [-9.81]])
+    C = np.array([[1, 0]])
+    F = None # Will override method
+
+    # The Default parameters. Overwritten by passing parameters dictionary into constructor
+    default_parameters = {
+        'thrower_height': 1.83,  # m
+        'throwing_speed': 40,  # m/s
+        'g': -9.81  # Acceleration due to gravity in m/s^2
+    }
+
+    def initialize(self, u=None, z=None):
+        return self.StateContainer({
+            'x': self.parameters['thrower_height'],  # Thrown, so initial altitude is height of thrower
+            'v': self.parameters['throwing_speed']  # Velocity at which the ball is thrown - this guy is a professional baseball pitcher
+            })
+    
+    # This is actually optional. Leaving thresholds_met empty will use the event state to define thresholds.
+    #  Threshold = Event State == 0. However, this implementation is more efficient, so we included it
+    def threshold_met(self, x):
+        return {
+            'falling': x['v'] < 0,
+            'impact': x['x'] <= 0
+        }
+
+    def event_state(self, x): 
+        x_max = x['x'] + np.square(x['v'])/(-self.parameters['g']*2) # Use speed and position to estimate maximum height
+        return {
+            'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0),  # Throwing speed is max speed
+            'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1  # 1 until falling begins, then it's fraction of height
+        }
+
+def run_example():
+    # Step 1: Instantiate the model
+    m = ThrownObject(process_noise = 0, measurement_noise = 0)
+
+    # Step 2: Instantiate the Kalman Filter State Estimator
+    # Define the initial state to be slightly off of actual
+    x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40}
+    kf = KalmanFilter(m, x_guess)
+
+    # Step 3: Run the Kalman Filter State Estimator
+    # Here we're using simulated data from the thrown_object. In a real application you would be using sensor data from the system
+    dt = 0.01  # Time step (s)
+    print_freq = 50  # Print every print_freq'th iteration
+    x = m.initialize()
+    u = m.InputContainer({})  # No input for this model
+    
+    for i in range(500):
+        # Get simulated output (would be measured in a real application)
+        z = m.output(x)
+
+        # Estimate New State
+        kf.estimate(i*dt, u, z)
+        x_est = kf.x.mean
+
+        # Print Results
+        if i%print_freq == 0:  # Print every print_freq'th iteration
+            print(f"t: {i*dt:.2f}\n\tEstimate: {x_est}\n\tTruth: {x}")
+            diff = {key: x_est[key] - x[key] for key in x.keys()}
+            print(f"\t Diff: {diff}")
+
+        # Update Real state for next step
+        x = m.next_state(x, u, dt)
+
+if __name__ == '__main__':
+    run_example()
diff --git a/examples/measurement_eqn_example.py b/examples/measurement_eqn_example.py
index d56086a6..cd2fdca4 100644
--- a/examples/measurement_eqn_example.py
+++ b/examples/measurement_eqn_example.py
@@ -3,8 +3,8 @@
 """
 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. 
+Method: An instance of the BatteryCircuit model in prog_models is created. We assume that we are only measuring one of the output values, and we define a subclass to remove the other output value.  
+        Estimation of the current state is performed at various time steps, using the defined state_estimator.
 
 Results: 
     i) Estimate of the current state given various times
@@ -18,7 +18,14 @@
 from prog_algs import *
 
 def run_example():
-    # Step 1: Setup model & future loading
+    # Step 1: Subclass model with measurement equation
+    # In this case we're only measuring 'v' (i.e., removing temperature)
+    # To do this we're creating a new class that's subclassed from the complete model.
+    # To change the outputs we just have to override outputs (the list of keys)
+    class MyBattery(Battery):
+        outputs = ['v']
+
+    # Step 2: Setup model & future loading
     def future_loading(t, x={}):
         # Variable (piece-wise) future loading scheme 
         if (t < 600):
@@ -33,18 +40,11 @@ def future_loading(t, x={}):
             i = 3
         return {'i': i}
 
-    batt = Battery()
+    batt = MyBattery()
     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 3: Use the updated model
+    filt = state_estimators.ParticleFilter(batt, x0)
 
     # Step 4: Run step and print results
     print('Running state estimation step with only one of 2 outputs measured')
@@ -54,6 +54,7 @@ def measure(x):
     print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
 
     # Estimate Step
+    # Note, only voltage was needed in the measurement step, since that is the only output we're measuring
     t = 0.1
     load = future_loading(t)
     filt.estimate(t, load, {'v': 3.915})
@@ -72,8 +73,34 @@ def measure(x):
     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
+    # The updated outputs can be used for any case where the measurement doesn't match the model outputs
     # For example, when units are different, or when the measurement is some combination of the outputs
+    # These are a little more complicated, since they require an instance of the parent class. For example:
+
+    parent = Battery()
+
+
+    class MyBattery(Battery):
+        outputs = ['tv']  # output is temperature * voltage (for some reason)
+
+        def output(self, x):
+            parent.parameters = self.parameters  # only needed if you expect to change parameters
+            z = parent.output(x)
+            return self.OutputContainer({'tv': z['v'] * z['t']})
+
+    batt = MyBattery()
+    filt = state_estimators.ParticleFilter(batt, x0)
+
+    print('-----------------\n\nExample 2')
+    print("\nPrior State:", filt.x.mean)
+    print("\toutput: ", batt.output(filt.x.mean))
+    print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
+    t = 0.1
+    load = future_loading(t)
+    filt.estimate(t, load, {'tv': 80})
+    print("\nPosterior State:", filt.x.mean)
+    print("\toutput: ", batt.output(filt.x.mean))
+    print('\tSOC: ', batt.event_state(filt.x.mean)['EOD'])
 
 # This allows the module to be executed directly 
 if __name__ == '__main__':
diff --git a/examples/new_state_estimator_example.py b/examples/new_state_estimator_example.py
index b5a4c621..cc409ce0 100644
--- a/examples/new_state_estimator_example.py
+++ b/examples/new_state_estimator_example.py
@@ -1,5 +1,11 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
+"""
+An example illustrating the creation of a new state estimator.
+
+In this example a basic state estimator is constructed by subclassing the StateEstimator class. This StateEstimator is then demonstrated with a ThrownObject model
+"""
+
 from prog_algs.state_estimators import StateEstimator
 from prog_algs.uncertain_data import ScalarData
 import random
diff --git a/examples/particle_filter_battery_example.py b/examples/particle_filter_battery_example.py
index 4a759da3..4b9a9856 100644
--- a/examples/particle_filter_battery_example.py
+++ b/examples/particle_filter_battery_example.py
@@ -1,6 +1,10 @@
 # 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
+"""
+In this example the BatteryElectroChemEOD model is used with a particle filter to estimate the state of the battery
+"""
+
+from prog_models.models import BatteryElectroChemEOD
 from prog_algs import *
 import numpy as np
 import matplotlib.pyplot as plt
@@ -37,30 +41,30 @@ def future_loading(t, x=None):
     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)
+    sim_results = battery.simulate_to_threshold(future_loading, start_y, save_freq = 1)
 
     # Run particle filter
     all_particles = []
-    n_times = int(np.round(np.random.uniform(len(times)*.25,len(times)*.45,1)))# Random current time
+    n_times = int(np.round(np.random.uniform(len(sim_results.times)*.25,len(sim_results.times)*.45,1)))# Random current time
 
     for i in range(n_times):
         if i == 0:
-            batt_pf = state_estimators.ParticleFilter(model = battery,  x0 = states[i], num_particles = 250)
+            batt_pf = state_estimators.ParticleFilter(model = battery,  x0 = sim_results.states[i], num_particles = 250)
         else:
-            batt_pf.estimate(t = times[i], u = inputs[i], z = outputs[i])
+            batt_pf.estimate(t = sim_results.times[i], u = sim_results.inputs[i], z = sim_results.outputs[i])
         all_particles.append(batt_pf.particles)
 
     # Mean of the particles
     alpha = 0.05
-    states_vsn = [s['tb'] for s in states]
+    states_vsn = [s['tb'] for s in sim_results.states]
     pf_mean = [{key: np.mean(ps[key]) for key in battery.states} for ps in all_particles]
     pf_low = [{key: np.quantile(ps[key], alpha / 2.0) for key in battery.states} for ps in all_particles]
     pf_upp = [{key: np.quantile(ps[key], 1.0 - alpha / 2.0) for key in battery.states} for ps in all_particles]
     print("First State:", pf_mean[0])
     print("Current State:", pf_mean[-1])
-    plt.plot(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.plot(sim_results.times[:n_times],[p['tb'] for p in pf_mean],linewidth=0.7,color="blue")
+    plt.plot(sim_results.times[:n_times], states_vsn[:n_times],"--",linewidth=0.7,color="red")
+    plt.fill_between(sim_results.times[:n_times],[p['tb'] for p in pf_low],[p['tb'] for p in pf_upp],alpha=0.5,color="blue")
     plt.show()
     
 
diff --git a/examples/playback.py b/examples/playback.py
index d2972d6d..bc982487 100644
--- a/examples/playback.py
+++ b/examples/playback.py
@@ -8,6 +8,7 @@
         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)
@@ -19,55 +20,61 @@
 # VVV Uncomment this to use Electro Chemistry Model VVV
 # from prog_models.models import BatteryElectroChem as Battery
 
-from prog_algs.state_estimators import ParticleFilter as StateEstimator
+from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator
 # VVV Uncomment this to use UnscentedKalmanFilter instead VVV
-# from prog_algs.state_estimators import UnscentedKalmanFilter as StateEstimator
+# from prog_algs.state_estimators import ParticleFilter as StateEstimator
 
-from prog_algs.predictors import MonteCarlo, ToEPredictionProfile
-from prog_algs.metrics import samples as metrics
+from prog_algs.predictors import ToEPredictionProfile
+
+from prog_algs.predictors import UnscentedTransformPredictor as Predictor
+# VVV Uncomment this to use MonteCarloPredictor instead
+# from prog_algs.predictors import MonteCarlo as Predictor
+
+from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist
 
 import csv
-import matplotlib.pyplot as plt 
+import matplotlib.pyplot as plt
+import numpy as np
 
 # Constants
-NUM_SAMPLES = 10
+NUM_SAMPLES = 20
+NUM_PARTICLES = 1000 # For state estimator (if using ParticleFilter)
 TIME_STEP = 1
-PREDICTION_UPDATE_FREQ = 5 # Number of steps between prediction update
+PREDICTION_UPDATE_FREQ = 50 # Number of steps between prediction update
+PLOT = True
+PROCESS_NOISE = 1e-4 # Percentage process noise
+MEASUREMENT_NOISE = 1e-4 # Percentage measurement noise
+X0_COV = 1 # Covariance percentage with initial state
+GROUND_TRUTH = {'EOD':2780}
+ALPHA = 0.05
+BETA = 0.90
+LAMBDA_VALUE = 1500
 
 def run_example():
     # Setup Model
     batt = Battery()
 
+    # Initial state
+    x0 = batt.initialize()
+    batt.parameters['process_noise'] = {key: PROCESS_NOISE * value for key, value in x0.items()}
+    z0 = batt.output(x0)
+    batt.parameters['measurement_noise'] = {key: MEASUREMENT_NOISE * value for key, value in z0.items()}
+    x0 = MultivariateNormalDist(x0.keys(), list(x0.values()), np.diag([max(1e-9, X0_COV * abs(x)) for x in x0.values()]))
+
     # Setup State Estimation
-    filt = StateEstimator(batt, batt.parameters['x0'])
+    filt = StateEstimator(batt, x0, num_particles = NUM_PARTICLES)
 
     # Setup Prediction
     def future_loading(t, x=None):
         return {'i': 2.35}
-    mc = MonteCarlo(batt)
-
-    # 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 = [], []
-    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()
+    Q = np.diag([batt.parameters['process_noise'][key] for key in batt.states])
+    R = np.diag([batt.parameters['measurement_noise'][key] for key in batt.outputs])
+    mc = Predictor(batt, Q = Q, R = R)
 
     # Run Playback
     step = 0
     profile = ToEPredictionProfile()
+    
     with open('examples/data_const_load.csv', 'r') as f:
         reader = csv.reader(f)
         next(reader) # Skip header
@@ -83,32 +90,54 @@ def future_loading(t, x=None):
             eod = batt.event_state(filt.x.mean)['EOD']
             print("  - Event State: ", eod)
 
-            # Update Plot
-            xdata.append(t)
-            ydata.append(eod)
-            xmin, xmax = ax.get_xlim()
-
-            if t >= xmax:
-                ax.set_xlim(xmin, 2*xmax)
-                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)
+                mc_results = mc.predict(filt.x, future_loading, t0 = t, n_samples=NUM_SAMPLES, dt=TIME_STEP)
+                metrics = mc_results.time_of_event.metrics()
+                print('  - ToE: {} (sigma: {})'.format(metrics['EOD']['mean'], metrics['EOD']['std']))
+                profile.add_prediction(t, mc_results.time_of_event)
+
+        # Calculating Prognostic Horizon once the loop completes
+        from prog_algs.uncertain_data.uncertain_data import UncertainData
+        from prog_algs.metrics import samples as metrics
+
+        def criteria_eqn(tte : UncertainData, ground_truth_tte : dict) -> dict:
+            """
+            Sample criteria equation for playback. 
+            # UPDATE THIS CRITERIA EQN AND WHAT IS CALCULATED
+
+            Args:
+                tte : UncertainData
+                    Time to event in UncertainData format.
+                ground_truth_tte : dict
+                    Dictionary of ground truth of time to event.
+            """
+            
+            # Set an alpha value
+            bounds = {}
+            for key, value in ground_truth_tte.items():
+                # Set bounds for precentage_in_bounds by adding/subtracting to the ground_truth
+                alpha_calc = value * ALPHA
+                bounds[key] = [value - alpha_calc, value + alpha_calc] # Construct bounds for all events
+            percentage_in_bounds = tte.percentage_in_bounds(bounds)
+            
+            # Verify if percentage in bounds for this ground truth meets beta distribution percentage limit
+            return {key: percentage_in_bounds[key] > BETA for key in percentage_in_bounds.keys()}
+
+        # Generate plots for playback example
+        playback_plots = profile.plot(GROUND_TRUTH, ALPHA, True)
+
+        # Calculate prognostic horizon with ground truth, and print
+        ph = profile.prognostic_horizon(criteria_eqn, GROUND_TRUTH)
+        print(f"Prognostic Horizon for 'EOD': {ph['EOD']}")
+
+        # Calculate alpha lambda with ground truth, lambda, alpha, and beta, and print
+        al = profile.alpha_lambda(GROUND_TRUTH, LAMBDA_VALUE, ALPHA, BETA)
+        print(f"Alpha Lambda for 'EOD': {al['EOD']}")
+
+        # Calculate cumulative relative accuracy with ground truth, and print
+        cra = profile.cumulative_relative_accuracy(GROUND_TRUTH)
+        print(f"Cumulative Relative Accuracy for 'EOD': {cra['EOD']}")
 
     input('Press any key to exit')
 
diff --git a/examples/predict_specific_event.py b/examples/predict_specific_event.py
index e2f10dbe..053a179f 100644
--- a/examples/predict_specific_event.py
+++ b/examples/predict_specific_event.py
@@ -1,13 +1,12 @@
 # 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.
 """
 
+from prog_models.models.thrown_object import ThrownObject
+from prog_algs import *
+
 def run_example():
     ## Setup
     def future_loading(t, x = None):
@@ -25,18 +24,18 @@ def future_loading(t, x = None):
     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'])
+    mc_results = pred.predict(filt.x, future_loading, dt=0.1, save_freq= 1, events=['impact'])
 
     # Print Results
-    for i, time in enumerate(times):
+    for i, time in enumerate(mc_results.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('\tu = {}'.format(mc_results.inputs.snapshot(i).mean))
+        print('\tx = {}'.format(mc_results.states.snapshot(i).mean))
+        print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(mc_results.states.snapshot(i).mean))
 
     # Note only impact event is shown here
-    print('\nToE:', toe.mean)
+    print('\nToE:', mc_results.time_of_event.mean)
 
 # This allows the module to be executed directly 
 if __name__ == '__main__':
diff --git a/examples/thrown_object_example.py b/examples/thrown_object_example.py
index f5de4827..54c8ac22 100644
--- a/examples/thrown_object_example.py
+++ b/examples/thrown_object_example.py
@@ -49,12 +49,12 @@ def future_loading(t, x = None):
 
     # 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, horizon=8)
+    STEP_SIZE = 0.01
+    mc_results = mc.predict(samples, future_loading, dt=STEP_SIZE, horizon=8)
     print("\nPredicted Time of Event:")
-    pprint(toe.metrics())  # Note this takes some time
-    toe.plot_hist(keys = 'impact')
-    toe.plot_hist(keys = 'falling')
+    pprint(mc_results.time_of_event.metrics())  # Note this takes some time
+    mc_results.time_of_event.plot_hist(keys = 'impact')
+    mc_results.time_of_event.plot_hist(keys = 'falling')
     
     # Step 4: Show all plots
     import matplotlib.pyplot as plt  # For plotting
diff --git a/examples/utpredictor.py b/examples/utpredictor.py
index abd7c426..3ec1544a 100644
--- a/examples/utpredictor.py
+++ b/examples/utpredictor.py
@@ -1,5 +1,9 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
+"""
+An example using the UnscentedTransformPredictor class to predict the degredation of a battery. 
+"""
+
 from prog_models.models import BatteryCircuit
 from prog_algs import *
 # from prog_algs.visualize import plot_hist
@@ -40,20 +44,20 @@ def future_loading(t, x = None):
     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)
+    mc_results = mc.predict(filt.x, future_loading, dt=0.1, save_freq= 100)
 
     # Print Results
-    for i, time in enumerate(times):
+    for i, time in enumerate(mc_results.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('\tu = {}'.format(mc_results.inputs.snapshot(i).mean))
+        print('\tx = {}'.format(mc_results.states.snapshot(i).mean))
+        print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(mc_results.event_states.snapshot(i).mean))
 
-    print('\nToE:', toe.mean)
+    print('\nToE:', mc_results.time_of_event.mean)
 
     # You can also access the final state (of type UncertainData), like so:
-    final_state = toe.final_state
+    final_state = mc_results.time_of_event.final_state
     print('Final state @EOD: ', final_state['EOD'].mean)
     # toe.plot_hist()
     # plt.show()
diff --git a/predictor_template.py b/predictor_template.py
index f7eec136..33cc3452 100644
--- a/predictor_template.py
+++ b/predictor_template.py
@@ -1,7 +1,7 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
 from copy import deepcopy
-from prog_algs.predictors import Predictor, Prediction
+from prog_algs.predictors import Predictor, Prediction, PredictionResults
 # Replace the following with whatever form of UncertainData you would like to use to represent ToE 
 from prog_algs.uncertain_data import ScalarData
 
@@ -39,7 +39,7 @@ def predict(self, state, future_loading_eqn, **kwargs):
         options : dict, optional
             Dictionary of any additional configuration values. See default parameters, above
 
-        Returns (tuple)
+        Returns (namedtuple)
         -------
         times : List[float]
             Times for each savepoint such that inputs.snapshot(i), states.snapshot(i), outputs.snapshot(i), and event_states.snapshot(i) are all at times[i]            
@@ -71,5 +71,14 @@ def predict(self, state, future_loading_eqn, **kwargs):
 
         # Time of event is represented by some type of UncertainData (e.g., MultivariateNormalDist)
         time_of_event = ScalarData({'event1': 748, 'event2': 300})
+        # Save the final state when each event occurs like this, with each final state represented by an UncertainData object (e.g., MultivariateNormalDist)
+        # time_of_event.final_state = {'event1': ScalarData({'state1': 10, 'state2': 20}), 'event2': ScalarData({'state1': 12, 'state2': 18})}
 
-        return (times, inputs, states, outputs, event_states, time_of_event)
+        return PredictionResults(
+            times, 
+            inputs, 
+            states, 
+            outputs, 
+            event_states, 
+            time_of_event
+        )
diff --git a/requirements.txt b/requirements.txt
index 1acb21ff..a3404e7d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,4 @@
-filterpy==1.4.5
-matplotlib==3.3.2
-numpy==1.19.2
-scipy==1.5.3
-prog_models>=1.2.2
+filterpy>=1.4.5
+matplotlib>=3.5.2
+scipy>=1.7.3
+prog_models>=1.3
diff --git a/setup.py b/setup.py
index a208a1ee..ffcb9429 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,5 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
 from setuptools import setup, find_packages
 import pathlib
 
@@ -8,7 +10,7 @@
 
 setup(
     name = 'prog_algs',
-    version = '1.2.3',
+    version = '1.3.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',
@@ -25,7 +27,6 @@
         'Topic :: Scientific/Engineering :: Physics',
         'License :: Other/Proprietary License ',   
         'Programming Language :: Python :: 3',     
-        'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
@@ -35,7 +36,7 @@
     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.11',
+    python_requires='>=3.7, <3.11',
     install_requires = [
         'numpy',
         'scipy',
diff --git a/sphinx_config/_templates/layout.html b/sphinx_config/_templates/layout.html
index 55762a16..8c1a51a3 100644
--- a/sphinx_config/_templates/layout.html
+++ b/sphinx_config/_templates/layout.html
@@ -2,5 +2,5 @@
 
 {% block footer %}
 
-Copyright © 2020 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 {% endblock %}
diff --git a/sphinx_config/conf.py b/sphinx_config/conf.py
index 62712743..530141aa 100644
--- a/sphinx_config/conf.py
+++ b/sphinx_config/conf.py
@@ -1,3 +1,6 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the
+# National Aeronautics and Space Administration.  All Rights Reserved.
+
 # Configuration file for the Sphinx documentation builder.
 #
 # This file only contains a selection of the most common options. For a full
diff --git a/sphinx_config/dev_guide.rst b/sphinx_config/dev_guide.rst
deleted file mode 100644
index 0e1e3a34..00000000
--- a/sphinx_config/dev_guide.rst
+++ /dev/null
@@ -1,102 +0,0 @@
-Developers Guide
-================
-
-This document includes some details relevant for developers. 
-
-..  contents:: 
-    :backlinks: top
-
-Guidance for Contributors
--------------------------
-
-Below are a few design decision made by the authors, documented for the reference of new contributors:
-
-* When supplied by or to the user, values with names (e.g., inputs, states, outputs, event_states, event occurance, etc.) should be supplied as dictionaries (or dict-like objects) where they can be referred to by name. 
-* Visualize and metrics subpackages shall be independent (i.e., not have any dependencies with the wider package or other subpackages)
-* Limit introduction of new external dependencies, when possible. 
-* This is a research tool, so when making a design decision between operational efficiency and usability, generally choose the more usable option
-* Except in the most extreme cases, maintain backwards compatibility for the convenience of existing users
-
-  * If a feature is to be removed, mark it as depreciated for at least 2 releases before removing
-
-* Whenever possible, design so a new feature can be used for any model, interchangably. 
-* Whenever possible, UncertainData types, state estimators, and predictors should be interchangable
-* Demonstrate common use cases as an example. 
-* Every feature should be demonstrated in an example
-
-  * The most commonly used features should be demonstrated in the tutorial
-
-Branching Strategy
-------------------
-Our project is following the git strategy described `here <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 merge into the dev branch that contains a functional change is done using a pull request (not commiting directly). Every commit should be functional. All unit tests must function before commiting to dev or merging another branch. 
-
-`Feature Branches`: These branches include changes specific to a new feature. Before merging into dev unit tests should all run, tests should be added for the feature, and documentation should be updated, as appropriate.
-
-Release Checklist
-*****************
-* Code review - all software must be checked by someone other than the author
-* Check that each new feature has a corresponding tests
-* Run unit tests `python -m tests`
-* Check documents- see if any updates are required
-* Rebuild sphinx documents: `sphinx-build sphinx-config/ docs/`
-* Write release notes
-* For releases adding new features- ensure that NASA release process has been followed
-
-NPR 7150
---------
-This section describes this project's compliance with the NASA's NPR 7150 requirements, documented `here <https://nodis3.gsfc.nasa.gov/displayDir.cfm?t=NPR&c=7150&s=2B>`_.
-
-* Software Classification: Class-E (Research Software)
-* Safety Criticality: Not Safety Critical 
-
-Compliance Notation Legend
-**************************
-* FC: Fully Compliant
-* T: Tailored (Specific tailoring described in mitigation) `SWE-121 <https://swehb.nasa.gov/display/7150/SWE-121+-+Document+Alternate+Requirements>`_
-* PC: Partially Compliant
-* NC: Not Compliant
-* NA: Not Applicable
-
-Compliance Matrix
-*****************
-+-------+----------------------------------+------------+---------------------+
-| SWE # | Description                      | Compliance | Evidence            |
-+=======+==================================+============+=====================+
-| 033   | Assess aquisiton Options         | FC         | See section below   |
-+-------+----------------------------------+------------+---------------------+
-| 013   | Maintain Software Plans          | FC         | This document       |
-+-------+----------------------------------+------------+---------------------+
-| 042   | Electronic Accesss to Source     | FC         | This repo           |
-+-------+----------------------------------+------------+---------------------+
-| 139   | Comply with 7150                 | FC         | This document       |
-+-------+----------------------------------+------------+---------------------+
-| 121   | Tailored Reqs                    | NA         | No tailoring        |
-+-------+----------------------------------+------------+---------------------+
-| 125   | Compliance Matrix                | FC         | This document       |
-+-------+----------------------------------+------------+---------------------+
-| 029   | Software Classification          | FC         | This document       |
-+-------+----------------------------------+------------+---------------------+
-| 022   | Software Assurance               | FC         | This document       |
-+-------+----------------------------------+------------+---------------------+
-| 205   | Safety Cricial Software          | FC         | See above           |
-+-------+----------------------------------+------------+---------------------+
-| 023   | Safety Critical Reqs             | NA         | Not safety critical |
-+-------+----------------------------------+------------+---------------------+
-| 206   | Autogen Software                 | NA         | No autogen          |
-+-------+----------------------------------+------------+---------------------+
-| 148   | Software Catolog                 | FC         | Will be added       |
-+-------+----------------------------------+------------+---------------------+
-| 156   | Perform CyberSecurity Assessment | FC         | See section below   |
-+-------+----------------------------------+------------+---------------------+
-
-Aquisition Options
-******************
-Assessed, there are some existing prognostics tools, but no general python package that can support model-based prognostics like we need. 
-
-Cybersecurity Assessment 
-************************
-Assessed, no significant Cybersecurity concerns were identified- research software. 
\ No newline at end of file
diff --git a/sphinx_config/exceptions.rst b/sphinx_config/exceptions.rst
deleted file mode 100644
index f9612fab..00000000
--- a/sphinx_config/exceptions.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-Exceptions
-======================
-Exceptions returned from prog_model fall into a few custom exception types, defined below. Catching any Prognostics Model Exception can be acomplished using `try-except ProgModelException`. Alternatively, a user can catch a specific exception type.
-
-Prognostics Model Exceptions
-----------------------------
-
-.. autoexception:: prog_algs.exceptions.ProgAlgException
-   :members:
-   :inherited-members:
-
-.. autoexception:: prog_algs.exceptions.ProgAlgInputException
-   :members:
-   :inherited-members:
-
-.. autoexception:: prog_algs.exceptions.ProgAlgTypeError
-   :members:
-   :inherited-members:
diff --git a/sphinx_config/getting_started.rst b/sphinx_config/getting_started.rst
index e199ce4e..ef2cb76c 100644
--- a/sphinx_config/getting_started.rst
+++ b/sphinx_config/getting_started.rst
@@ -3,20 +3,20 @@ 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 with the Prognostics Models Package (`prog_models`) to perform research in prognostics with prognostics systems.
 
 Installing
 -----------------------
 
 Installing from pip (recommended)
 ********************************************
-The latest stable release of `prog_algs` is hosted on PyPi. For most users (unless you want to contribute to the development of `prog_algs`), the version on PyPi will be adequate. To install from the command line, use the following command:
+The latest stable release of `prog_algs` is hosted on PyPi. For most users (unless you want to contribute to the development of `prog_algs`), this version will be adequate. To install from the command line, use the following command:
 
 .. code-block:: console
 
     $ pip install prog_algs
 
-Installing pre-release versions with GitHub
+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:
 
@@ -33,31 +33,31 @@ 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"
+* **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)
+* **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)
+* **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.
+* **performance metrics**: performance characteristics of a system that are a function of system state, but are not directly measured.
 
 * **states**: Internal parameters (typically hidden states) used to represent the state of the system- can be same as inputs/outputs but do not have to be. 
 
-* **process noise**: stochastic process representing uncertainty in the model transition. 
+* **process noise**: representing uncertainty in the model transition (e.g., model uncertainty). 
 
-* **measurement noise**: stochastic process representing uncertainty in the measurement process; e.g., sensor sensitivity, sensor misalignements, environmental effects 
+* **measurement noise**: representing uncertainty in the measurement process (e.g., sensor sensitivity, sensor misalignements, environmental effects).
 
 The structure of the packages is illustrated below:
 
 .. image:: images/package_structure.png
 
-Prognostics is performed using `State Estimators <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).
+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 <prediction.html#id1>`__).
 
 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.
+ * `SimResult (from prog_models) <https://nasa.github.io/prog_models/simresult.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. 
@@ -84,6 +84,9 @@ The best way to learn how to use `prog_algs` is through the `tutorial <https://m
 * :download:`examples.horizon <../examples/horizon.py>`
     .. automodule:: examples.horizon
     |
+* :download:`examples.kalman_filter <../examples/kalman_filter.py>`
+    .. automodule:: examples.kalman_filter
+    |
 * :download:`examples.measurement_eqn_example <../examples/measurement_eqn_example.py>`
     .. automodule:: examples.measurement_eqn_example
     |
@@ -107,32 +110,3 @@ Extending
 New State Estimators and Predictors are created by extending the :class:`prog_algs.state_estimators.StateEstimator` and :class:`prog_algs.predictors.Predictor` class, respectively. 
 
 See :download:`examples.new_state_estimator_example <../examples/new_state_estimator_example.py>` for an example of this approach.
-
-Updates in v1.2
----------------
-
-Note for existing users
-***********************
-This release includes changes to the return format of the MonteCarlo Predictor's `predict` method. These changes were necessary to support non-sample based predictors. The non backwards-compatible changes are listed below:
-* times: 
-    * previous ```List[List[float]]``` where times[n][m] corresponds to timepoint m of sample n. 
-    * new ```List[float]``` where times[m] corresponds to timepoint m for all samples
-* End of Life (EOL)/ Time of Event (ToE) estimates:
-    * previous ```List[float]``` where the times correspond to the time that the first event occurs.
-    * new ```UnweightedSamples``` where keys correspond to the inidividualevents predicted.
-* State at time of event (ToE)
-   * previous: element in states 
-   * new: member of toe event (e.g., toe.final_state['event1'])
-
-General Updates
-***************
-* New Feature: Histogram and Scatter Plot of UncertainData
-* New Feature: Vectorized particle filter
-    * Particle Filter State Estimator is now vectorized for vectorized models - this significantly improves performance.
-* New Feature: Unscented Transform Predictor
-    * New predictor that propogates sigma points forward to estimate time of event and future states
-* New Feature: `Prediction` class to represent predicted future values
-* New Feature: `ToEPredictionProfile` class to represent and operate on the result of multiple predictions generated at different prediction times.
-* Added metrics `percentage_in_bounds` and `metrics` and plots to UncertainData 
-* Add support for Python3.9
-* General Bugfixes
diff --git a/sphinx_config/index.rst b/sphinx_config/index.rst
index d0080df5..20f484b2 100644
--- a/sphinx_config/index.rst
+++ b/sphinx_config/index.rst
@@ -18,11 +18,11 @@ If you are new to this package, see `getting started <getting_started.html>`__.
    predictors
    uncertain_data
    prediction
-   metrics
-   exceptions
    ProgModels <https://nasa.github.io/prog_models>
-   dev_guide
+   ProgServer <https://nasa.github.io/prog_server>
+   dev_guide <https://nasa.github.io/prog_models/dev_guide.html>
    GitHub <https://github.com/nasa/prog_algs>
+   release
 
 Citing this repository
 -----------------------
@@ -31,15 +31,15 @@ Use the following to cite this repository:
 @misc{2021_nasa_prog_algs,
   | author    = {Christopher Teubert and Chetan Kulkarni and Matteo Corbetta},
   | title     = {Prognostics Algorithms Python Package},
-  | month     = Dec,
-  | year      = 2021,
-  | version   = {1.2.3},
+  | month     = May,
+  | year      = 2022,
+  | version   = {1.3.0},
   | url       = {https://github.com/nasa/prog_algs}
   | }
 
 The corresponding reference should look like this:
 
-C. Teubert, C. Kulkarni, M. Corbetta Prognostics Algorithms Python Package, v1.2.3, Dec 2021. URL https://github.com/nasa/prog_algs.
+C. Teubert, C. Kulkarni, M. Corbetta. Prognostics Algorithms Python Package, v1.3.0, May 2022. URL https://github.com/nasa/prog_algs.
 
 Indices and tables
 -----------------------
diff --git a/sphinx_config/metrics.rst b/sphinx_config/metrics.rst
deleted file mode 100644
index 04d59aa0..00000000
--- a/sphinx_config/metrics.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-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
-   :members:
-   :inherited-members:
-   :exclude-members: calc_metrics, alpha_lambda
diff --git a/sphinx_config/prediction.rst b/sphinx_config/prediction.rst
index 34ad13d7..de7739ea 100644
--- a/sphinx_config/prediction.rst
+++ b/sphinx_config/prediction.rst
@@ -3,7 +3,7 @@ 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.
+Predictions store the result of a prediction (i.e., returned by the predict method of a predictor). They store values (with uncertainty) at different future times. These are used to store states, inputs, outputs, perfomance metrics, and event states with uncertainty at savepoints.
 
 Two types of predictions are distributed with this package: `Prediction` and `UnweightedSamplesPrediction`, described below. `UnweightedSamplesPrediction` extends `Prediction` to allow some operations specific to cases where each prediction is represented by an UnweightedSamples object (e.g., accessing SimResult for a single sample).
 
diff --git a/sphinx_config/predictors.rst b/sphinx_config/predictors.rst
index f91569b0..3e09496a 100644
--- a/sphinx_config/predictors.rst
+++ b/sphinx_config/predictors.rst
@@ -1,7 +1,7 @@
 Predictors
 =======================
 
-The Predictor uses a state estimate (type UncertainData subclass, output of a state estimator), information about expected future loading, and a Prognostics Model (see: `prog_models package <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.
+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, performance metrics, event_states) at predefined points and the time that an event will occur (Time of Event, ToE) with uncertainty.
 
 Here's an example of its use. In this example we use the ThrownObject model and the MonteCarlo predictor, and we save the state every 1s. We also use a scalar first state (i.e., no uncertainty).
 
@@ -17,8 +17,8 @@ Here's an example of its use. In this example we use the ThrownObject model and
    >>> 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
+   >>> pred_results = pred.predict(first_state, future_loading, save_freq=1)
+   >>> pred_results.time_of_event.plot_hist(events='impact')  # Plot a histogram of when the impact event occurred
 
 See tutorial and examples for more information and additional features.
 
diff --git a/sphinx_config/release.rst b/sphinx_config/release.rst
new file mode 100644
index 00000000..05538bd6
--- /dev/null
+++ b/sphinx_config/release.rst
@@ -0,0 +1,48 @@
+Release Notes
+=======================
+
+..  contents:: 
+    :backlinks: top
+
+Updates in V1.3
+-----------------------
+* **New State Estimator Added** :class:`prog_algs.state_estimators.KalmanFilter`. Works with models derived from :class:`prog_models.LinearModel`. See :download:`examples.kalman_filter <../examples/kalman_filter.py>`
+* **New Predictor Added** :class:`prog_algs.predictors.UnscentedTransformPredictor`. See :download:`examples.utpredictor <../examples/utpredictor.py>`
+* Initial state estimate (x0) can now be passed as `UncertainData` to represent initial state uncertainty. See :download:`examples.playback <../examples/playback.py>`
+* Added new metrics for :class:`prog_algs.predictors.ToEPredictionProfile`: Prognostics horizon, Cumulative Relative Accuracy (CRA). See :download:`examples.playback <../examples/playback.py>`
+* Added ability to plot :class:`prog_algs.predictors.ToEPredictionProfile`: profile.plot(). See :download:`examples.playback <../examples/playback.py>`
+* Added new metric for :class:`prog_algs.predictors.Prediction`: Monotonicity, Relative Accuracy (RA)
+* Added new metric for :class:`prog_algs.uncertain_data.UncertainData` (and subclasses): Root Mean Square Error (RMSE)
+* Added new describe method for :class:`prog_algs.uncertain_data.UncertainData` (and subclasses)
+* Add support for python 3.10
+* Various performance improvements and bugfixes
+
+
+Updates in v1.2
+---------------
+
+Note for Existing Users
+***********************
+This release includes changes to the return format of the MonteCarlo Predictor's `predict` method. These changes were necessary to support non-sample based predictors. The non backwards-compatible changes are listed below:
+* times: 
+    * previous ```List[List[float]]``` where times[n][m] corresponds to timepoint m of sample n. 
+    * new ```List[float]``` where times[m] corresponds to timepoint m for all samples.
+* End of Life (EOL)/ Time of Event (ToE) estimates:
+    * previous ```List[float]``` where the times correspond to the time that the first event occurs.
+    * new ```UnweightedSamples``` where keys correspond to the inidividualevents predicted.
+* State at time of event (ToE).
+   * previous: element in states.
+   * new: member of ToE structure (e.g., ToE.final_state['event1']).
+
+General Updates
+***************
+* New Feature: Histogram and Scatter Plot of UncertainData.
+* New Feature: Vectorized particle filter.
+    * Particle Filter State Estimator is now vectorized for vectorized models - this significantly improves performance.
+* New Feature: Unscented Transform Predictor.
+    * New predictor that propogates sigma points forward to estimate time of event and future states.
+* New Feature: `Prediction` class to represent predicted future values.
+* New Feature: `ToEPredictionProfile` class to represent and operate on the result of multiple predictions generated at different prediction times.
+* Added metrics `percentage_in_bounds` and `metrics` and plots to UncertainData .
+* Add support for Python3.9.
+* General Bugfixes.
diff --git a/sphinx_config/state_estimators.rst b/sphinx_config/state_estimators.rst
index af7b1429..ee41ad5e 100644
--- a/sphinx_config/state_estimators.rst
+++ b/sphinx_config/state_estimators.rst
@@ -1,6 +1,6 @@
 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.
+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 performance metrics). This state estimate can either be used by itself or as input to a `Predictor <predictors.html>`__. A state estimator is typically run each time new information is available.
 
 Here's an example of its use. In this example we use the unscented kalman filter state estimator and the ThrownObject model. 
 
@@ -25,6 +25,10 @@ Included State Estimators
 -------------------------
 The following state estimators are included with this package. A new state estimator can be created by subclassing `prog_algs.state_estimators.StateEstimator`. See also: `state_estimator_template.py`
 
+Kalman Filter
+*************************
+.. autoclass:: prog_algs.state_estimators.KalmanFilter
+
 Unscented Kalman Filter
 *************************
 .. autoclass:: prog_algs.state_estimators.UnscentedKalmanFilter
diff --git a/src/prog_algs/__init__.py b/src/prog_algs/__init__.py
index 48810b36..0dcea9e9 100644
--- a/src/prog_algs/__init__.py
+++ b/src/prog_algs/__init__.py
@@ -5,7 +5,7 @@
 
 import warnings
 
-__version__ = '1.2.3'
+__version__ = '1.3.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)
@@ -27,13 +27,20 @@ def run_prog_playback(obs, pred, future_loading, output_measurements, **kwargs):
     for (t, measurement) in output_measurements:
         obs.estimate(t, future_loading(t), measurement)
         if t >= next_predict:
-            (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)
+            pred_results = pred.predict(obs.x.sample(config['num_samples']), future_loading, **config['predict_config'])
+            times.append(pred_results.times)
+            inputs.append(pred_results.inputs)
+            states.append(pred_results.states)
+            outputs.append(pred_results.outputs)
+            event_states.append(pred_results.event_states)
+            toes.append(pred_results.time_of_event)
             index += 1
             next_predict += config['predict_rate']
-    return (times, inputs, states, outputs, event_states, toes) 
+    return predictors.PredictionResults(
+        times, 
+        inputs, 
+        states, 
+        outputs, 
+        event_states, 
+        toes
+    ) 
diff --git a/src/prog_algs/metrics/__init__.py b/src/prog_algs/metrics/__init__.py
index 90b082b4..eb52dd7a 100644
--- a/src/prog_algs/metrics/__init__.py
+++ b/src/prog_algs/metrics/__init__.py
@@ -3,6 +3,6 @@
 from . import samples
 from .toe_metrics import prob_success
 from .uncertain_data_metrics import calc_metrics
-from .toe_profile_metrics import alpha_lambda
+from .toe_profile_metrics import alpha_lambda, prognostic_horizon, cumulative_relative_accuracy
 
-__all__ = ['alpha_lambda', 'calc_metrics', 'prob_success']
+__all__ = ['alpha_lambda', 'calc_metrics', 'prob_success', 'prognostic_horizon', 'cumulative_relative_accuracy']
diff --git a/src/prog_algs/metrics/samples.py b/src/prog_algs/metrics/samples.py
index 6ca17b3b..6dcb5f60 100644
--- a/src/prog_algs/metrics/samples.py
+++ b/src/prog_algs/metrics/samples.py
@@ -5,25 +5,35 @@
 from .toe_metrics import prob_success
 from .toe_profile_metrics import alpha_lambda
 
-from numpy import mean
+from numpy import mean, sqrt
 from warnings import warn
 
-def mean_square_error(values, ground_truth):
+def mean_square_error(values : list, ground_truth : float) -> float:
     """Mean Square Error
     Args:
-        values ([float]): time of event for a single event, output from predictor
+        values (List[float]): Times of Event (ToE) for a single event, output from predictor
         ground_truth (float): Ground truth ToE
     Returns:
-        float: mean square error of toe predictions
+        float: mean square error of ToE predictions
     """
     return sum([(mean(x) - ground_truth)**2 for x in values])/len(values)
 
-def percentage_in_bounds(toe, bounds):
+def root_mean_square_error(values, ground_truth):
+    """Root Mean Square Error
+    Args:
+        values (List[float]): Times of Event (ToE) for a single event, output from predictor
+        ground_truth (float): Ground truth ToE
+    Returns:
+        float: root mean square error of ToE predictions
+    """
+    return sqrt(sum([(mean(x) - ground_truth)**2 for x in values])/len(values))
+
+def percentage_in_bounds(toe : list, bounds : tuple) -> float:
     """Calculate percentage of ToE dist is within specified bounds
 
     Args:
-        toe ([float]): Times of event for a single event, output from predictor
-        bounds ([float, float]): Lower and upper bounds
+        toe (List[float]): Times of Event (ToE) for a single event, output from predictor
+        bounds ((float, float)): Lower and upper bounds
 
     Returns:
         float: Percentage within bounds (where 1 = 100%)
diff --git a/src/prog_algs/metrics/toe_metrics.py b/src/prog_algs/metrics/toe_metrics.py
index 1dcb8ac3..c9a36b82 100644
--- a/src/prog_algs/metrics/toe_metrics.py
+++ b/src/prog_algs/metrics/toe_metrics.py
@@ -1,21 +1,21 @@
 # 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
+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):
+def prob_success(toe : UncertainData, time : float, **kwargs) -> float:
     """Calculate probability of success - i.e., probability that event will not occur within a given time (i.e., success)
 
     Args:
-        toe (UncertainData or array[float]): Times of event for a single event (array[float]) or multiple events, output from predictor
+        toe (UncertainData or list[float]): Times of event for a single event (array[float]) or multiple events, output from predictor
         time (float): time for calculation
         **kwargs (optional): Configuration parameters. Supported parameters include:
-          * n_samples (int): Number of samples to use for calculating metrics (if toe is not UnweightedSamples). Defaults to 10,000.
+          * 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:
diff --git a/src/prog_algs/metrics/toe_profile_metrics.py b/src/prog_algs/metrics/toe_profile_metrics.py
index 1d7cc095..794c9401 100644
--- a/src/prog_algs/metrics/toe_profile_metrics.py
+++ b/src/prog_algs/metrics/toe_profile_metrics.py
@@ -1,11 +1,14 @@
 # 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)
+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 collections import defaultdict
+from typing import Callable
 from ..predictors import ToEPredictionProfile
+from typing import Dict
 
-def alpha_lambda(toe_profile : ToEPredictionProfile, ground_truth : dict, lambda_value : float, alpha : float, beta : float, **kwargs): 
+def alpha_lambda(toe_profile : ToEPredictionProfile, ground_truth : dict, lambda_value : float, alpha : float, beta : float, **kwargs) -> dict: 
     """
     Compute alpha lambda metric, a common metric in prognostics. Alpha-Lambda is met if alpha % of the Time to Event (TtE) distribution is within beta % of the ground truth at prediction time lambda.
 
@@ -15,11 +18,11 @@ def alpha_lambda(toe_profile : ToEPredictionProfile, ground_truth : dict, lambda
         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:
+        kwargs (optional): configuration arguments. Accepted args include:
             * keys (list[string], optional): list of keys to use. If not provided, all keys are used.
 
     Returns:
-        bool: if alpha-lambda met
+        dict: dictionary containing key value pairs for each key and whether the alpha-lambda was met.
     """
     params = {
         'print': False
@@ -31,13 +34,77 @@ def alpha_lambda(toe_profile : ToEPredictionProfile, ground_truth : dict, lambda
             # 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']:
+            bounds = {key : [gt - alpha*(gt-t_prediction), gt + alpha*(gt-t_prediction)] for key, gt in ground_truth.items()}
+            pib = toe.percentage_in_bounds(bounds)
+            result = {key: pib[key] >= beta for key in keys}
+            if params['print']:
+                for key in keys:
                     print('\n', key)
                     print('\ttoe:', toe.key(key))
-                    print('\tBounds: [{} - {}]({}%)'.format(lower_bound, upper_bound, toe.percentage_in_bounds([lower_bound, upper_bound])[key]))
+                    print('\tBounds: [{} - {}]({}%)'.format(bounds[key][0], bounds[key][1], pib[key]))
             return result
+
+def prognostic_horizon(toe_profile : ToEPredictionProfile, criteria_eqn : Callable, ground_truth : dict, **kwargs) -> dict:
+    """
+    Compute prognostic horizon metric, defined as the difference between a time ti, when the predictions meet specified performance criteria, and the time corresponding to the true Time of Event (ToE), for each event.
+    PH = ToE - ti
+    Args:
+        toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions
+        criteria_eqn (Callable function): A function (tte: UncertainData, ground_truth_tte: dict[str, float]) -> dict[str, bool] calculating whether a prediction in ToEPredictionProfile meets some criteria. \n
+            | Args: 
+            |  * tte (UncertainData): A single prediction of Time of Event (ToE)
+            |  * ground truth tte (dict[str, float]): Ground truth passed into prognostics_horizon
+            | Returns: Map of event names to boolean representing if the event has been met. 
+            |   e.g., {'event1': True, 'event2': False}
+        ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1}
+        kwargs (optional): configuration arguments. Accepted args include:
+            * print (bool): Boolean specifying whether the prognostic horizon metric should be printed.
+
+    Returns:
+        dict: Dictionary containing prognostic horizon calculations (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1}
+    """
+    params = {
+        'print': False
+    }
+    params.update(kwargs)
+
+    ph_result = {k:None for k in ground_truth.keys()} # False means not yet met; will be either a numerical value or None if met
+    for (t_prediction, toe) in toe_profile.items():
+        # Convert to TtE for toe and ground_truth
+        tte = toe - t_prediction
+        ground_truth_tte = {}
+        ground_truth_tte = {k:v-t_prediction for k,v in ground_truth.items()}
+        # Pass to criteria_eqn
+        criteria_eqn_dict = criteria_eqn(tte, ground_truth_tte) # -> dict[event_names as str, bool]
+        for k,v in criteria_eqn_dict.items():
+            if v and (ph_result[k] == None):
+                ph_calc = ground_truth[k] - t_prediction
+                if ph_calc > 0:
+                    ph_result[k] = ph_calc # PH = EOL - ti # ground truth is a dictionary {'EOD': 3005.2} should be ph_result[k] = g_truth[key] - t_prediction
+                if (all(v != None for v in ph_result.values())):
+                    # Return PH once all criteria are met
+                    return ph_result
+    # Return PH when criteria not met for at least one event key
+    return ph_result
+
+def cumulative_relative_accuracy(toe_profile : ToEPredictionProfile, ground_truth : dict, **kwargs) -> Dict[str, float]:
+    """
+    Compute cumulative relative accuracy for a given profile, defined as the normalized sum of relative prediction accuracies at specific time instances.
+    
+    CRA = Σ(RA)/N for each event
+    Where Σ is summation of all relative accuracies for a given profile and N is the total count of profiles (Journal Prognostics Health Management, Saxena et al.)
+    Args:
+        toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions
+        ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1}
+        kwargs (optional): configuration arguments. Accepted args include:
+
+    Returns:
+        dict: Dictionary containing cumulative relative accuracy (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1}
+    """
+    ra_sums = defaultdict(int)
+    for uncertaindata in toe_profile.values():
+        for event,value in uncertaindata.relative_accuracy(ground_truth).items():
+            ra_sums[event] += value
+    return {event:ra_sum/len(toe_profile) for event, ra_sum in ra_sums.items()}
+
+
diff --git a/src/prog_algs/metrics/uncertain_data_metrics.py b/src/prog_algs/metrics/uncertain_data_metrics.py
index f940a5aa..acfaeb0d 100644
--- a/src/prog_algs/metrics/uncertain_data_metrics.py
+++ b/src/prog_algs/metrics/uncertain_data_metrics.py
@@ -3,18 +3,18 @@
 """
 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 typing import Iterable, Union
 from numpy import isscalar, mean, std, array
 from scipy import stats
 from warnings import warn
 from ..uncertain_data import UncertainData, UnweightedSamples
 
-def calc_metrics(data, ground_truth = None, **kwargs):
+def calc_metrics(data : UncertainData, ground_truth : Union[float, dict] = None, **kwargs) -> dict:
     """Calculate all time of event metrics
 
     Args:
         data (array[float] or UncertainData): data from a single event
-        ground_truth (float, optional): Ground truth value. Defaults to None.
+        ground_truth (float, optional): Ground truth value. Defaults to None. dict when data is  of type UncertainData.
         **kwargs (optional): Configuration parameters. Supported parameters include:
           * n_samples (int): Number of samples to use for calculating metrics (if data is not UnweightedSamples). Defaults to 10,000.
           * keys (list of strings, optional): Keys to calculate metrics for. Defaults to all keys.
@@ -72,6 +72,8 @@ def calc_metrics(data, ground_truth = None, **kwargs):
 
     # If we get here then Data is a list of numbers- calculate metrics for numbers
     data_abridged = array([d for d in data if d is not None]) # Must be array
+    if len(data_abridged) == 0:
+        raise ValueError('All samples were none')
     if len(data_abridged) < len(data):
         warn("Some samples were None, resulting metrics only consider non-None samples. Note: in some cases, this will bias the metrics.")
     data_abridged.sort()
diff --git a/src/prog_algs/predictors/__init__.py b/src/prog_algs/predictors/__init__.py
index 59d3641f..29642d4d 100644
--- a/src/prog_algs/predictors/__init__.py
+++ b/src/prog_algs/predictors/__init__.py
@@ -2,13 +2,13 @@
 
 from .monte_carlo import MonteCarlo
 from .predictor import Predictor
-from .prediction import Prediction, UnweightedSamplesPrediction
+from .prediction import Prediction, UnweightedSamplesPrediction, PredictionResults
 from .toe_prediction_profile import ToEPredictionProfile
 from .unscented_transform import UnscentedTransformPredictor
 
 # For naming consistancy
-# Unortionately, prog_algs was released with inconsistant naming (UnscentedTransformPredictor vs MonteCarlo).
-# For naming consistancy and to avoid confusion, we created aliases for the two classes.
+# Unfortunately, prog_algs was released with inconsistent naming (UnscentedTransformPredictor vs MonteCarlo).
+# For naming consistency and to avoid confusion, we created aliases for the two classes.
 # They can be called by the name of the method (e.g., UnscentedTranform) or with 'predictor' at the end (e.g., UnscentedTransformPredictor).
 UnscentedTransform = UnscentedTransformPredictor
 MonteCarloPredictor = MonteCarlo
diff --git a/src/prog_algs/predictors/monte_carlo.py b/src/prog_algs/predictors/monte_carlo.py
index 47d040bd..e0a3e9a9 100644
--- a/src/prog_algs/predictors/monte_carlo.py
+++ b/src/prog_algs/predictors/monte_carlo.py
@@ -1,65 +1,12 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
-from .prediction import UnweightedSamplesPrediction
+from .prediction import UnweightedSamplesPrediction, PredictionResults
 from .predictor import Predictor
 from copy import deepcopy
-from functools import partial
+from typing import Callable
 from prog_models.sim_result import SimResult, LazySimResult
 from prog_algs.uncertain_data import UnweightedSamples, UncertainData
 
-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 = deepcopy(params)
-    params['x'] = x
-    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)
-
-        # 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
-                last_state[event] = None
-            break
-
-        # An event has occured
-        time_of_event[event] = times[-1]
-        events_remaining.remove(event)  # No longer an event to predect to
-
-        # Remove last state (event)
-        params['t0'] = times.pop()
-        if 'horizon' in params:
-            # Reset horizon to account for time spent
-            params['horizon'] = params['horizon'] - params['t0']
-        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)
-
 
 class MonteCarlo(Predictor):
     """
@@ -86,10 +33,13 @@ class MonteCarlo(Predictor):
     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
 
-    def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
-        if isinstance(state, dict):
+    default_parameters = { 
+        'n_samples': 100  # Default number of samples to use, if none specified
+    }
+
+    def predict(self, state : UncertainData, future_loading_eqn : Callable, **kwargs) -> PredictionResults:
+        if isinstance(state, dict) or isinstance(state, self.model.StateContainer):
             # Convert to UnweightedSamples
             from prog_algs.uncertain_data import ScalarData
             state = ScalarData(state)
@@ -98,23 +48,91 @@ def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
         params.update(kwargs) # update for specific run
 
         # 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)
+        state = state.sample(params['n_samples'])
+
+        ouput_eqn = self.model.output
+        es_eqn = self.model.event_state
+        tm_eqn = self.model.threshold_met
+        simulate_to_threshold = self.model.simulate_to_threshold
+
+        time_of_event_all = []
+        last_states = []
+        times_all = []
+        inputs_all = []
+        states_all = []
+        outputs_all = []
+        event_states_all = []
 
         # Perform prediction
-        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]
-        times_all, inputs_all, states_all, outputs_all, event_states_all, time_of_event, last_states = map(list, zip(*result))
+        for x in state:
+            events_remaining = deepcopy(params['events'])
+            first_output = ouput_eqn(x)
+            
+            time_of_event = {}
+            last_state = {}
+            times = []
+            inputs = SimResult()
+            states = SimResult()
+            outputs = LazySimResult(fcn = ouput_eqn)
+            event_states = LazySimResult(fcn = es_eqn)
+
+            t0 = params.get('t0', 0)
+            if 'save_freq' in params and not isinstance(params['save_freq'], tuple):
+                params['save_freq'] = (t0, params['save_freq'])
+            
+
+            # Non-vectorized prediction
+            while len(events_remaining) > 0:  # Still events to predict
+                (t, u, xi, z, es) = simulate_to_threshold(future_loading_eqn, first_output, 
+                    **{**params, 'threshold_keys': events_remaining, 'print': False, 'progress': False, 't0': t0, 'x': x}  # Merge then separate
+                )
+                if len(times) != 0:
+                    # is not the first iteration, therefore the first saved state is when the last event 
+                    t.pop(0)
+                    u.pop(0)
+                    xi.pop(0)
+                    z.pop(0)
+                    es.pop(0)
+
+                # Add results
+                times.extend(t)
+                inputs.extend(u)
+                states.extend(xi)
+                outputs.extend(z)
+                event_states.extend(es)
+
+                # Get which event occurs
+                t_met = tm_eqn(states[-1])
+                t_met = {key: t_met[key] for key in events_remaining}  # Only look at remaining keys
+                try:
+                    event = list(t_met.keys())[list(t_met.values()).index(True)]
+                except ValueError:
+                    # no event has occured
+                    for event in events_remaining:
+                        time_of_event[event] = None
+                        last_state[event] = None
+                    break
+
+                # An event has occured
+                time_of_event[event] = times[-1]
+                events_remaining.remove(event)  # No longer an event to predect to
+
+                # Remove last state (event)
+                t0 = times.pop()
+                inputs.pop()
+                x = states.pop()
+                last_state[event] = deepcopy(x)
+                outputs.pop()
+                event_states.pop()
+            
+            # Add to "all" structures
+            times_all.append(times)
+            inputs_all.append(inputs)
+            states_all.append(states)
+            outputs_all.append(outputs)
+            event_states_all.append(event_states)
+            time_of_event_all.append(time_of_event)
+            last_states.append(last_state)
         
         # Return longest time array
         times_length = [len(t) for t in times_all]
@@ -125,11 +143,19 @@ def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
         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)
+        time_of_event = UnweightedSamples(time_of_event_all)
 
         # 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)
+
+        return PredictionResults(
+            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
index 16fd73aa..ff683393 100644
--- a/src/prog_algs/predictors/prediction.py
+++ b/src/prog_algs/predictors/prediction.py
@@ -1,9 +1,13 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
-from collections import UserList
+from collections import UserList, defaultdict, namedtuple
+from typing import Dict
+from numpy import sign
 from warnings import warn
 
-from ..uncertain_data import UnweightedSamples
+from ..uncertain_data import UnweightedSamples, UncertainData
+
+PredictionResults = namedtuple('PredictionResults', ["times", "inputs", "states", "outputs", "event_states", "time_of_event"])
 
 
 class Prediction():
@@ -11,28 +15,28 @@ class Prediction():
     Class for the result of a prediction. Is returned by the predict method of a predictor.
 
     Args:
-        times (array(float)):
+        times (list[float]):
             Times for each data point where times[n] corresponds to data[n]
-        data (array(UncertainData)):
+        data (list[UncertainData]):
             Data points for each time in times 
     """
 
-    def __init__(self, times, data):
+    def __init__(self, times : list, data : list):
         self.times = times
         self.data = data
 
-    def __eq__(self, other):
+    def __eq__(self, other : "Prediction") -> bool:
         """Compare 2 Predictions
 
         Args:
-            other (Precition):
+            other (Prediction):
 
         Returns:
             bool: If the two Predictions are equal
         """
         return self.times == other.times and self.data == other.data
 
-    def snapshot(self, time_index):
+    def snapshot(self, time_index : int) -> UncertainData:
         """Get all samples from a specific timestep
 
         Args:
@@ -45,11 +49,11 @@ def snapshot(self, time_index):
         return self.data[time_index]
 
     @property
-    def mean(self):
+    def mean(self) -> list:
         """Estimate of the mean value of the prediction at each time
 
         Returns:
-            array(dict): 
+            list[dict]: 
                 Mean value of the prediction at each time where mean[n] corresponds to the mean value of the prediction at time times[n].\n
                 The mean value at each time is a dictionary. \n
                 e.g., [{'state1': 1.2, 'state2': 1.3, ...}, ...]
@@ -59,10 +63,41 @@ def mean(self):
         """
         return [dist.mean for dist in self.data]
 
-    def time(self, index):
+    def time(self, index : int):
         warn("Deprecated. Please use prediction.times[index] instead.")
         return self.times[index]
 
+    def monotonicity(self) -> Dict[str, float]:
+        """Calculate monotonicty for a single prediction. 
+        Given a single prediction, for each event: go through all predicted states and compare those to the next one.
+        Calculates monotonicity for each event key using its associated mean value in UncertainData.
+        
+        monotonoicity = |Σsign(i+1 - i) / N-1|
+        Where N is number of measurements and sign indicates sign of calculation.
+        Coble, J., et. al. (2021). Identifying Optimal Prognostic Parameters from Data: A Genetic Algorithms Approach. Annual Conference of the PHM Society.
+        http://www.papers.phmsociety.org/index.php/phmconf/article/view/1404
+        Baptistia, M., et. al. (2022). Relation between prognostics predictor evaluation metrics and local interpretability SHAP values. Aritifical Intelligence, Volume 306.
+        https://www.sciencedirect.com/science/article/pii/S0004370222000078
+
+        Args:
+            None
+        Returns:
+            float: Value between [0, 1] indicating monotonicity of a given event for the Prediction.
+        """
+        # Collect and organize mean values for each event
+        by_event = defaultdict(list)
+        for uncertaindata in self.data:
+            for key,value in uncertaindata.mean.items():
+                by_event[key].append(value)
+
+        # For each event, calculate monotonicity using formula
+        result = {}
+        for key,l in by_event.items():
+            mono_sum = []
+            for i in range(len(l)-1): 
+                mono_sum.append(sign(l[i+1] - l[i])) 
+            result[key] = abs(sum(mono_sum) / (len(l)-1))
+        return result
 
 class UnweightedSamplesPrediction(Prediction, UserList):
     """
@@ -75,7 +110,7 @@ class UnweightedSamplesPrediction(Prediction, UserList):
             Data points where data[n] is a SimResult for sample n
     """
 
-    def __init__(self, times, data):
+    def __init__(self, times : list, data : list):
         super(UnweightedSamplesPrediction, self).__init__(times, data)
         self.__transformed = False  # If transform has been calculated
 
@@ -89,20 +124,20 @@ def __calculate_tranform(self):
         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):
+    def __str__(self) -> str:
         return "UnweightedSamplesPrediction with {} savepoints".format(len(self.times))
 
     @property
-    def mean(self):
+    def mean(self) -> list:
         if not self.__transformed:
             self.__calculate_tranform()
         return [dist.mean for dist in self.__transform]
 
-    def sample(self, sample_id):
+    def sample(self, sample_id : int):
         warn("Deprecated. Please use prediction[sample_id] instead.")
         return self[sample_id]
 
-    def snapshot(self, time_index):
+    def snapshot(self, time_index : int) -> UnweightedSamples:
         """Get all samples from a specific timestep
 
         Args:
diff --git a/src/prog_algs/predictors/predictor.py b/src/prog_algs/predictors/predictor.py
index 10caef34..a8ebd65c 100644
--- a/src/prog_algs/predictors/predictor.py
+++ b/src/prog_algs/predictors/predictor.py
@@ -2,6 +2,9 @@
 
 from abc import ABC, abstractmethod
 from copy import deepcopy
+from typing import Callable
+
+from prog_algs.predictors.prediction import PredictionResults
 from ..exceptions import ProgAlgTypeError
 from ..uncertain_data import UncertainData
 
@@ -41,7 +44,7 @@ def __init__(self, model, **kwargs):
         self.parameters.update(kwargs)
 
     @abstractmethod
-    def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
+    def predict(self, state : UncertainData, future_loading_eqn : Callable, **kwargs) -> PredictionResults:
         """
         Perform a single prediction
 
@@ -59,7 +62,7 @@ def predict(self, state : UncertainData, future_loading_eqn, **kwargs):
             * 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
+        Return PredictionResults namedtuple
         ----------
         times : List[float]
             Times for each savepoint such that inputs.snapshot(i), states.snapshot(i), outputs.snapshot(i), and event_states.snapshot(i) are all at times[i]            
diff --git a/src/prog_algs/predictors/toe_prediction_profile.py b/src/prog_algs/predictors/toe_prediction_profile.py
index 33b42e56..95b034e4 100644
--- a/src/prog_algs/predictors/toe_prediction_profile.py
+++ b/src/prog_algs/predictors/toe_prediction_profile.py
@@ -1,12 +1,14 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
+import matplotlib.pyplot as plt
 from collections import UserDict
 from typing import Dict
+import numpy as np
 
 from prog_algs.uncertain_data import UncertainData 
 
 class ToEPredictionProfile(UserDict):
     """
-    Data structure for storing the result of multiple predictions, including time of prediction. This data structure can be treated as a dictionary of time of prediction to toe prediction. Iteration of this data structure is in order of increasing time of prediction
+    Data structure for storing the result of multiple predictions, including time of prediction. This data structure can be treated as a dictionary of time of prediction to Time of Event (ToE) prediction. Iteration of this data structure is in order of increasing time of prediction
     """
     def add_prediction(self, time_of_prediction: float, toe_prediction: UncertainData):
         """Add a single prediction to the profile
@@ -61,3 +63,86 @@ def alpha_lambda(self, ground_truth : Dict[str, float], lambda_value : float, al
         """
         from ..metrics import alpha_lambda
         return alpha_lambda(self, ground_truth, lambda_value, alpha, beta, **kwargs)
+
+    def prognostic_horizon(self, criteria_eqn, ground_truth, **kwargs) -> Dict[str, float]:
+        """
+        Compute prognostic horizon metric, defined as the difference between a time ti, when the predictions meet specified performance criteria, and the time corresponding to the true Time of Event (ToE), for each event.
+        PH = ToE - ti
+        Args:
+            toe_profile (ToEPredictionProfile): A profile of predictions, the combination of multiple predictions
+            criteria_eqn (Callable function): A function (toe: UncertainData, ground_truth: dict[str, float]) -> dict[str, bool] calculating whether a prediction in ToEPredictionProfile meets some criteria. \n
+                | Args: 
+                |  * toe (UncertainData): A single prediction of Time of Event (ToE)
+                |  * ground truth (dict[str, float]): Ground truth passed into prognostics_horizon
+                | Returns: Map of event names to boolean representing if the event has been met. 
+                |   e.g., {'event1': True, 'event2': False}
+            ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1}
+            kwargs (optional): configuration arguments. Accepted args include:
+                * print (bool): Boolean specifying whether the prognostic horizon metric should be printed.
+
+        Returns:
+            dict: Dictionary containing prognostic horizon calculations (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1}
+        """
+        from ..metrics import prognostic_horizon
+        return prognostic_horizon(self, criteria_eqn, ground_truth, **kwargs)
+
+
+    def cumulative_relative_accuracy(self, ground_truth, **kwargs) -> Dict[str, float]:
+        """
+        Compute cumulative relative accuracy for a given profile, defined as the normalized sum of relative prediction accuracies at specific time instances.
+        
+        CRA = Σ(RA)/N for each event
+        Where Σ is summation of all relative accuracies for a given profile and N is the total count of profiles (Journal Prognostics Health Management, Saxena et al.)
+        Args:
+            ground_truth (dict): Dictionary containing ground truth; specified as key, value pairs for event and its value. E.g, {'event1': 47.3, 'event2': 52.1, 'event3': 46.1}
+            kwargs (optional): configuration arguments. Accepted args include:
+
+        Returns:
+            dict: Dictionary containing cumulative relative accuracy (value) for each event (key). e.g., {'event1': 12.3, 'event2': 15.1}
+        """
+        from ..metrics import cumulative_relative_accuracy
+        return cumulative_relative_accuracy(self, ground_truth, **kwargs)
+    
+    def plot(self, ground_truth : dict = None , alpha : float = None, show : bool = True) -> dict: # use ground truth, alpha if given,
+        """Produce an alpha-beta plot depicting the TtE distribution by time of prediction.
+
+        Args:
+            ground_truth : dict = None
+                Optional dictionary argument containing event and its respective ground truth value; none by default and plotted if specified
+            alpha : float
+                Optional alpha value; none by default and plotted if specified
+            show : bool = True
+                Optional bool value; specify whether to display generated plots or not
+        Returns:
+            dict
+                Collection of generated matplotlib figures for each event in profile
+        """
+        result_figs = {}
+        for t,v in self.items():
+            raw_samples = v.sample(100) # sample distribution (red scatter plot)
+            for key in v.keys():
+                if key not in result_figs:
+                    # Prepare Figure for Plot
+                    fig_window = plt.figure() # Create new figure for this event key
+                    fig_sub = fig_window.subplots()
+                    fig_sub.grid()
+                    fig_sub.set_title(f"{key} Event")
+                    fig_sub.set_xlabel('Time of Prediction (s)') # time to prediction
+                    fig_sub.set_ylabel('Time to Event (s)') # time to event
+                    result_figs[key] = fig_window
+                # Create scatter plot for this event
+                samples = [e[key]-t for e in raw_samples]
+                result_figs[key].get_axes()[0].scatter([t]*len(samples), samples, color='red') # Adding single distribution of estimates
+
+        if ground_truth: # If ground_truth is specified, add ground_truth to each event plot (green line)
+            for key, val in ground_truth.items():
+                gt_x = range(int(val))
+                gt_y = range(int(val), 0, -1)
+                result_figs[key].get_axes()[0].plot(gt_x, gt_y, color='green')
+                if alpha: # if ground_truth and alpha are specified, add alpha bounds (faded green highlight)
+                    result_figs[key].get_axes()[0].fill_between(gt_x, np.array(gt_y)*(1-alpha), np.array(gt_y)*(1+alpha), color='green', alpha=0.2)
+                result_figs[key].get_axes()[0].set_xlim(0, val+1)
+
+        if show: # Optionally not display plots and just return plot objects
+            plt.show()
+        return result_figs 
diff --git a/src/prog_algs/predictors/unscented_transform.py b/src/prog_algs/predictors/unscented_transform.py
index e2ae699b..633a2720 100644
--- a/src/prog_algs/predictors/unscented_transform.py
+++ b/src/prog_algs/predictors/unscented_transform.py
@@ -1,6 +1,7 @@
 # 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 typing import Callable
+from .prediction import Prediction, UnweightedSamplesPrediction, PredictionResults
 from .predictor import Predictor
 from numpy import diag, array, transpose
 from copy import deepcopy
@@ -10,7 +11,7 @@
 
 
 class LazyUTPrediction(Prediction):
-    def __init__(self, state_prediction, sigma_fcn, ut_fcn, transform_fcn):
+    def __init__(self, state_prediction, sigma_fcn : Callable, ut_fcn : Callable, transform_fcn : Callable):
         self.times = state_prediction.times
         self.__states = state_prediction
         self.__data = None
@@ -98,8 +99,8 @@ def __init__(self, model, **kwargs):
         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)
+        num_states = model.n_states
+        num_measurements = model.n_outputs
 
         if 'Q' not in self.parameters:
             # Default 
@@ -123,7 +124,7 @@ def state_transition(x, dt):
         self.filter.Q = self.parameters['Q']
         self.filter.R = self.parameters['R']
 
-    def predict(self, state, future_loading_eqn, **kwargs):
+    def predict(self, state, future_loading_eqn : Callable, **kwargs) -> PredictionResults:
         """
         Perform a single prediction
 
@@ -140,7 +141,7 @@ def predict(self, state, future_loading_eqn, **kwargs):
             * horizon : Prediction horizon (s)
             * events : List of events to be predicted (subset of model.events, default is all events)
 
-        Returns (tuple)
+        Returns (PredictionResults)
         -------
         times: [number]
             Times for each simulated point in format times[index]
@@ -256,5 +257,12 @@ def update_all():
         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)  
+        return PredictionResults(
+            times, 
+            inputs_prediction, 
+            state_prediction, 
+            output_prediction, 
+            event_state_prediction, 
+            time_of_event
+        )  
       
\ No newline at end of file
diff --git a/src/prog_algs/state_estimators/__init__.py b/src/prog_algs/state_estimators/__init__.py
index d776766e..4c034066 100644
--- a/src/prog_algs/state_estimators/__init__.py
+++ b/src/prog_algs/state_estimators/__init__.py
@@ -1,6 +1,7 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
+from .kalman_filter import KalmanFilter
 from .particle_filter import ParticleFilter
 from .state_estimator import StateEstimator
 from .unscented_kalman_filter import UnscentedKalmanFilter
-__all__ = ['state_estimator', 'StateEstimator', 'unscented_kalman_filter', 'UnscentedKalmanFilter', 'particle_filter', 'ParticleFilter']
+__all__ = ['KalmanFilter', 'state_estimator', 'StateEstimator', 'unscented_kalman_filter', 'UnscentedKalmanFilter', 'particle_filter', 'ParticleFilter']
diff --git a/src/prog_algs/state_estimators/kalman_filter.py b/src/prog_algs/state_estimators/kalman_filter.py
new file mode 100644
index 00000000..1a2c2444
--- /dev/null
+++ b/src/prog_algs/state_estimators/kalman_filter.py
@@ -0,0 +1,163 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+from copy import deepcopy
+from typing import Callable
+import numpy as np
+from warnings import warn
+from filterpy import kalman
+from prog_models import LinearModel
+from . import state_estimator
+from ..uncertain_data import MultivariateNormalDist, UncertainData
+
+class KalmanFilter(state_estimator.StateEstimator):
+    """
+    A Kalman Filter (KF) for state estimation
+
+    This class defines the logic for performing a kalman filter with a LinearModel (see Prognostics Model Package). This filter uses measurement data with noise to generate a state estimate and covariance matrix. 
+
+    The supported configuration parameters (keyword arguments) for UKF construction are described below:
+
+    Args:
+        model : ProgModel
+            A prognostics model to be used in state estimation
+            See: Prognostics Model Package
+        x0 : UncertainData, model.StateContainer, or dict
+            Initial (starting) state, with keys defined by model.states \n
+            e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def']
+
+    Keyword Args:
+        alpha: float
+            KF Scaling parameter. An alpha > 1 turns this into a fading memory filter.
+        t0 : float
+            Starting time (s)
+        dt : float 
+            time step (s)
+        Q : List[List[float]]
+            Process Noise Matrix 
+        R : List[List[float]]
+            Measurement Noise Matrix
+
+    Note:
+        The Kalman Filter does not support a custom measurement function
+    """
+    default_parameters = {
+        'alpha': 1, 
+        't0': -1e-10,
+        'dt': 1
+    } 
+    
+    def __init__(self, model, x0, measurement_eqn : Callable = None, **kwargs):
+        # Note: Measurement equation kept in constructor to keep it consistent with other state estimators. This way measurement equation can be provided as an ordered argument, and will just be ignored here
+        if not isinstance(model, LinearModel):
+            raise Exception('Kalman Filter only supports Linear Models (i.e., models derived from prog_models.LinearModel)')
+
+        super().__init__(model, x0, None, **kwargs)
+
+        self.x0 = x0
+
+        if 'Q' not in self.parameters:
+            self.parameters['Q'] = np.diag([1.0e-3 for i in x0.keys()])
+        if 'R' not in self.parameters:
+            # Size of what's being measured (not output) 
+            # This is determined by running the measure function on the first state
+            self.parameters['R'] = np.diag([1.0e-3 for i in range(model.n_outputs)])
+        
+        num_states = len(x0.keys())
+        num_inputs = model.n_inputs + 1
+        num_measurements = model.n_outputs
+        F = deepcopy(model.A)
+        B = deepcopy(model.B)
+        if np.size(B) == 0:
+            # If B is empty, replace with E. 
+            # Append wont work if B is empty
+            B = deepcopy(model.E)
+        else:
+            B = np.append(B, deepcopy(model.E), 0)
+
+        self.filter = kalman.KalmanFilter(num_states, num_measurements, num_inputs)
+
+        self.__state_keys = list(x0.keys())
+        if isinstance(x0, dict) or isinstance(x0, model.StateContainer):
+            warn("Warning: Use UncertainData type if estimating filtering with uncertain data.")
+            self.filter.x = np.array([[x0[key]] for key in model.states]) # x0.keys()
+            self.filter.P = self.parameters['Q'] / 10
+        elif isinstance(x0, UncertainData):
+            x_mean = x0.mean
+            self.filter.x = np.array([[x_mean[key]] for key in model.states])
+
+            # Reorder covariance to be in same order as model.states
+            mapping = {i: list(x0.keys()).index(key) for i, key in enumerate(model.states)}
+            cov = x0.cov  # Set covariance in case it has been calculated
+            mapped_cov = [[cov[mapping[i]][mapping[j]] for j in range(len(cov))] for i in range(len(cov))] # Set covariance based on mapping
+            self.filter.P = np.array(mapped_cov)
+        else:
+            raise TypeError("TypeError: x0 initial state must be of type {{dict, UncertainData}}")
+
+        self.filter.Q = self.parameters['Q']
+        self.filter.R = self.parameters['R']
+        self.filter.F = F
+        self.filter.B = B
+
+    def estimate(self, t : float, u, z):
+        """
+        Perform one state estimation step (i.e., update the state estimate)
+
+        Parameters
+        ----------
+        t : double
+            Current timestamp in seconds (≥ 0.0)
+            e.g., t = 3.4
+        u : dict
+            Measured inputs, with keys defined by model.inputs.
+            e.g., u = {'i':3.2} given inputs = ['i']
+        z : dict
+            Measured outputs, with keys defined by model.outputs.
+            e.g., z = {'t':12.4, 'v':3.3} given inputs = ['t', 'v']
+        """
+        assert t > self.t, "New time must be greater than previous"
+
+        dt = t - self.t
+        # Create u array, ensuring order of model.inputs
+        inputs = np.array([u[key] for key in self.model.inputs])
+
+        # Add row of ones (to account for constant E term)
+        if np.size(inputs) == 0:
+            inputs = np.array([[1]])
+        else:
+            inputs = np.append(inputs, [[1]], 0)
+
+        self.t = t
+
+        # Update equations
+        # prog_models is dx = Ax + Bu + E
+        # kalman_models is x' = Fx + Bu, where x' is the next state
+        # Therefore we need to add the diagnol matrix 1 to A to convert
+        # And A and B should be multiplied by the time step
+        B = np.multiply(self.filter.B, dt) 
+        F = np.multiply(self.filter.F, dt) + np.diag([1]* self.model.n_states)
+
+        # Predict
+        self.filter.predict(u = inputs, B = B, F = F)
+
+        # Create z array, ensuring order of model.outputs
+        outputs = np.array([z[key] for key in self.model.outputs])
+
+        # Subtract D from outputs
+        # This is done because prog_models expects the form: 
+        #   z = Cx + D
+        # While kalman expects
+        #   z = Cx
+        outputs = outputs - self.model.D
+
+        self.filter.update(outputs, H=self.model.C)
+    
+    @property
+    def x(self) -> MultivariateNormalDist:
+        """
+        Getter for property 'x', the current estimated state. 
+
+        Example
+        -------
+        state = observer.x
+        """
+        return MultivariateNormalDist(self.model.states, self.filter.x.ravel(), self.filter.P)
diff --git a/src/prog_algs/state_estimators/particle_filter.py b/src/prog_algs/state_estimators/particle_filter.py
index fb12c0a3..c2dc93bc 100644
--- a/src/prog_algs/state_estimators/particle_filter.py
+++ b/src/prog_algs/state_estimators/particle_filter.py
@@ -1,5 +1,7 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
+from prog_algs.uncertain_data.uncertain_data import UncertainData
+from typing import Callable
 from . import state_estimator
 from numpy import array, empty, random, take, exp, max, take
 from filterpy.monte_carlo import residual_resample
@@ -12,13 +14,21 @@
 
 class ParticleFilter(state_estimator.StateEstimator):
     """
-    Estimates state using a particle filter (PF) algorithm.
+    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:
+    Args:
+        model : ProgModel
+            A prognostics model to be used in state estimation
+            See: Prognostics Model Package
+        x0 : UncertainData, model.StateContainer, or dict
+            Initial (starting) state, with keys defined by model.states \n
+            e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def']
+
+    Keyword Args:
         t0 : float
             Starting time (s)
         dt : float 
@@ -27,9 +37,6 @@ class ParticleFilter(state_estimator.StateEstimator):
             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 = {
             't0': -1e-99,  # practically 0, but allowing for a 0 first estimate
@@ -38,20 +45,44 @@ class ParticleFilter(state_estimator.StateEstimator):
             'x0_uncertainty': 0.5
         }
 
-    def __init__(self, model, x0, measurement_eqn = None, **kwargs):
+    def __init__(self, model, x0, measurement_eqn : Callable = None, **kwargs):
         super().__init__(model, x0, measurement_eqn = measurement_eqn, **kwargs)
         
-        self.__measure = measurement_eqn if measurement_eqn else model.output
+        if measurement_eqn:
+            # update output_container
+            from prog_models.utils.containers import DictLikeMatrixWrapper
+            z0 = measurement_eqn(x0)
+            class MeasureContainer(DictLikeMatrixWrapper):
+                def __init__(self, z):
+                    super().__init__(list(z0.keys()), z)
+
+            def __measure(x):
+                return MeasureContainer(measurement_eqn(x))
+                
+            self._measure = __measure
+        else:
+            self._measure = model.output
 
+        # Caching for optimization
+        paramters_x0_exist = 'x0_uncertainty' in self.parameters
+        if paramters_x0_exist: # Only create these optimizations if x0_uncertainty exists as key in self.parameters
+            parameters_x0_dict, parameters_x0_num = isinstance(self.parameters['x0_uncertainty'], dict), 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))
+        if isinstance(x0, UncertainData):
+            sample_gen = x0.sample(self.parameters['num_particles'])
+            samples = [array(sample_gen.key(k)) for k in x0.keys()]
+        elif paramters_x0_exist and (parameters_x0_dict or parameters_x0_num):
+            warn("Warning: Use UncertainData type if estimating filtering with uncertain data.")
+            x = array(list(x0.values()))
+            if parameters_x0_dict:
+                sd = array([self.parameters['x0_uncertainty'][key] for key in x0.keys()])
+            elif parameters_x0_num:
+                sd = array([self.parameters['x0_uncertainty']] * len(x0))
+            samples = [random.normal(x[i], sd[i], self.parameters['num_particles']) for i in range(len(x))]
         else:
-            raise ProgAlgTypeError
+            raise ProgAlgTypeError("ProgAlgTypeError: x0 must be of type UncertainData or x0_uncertainty must be of type [dict, Number].")
+        self.particles = dict(zip(x0.keys(), samples))
+
 
         if 'R' in self.parameters:
             # For backwards compatibility
@@ -59,15 +90,11 @@ def __init__(self, model, x0, measurement_eqn = None, **kwargs):
             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):
+    def estimate(self, t : float, u, z):
         assert t > self.t, "New time must be greater than previous"
         dt = t - self.t
         self.t = t
@@ -76,7 +103,7 @@ def estimate(self, t, u, z):
         particles = self.particles
         next_state = self.model.next_state
         apply_process_noise = self.model.apply_process_noise
-        output = self.__measure
+        output = self._measure
         apply_measurement_noise = self.model.apply_measurement_noise
         noise_params = self.model.parameters['measurement_noise']
         num_particles = self.parameters['num_particles']
@@ -86,20 +113,19 @@ def estimate(self, t, u, z):
 
         if self.model.is_vectorized:
             # Propagate particles state
-            self.particles = apply_process_noise(next_state(particles, u, dt))
+            self.particles = apply_process_noise(next_state(particles, u, dt), dt)
 
             # Get particle measurements
-            zPredicted = apply_measurement_noise(output(self.particles))
+            zPredicted = output(self.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)
+                x = apply_process_noise(x, dt)
                 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]
 
@@ -122,7 +148,7 @@ def estimate(self, t, u, z):
 
         # Convert to weights
         unnorm_weights = exp(scaled_weights)
-
+        
         # Normalize
         total_weight = sum(unnorm_weights)
         self.weights = unnorm_weights / total_weight
@@ -138,7 +164,7 @@ def estimate(self, t, u, z):
         self.particles = dict(zip(self.particles.keys(), samples))
 
     @property
-    def x(self):
+    def x(self) -> UnweightedSamples:
         """
         Getter for property 'x', the current estimated state. 
 
diff --git a/src/prog_algs/state_estimators/state_estimator.py b/src/prog_algs/state_estimators/state_estimator.py
index d069e5be..b4be56ca 100644
--- a/src/prog_algs/state_estimators/state_estimator.py
+++ b/src/prog_algs/state_estimators/state_estimator.py
@@ -1,9 +1,12 @@
 # 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 typing import Callable
+
 from ..uncertain_data import UncertainData
 from ..exceptions import ProgAlgTypeError
 from copy import deepcopy
+from warnings import warn
 
 class StateEstimator(ABC):
     """
@@ -11,25 +14,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
 
-    parameters:
-     model : ProgModel
-        A prognostics model to be used in state estimation
-        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
-        Dictionary of any additional configuration values. See state-estimator specific documentation
+    Args:
+        model : ProgModel
+            A prognostics model to be used in state estimation
+            See: Prognostics Model Package
+        x0 : UncertainData, model.StateContainer, or dict
+            Initial (starting) state, with keys defined by model.states \n
+            e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def']
+        options : optional, kwargs
+            configuration options\n
+            Dictionary of any additional configuration values. See state-estimator specific documentation
     """
 
     default_parameters = {
         't0': 0
     }
 
-    def __init__(self, model, x0, measurement_eqn = None, **kwargs):
+    def __init__(self, model, x0, measurement_eqn : Callable = None, **kwargs):
         # Check model
         if not hasattr(model, 'output'):
             raise ProgAlgTypeError("model must have `output` method")
@@ -39,7 +40,10 @@ def __init__(self, model, x0, measurement_eqn = None, **kwargs):
             raise ProgAlgTypeError("model must have `outputs` property")
         if not hasattr(model, 'states'):
             raise ProgAlgTypeError("model must have `states` property")
-        self.model = model
+        self.model = deepcopy(model)
+
+        if measurement_eqn is not None:
+            warn('Measurement_eqn was deprecated in v1.3 in favor of model subclassing. I will remove this in v1.4. See measurement_equation example for more information', DeprecationWarning)
 
         # Check x0
         for key in model.states:
@@ -57,7 +61,7 @@ def __init__(self, model, x0, measurement_eqn = None, **kwargs):
         self.t = self.parameters['t0']  # Initial Time
 
     @abstractmethod
-    def estimate(self, t, u, z, **kwargs) -> None:
+    def estimate(self, t : float, u, z, **kwargs) -> None:
         """
         Perform one state estimation step (i.e., update the state estimate, filt.x)
 
diff --git a/src/prog_algs/state_estimators/unscented_kalman_filter.py b/src/prog_algs/state_estimators/unscented_kalman_filter.py
index 25be7d8e..b3386c04 100644
--- a/src/prog_algs/state_estimators/unscented_kalman_filter.py
+++ b/src/prog_algs/state_estimators/unscented_kalman_filter.py
@@ -1,9 +1,11 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 
+from warnings import warn
+from typing import Callable
 from . import state_estimator
 from filterpy import kalman
 from numpy import diag, array
-from ..uncertain_data import MultivariateNormalDist
+from ..uncertain_data import MultivariateNormalDist, UncertainData
 
 class UnscentedKalmanFilter(state_estimator.StateEstimator):
     """
@@ -13,7 +15,15 @@ class UnscentedKalmanFilter(state_estimator.StateEstimator):
 
     The supported configuration parameters (keyword arguments) for UKF construction are described below:
 
-    Constructor Configuration Parameters:
+    Args:
+        model : ProgModel
+            A prognostics model to be used in state estimation
+            See: Prognostics Model Package
+        x0 : UncertainData, model.StateContainer, or dict
+            Initial (starting) state, with keys defined by model.states \n
+            e.g., x = ScalarData({'abc': 332.1, 'def': 221.003}) given states = ['abc', 'def']
+
+    Keyword Args
         alpha, beta, kappa: float
             UKF Scaling parameters
         t0 : float
@@ -29,15 +39,16 @@ class UnscentedKalmanFilter(state_estimator.StateEstimator):
         'alpha': 1, 
         'beta': 0, 
         'kappa': -1,
-        't0': -1,
+        't0': -1e-10,
         'dt': 1
     } 
 
-    def __init__(self, model, x0, measurement_eqn = None, **kwargs):
+    def __init__(self, model, x0, measurement_eqn : Callable = None, **kwargs):
         super().__init__(model, x0, measurement_eqn, **kwargs)
 
         self.__input = None
         self.x0 = x0
+        # Saving for reduce pickling
 
         if measurement_eqn is None: 
             def measure(x):
@@ -55,10 +66,6 @@ def measure(x):
 
         if 'Q' not in self.parameters:
             self.parameters['Q'] = diag([1.0e-3 for i in x0.keys()])
-        if 'R' not in self.parameters:
-            # Size of what's being measured (not output) 
-            # This is determined by running the measure function on the first state
-            self.parameters['R'] = diag([1.0e-3 for i in range(len(measure(x0.values())))])
 
         def state_transition(x, dt):
             x = {key: value for (key, value) in zip(x0.keys(), x)}
@@ -70,15 +77,29 @@ def state_transition(x, dt):
             return array(list(x.values())).ravel()
 
         num_states = len(x0.keys())
-        num_measurements = len(model.outputs)
+        num_measurements = model.n_outputs
         points = kalman.MerweScaledSigmaPoints(num_states, alpha=self.parameters['alpha'], beta=self.parameters['beta'], kappa=self.parameters['kappa'])
         self.filter = kalman.UnscentedKalmanFilter(num_states, num_measurements, self.parameters['dt'], measure, state_transition, points)
-        self.filter.x = array(list(x0.values())).ravel()
-        self.filter.P = self.parameters['Q'] / 10
+        
+        if isinstance(x0, dict) or isinstance(x0, model.StateContainer):
+            warn("Warning: Use UncertainData type if estimating filtering with uncertain data.")
+            self.filter.x = array(list(x0.values()))
+            self.filter.P = self.parameters['Q'] / 10
+        elif isinstance(x0, UncertainData):
+            x_mean = x0.mean
+            self.filter.x = array(list(x_mean.values()))
+            self.filter.P = x0.cov
+        else:
+            raise TypeError("TypeError: x0 initial state must be of type {{dict, UncertainData}}")
+
+        if 'R' not in self.parameters:
+                # Size of what's being measured (not output) 
+                # This is determined by running the measure function on the first state
+                self.parameters['R'] = diag([1.0e-3 for i in range(len(measure(self.filter.x)))])
         self.filter.Q = self.parameters['Q']
         self.filter.R = self.parameters['R']
 
-    def estimate(self, t, u, z):
+    def estimate(self, t : float, u, z):
         """
         Perform one state estimation step (i.e., update the state estimate)
 
@@ -102,7 +123,7 @@ def estimate(self, t, u, z):
         self.filter.update(array(list(z.values())))
     
     @property
-    def x(self):
+    def x(self) -> MultivariateNormalDist:
         """
         Getter for property 'x', the current estimated state. 
 
diff --git a/src/prog_algs/uncertain_data/multivariate_normal_dist.py b/src/prog_algs/uncertain_data/multivariate_normal_dist.py
index fbe621d8..b1216fb8 100644
--- a/src/prog_algs/uncertain_data/multivariate_normal_dist.py
+++ b/src/prog_algs/uncertain_data/multivariate_normal_dist.py
@@ -19,10 +19,40 @@ def __init__(self, labels, mean: array, covar : array):
         self.__mean = array(list(mean))
         self.__covar = array(list(covar))
 
-    def __eq__(self, other):
+    def __eq__(self, other : "MultivariateNormalDist") -> bool:
         return self.keys() == other.keys() and self.mean == other.mean and (self.cov == other.cov).all()
 
-    def sample(self, num_samples = 1):
+    def __add__(self, other : int) -> "UncertainData":
+        if other == 0:
+            return self
+        return MultivariateNormalDist(self.__labels, array([i+other for i in self.__mean]), self.__covar)
+
+    def __radd__(self, other : int) -> "UncertainData":
+        return self.__add__(other)
+
+    def __iadd__(self, other : int) -> "UncertainData":
+        if not isinstance(other, (int, float)):
+            raise TypeError(f" unsupported operand type(s) for +: '{type(other)}' and '{type(self.__mean[0])}'")
+        if other != 0:
+            self.__mean = array([i+other for i in self.__mean])
+        return self
+
+    def __sub__(self, other : int) -> "UncertainData":
+        if other == 0:
+            return self
+        return MultivariateNormalDist(self.__labels, array([i-other for i in self.__mean]), self.__covar)
+
+    def __rsub__(self, other : int) -> "UncertainData":
+        return self.__sub__(other)
+
+    def __isub__(self, other : int) -> "UncertainData":
+        if not isinstance(other, (int, float)):
+            raise TypeError(f" unsupported operand type(s) for -: '{type(other)}' and '{type(self.__mean[0])}'")
+        if other != 0:
+            self.__mean = array([i-other for i in self.__mean])
+        return self
+
+    def sample(self, num_samples : int = 1) -> UnweightedSamples:
         if len(self.__mean) != len(self.__labels):
             raise Exception("labels must be provided for each value")
     
@@ -30,21 +60,21 @@ def sample(self, num_samples = 1):
         samples = [{key: value for (key, value) in zip(self.__labels, x)} for x in samples]
         return UnweightedSamples(samples)
 
-    def keys(self):
+    def keys(self) -> list:
         return self.__labels
 
     @property
-    def median(self):
+    def median(self) -> float:
         # For normal distribution medain = mean
         return self.mean
 
     @property
-    def mean(self):
+    def mean(self) -> array:
         return {key: value for (key, value) in zip(self.__labels, self.__mean)}
 
-    def __str__(self):
+    def __str__(self) -> str:
         return 'MultivariateNormalDist(mean: {}, covar: {})'.format(self.__mean, self.__covar)     
 
     @property
-    def cov(self):
+    def cov(self) -> array:
         return self.__covar
diff --git a/src/prog_algs/uncertain_data/scalar_data.py b/src/prog_algs/uncertain_data/scalar_data.py
index 65aac5c7..48270ab2 100644
--- a/src/prog_algs/uncertain_data/scalar_data.py
+++ b/src/prog_algs/uncertain_data/scalar_data.py
@@ -1,5 +1,7 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
+from typing import Union
+from numpy import array
 from . import UncertainData, UnweightedSamples
 
 
@@ -8,36 +10,68 @@ class ScalarData(UncertainData):
     Data without uncertainty- single value
 
     Args:
-            state (dict): Single state
+            state (dict or Container): Single state in the form of dict or model.*Container (InputContainer, OutputContainer, Statecontainer) representing states and respective values.
     """
     def __init__(self, state): 
         self.__state = state
 
-    def __eq__(self, other):
+    def __eq__(self, other : "ScalarData") -> bool:
         return isinstance(other, ScalarData) and other.mean == self.__state
 
+    def __add__(self, other : int) -> "UncertainData":
+        if other == 0:
+            return self
+        new_state = dict()
+        for k,v in self.__state.items():
+            new_state[k] = v + other
+        return ScalarData(new_state)
+
+    def __radd__(self, other : int) -> "UncertainData":
+        return self.__add__(other)
+
+    def __iadd__(self, other : int) -> "UncertainData":
+        if other != 0:
+            for k in self.__state.keys():
+                self.__state[k] += other
+        return self
+
+    def __sub__(self, other : int) -> "UncertainData":
+        new_state = dict()
+        for k,v in self.__state.items():
+            new_state[k] = v - other
+        return ScalarData(new_state)
+
+    def __rsub__(self, other : int) -> "UncertainData":
+        return self.__sub__(other)
+
+    def __isub__(self, other : int) -> "UncertainData":
+        if other != 0:
+            for k in self.__state.keys():
+                self.__state[k] -= other
+        return self
+
     @property
-    def median(self):
+    def median(self) -> array:
         return self.mean
         
     @property
-    def mean(self):
+    def mean(self) -> array:
         return self.__state
 
     @property
-    def cov(self):
-        return [[0]]
+    def cov(self) -> array:
+        return [[0 for _ in range(len(self.__state))] for _ in range(len(self.__state))]
 
     def keys(self):
         return self.__state.keys()
         
-    def sample(self, num_samples = 1):
+    def sample(self, num_samples : int = 1) -> UnweightedSamples:
         return UnweightedSamples([self.__state] * num_samples)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return 'ScalarData({})'.format(self.__state)
 
-    def percentage_in_bounds(self, bounds):
+    def percentage_in_bounds(self, bounds : Union[list, dict]) -> dict:
         if isinstance(bounds, list):
             bounds = {key: bounds for key in self.keys()}
         if not isinstance(bounds, dict) and all([isinstance(b, list) for b in bounds]):
diff --git a/src/prog_algs/uncertain_data/uncertain_data.py b/src/prog_algs/uncertain_data/uncertain_data.py
index d7699537..78d140e0 100644
--- a/src/prog_algs/uncertain_data/uncertain_data.py
+++ b/src/prog_algs/uncertain_data/uncertain_data.py
@@ -1,7 +1,13 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
 from abc import ABC, abstractmethod, abstractproperty
+from collections import defaultdict
+from matplotlib.figure import Figure
+from numpy import array
+
+from ..utils.table import print_table_recursive
 from ..visualize import plot_scatter, plot_hist
+from prog_models.utils.containers import DictLikeMatrixWrapper
 
 
 class UncertainData(ABC):
@@ -24,11 +30,11 @@ def sample(self, nSamples : int = 1):
 
     @property
     @abstractproperty
-    def median(self):
+    def median(self) -> array:
         """The median of the UncertainData distribution or samples 
 
         Returns:
-            Dict[str, float]: Mean value. e.g., {'key1': 23.2, ...}
+            Dict[str, float]: Median value. e.g., {'key1': 23.2, ...}
 
         Example:
             median_value = data.median
@@ -36,7 +42,7 @@ def median(self):
 
     @property
     @abstractproperty
-    def mean(self):
+    def mean(self) -> array:
         """The mean of the UncertainData distribution or samples 
 
         Returns:
@@ -48,13 +54,30 @@ def mean(self):
 
     @property
     @abstractproperty
-    def cov(self):
+    def cov(self) -> array:
         """The covariance matrix of the UncertiantyData distribution or samples in order of keys (i.e., cov[1][1] is the standard deviation for key keys()[1])
 
         Returns:
             array[array[float]]: Covariance matrix
         """
 
+    def relative_accuracy(self, ground_truth : dict) -> dict:
+        """The relative accuracy of the mean of the distribution. 
+        
+        RA = 1 - |r-p|/r 
+        Where r is ground truth and p is mean of predicted distribution
+        Prognostics: The Science of Making Predictions (Goebel et al, 239)
+
+        Returns:
+            dict(str:float): Relative accuracy for each event where value is relative accuracy between [0,1]
+        """
+        # if this check isn't here, goes to divide by zero check and raises AttributeError instead of TypeError. Keep? There are unittests checking for type
+        if not (isinstance(ground_truth, dict) or isinstance(ground_truth, DictLikeMatrixWrapper)):
+            raise TypeError("Ground truth must be passed as a dictionary or *.container argument.")
+        if not all(ground_truth.values()):
+            raise ZeroDivisionError("Ground truth values must be non-zero in calculating relative accuracy.")
+        return {k:1 - (abs(ground_truth[k] - v)/ground_truth[k]) for k,v in self.mean.items()}
+
     @abstractmethod
     def keys(self):
         """Get the keys for the property represented
@@ -63,11 +86,14 @@ def keys(self):
             [string]: keys
         """
 
-    def percentage_in_bounds(self, bounds, keys = None):
+    def __contains__(self, key):
+        return key in self.keys()
+
+    def percentage_in_bounds(self, bounds : tuple, keys : list = None) -> float:
         """Calculate percentage of dist is within specified bounds
 
         Args:
-            bounds ([float, float]): Lower and upper bounds
+            bounds ((float, float)): Lower and upper bounds
             keys (list of strings, optional): Keys to analyze. Defaults to all keys.
 
         Returns:
@@ -75,7 +101,7 @@ def percentage_in_bounds(self, bounds, keys = None):
         """
         return self.sample(1000).percentage_in_bounds(bounds)
 
-    def metrics(self, **kwargs):
+    def metrics(self, **kwargs) -> dict:
         """Calculate Metrics for this dist
 
         Keyword Args:
@@ -89,7 +115,7 @@ def metrics(self, **kwargs):
         from ..metrics import calc_metrics
         return calc_metrics(self, **kwargs)
 
-    def plot_scatter(self, fig = None, keys = None, num_samples = 100, **kwargs):
+    def plot_scatter(self, fig : Figure = None, keys : list = None, num_samples : int = 100, **kwargs) -> Figure:
         """
         Produce a scatter plot
 
@@ -114,7 +140,8 @@ def plot_scatter(self, fig = None, keys = None, num_samples = 100, **kwargs):
         return plot_scatter(samples, fig=fig, keys=keys, **kwargs)
 
     def plot_hist(self, fig = None, keys = None, num_samples = 100, **kwargs):
-        """Create a histogram
+        """
+        Create a histogram
 
         Args:
             fig (MatPlotLib Figure, optional): Existing histogram figure to be overritten. Defaults to create new figure.
@@ -125,3 +152,68 @@ def plot_hist(self, fig = None, keys = None, num_samples = 100, **kwargs):
             keys = self.keys()
         samples = self.sample(num_samples)
         return plot_hist(samples, fig=fig, keys=keys, **kwargs)
+
+    def describe(self, title : str = "UncertainData Metrics", print : bool = True) -> defaultdict:
+        """
+        Print and view basic statistical information about this UncertainData object in a text-based printed table.
+
+        Args:
+            title : str
+                Title of the table, printed before data rows.
+            print : bool = True 
+                Optional argument specifying whether to print or not; default true.
+
+        Returns:
+            defaultdict
+                Dictionary of lists used to print metrics.
+        """
+        recursive_metrics_table = print_table_recursive(self.metrics(), title, print)
+        return recursive_metrics_table
+
+    @abstractmethod
+    def __add__(self, other : int) -> "UncertainData":
+        """Overriding __add__ (+ operator) for UncertainData. 
+
+        Args:
+            other (int): Integer value to be applied to class where appropriate.
+        """
+
+    @abstractmethod
+    def __radd__(self, other : int) -> "UncertainData":
+        """Overriding __radd__ (+ operator right) for UncertainData. 
+
+        Args:
+            other (int): Integer value to be applied to class where appropriate.
+        """
+
+    @abstractmethod
+    def __iadd__(self, other : int) -> "UncertainData":
+        """Overriding __iadd__ (+= operator) for UncertainData. 
+
+        Args:
+            other (int): Integer value to be applied to class where appropriate.
+        """
+
+    @abstractmethod
+    def __sub__(self, other : int) -> "UncertainData":
+        """Overriding __sub__ (- operator) for UncertainData. 
+
+        Args:
+            other (int): Integer value to be applied to class where appropriate.
+        """
+
+    @abstractmethod
+    def __rsub__(self, other : int) -> "UncertainData":
+        """Overriding __rsub__ (- operator right) for UncertainData. 
+
+        Args:
+            other (int): Integer value to be applied to class where appropriate.
+        """
+
+    @abstractmethod
+    def __isub__(self, other : int) -> "UncertainData":
+        """Overriding __isub__ (-= operator) for UncertainData. 
+
+        Args:
+            other (int): Integer value to be applied to class where appropriate.
+        """
diff --git a/src/prog_algs/uncertain_data/unweighted_samples.py b/src/prog_algs/uncertain_data/unweighted_samples.py
index 97cd840a..5ee02895 100644
--- a/src/prog_algs/uncertain_data/unweighted_samples.py
+++ b/src/prog_algs/uncertain_data/unweighted_samples.py
@@ -12,11 +12,12 @@ 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
+        samples (array, dict, or model.*Container, optional): array of samples. Defaults to empty array.\n
             If dict, must be of the form of {key: [value, ...], ...}\n
-            If list, must be of the form of [{key: value, ...}, ...]
+            If list, must be of the form of [{key: value, ...}, ...]\n
+            If InputContainer, OutputContainer, or StateContainer, must be of the form of *Container({'key': value, ...})
     """
-    def __init__(self, samples = []):
+    def __init__(self, samples : list = []):
         if isinstance(samples, dict):
             # Is in form of {key: [value, ...], ...}
             # Convert to array of samples
@@ -31,11 +32,54 @@ def __init__(self, samples = []):
         else:
             raise ValueError('Invalid input. Must be list or dict, was {}'.format(type(samples)))
 
-    def sample(self, num_samples = 1, replace = True):
+    def __add__(self, other : int) -> "UncertainData":
+        if other == 0:
+            return self
+        result = []
+        for i in range(len(self.data)):
+            new_dict = {}
+            for k,v in self.data[i].items():
+                new_dict[k] = v + other
+            result.append(new_dict)
+        return UnweightedSamples(result)
+
+    def __radd__(self, other : int) -> "UncertainData":
+        return self.__add__(other)
+
+    def __iadd__(self, other : int) -> "UncertainData":
+        if other != 0:
+            for i in range(len(self.data)):
+                for k,v in self.data[i].items():
+                    self.data[i][k] += other
+        return self
+
+    def __sub__(self, other : int) -> "UncertainData":
+        if other == 0:
+            return self
+        result = []
+        for i in range(len(self.data)):
+            new_dict = {}
+            for k,v in self.data[i].items():
+                new_dict[k] = v - other
+            result.append(new_dict)
+        return UnweightedSamples(result)
+
+    def __rsub__(self, other : int) -> "UncertainData":
+        return self.__sub__(other)
+
+    def __isub__(self, other : int) -> "UncertainData":
+        if other != 0:
+            for i in range(len(self.data)):
+                for k,v in self.data[i].items():
+                    self.data[i][k] -= other
+        return self
+
+    def sample(self, num_samples : int = 1, replace = True) -> "UnweightedSamples":
         # Completely random resample
-        return UnweightedSamples(random.choice(self.data, num_samples, replace = replace))
+        indices = random.choice(len(self.data), num_samples, replace = replace)
+        return UnweightedSamples([self.data[i] for i in indices])
 
-    def keys(self):
+    def keys(self) -> list:
         if len(self.data) == 0:
             return []  # is empty
         for sample in self:
@@ -43,7 +87,7 @@ def keys(self):
                 return sample.keys()
         return []  # Every element is none
 
-    def key(self, key):
+    def key(self, key) -> list:
         """Return samples for given key
 
         Args:
@@ -55,7 +99,7 @@ def key(self, key):
         return [sample[key] for sample in self.data if sample is not None]
 
     @property
-    def median(self):
+    def median(self) -> array:
         # Calculate Geometric median of all samples
         min_value = float('inf')
         none_flag = False
@@ -75,7 +119,7 @@ def median(self):
         return self[min_index]
 
     @property
-    def mean(self):
+    def mean(self) -> array:
         mean = {}
         for key in self.keys():
             values = array([x[key] for x in self.data if x is not None and x[key] is not None])
@@ -85,7 +129,7 @@ def mean(self):
         return mean
 
     @property
-    def cov(self):
+    def cov(self) -> array:
         if len(self.data) == 0:
             return [[]]
         unlabeled_samples = array([[x[key] for x in self.data if x is not None and x[key] is not None] for key in self.keys()])
@@ -97,7 +141,7 @@ def __str__(self):
         return 'UnweightedSamples({})'.format(self.data)
 
     @property
-    def size(self):
+    def size(self) -> int:
         """Get the number of samples. Note: kept for backwards compatibility, prefer using len() instead.
 
         Returns:
@@ -105,7 +149,7 @@ def size(self):
         """
         return len(self)
 
-    def percentage_in_bounds(self, bounds, keys = None):
+    def percentage_in_bounds(self, bounds, keys : list = None) -> dict:
         if not keys:
             keys = self.keys()
         if isinstance(bounds, list):
@@ -116,5 +160,5 @@ def percentage_in_bounds(self, bounds, keys = None):
         return {key: sum([x is not None and x < bounds[key][1] and x > bounds[key][0] for x in self.key(key)])/n_elements for key in keys}
 
     def raw_samples(self):
-        warn("raw_samples is deprecated and will be removed in the future")
+        warn("raw_samples is deprecated and will be removed in the future.")
         return self.data
diff --git a/src/prog_algs/utils/__init__.py b/src/prog_algs/utils/__init__.py
new file mode 100644
index 00000000..d84bee52
--- /dev/null
+++ b/src/prog_algs/utils/__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 .table import print_table_recursive
+
diff --git a/src/prog_algs/utils/table.py b/src/prog_algs/utils/table.py
new file mode 100644
index 00000000..76bf8831
--- /dev/null
+++ b/src/prog_algs/utils/table.py
@@ -0,0 +1,93 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the
+# National Aeronautics and Space Administration.  All Rights Reserved.
+
+from collections import defaultdict
+from typing import Union
+
+MAX_COLUMN_WIDTH = 5 # numerical value will actually be min MAX_COLUMN_WIDTH-2 due to allocating spaces
+
+def print_table_recursive(input_dict : dict, title : str, print_bool : bool = True) -> defaultdict:
+    """
+    Prints a table where keys are column headers and values are items in a row. 
+    Returns the table formatted as a dictionary of tables represented by a list of str.
+
+    Arguments
+    ---------
+    input_dict : dict
+        A dictionary of keys and values to print out in a table. Values can be dictionaries.
+    title : str
+        Title of the table, printed before data rows.
+    print_flag : bool = True
+        An optional boolean value determining whether the generated table is printed.
+    """
+    row_list = _print_table_recursive_helper([], input_dict, title)[:-7]
+    sub_tables = defaultdict(list)
+    new_sub_table = []
+    for row in row_list:
+        new_sub_table.append(row)
+        if len(new_sub_table) == 7:
+            if sub_tables[len(new_sub_table[0])]:
+                sub_tables[len(new_sub_table[0])].extend([new_sub_table[5], new_sub_table[6]])
+            else:
+                sub_tables[len(new_sub_table[0])].extend(new_sub_table)
+            new_sub_table = []
+
+    if print_bool:
+        for k in sorted(sub_tables.keys(), reverse=True):
+            print(*sub_tables[k], sep='\n')
+    return sub_tables
+
+def _set_width(max_width : int, input_value : Union[float, int]) -> str:
+    if input_value < (10**max_width):
+        ndigits = len(str(input_value))
+        return f"{input_value:^{ndigits}.{max_width-ndigits}f}"
+    else:
+        scientific_input = f"{input_value:e}"
+        split_e = scientific_input.split("e+")
+        num_space = max_width - len(str(split_e[1])) - 2
+        split_e[0] = str(split_e[0])[:num_space]
+        return f"{split_e[0]}e+{split_e[1]}"
+    # using this approach because e+ can't be formatted with f"{x:{some_width}g}"
+    # what happens if we have 9.999999e+100 but are limited to 5?
+    # the exponent itself will occupy 5, leaving no space for the numbers in front
+
+def _print_table_recursive_helper(table_prog : list, input_dict : dict, title : str, key : str = None) -> list:
+    """
+    Helper function to recursively build subtables as a list of str.
+
+    Arguments
+    ---------
+    table_prog : list
+        A list of the table built so far. List of strings, where each string is a printable representation of a row.
+    input_dict : dict
+        A dictionary of keys and values to print out in a table. Values can be dictionaries.
+    title : str
+        Title of the table, printed before data rows.
+    key : str = None
+        Key for a value row, identifying what event the values belong to.
+    """
+    col_name_row = "| key |"
+    value_row = f"| {str(key):^3} |"
+    for k,v in input_dict.items():
+        if isinstance(v, dict):
+            if key != None:
+                to_pass = key
+                _print_table_recursive_helper(table_prog, v, f"{title} {k}", to_pass)
+            else:
+                to_pass = k
+                _print_table_recursive_helper(table_prog, v, f"{title}", to_pass)
+        else:
+            col_len = len(max(str(k), str(v))) + 2
+            col_name_row += f"{str(k):^{col_len}}|"
+            if isinstance(v, (int, float)):
+                adj_width = _set_width(MAX_COLUMN_WIDTH-2, v)
+                value_row += f"{adj_width:^{col_len}}|"
+            else:
+                value_row += f"{str(v):^{col_len}}|"
+
+    break_row = "+{}+".format((len(col_name_row)-2)*'-')
+    title_row = f"+{title:^{len(break_row)-2}}+".title()
+    table_prog.extend([break_row, title_row, break_row, col_name_row, break_row, value_row, break_row])
+    
+    return table_prog
+
diff --git a/src/prog_algs/visualize/plot_hist.py b/src/prog_algs/visualize/plot_hist.py
index b2d9222f..428e3fb2 100644
--- a/src/prog_algs/visualize/plot_hist.py
+++ b/src/prog_algs/visualize/plot_hist.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 matplotlib.figure import Figure
 import matplotlib.pyplot as plt
+from numpy import array
 
-def plot_hist(samples, fig = None, keys = None, **kwargs):
+from typing import List
+
+def plot_hist(samples : array, fig : Figure = None, keys : List[str] = None, **kwargs) -> Figure:
     """Create a histogram
 
     Args:
diff --git a/src/prog_algs/visualize/plot_scatter.py b/src/prog_algs/visualize/plot_scatter.py
index 02af545b..38c6a2b6 100644
--- a/src/prog_algs/visualize/plot_scatter.py
+++ b/src/prog_algs/visualize/plot_scatter.py
@@ -1,9 +1,13 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
 from matplotlib.collections import PathCollection
+from matplotlib.figure import Figure
 import matplotlib.pyplot as plt
 from math import sqrt
 
-def plot_scatter(samples, fig = None, keys = None, legend = 'auto', **kwargs):
+from numpy import array
+from typing import List
+
+def plot_scatter(samples : List[dict], fig : Figure = None, keys : List[str] = None, legend : str = 'auto', **kwargs) -> Figure:
     """
     Produce a scatter plot for a given list of states
 
diff --git a/state_estimator_template.py b/state_estimator_template.py
index 1514ee78..9a6ef7d1 100644
--- a/state_estimator_template.py
+++ b/state_estimator_template.py
@@ -57,6 +57,6 @@ def x(self) -> UncertainData:
         # REPLACE THE FOLLOWING WITH THE LOGIC TO CONSTRUCT/RETURN THE STATE
 
         # Here we're using ScalarData, but the state could be represented by any other type of UncertainData (e.g., MultivariateNormalDist)
-        x = ScalarData({key: 0.0 for key in self.model.states})
+        x = ScalarData(self.model.StateContainer({key: 0.0 for key in self.model.states}))
 
         return x
diff --git a/tutorial.ipynb b/tutorial.ipynb
index 31fcbff5..b20c4b48 100644
--- a/tutorial.ipynb
+++ b/tutorial.ipynb
@@ -17,7 +17,7 @@
     "\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",
+    "* prediction: The process of predicting the evolution of a system state with time and the occurrence of events. \n",
     "\n",
     "The `prog_algs` package has the following primary subpackages\n",
     "* `prog_algs.state_estimators` - Tools for performing state estimation\n",
@@ -33,7 +33,148 @@
     "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"
+    "Now let's get started with some examples"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## UncertainData - Representing a Distribution"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Uncertainty is sometimes present in data used for performing state estimations or making predictions.\n",
+    "\n",
+    "In `prog_algs`, data with uncertainty is represented using classes inheriting from `UncertainData`:\n",
+    "* `prog_algs.uncertain_data.MultivariateNormalDist` - Data represented by a multivariate normal distribution with mean and covariance matrix\n",
+    "* `prog_algs.uncertain_data.ScalarData` - Data without uncertainty, a single value\n",
+    "* `prog_algs.uncertain_data.UnweightedSamples` - Data represented by a set of unweighted samples. Objects of this class can be treated like a list where samples[n] returns the nth sample (Dict)\n",
+    "\n",
+    "To begin using `UncertainData`, import the type that best portrays the data. In this simple demonstration, we import the `UnweightedSamples` data type. See <https://nasa.github.io/prog_algs> for full details on the available `UncertainData` types."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from prog_algs.uncertain_data import UnweightedSamples"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "With `UnweightedSamples` imported, construct an object with samples. This object can be initialized using either a dictionary, list, or model.*Container type from prog_models (e.g., StateContainer). Let's try creating an object using a dictionary. "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "samples = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])\n",
+    "print(samples)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Given an integer value, addition and subtraction can be performed on the `UncertainData` classes to adjust the distribution by a scalar amount."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "samples = samples + 5\n",
+    "print(samples)\n",
+    "samples -= 3\n",
+    "print(samples)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can also sample from any `UncertainData` distribution using the `sample` method. In this case it resamples from the existing samples"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(samples.sample()) # A single sample\n",
+    "print(samples.sample(10)) # 10 samples"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can see the keys present using the `.keys()` method:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(samples.keys())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "and the data corresponding to a specific key can be retrieved using `.key()`"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(samples.key('x'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Various properties are available to quantify the `UncertainData` distribution"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print('mean', samples.mean)\n",
+    "print('median', samples.median)\n",
+    "print('covariance', samples.cov)\n",
+    "print('size', samples.size)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "These `UncertainData` classes are used throughout the prog_algs package to represent data with uncertainty, as described in the following sections."
    ]
   },
   {
@@ -51,7 +192,7 @@
     "\n",
     "In `prog_algs` a State Estimator is used to estimate the system state. \n",
     "\n",
-    "To start import the needed packages. Here we will import the `BatteryCircuit` model and the `UnscentedKalmanFilter` state estimator. See <https://nasa.github.io/prog_algs> for more details on the available state estimators.\n"
+    "To start, import the needed packages. Here we will import the `BatteryCircuit` model and the `UnscentedKalmanFilter` state estimator. See <https://nasa.github.io/prog_algs> for more details on the available state estimators.\n"
    ]
   },
   {
@@ -82,6 +223,15 @@
     "m = BatteryCircuit()\n",
     "x0 = m.initialize()\n",
     "\n",
+    "# Turn into a distribution - this represents uncertainty in the initial state\n",
+    "from prog_algs.uncertain_data import MultivariateNormalDist\n",
+    "from numpy import diag\n",
+    "INITIAL_UNCERT = 0.05  # Uncertainty in initial state (%)\n",
+    "# Construct covariance matrix (making sure each value is positive)\n",
+    "cov = diag([max(abs(INITIAL_UNCERT * value), 1e-9) for value in x0.values()])\n",
+    "x0 = MultivariateNormalDist(x0.keys(), x0.values(), cov)\n",
+    "\n",
+    "# Construct State estimator\n",
     "est = UnscentedKalmanFilter(m, x0)"
    ]
   },
@@ -164,7 +314,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "Next, let's define future loading and the first state. The first state is the output of the state estimator, and the future loading scheme is a simple peacewise function"
+    "Next, let's define future loading and the first state. The first state is the output of the state estimator, and the future loading scheme is a simple piecewise function"
    ]
   },
   {
@@ -194,7 +344,7 @@
    "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"
+    "Now let's use the constructed mc predictor to perform a single prediction. Here we're setting dt to 0.25. Note this may take up to a minute"
    ]
   },
   {
@@ -203,22 +353,22 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "(times, inputs, states, outputs, event_states, toe) = mc.predict(x, future_loading, dt=0.25, n_samples=20)"
+    "mc_results = mc.predict(x, future_loading, dt=0.25, n_samples=20)"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format `[sample #][time]` so that `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",
+    "The predict function returns predictions of future inputs, states, outputs, and event_states at each save point. For sample-based predictors like the monte carlo, these can be accessed like an array with the format `[sample #][time]` so that `mc_results.states[m][n]` corresponds to the state for sample `m` at time `mc_results.times[m][n]`. Alternately, use the method `snapshot` to get a  single point in time. e.g., \n",
     "\n",
-    "    `state = states.snapshot(3)`\n",
+    "    `state = mc_results.states.snapshot(3)`\n",
     "\n",
-    "In this case the state snapshot corresponds to time `times[3]`. The snapshot method returns type UncertainData. \n",
+    "In this case the state snapshot corresponds to time `mc_results.times[3]`. The snapshot method returns type UncertainData. \n",
     "\n",
     "The `predict` method also returns Time of Event (ToE) as a type UncertainData, representing the predicted time of event (for each event predicted), with uncertainty.\n",
     "\n",
-    "Next, let's use the metrics package to analyse the ToE"
+    "Next, let's use the metrics package to analyze the ToE"
    ]
   },
   {
@@ -228,10 +378,10 @@
    "outputs": [],
    "source": [
     "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",
+    "print('\\tPortion between 3005.2 and 3005.6: ', mc_results.time_of_event.percentage_in_bounds([3005.2, 3005.6]))\n",
+    "print('\\tAssuming ground truth 3005.25: ', mc_results.time_of_event.metrics(ground_truth = 3005.25))\n",
     "from prog_algs.metrics import prob_success \n",
-    "print('\\tP(Success) if mission ends at 3005.25: ', prob_success(toe, 3005.25))"
+    "print('\\tP(Success) if mission ends at 3005.25: ', prob_success(mc_results.time_of_event, 3005.25))"
    ]
   },
   {
@@ -249,11 +399,11 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "fig = states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(times[0])))\n",
+    "fig = mc_results.states.snapshot(0).plot_scatter(label = \"t={:.0f}\".format(int(mc_results.times[0])))\n",
     "for i in range(1, 4):\n",
-    "    index = int(len(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])))"
+    "    index = int(len(mc_results.times)/4*i)\n",
+    "    mc_results.states.snapshot(index).plot_scatter(fig=fig, label = \"t={:.0f}\".format(mc_results.times[index]))\n",
+    "mc_results.states.snapshot(-1).plot_scatter(fig = fig, label = \"t={:.0f}\".format(int(mc_results.times[-1])))"
    ]
   },
   {
@@ -269,7 +419,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "fig = toe.plot_hist()"
+    "fig = mc_results.time_of_event.plot_hist()"
    ]
   },
   {
@@ -416,8 +566,8 @@
    "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.basic_example` : A basic Example using prog_algs for Prognostics \n",
+    "* `examples.benchmarking_example` : An example benchmarking the performance of prognostics algorithms\n",
     "* `examples.eol_event` : An example where a model has multiple events, but the user is only interested in predicting the time when the first event occurs (whatever it is).\n",
     "* `examples.measurement_eqn_example` : An example where not every output is measured or measurements are not in the same format as outputs, so a measurement equation is defined to translate between outputs and what's measured. \n",
     "* `examples.new_state_estimator_example` : An example of extending StateEstimator to create a new state estimator class\n",
@@ -428,14 +578,22 @@
     "\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."
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved."
+   ]
   }
  ],
  "metadata": {
   "interpreter": {
-   "hash": "ff94885aa2d97705a9dae03869c2058fa855d1acd9df351499300343e2e591a2"
+   "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
   },
   "kernelspec": {
-   "display_name": "Python 3.8.12 64-bit",
+   "display_name": "Python 3.9.1 64-bit",
+   "language": "python",
    "name": "python3"
   },
   "language_info": {
@@ -448,7 +606,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.12"
+   "version": "3.8.13"
   },
   "metadata": {
    "interpreter": {


Test patch:
diff --git a/.github/ISSUE_TEMPLATE/testing-update.md b/.github/ISSUE_TEMPLATE/testing-update.md
new file mode 100644
index 00000000..6b57a323
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/testing-update.md
@@ -0,0 +1,14 @@
+---
+name: Testing update
+about: Update to tests or CI/CD pipeline
+title: ''
+labels: CI/CD
+assignees: ''
+
+---
+
+** Test to Update **
+
+** Change Recommended **
+
+** Reason **
diff --git a/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py b/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py
index abd7c426..3ec1544a 100644
--- a/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py
+++ b/docs/_downloads/3b9c0c03440e6a6ef2e2e2ccdbd961c5/utpredictor.py
@@ -1,5 +1,9 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
 
+"""
+An example using the UnscentedTransformPredictor class to predict the degredation of a battery. 
+"""
+
 from prog_models.models import BatteryCircuit
 from prog_algs import *
 # from prog_algs.visualize import plot_hist
@@ -40,20 +44,20 @@ def future_loading(t, x = None):
     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)
+    mc_results = mc.predict(filt.x, future_loading, dt=0.1, save_freq= 100)
 
     # Print Results
-    for i, time in enumerate(times):
+    for i, time in enumerate(mc_results.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('\tu = {}'.format(mc_results.inputs.snapshot(i).mean))
+        print('\tx = {}'.format(mc_results.states.snapshot(i).mean))
+        print('\tz = {}'.format(mc_results.outputs.snapshot(i).mean))
+        print('\tevent state = {}'.format(mc_results.event_states.snapshot(i).mean))
 
-    print('\nToE:', toe.mean)
+    print('\nToE:', mc_results.time_of_event.mean)
 
     # You can also access the final state (of type UncertainData), like so:
-    final_state = toe.final_state
+    final_state = mc_results.time_of_event.final_state
     print('Final state @EOD: ', final_state['EOD'].mean)
     # toe.plot_hist()
     # plt.show()
diff --git a/scripts/test_copyright.py b/scripts/test_copyright.py
new file mode 100644
index 00000000..cb268f83
--- /dev/null
+++ b/scripts/test_copyright.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.
+
+import os
+COPYRIGHT_TAG = "Copyright © 2021 United States Government as represented by the Administrator" # String to check file lines for
+
+def check_copyright(directory : str, invalid_files : list) -> bool:
+    result = True
+
+    for filename in os.listdir(directory):
+        path = os.path.join(directory, filename)
+
+        # If path is subdirectory, recursively check files/subdirectories within
+        if os.path.isdir(path):
+            result = result and check_copyright(path, invalid_files)
+        # If path is a file, ensure it is of type py and check for copyright
+        elif os.path.isfile(path) and path[-2:] == "py":
+            file = open(path, 'r')
+            copyright_met = False
+            # Iterate over lines in file, check each line against COPYRIGHT_TAG
+            for line in file:
+                if COPYRIGHT_TAG in line: # File contains copyright, skip rest of lines
+                    file.close()
+                    copyright_met = True
+                if copyright_met:
+                    break
+            if not copyright_met:
+                result = False
+                invalid_files.append(path)
+            file.close()
+
+    return result
+
+def main():
+    print("\n\nTesting Files for Copyright Information")
+
+    root = '../prog_algs'
+    invalid_files = []
+    copyright_confirmed = check_copyright(root, invalid_files)
+
+    if not copyright_confirmed:
+        raise Exception(f"Failed test\nFiles missing copyright information: {invalid_files}")
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/__init__.py b/tests/__init__.py
index 0585f535..22e6d337 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_examples', 'test_integration', 'test_metrics', 'test_predictors', 'test_state_estimators', 'test_uncertain_data', 'test_visualize']
+__all__ = ['test_examples', 'test_integration', 'test_metrics', 'test_predictors', 'test_state_estimators', 'test_uncertain_data', 'test_visualize', 'test_tutorials', 'test_manual']
diff --git a/tests/__main__.py b/tests/__main__.py
index ed1e99a9..d38fc221 100644
--- a/tests/__main__.py
+++ b/tests/__main__.py
@@ -6,6 +6,7 @@
 from .test_examples import main as examples_main
 from .test_metrics import run_tests as metrics_main
 from .test_visualize import run_tests as visualize_main
+from .test_tutorials import run_tests as tutorials_main
 
 import unittest
 import sys
@@ -26,7 +27,11 @@ def run_basic_ex():
 if __name__ == '__main__':
     l = unittest.TestLoader()
 
-    print("\nExample Runtime: ", timeit(run_basic_ex, number=3))
+    try:
+        print("\nExample Runtime: ", timeit(run_basic_ex, number=3))
+    except Exception:
+        print("\nFailed benchmarking")
+        was_successful = False
 
     was_successful = True
     try:
@@ -59,5 +64,10 @@ def run_basic_ex():
     except Exception:
         was_successful = False
 
+    try:
+        tutorials_main()
+    except Exception:
+        was_successful = False
+
     if not was_successful:
         raise Exception('Tests Failed')
diff --git a/tests/test_examples.py b/tests/test_examples.py
index c2c9c5b5..00144003 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -1,115 +1,50 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+# This ensures that the directory containing examples is in the python search directories 
 
-import unittest
 import sys
+from os.path import dirname, join
+sys.path.append(join(dirname(__file__), ".."))
+
+import unittest
 from io import StringIO 
 from examples import *
 from unittest.mock import patch
+import pkgutil
+from importlib import import_module
 
+skipped_examples = ['playback']
 
-class TestExamples(unittest.TestCase):
-    def test_main_ex(self):
-        # set stdout (so it wont print)
-        _stdout = sys.stdout
-        sys.stdout = StringIO()
-
-        from examples import basic_example
-        with patch('matplotlib.pyplot') as p:
-            basic_example.run_example()
-
-        # Reset stdout 
-        sys.stdout = _stdout
-
-    def test_benchmarking_ex(self):
-        # set stdout (so it wont print)
-        _stdout = sys.stdout
-        sys.stdout = StringIO()
-
-        from examples import benchmarking_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
+def make_test_function(example):
+    def test(self):
+        ex = import_module("examples." + example)
 
-    def test_horizon_ex(self):
-        # set stdout (so it wont print)
-        _stdout = sys.stdout
-        sys.stdout = StringIO()
+        with patch('matplotlib.pyplot.show'):
+            ex.run_example()
+    return test
 
-        from examples import horizon
-        with patch('matplotlib.pyplot') as p:
-            horizon.run_example()
 
-        # Reset stdout 
-        sys.stdout = _stdout
-
-    def test_new_state_est_ex(self):
-        # set stdout (so it wont print)
-        _stdout = sys.stdout
-        sys.stdout = StringIO()
-
-        from examples import new_state_estimator_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
-        with patch('matplotlib.pyplot') as p:
-            thrown_object_example.run_example()
-
-        # Reset stdout 
-        sys.stdout = _stdout
-
-    def test_measurement_ex(self):
-        # set stdout (so it wont print)
-        _stdout = sys.stdout
-        sys.stdout = StringIO()
-
-        from examples import measurement_eqn_example
-        with patch('matplotlib.pyplot') as p:
-            measurement_eqn_example.run_example()
-
-        # Reset stdout 
-        sys.stdout = _stdout
-
-    def test_utpredictor_ex(self):
+class TestExamples(unittest.TestCase):
+    def setUp(self):
         # set stdout (so it wont print)
-        _stdout = sys.stdout
+        self._stdout = sys.stdout
         sys.stdout = StringIO()
 
-        from examples import utpredictor
-        with patch('matplotlib.pyplot') as p:
-            utpredictor.run_example()
-
-        # Reset stdout 
-        sys.stdout = _stdout
+    def tearDown(self):
+        # reset stdout
+        sys.stdout = self._stdout
 
 # This allows the module to be executed directly
+def run_tests():
+    unittest.main()
+    
 def main():
-    # This ensures that the directory containing ProgModelTemplate is in the python search directory
-    import sys
-    from os.path import dirname, join
-    sys.path.append(join(dirname(__file__), ".."))
+    # Create tests for each example
+    for _, name, _ in pkgutil.iter_modules(['examples']):
+        if name in skipped_examples:
+            continue
+        test_func = make_test_function(name)
+        setattr(TestExamples, 'test_{0}'.format(name), test_func)   
+
 
     l = unittest.TestLoader()
     runner = unittest.TextTestRunner()
diff --git a/tests/test_manual.py b/tests/test_manual.py
new file mode 100644
index 00000000..f98722a6
--- /dev/null
+++ b/tests/test_manual.py
@@ -0,0 +1,35 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+import sys
+import unittest
+
+"""
+This file includes tests that are too long to be run as part of the automated tests. Instead, these tests are run manually as part of the release process.
+"""
+
+class TestManual(unittest.TestCase):
+    # Test playback example
+    def test_playback_example(self):
+        from examples import playback
+        playback.run_example()
+
+# This allows the module to be executed directly
+def run_tests():
+    unittest.main()
+
+def main():
+    # This ensures that the directory containing ProgModelTemplate is in the python search directory
+    from os.path import dirname, join
+    sys.path.append(join(dirname(__file__), ".."))
+
+    l = unittest.TestLoader()
+    runner = unittest.TextTestRunner()
+    print("\n\nTesting Manual")
+    result = runner.run(l.loadTestsFromTestCase(TestManual)).wasSuccessful()
+
+    if not result:
+        raise Exception("Failed test")
+
+if __name__ == '__main__':
+    main()
+
diff --git a/tests/test_metrics.py b/tests/test_metrics.py
index 8e001ca5..2b87cc93 100644
--- a/tests/test_metrics.py
+++ b/tests/test_metrics.py
@@ -1,9 +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_algs import uncertain_data
 from prog_algs.metrics import prob_success
 from prog_algs.metrics import calc_metrics as toe_metrics
-from prog_algs.uncertain_data import UnweightedSamples, MultivariateNormalDist, ScalarData
+from prog_algs.uncertain_data import UncertainData, UnweightedSamples, MultivariateNormalDist, ScalarData
 
 
 class TestMetrics(unittest.TestCase):
@@ -359,6 +360,115 @@ def test_toe_profile_metrics(self):
         self.assertIn('b', metrics)
         self.assertNotIn('c', metrics)
 
+    def test_toe_profile_metrics(self):
+        from prog_algs.predictors import ToEPredictionProfile
+        profile = ToEPredictionProfile()  # Empty profile
+        for i in range(10):
+            # a will shift upward from 0-19 to 9-28
+            # b is always a-1
+            # c is always a * 2, and will therefore always have twice the spread
+            data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)]
+            profile.add_prediction(
+                10-i,  # Time (reverse so data is decreasing)
+                UnweightedSamples(data)  # ToE Prediction
+            )
+
+        from prog_algs.metrics import alpha_lambda
+
+        # Test 1: Ground truth at median
+        ground_truth = {'a': 9.0, 'b': 8.0, 'c': 18.0}
+        lambda_value = 8  # Almost at prediction 
+        alpha = 0.5
+        beta = 0.05  # 5% is really bad
+        metrics = alpha_lambda(profile, ground_truth, lambda_value, alpha, beta)
+        
+        # Test pickling ToEPredictionProfile
+        import pickle
+        pickle.dump(profile, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(profile, pickle_converted_result)
+        # Test pickling alpha_lambda result
+        pickle.dump(metrics, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(metrics, pickle_converted_result)
+
+    def test_toe_profile_prognostic_horizon(self):
+        from prog_algs.predictors import ToEPredictionProfile
+        profile = ToEPredictionProfile()  # Empty profile
+        # Define test sample ground truth
+        GROUND_TRUTH = {'a': 9.0, 'b': 8.0, 'c': 18.0}
+        # Create rudimentary criteria equation
+        def criteria_eqn(tte : UncertainData, ground_truth_tte : dict) -> dict:
+            """
+            Sample criteria equation for unittesting. 
+
+            Args:
+                tte : UncertainData
+                    Time to event in UncertainData format.
+                ground_truth_tte : dict
+                    Dictionary of ground truth of time to event.
+            """
+            result = {}
+            for key, value in ground_truth_tte.items():
+                result[key] = abs(tte.mean[key] - value) < 0.6
+            return result
+        from prog_algs.metrics import prognostic_horizon
+        # Prognostic horizon metric testing
+        # Test 0: Empty profile (should return None for all)
+        self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': None, 'c': None})
+
+        # Loading profile
+        for i in range(10):
+            # a will shift upward from 0-19 to 9-28
+            # b is always a-1
+            # c is always a * 2, and will therefore always have twice the spread
+            data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)]
+            profile.add_prediction(
+                10-i,  # Time (reverse so data is decreasing)
+                UnweightedSamples(data)  # ToE Prediction
+            )
+        # Test 1: simple 1 criteria met
+        self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': None, 'c': 10.0})
+        # Test 2: all criteria are met at different times
+        GROUND_TRUTH = {'a': 10.0, 'b': 10.0, 'c': 18.0} # Adjust ground truth to match 3 criteria
+        self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': 1.0, 'b': 2.0, 'c': 10.0})
+        # Test 3: 2 criteria are met at once (at 10.0)
+        GROUND_TRUTH = {'a': 9.0, 'b': 14.0, 'c': 18.0}
+        self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': 10.0, 'c': 10.0})
+        # Test 4: at least one criteria is met at beginning of the prediction
+        GROUND_TRUTH = {'a': 10, 'b': 8.0, 'c': 10.0}
+        self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': 1.0, 'b': None, 'c': None})
+        # Test 5: criteria not met for any
+        GROUND_TRUTH = {'a': 9.0, 'b': 8.0, 'c': 8.0}
+        self.assertDictEqual(prognostic_horizon(profile, criteria_eqn, GROUND_TRUTH), {'a': None, 'b': None, 'c': None})
+
+    def test_toe_profile_cumulative_relative_accuracy(self):
+        from prog_algs.predictors import ToEPredictionProfile
+        profile = ToEPredictionProfile()  # Empty profile
+        # Loading profile
+        for i in range(10):
+            data = [{'a': j, 'b': j -1 , 'c': (j-4.5) * 2 + 4.5} for j in range(i, i+20)]
+            profile.add_prediction(
+                10-i,  # Time (reverse so data is decreasing)
+                UnweightedSamples(data)  # ToE Prediction
+            )
+        # Test positive floats ground truth
+        GROUND_TRUTH = {'a': 9.0, 'b': 8.0, 'c': 18.0}
+        self.assertEquals(profile.cumulative_relative_accuracy(GROUND_TRUTH), {'a': 0.4444444444444445, 'b': 0.375, 'c': 0.6388888888888888})
+        # Test negative floats ground truth
+        GROUND_TRUTH = {'a': -9.0, 'b': -8.0, 'c': -18.0}
+        self.assertEquals(profile.cumulative_relative_accuracy(GROUND_TRUTH), {'a': 3.555555555555556, 'b': 3.625, 'c': 3.305555555555556})
+        # Test ground truth values of 0; already caught by relative_accuracy
+        with self.assertRaises(ZeroDivisionError):
+            GROUND_TRUTH = {'a': 0, 'b': 0, 'c': 0}
+            raise_error = profile.cumulative_relative_accuracy(GROUND_TRUTH)
+        # Test ground truth in invalid input types; already caught by relative_accuracy
+        with self.assertRaises(TypeError):
+            GROUND_TRUTH = []
+            raise_error = profile.cumulative_relative_accuracy(GROUND_TRUTH)
+    
+
+
 # This allows the module to be executed directly    
 def run_tests():
     l = unittest.TestLoader()
diff --git a/tests/test_predictors.py b/tests/test_predictors.py
index e5d3bd90..4eb79051 100644
--- a/tests/test_predictors.py
+++ b/tests/test_predictors.py
@@ -1,7 +1,11 @@
 # Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+import sys
 import unittest
 
 from prog_models import PrognosticsModel
+# This ensures that the directory containing predictor_template is in the Python search directory
+from os.path import dirname, join
+sys.path.append(join(dirname(__file__), ".."))
 
 
 class MockProgModel(PrognosticsModel):
@@ -35,42 +39,43 @@ def event_state(self, x):
     def threshold_met(self, x):
         return {key : value < 1e-6 for (key, value) in self.event_state(x).items()}
 
+
 class TestPredictors(unittest.TestCase):
     def test_pred_template(self):
         from predictor_template import TemplatePredictor
         m = MockProgModel()
         pred = TemplatePredictor(m)
 
-    def test_UKP_ThrownObject(self):
+    def test_UTP_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', 'max_x'], [1.83, 40, 1.83], [[0.1, 0.01, 0.01], [0.01, 0.1, 0.01], [0.01, 0.01, 0.1]])
+        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)
+        mc_results = pred.predict(samples, future_loading, dt=0.01, save_freq=1)
+        self.assertAlmostEqual(mc_results.time_of_event.mean['impact'], 8.21, 0)
+        self.assertAlmostEqual(mc_results.time_of_event.mean['falling'], 4.15, 0)
+        # self.assertAlmostEqual(mc_results.times[-1], 9, 1)  # Saving every second, last time should be around the 1s after impact event (because one of the sigma points fails afterwards)
 
-    def test_UKP_ThrownObject_One_Event(self):
+    def test_UTP_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', 'max_x'], [1.83, 40, 1.83], [[0.1, 0.01, 0.01], [0.01, 0.1, 0.01], [0.01, 0.01, 0.1]])
+        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
+        mc_results = pred.predict(samples, future_loading, dt=0.01, events=['falling'], save_freq=1)
+        self.assertAlmostEqual(mc_results.time_of_event.mean['falling'], 4.15, 0)
+        self.assertTrue('impact' not in mc_results.time_of_event.mean)
+        self.assertAlmostEqual(mc_results.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
@@ -106,12 +111,12 @@ def future_loading(t, x = None):
         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)
+        mc_results = ut.predict(filt.x, future_loading, dt=0.1)
+        self.assertAlmostEqual(mc_results.time_of_event.mean['EOD'], 3004, -2)
 
         # Test Metrics
         from prog_algs.metrics import samples
-        s = toe.sample(100).key('EOD')
+        s = mc_results.time_of_event.sample(100).key('EOD')
         samples.eol_metrics(s)  # Kept for backwards compatibility
 
     def test_MC(self):
@@ -122,7 +127,7 @@ def test_MC(self):
         def future_loading(t = None, x = None):
             return {}
             
-        (times, inputs, states, outputs, event_states, toe) = mc.predict(m.initialize(), future_loading, dt=0.2, num_samples=3, save_freq=1)
+        mc.predict(m.initialize(), future_loading, dt=0.2, num_samples=3, save_freq=1)
 
     def test_prediction_mvnormaldist(self):
         from prog_algs.predictors import Prediction as MultivariateNormalDistPrediction
@@ -148,11 +153,6 @@ def test_prediction_mvnormaldist(self):
         except Exception:
             pass
 
-        # Test pickle
-        import pickle
-        p2 = pickle.loads(pickle.dumps(p))
-        self.assertEqual(p2, p)
-
     def test_prediction_uwsamples(self):
         from prog_algs.predictors.prediction import UnweightedSamplesPrediction
         from prog_algs.uncertain_data import UnweightedSamples
@@ -196,11 +196,6 @@ def test_prediction_uwsamples(self):
             self.fail()
         except Exception:
             pass
-
-        # Test pickle
-        import pickle
-        p2 = pickle.loads(pickle.dumps(p))
-        self.assertEqual(p2, p)
     
     def test_prediction_profile(self):
         from prog_algs.predictors import ToEPredictionProfile
@@ -233,6 +228,205 @@ def test_prediction_profile(self):
         except Exception:
             pass
 
+    def test_pickle_UTP_ThrownObject_pickle_result(self): # PREDICTION TEST
+        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 {}
+
+        mc_results = pred.predict(samples, future_loading, dt=0.01, save_freq=1)
+        import pickle # try pickle'ing
+        pickle.dump(mc_results, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(mc_results, pickle_converted_result)
+
+    def test_UTP_ThrownObject_One_Event_pickle_result(self): # PREDICTION TEST
+        # Test thrown object, similar to test_UKP_ThrownObject, but with only the 'falling' event
+        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 {}
+
+        mc_results = pred.predict(samples, future_loading, dt=0.01, events=['falling'], save_freq=1)
+        import pickle # try pickle'ing
+        pickle.dump(mc_results, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(mc_results, pickle_converted_result)
+
+    def test_UKP_Battery_pickle_result(self):
+        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
+        mc_results = ut.predict(filt.x, future_loading, dt=0.1)
+        import pickle # try pickle'ing
+        pickle.dump(mc_results, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(mc_results, pickle_converted_result)
+
+    def test_pickle_prediction_mvnormaldist(self):
+        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)
+
+        import pickle
+        p2 = pickle.loads(pickle.dumps(p))
+        self.assertEqual(p2, p)
+
+    def test_pickle_prediction_uwsamples(self):
+        from prog_algs.predictors.prediction import UnweightedSamplesPrediction
+        from prog_algs.uncertain_data import UnweightedSamples
+        times = list(range(10))
+        states = [UnweightedSamples(list(range(10))), 
+            UnweightedSamples(list(range(1, 11))), 
+            UnweightedSamples(list(range(-1, 9)))]
+        p = UnweightedSamplesPrediction(times, states)
+
+        import pickle
+        p2 = pickle.loads(pickle.dumps(p))
+        self.assertEqual(p2, p)
+    
+    def test_pickle_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)
+        
+        import pickle
+        p2 = pickle.loads(pickle.dumps(profile))
+        self.assertEqual(p2, profile)
+
+    # Testing LazyUTPrediction
+    def test_pickle_UTP_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 {}
+        mc_results = pred.predict(samples, future_loading, dt=0.01, save_freq=1)
+        # LazyUTPrediction objects from pre
+        pred_op = mc_results.outputs
+        pred_es = mc_results.event_states
+
+        import pickle # try pickle'ing
+        pickle.dump(pred_op, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(pred_op, pickle_converted_result)
+        
+        pickle.dump(pred_es, open('predictor_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('predictor_test.pkl', 'rb'))
+        self.assertEqual(pred_es, pickle_converted_result)
+
+    def test_profile_plot(self):
+        from prog_algs.predictors import ToEPredictionProfile
+        from prog_algs.uncertain_data import ScalarData
+        profile = ToEPredictionProfile()
+        profile.add_prediction(0, ScalarData({'a': 1, 'b': 2, 'c': -3.2}))
+        profile.add_prediction(1, ScalarData({'a': 1.1, 'b': 2.2, 'c': -3.1}))
+        profile.add_prediction(0.5, ScalarData({'a': 1.05, 'b': 2.1, 'c': -3.15}))
+
+        # No ground truth or alpha provided
+        no_gt_alpha_plots = profile.plot(show=True)
+
+        # Ground truth provided, no alpha provided
+        sample_gt = {'a': 1.075, 'b': 2.15, 'c': -3.125}
+        gt_no_alpha_plots = profile.plot(ground_truth=sample_gt, show=True)
+
+        # Ground truth and alpha provided
+        sample_alpha = 0.50
+        gt_and_alpha_plots = profile.plot(ground_truth=sample_gt, alpha=sample_alpha, show=True)
+
+    def test_prediction_monotonicity(self):
+        from prog_algs.predictors.prediction import Prediction
+        from prog_algs.uncertain_data import MultivariateNormalDist, ScalarData, UnweightedSamples
+        times = list(range(10))
+        covar = [[0.1, 0.01], [0.01, 0.1]]
+
+        # Test monotonically increasing and decreasing
+        means = [{'a': 1+i/10, 'b': 2-i/5} for i in range(10)]
+        states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)]
+        p = Prediction(times, states)
+        self.assertDictEqual(p.monotonicity(), {'a': 1.0, 'b': 1.0})
+
+        # Test no monotonicity
+        means = [{'a': i*(i%2-1), 'b': i*(i%2-1)} for i in range(10)]
+        states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)]
+        p = Prediction(times, states)
+        self.assertDictEqual(p.monotonicity(), {'a': 0.0, 'b': 0.0})
+
+        # Test monotonicity between range [0,1]
+        means = [{'a': i*(i%3-1), 'b': i*(i%3-1)} for i in range(10)]
+        states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)]
+        p = Prediction(times, states)
+        self.assertDictEqual(p.monotonicity(), {'a': 0.2222222222222222, 'b': 0.2222222222222222})
+
+        # Test mixed
+        means = [{'a': i, 'b': i*(i%3+5)} for i in range(10)]
+        states = [MultivariateNormalDist(means[i].keys(), means[i].values(), covar) for i in range(10)]
+        p = Prediction(times, states)
+        self.assertDictEqual(p.monotonicity(), {'a': 1, 'b': 0.5555555555555556})
+
+        # Test Scalar
+        samples = [{'a': 1+i/10, 'b': 2-i/5, 'c': i*(i%2-1), 'd': i*(i%3-1)} for i in range(10)]
+        states = [ScalarData(samples[i]) for i in range(10)]
+        p = Prediction(times, states)
+        self.assertDictEqual(p.monotonicity(), {'a': 1, 'b': 1, 'c': 0, 'd': 0.2222222222222222})
+
+        # Test UnweightedSamples
+        samples = [{'a': 1+i/10, 'b': 2-i/5, 'c': i*(i%2-1), 'd': i*(i%3-1)} for i in range(10)]
+        states = [UnweightedSamples([samples[i]]) for i in range(10)]
+        p = Prediction(times, states)
+        self.assertDictEqual(p.monotonicity(), {'a': 1, 'b': 1, 'c': 0, 'd': 0.2222222222222222})
+
 # This allows the module to be executed directly    
 def run_tests():
     l = unittest.TestLoader()
diff --git a/tests/test_state_estimators.py b/tests/test_state_estimators.py
index 371834a5..a0cbccdb 100644
--- a/tests/test_state_estimators.py
+++ b/tests/test_state_estimators.py
@@ -1,8 +1,27 @@
 # 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
+import numpy as np
+
+from prog_models import PrognosticsModel, LinearModel
 from prog_algs.exceptions import ProgAlgTypeError
 
+def equal_cov(pair1, pair2):
+    """
+    Compare 2 covariance matricies, considering the order
+
+    Args:
+        pair1 (tuple[list, array[array[float]]]):
+            keys (list[str]): Labels for keys
+            covar (array[array[float]]): Covariance matrix
+        pair2 (tuple[list, array[array[float]]]):
+            keys (list[str]): Labels for keys
+            covar (array[array[float]]): Covariance matrix
+    """
+    (keys1, cov1) = pair1
+    (keys2, cov2) = pair2
+    mapping = {i: keys2.index(key) for i, key in enumerate(keys1)}
+    return all([cov1[i][j] == cov2[mapping[i]][mapping[j]] for i in range(len(keys1)) for j in range(len(keys1))])
+
 
 class MockProgModel(PrognosticsModel):
     states = ['a', 'b', 'c', 't']
@@ -41,25 +60,92 @@ def test_state_est_template(self):
         m = MockProgModel()
         se = TemplateStateEstimator(m, {'a': 0.0, 'b': 0.0, 'c': 0.0, 't':0.0})
 
+    def __test_state_est(self, filt, m):
+        x_guess = m.StateContainer(filt.x.mean)  # Might be new
+        x = m.initialize()
+
+        self.assertTrue(all(key in filt.x.mean for key in m.states))
+
+        # run for a while
+        dt = 0.01
+        u = m.InputContainer({})
+        for i in range(1250):
+            # Get simulated output (would be measured in a real application)
+            x = m.next_state(x, u, dt)
+            x_guess = m.next_state(x_guess, u, dt)
+            z = m.output(x)
+
+            # Estimate New State
+            filt.estimate((i+1)*dt, u, z)
+
+        # Check results - make sure it converged
+        x_est = filt.x.mean
+        for key in m.states:
+            # should be close to right
+            self.assertAlmostEqual(x_est[key], x[key], delta=0.4)
+
     def test_UKF(self):
+        from prog_models.models import ThrownObject
         from prog_algs.state_estimators import UnscentedKalmanFilter
-        m = MockProgModel(process_noise = 1e-3, measurement_noise = 1e-4)
-        x0 = m.initialize()
-        filt = UnscentedKalmanFilter(m, x0)
-        self.assertTrue(all(key in filt.x.mean for key in m.states))
-        self.assertDictEqual(x0, filt.x.mean)
-        filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': 0.8}) 
-        filt.estimate(0.15, {'i1': 1, 'i2': 2}, {'o1': 0.8})# note- if input is correct, o1 should be 0.9
-        x = filt.x.mean
-        self.assertFalse( x0 == x )
-        self.assertFalse( {'a': 1.1, 'b': 2, 'c': -5.2, 't': 0} == x )
-
-        # Between the model and sense outputs
-        o = m.output(x)
-        o0 = m.output(x0)
-        self.assertGreater(o['o1'], 0.5)
-        self.assertLess(o['o1'], o0['o1']) 
 
+        m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2)
+        x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40}
+
+        filt = UnscentedKalmanFilter(m, x_guess)
+        self.__test_state_est(filt, m)
+
+        m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2)
+
+        # Test UnscentedKalmanFilter ScalarData
+        from prog_algs.uncertain_data.scalar_data import ScalarData
+        x_scalar = ScalarData({'x': 1.75, 'v': 35})
+        filt_scalar = UnscentedKalmanFilter(m, x_scalar)
+        self.assertDictEqual(filt_scalar.x.mean, x_scalar.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_scalar.keys()), x_scalar.cov), 
+                (list(filt_scalar.x.keys()), filt_scalar.x.cov)))
+
+        # Test UnscentedKalmanFilter MultivariateNormalDist
+        from numpy import array
+        from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist
+        x_mvnd = MultivariateNormalDist(['x', 'v'], array([2, 10]), array([[1, 0], [0, 1]]))
+        filt_mvnd = UnscentedKalmanFilter(m, x_mvnd)
+        self.assertDictEqual(filt_mvnd.x.mean, x_mvnd.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_mvnd.keys()), x_mvnd.cov), 
+                (list(filt_mvnd.x.keys()), filt_mvnd.x.cov)))
+
+        # Now with a different order
+        x_mvnd = MultivariateNormalDist(['v', 'x'], array([10, 2]), array([[1, 0], [0, 2]]))
+        filt_mvnd = UnscentedKalmanFilter(m, x_mvnd)
+        self.assertDictEqual(filt_mvnd.x.mean, x_mvnd.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_mvnd.keys()), x_mvnd.cov), 
+                (list(filt_mvnd.x.keys()), filt_mvnd.x.cov)), "Covs are not equal for multivariate in different order")
+
+        # Test UnscentedKalmanFilter UnweightedSamples
+        from prog_algs.uncertain_data.unweighted_samples import UnweightedSamples
+        x_us = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])
+        filt_us = UnscentedKalmanFilter(m, x_us)
+        self.assertDictEqual(filt_us.x.mean, x_us.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_us.keys()), x_us.cov), 
+                (list(filt_us.x.keys()), filt_us.x.cov)))
+
+        from prog_models.models import BatteryElectroChem
+
+        with self.assertRaises(Exception):
+            # Not linear model
+            UnscentedKalmanFilter(BatteryElectroChem, {})
+
+        with self.assertRaises(Exception):
+            # Missing states
+            UnscentedKalmanFilter(ThrownObject, {})
+        
     def __incorrect_input_tests(self, filter):
         class IncompleteModel:
             outputs = []
@@ -70,11 +156,8 @@ def output(self):
                 pass
         m = IncompleteModel()
         x0 = {'a': 0, 'c': 2}
-        try:
-            filt = filter(m, x0)
-            self.fail()
-        except ProgAlgTypeError:
-            pass
+        with self.assertRaises(ProgAlgTypeError):
+            filter(m, x0)
 
         class IncompleteModel:
             states = ['a', 'b']
@@ -84,11 +167,8 @@ def output(self):
                 pass
         m = IncompleteModel()
         x0 = {'a': 0, 'b': 2}
-        try:
-            filt = filter(m, x0)
-            self.fail()
-        except ProgAlgTypeError:
-            pass
+        with self.assertRaises(ProgAlgTypeError):
+            filter(m, x0)
 
         class IncompleteModel:
             outputs = []
@@ -98,11 +178,8 @@ def output(self):
                 pass
         m = IncompleteModel()
         x0 = {'a': 0, 'b': 2}
-        try:
-            filt = filter(m, x0)
-            self.fail()
-        except ProgAlgTypeError:
-            pass
+        with self.assertRaises(ProgAlgTypeError):
+            filter(m, x0)
 
         class IncompleteModel:
             outputs = []
@@ -111,12 +188,8 @@ def output(self):
                 pass
         m = IncompleteModel()
         x0 = {'a': 0, 'b': 2}
-        try:
-            filt = filter(m, x0)
-            self.fail()
-        except ProgAlgTypeError:
-            pass
-
+        with self.assertRaises(ProgAlgTypeError):
+            filter(m, x0)
         class IncompleteModel:
             outputs = []
             states = ['a', 'b']
@@ -124,11 +197,8 @@ def next_state(self):
                 pass
         m = IncompleteModel()
         x0 = {'a': 0, 'b': 2}
-        try:
-            filt = filter(m, x0)
-            self.fail()
-        except ProgAlgTypeError:
-            pass
+        with self.assertRaises(ProgAlgTypeError):
+            filter(m, x0)
 
     def test_UKF_incorrect_input(self):
         from prog_algs.state_estimators import UnscentedKalmanFilter
@@ -136,46 +206,69 @@ def test_UKF_incorrect_input(self):
 
     def test_PF(self):
         from prog_algs.state_estimators import ParticleFilter
-        m = MockProgModel(process_noise=5e-2, measurement_noise=0)
-        x0 = m.initialize()
-        filt = ParticleFilter(m, x0, n_samples=200, x0_uncertainty=0.1)
-        self.assertTrue(all(key in filt.x[0] for key in m.states))
-        # self.assertDictEqual(x0, filt.x) // Not true - sample production means they may not be equal
-        u = {'i1': 1, 'i2': 2}
-        x = m.next_state(m.initialize(), u, 0.1)
-        filt.estimate(0.1, u, m.output(x))  
-        x_est = filt.x.mean
-        self.assertFalse( x0 == x_est )
-        self.assertFalse( {'a': 1.1, 'b': 2, 'c': -5.2} == x_est )
-
-        # Between the model and sense outputs
-        o_est = m.output(x_est)
-        o0 = m.output(x0)
-        self.assertGreater(o_est['o1'], 0.7) # Should be between 0.9-o0['o1'], choosing this gives some buffer for noise
-        self.assertLess(o_est['o1'], o0['o1']) # Should be between 0.8-0.9, choosing this gives some buffer for noise. Testing that the estimate is improving
-
-        try:
-            # Only given half of the inputs 
-            filt.estimate(0.5, {'i1': 0}, {'o1': -2.0})
-            self.fail("Shouldn't have made it here- only half the inputs")
-        except Exception:
-            pass
-
-        try:
-            # Missing output
-            filt.estimate(0.5, {'i1': 0, 'i2': 0}, {})
-            self.fail("Shouldn't have made it here- missing output")
-        except Exception:
-            pass
+        from prog_models.models import ThrownObject
+
+        m = ThrownObject(process_noise={'x': 1, 'v': 3}, measurement_noise=1, num_particles = 1000)
+        x_guess = {'x': 1.75, 'v': 38.5} # Guess of initial state, actual is {'x': 1.83, 'v': 40}
+
+        filt = ParticleFilter(m, x_guess)
+        self.__test_state_est(filt, m)
+
+        # Test ParticleFilter ScalarData
+        from prog_algs.uncertain_data.scalar_data import ScalarData
+        x_scalar = ScalarData({'x': 1.75, 'v': 38.5})
+        filt_scalar = ParticleFilter(m, x_scalar, num_particles = 20) # Sample count does not affect ScalarData testing
+        self.assertDictEqual(filt_scalar.x.mean, x_scalar.mean)
+        self.assertTrue((filt_scalar.x.cov == x_scalar.cov).all())
+        
+        # Test ParticleFilter MultivariateNormalDist
+        from numpy import array
+        from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist
+        x_mvnd = MultivariateNormalDist(['x', 'v'], array([2, 10]), array([[1, 0], [0, 1]]))
+        filt_mvnd = ParticleFilter(m, x_mvnd, num_particles = 100000)
+        for k, v in filt_mvnd.x.mean.items():
+            self.assertAlmostEqual(v, x_mvnd.mean[k], delta = 0.01)
+        for i in range(len(filt_mvnd.x.cov)):
+            for j in range(len(filt_mvnd.x.cov[i])):
+                self.assertAlmostEqual(filt_mvnd.x.cov[i][j], x_mvnd.cov[i][j], delta=0.1)
+
+        # Test ParticleFilter UnweightedSamples
+        from prog_algs.uncertain_data.unweighted_samples import UnweightedSamples
+        import random
+        uw_input = []
+        x_bounds, v_bounds, x0_samples = 5, 5, 10000
+        for i in range(x0_samples):
+            uw_input.append({'x': random.randrange(-x_bounds, x_bounds), 'v': random.randrange(-v_bounds, v_bounds)})
+        x_us = UnweightedSamples(uw_input)
+        filt_us = ParticleFilter(m, x_us, num_particles = 100000)
+        for k, v in filt_us.x.mean.items():
+            self.assertAlmostEqual(v, x_us.mean[k], delta=0.02)
+        for i in range(len(filt_us.x.cov)):
+            for j in range(len(filt_us.x.cov[i])):
+                self.assertAlmostEqual(filt_us.x.cov[i][j], x_us.cov[i][j], delta=0.1)
+
+        # Test x0 if-else Control
+        # Case 0: Both isinstance(x0, UncertainData) and x0_uncertainty parameter provided; expect x0_uncertainty to be skipped
+        x_scalar = ScalarData({'x': 1.75, 'v': 38.5}) # Testing with ScalarData
+        filt_scalar = ParticleFilter(m, x_scalar, num_particles = 20, x0_uncertainty = 0.5) # Sample count does not affect ScalarData testing
+        self.assertDictEqual(filt_scalar.x.mean, x_scalar.mean)
+        self.assertTrue((filt_scalar.x.cov == x_scalar.cov).all())
+        # Case 1: Only x0_uncertainty provided; expect a warning issued
+        with self.assertWarns(Warning):
+            filt_scalar = ParticleFilter(m, {'x': 1.75, 'v': 38.5}, x0_uncertainty = 0.5)
+        # Case 2: Raise ProgAlgTypeError if x0 not UncertainData or x0_uncertainty not of type {{dict, Number}}.
+        from prog_algs.exceptions import ProgAlgTypeError
+        with self.assertRaises(ProgAlgTypeError):
+            filt_scalar = ParticleFilter(m, {'x': 1.75, 'v': 38.5}, num_particles = 20, x0_uncertainty = [])
 
     def test_measurement_eq_UKF(self):
         class MockProgModel2(MockProgModel):
             outputs = ['o1', 'o2']
             def output(self, x):
-                return {
+                return self.OutputContainer({
                     'o1': x['a'] + x['b'] + x['c'], 
                     'o2': 7
-                    }
+                    })
 
         m = MockProgModel2()
         x0 = m.initialize()
@@ -185,12 +278,6 @@ def output(self, x):
         filt = UnscentedKalmanFilter(m, x0)
         
         # Try using
-        try:
-            filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': -2.0}) 
-            self.fail('Should have failed- missing o2')
-        except Exception:
-            pass
-        
         filt.estimate(0.2, {'i1': 1, 'i2': 2}, {'o1': -2.0, 'o2': 7})
 
         # Add Measurement eqn
@@ -201,14 +288,20 @@ def measurement_eqn(x):
         filt = UnscentedKalmanFilter(m, x0, measurement_eqn=measurement_eqn)
         filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': -2.0})
 
+        # New Measurement eqn method
+        class MyModel(MockProgModel2):
+            outputs = ['o1']
+        filt = UnscentedKalmanFilter(MyModel(), x0)
+        filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': -2.0})
+
     def test_measurement_eq_PF(self):
         class MockProgModel2(MockProgModel):
             outputs = ['o1', 'o2']
             def output(self, x):
-                return {
+                return self.OutputContainer({
                     'o1': x['a'] + x['b'] + x['c'], 
                     'o2': 7
-                    }
+                    })
 
         m = MockProgModel2()
         x0 = m.initialize()
@@ -217,13 +310,7 @@ def output(self, x):
         from prog_algs.state_estimators import ParticleFilter
         filt = ParticleFilter(m, x0)
         
-        # Try using
-        try:
-            filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': -2.0}) 
-            self.fail('Should have failed- missing o2')
-        except Exception:
-            pass
-        
+        # This one should work
         filt.estimate(0.2, {'i1': 1, 'i2': 2}, {'o1': -2.0, 'o2': 7})
 
         # Add Measurement eqn
@@ -233,11 +320,114 @@ def measurement_eqn(x):
             return z
         filt = ParticleFilter(m, x0, measurement_eqn=measurement_eqn)
         filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': -2.0}) 
+
+        # New Measurement eqn method
+        class MyModel(MockProgModel2):
+            outputs = ['o1']
+        filt = ParticleFilter(MyModel(), x0)
+        filt.estimate(0.1, {'i1': 1, 'i2': 2}, {'o1': -2.0})
         
     def test_PF_incorrect_input(self):
         from prog_algs.state_estimators import ParticleFilter
         self.__incorrect_input_tests(ParticleFilter)
 
+    def test_KF(self):
+        from prog_algs.state_estimators import KalmanFilter
+        class ThrownObject(LinearModel):
+            inputs = []  # no inputs, no way to control
+            states = ['x', 'v']
+            outputs = ['x']
+            events = ['falling', 'impact']
+
+            A = np.array([[0, 1], [0, 0]])
+            E = np.array([[0], [-9.81]])
+            C = np.array([[1, 0]])
+            F = None # Will override method
+
+            default_parameters = {
+                'thrower_height': 1.83, 
+                'throwing_speed': 40, 
+                'g': -9.81 
+            }
+
+            def initialize(self, u=None, z=None):
+                return self.StateContainer({
+                    'x': self.parameters['thrower_height'], 
+                    'v': self.parameters['throwing_speed'] 
+                    })
+            
+            def threshold_met(self, x):
+                return {
+                    'falling': x['v'] < 0,
+                    'impact': x['x'] <= 0
+                }
+
+            def event_state(self, x): 
+                x_max = x['x'] + np.square(x['v'])/(-self.parameters['g']*2) # Use speed and position to estimate maximum height
+                return {
+                    'falling': np.maximum(x['v']/self.parameters['throwing_speed'],0),  # Throwing speed is max speed
+                    'impact': np.maximum(x['x']/x_max,0) if x['v'] < 0 else 1  # 1 until falling begins, then it's fraction of height
+                }
+
+        m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2)
+        x_guess = {'x': 1.75, 'v': 35} # Guess of initial state, actual is {'x': 1.83, 'v': 40}
+
+        filt = KalmanFilter(m, x_guess)
+
+        self.__test_state_est(filt, m)
+
+        m = ThrownObject(process_noise=5e-2, measurement_noise=5e-2)
+
+        # Test KalmanFilter ScalarData
+        from prog_algs.uncertain_data.scalar_data import ScalarData
+        x_scalar = ScalarData({'x': 1.75, 'v': 35})
+        filt_scalar = KalmanFilter(m, x_scalar)
+        self.assertDictEqual(filt_scalar.x.mean, x_scalar.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_scalar.keys()), x_scalar.cov), 
+                (list(filt_scalar.x.keys()), filt_scalar.x.cov)))
+
+        # Test KalmanFilter MultivariateNormalDist
+        from numpy import array
+        from prog_algs.uncertain_data.multivariate_normal_dist import MultivariateNormalDist
+        x_mvnd = MultivariateNormalDist(['x', 'v'], array([2, 10]), array([[1, 0], [0, 1]]))
+        filt_mvnd = KalmanFilter(m, x_mvnd)
+        self.assertDictEqual(filt_mvnd.x.mean, x_mvnd.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_mvnd.keys()), x_mvnd.cov), 
+                (list(filt_mvnd.x.keys()), filt_mvnd.x.cov)))
+
+        # Now with a different order
+        x_mvnd = MultivariateNormalDist(['v', 'x'], array([10, 2]), array([[1, 0], [0, 2]]))
+        filt_mvnd = KalmanFilter(m, x_mvnd)
+        self.assertDictEqual(filt_mvnd.x.mean, x_mvnd.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_mvnd.keys()), x_mvnd.cov), 
+                (list(filt_mvnd.x.keys()), filt_mvnd.x.cov)), "Covs are not equal for multivariate in different order")
+
+        # Test KalmanFilter UnweightedSamples
+        from prog_algs.uncertain_data.unweighted_samples import UnweightedSamples
+        x_us = UnweightedSamples([{'x': 1, 'v':2}, {'x': 3, 'v':-2}])
+        filt_us = KalmanFilter(m, x_us)
+        self.assertDictEqual(filt_us.x.mean, x_us.mean)
+        self.assertTrue(
+            equal_cov(
+                (list(x_us.keys()), x_us.cov), 
+                (list(filt_us.x.keys()), filt_us.x.cov)))
+
+        from prog_models.models import BatteryElectroChem
+
+        with self.assertRaises(Exception):
+            # Not linear model
+            KalmanFilter(BatteryElectroChem, {})
+
+        with self.assertRaises(Exception):
+            # Missing states
+            KalmanFilter(ThrownObject, {})
+
 # This allows the module to be executed directly    
 def run_tests():
      # This ensures that the directory containing StateEstimatorTemplate is in the python search directory
diff --git a/tests/test_tutorials.py b/tests/test_tutorials.py
new file mode 100644
index 00000000..cd5bb9cc
--- /dev/null
+++ b/tests/test_tutorials.py
@@ -0,0 +1,23 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
+import unittest
+from testbook import testbook
+
+class TestTutorials(unittest.TestCase):
+    def test_tutorial_ipynb(self):
+        with testbook('./tutorial.ipynb', execute=True) as tb:
+            self.assertEqual(tb.__class__.__name__, "TestbookNotebookClient")
+            
+
+def run_tests():
+    l = unittest.TestLoader()
+    runner = unittest.TextTestRunner()
+    print("\n\nTesting Tutorials")
+    result = runner.run(l.loadTestsFromTestCase(TestTutorials)).wasSuccessful()
+
+    if not result:
+        raise Exception("Failed test")
+
+if __name__ == '__main__':
+    run_tests()
+    
\ No newline at end of file
diff --git a/tests/test_uncertain_data.py b/tests/test_uncertain_data.py
index b3e48288..8b6ab2de 100644
--- a/tests/test_uncertain_data.py
+++ b/tests/test_uncertain_data.py
@@ -50,12 +50,6 @@ def test_unweightedsamples(self):
         self.assertEqual(data.percentage_in_bounds({'a': [0, 2.5], 'b': [0, 1.5]}), 
             {'a':0.6, 'b': 0.2})
 
-        # Pickle test
-        import pickle
-        pickle.dump(data, open('test.pkl', 'wb'))
-        data2 = pickle.load(open('test.pkl', 'rb'))
-        self.assertEqual(data, data2)
-
     def test_multivariatenormaldist(self):
         try: 
             dist = MultivariateNormalDist()
@@ -71,12 +65,6 @@ def test_multivariatenormaldist(self):
         self.assertTrue((dist.cov == array([[1, 0], [0, 1]])).all())
         dist.percentage_in_bounds([0, 10])
 
-        # Pickle test
-        import pickle
-        pickle.dump(dist, open('test.pkl', 'wb'))
-        data2 = pickle.load(open('test.pkl', 'rb'))
-        self.assertEqual(dist, data2)
-
     def test_scalardist(self):
         data = {'a': 12, 'b': 14}
         d = ScalarData(data)
@@ -87,11 +75,489 @@ def test_scalardist(self):
         self.assertEqual(d.percentage_in_bounds([0, 10]), {'a': 0, 'b': 0})
         self.assertEqual(d.percentage_in_bounds([0, 20]), {'a': 1, 'b': 1})
 
-        # Pickle test
-        import pickle
-        pickle.dump(d, open('test.pkl', 'wb'))
-        data2 = pickle.load(open('test.pkl', 'rb'))
-        self.assertEqual(d, data2)
+    def test_pickle_unweightedsamples(self):
+        data = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+        import pickle # try pickle'ing
+        pickle.dump(d, open('data_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('data_test.pkl', 'rb'))
+        self.assertEqual(d, pickle_converted_result)
+
+    def test_pickle_unweightedsamples(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        import pickle # try pickle'ing
+        pickle.dump(s, open('data_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('data_test.pkl', 'rb'))
+        self.assertEqual(s, pickle_converted_result)
+
+    def test_pickle_multivariatenormaldist(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        import pickle # try pickle'ing
+        pickle.dump(dist, open('data_test.pkl', 'wb'))
+        pickle_converted_result = pickle.load(open('data_test.pkl', 'rb'))
+        self.assertEqual(dist, pickle_converted_result)
+
+    def test_unweighted_samples_describe(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        table_list = s.describe()
+
+    def test_scalardata_add_override(self):
+        data = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+
+        # Testing __add__ override
+        mod_d = d + 0
+        for k in d.keys():
+            self.assertEqual(d.mean[k], mod_d.mean[k])
+        mod_d = d + 5
+        for k in d.keys():
+            self.assertEqual(d.mean[k]+5, mod_d.mean[k])
+        mod_d = d + -5
+        for k in d.keys():
+            self.assertEqual(d.mean[k]-5, mod_d.mean[k])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = d + []
+            mod_d = d + {}
+            mod_d = d + "test"
+        # Also works with floats
+        mod_d = d + 5.5
+        for k in d.keys():
+            self.assertEqual(d.mean[k]+5.5, mod_d.mean[k])
+
+    def test_scalardata_radd_override(self):
+        data = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+
+        # Testing __radd__ override
+        mod_d = 0 + d
+        for k in d.keys():
+            self.assertEqual(d.mean[k], mod_d.mean[k])
+        mod_d = 5 + d
+        for k in d.keys():
+            self.assertEqual(d.mean[k]+5, mod_d.mean[k])
+        mod_d = -5 + d
+        for k in d.keys():
+            self.assertEqual(d.mean[k]-5, mod_d.mean[k])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = [] + d
+            mod_d = {} + d
+            mod_d = "test" + d
+        # Also works with floats
+        mod_d = 5.5 + d
+        for k in d.keys():
+            self.assertEqual(d.mean[k]+5.5, mod_d.mean[k])
+
+    def test_scalardata_iadd_override(self):
+        data = {'a': 12, 'b': 14}
+        data_copy = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+
+        # Testing __iadd__ override
+        d += 0
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k])
+        d += 5
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k]+5)
+        d += -5
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            d += []
+            d += {}
+            d += "test"
+        # Also works with floats
+        d += 5.5
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k] + 5.5)
+
+    def test_scalardata_sub_override(self):
+        data = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+
+        # Testing __sub__ override
+        mod_d = d - 0
+        for k in d.keys():
+            self.assertEqual(d.mean[k], mod_d.mean[k])
+        mod_d = d - 5
+        for k in d.keys():
+            self.assertEqual(d.mean[k]-5, mod_d.mean[k])
+        mod_d = d - -5
+        for k in d.keys():
+            self.assertEqual(d.mean[k]+5, mod_d.mean[k])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = d - []
+            mod_d = d - {}
+            mod_d = d - "test"
+        # Also works with floats
+        mod_d = d - 5.5
+        for k in d.keys():
+            self.assertEqual(d.mean[k]-5.5, mod_d.mean[k])
+
+    def test_scalardata_rsub_override(self):
+        data = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+
+        # Testing __rsub__ override
+        mod_d = 0 - d
+        for k in d.keys():
+            self.assertEqual(d.mean[k], mod_d.mean[k])
+        mod_d = 5 - d
+        for k in d.keys():
+            self.assertEqual(d.mean[k]-5, mod_d.mean[k])
+        mod_d = -5 - d
+        for k in d.keys():
+            self.assertEqual(d.mean[k]+5, mod_d.mean[k])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = [] - d
+            mod_d = {} - d
+            mod_d = "test" - d
+        # Also works with floats
+        mod_d = 5.5 - d
+        for k in d.keys():
+            self.assertEqual(d.mean[k]-5.5, mod_d.mean[k])
+
+    def test_scalardata_isub_override(self):
+        data = {'a': 12, 'b': 14}
+        data_copy = {'a': 12, 'b': 14}
+        d = ScalarData(data)
+
+        # Testing __isub__ override
+        d -= 0
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k])
+        d -= 5
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k]-5)
+        d -= -5
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            d -= []
+            d -= {}
+            d -= "test"
+        # Also works with floats
+        d -= 5.5
+        for k in d.keys():
+            self.assertEqual(d.mean[k], data_copy[k] - 5.5)
+
+    def test_unweightedsamples_add_override(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+
+        # Testing __add__ override
+        mod_d = s + 0
+        self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        mod_d = s + 5
+        self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}])
+        mod_d = s + -5
+        self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = s + []
+            mod_d = s + {}
+            mod_d = s + "test"
+        # Also works with floats
+        mod_d = s + 5.5
+        self.assertEqual(mod_d.data, [{'a': 6.5, 'b':7.5}, {'a': 8.5, 'b':3.5}])
+
+    def test_unweightedsamples_radd_override(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+
+        # Testing __radd__ override
+        mod_d = 0 + s
+        self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        mod_d = 5 + s
+        self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}])
+        mod_d = -5 + s
+        self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = [] + s
+            mod_d = {} + s
+            mod_d = "test" + s
+        # Also works with floats
+        mod_d = 5.5 + s
+        self.assertEqual(mod_d.data, [{'a': 6.5, 'b':7.5}, {'a': 8.5, 'b':3.5}])
+
+    def test_unweightedsamples_iadd_override(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+
+        # Testing __iadd__ override
+        s += 0
+        self.assertEqual(s.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        s += 5
+        self.assertEqual(s.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}])
+        s += -5
+        self.assertEqual(s.data, [{'a': 1, 'b': 2}, {'a': 3, 'b': -2}])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            s += []
+            s += {}
+            s += "test"
+        # Also works with floats
+        s += 5.5
+        self.assertEqual(s.data, [{'a': 6.5, 'b': 7.5}, {'a': 8.5, 'b': 3.5}])
+
+    def test_unweightedsamples_sub_override(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+
+        # Testing __sub__ override
+        mod_d = s - 0
+        self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        mod_d = s - -5
+        self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}])
+        mod_d = s - 5
+        self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = s + []
+            mod_d = s + {}
+            mod_d = s + "test"
+        # Also works with floats
+        mod_d = s - 5.5
+        self.assertEqual(mod_d.data, [{'a': -4.5, 'b': -3.5}, {'a': -2.5, 'b': -7.5}])
+
+    def test_unweightedsamples_rsub_override(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+
+        # Testing __rsub__ override
+        mod_d = 0 - s
+        self.assertEqual(mod_d.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        mod_d = -5 - s
+        self.assertEqual(mod_d.data, [{'a': 6, 'b':7}, {'a': 8, 'b':3}])
+        mod_d = 5 - s
+        self.assertEqual(mod_d.data, [{'a': -4, 'b':-3}, {'a': -2, 'b':-7}])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = [] - s
+            mod_d = {} - s
+            mod_d = "test" - s
+        # Also works with floats
+        mod_d = 5.5 - s
+        self.assertEqual(mod_d.data, [{'a': -4.5, 'b': -3.5}, {'a': -2.5, 'b': -7.5}])
+
+    def test_unweightedsamples_isub_override(self):
+        s = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+
+        # Testing __isub__ override
+        s -= 0
+        self.assertEqual(s.data, [{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        s -= 5
+        self.assertEqual(s.data, [{'a': -4, 'b': -3}, {'a': -2, 'b': -7}])
+        s -= -5
+        self.assertEqual(s.data, [{'a': 1, 'b': 2}, {'a': 3, 'b': -2}])
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            s -= []
+            s -= {}
+            s -= "test"
+        # Also works with floats
+        s -= 5.5
+        self.assertEqual(s.data, [{'a': -4.5, 'b': -3.5}, {'a': -2.5, 'b': -7.5}])
+
+    def test_MultivariateNormalDist_add_override(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        
+        mod_d = dist + 0
+        self.assertEqual(mod_d.mean, {'a': 2, 'b': 10})
+        mod_d = dist + 5
+        self.assertEqual(mod_d.mean, {'a': 7, 'b': 15})
+        mod_d = dist + -5
+        self.assertEqual(mod_d.mean, {'a': -3, 'b': 5})
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = dist + []
+            mod_d = dist + {}
+            mod_d = dist + "test"
+        # Also works with floats
+        mod_d = dist + 5.5
+        self.assertEqual(mod_d.mean, {'a': 7.5, 'b': 15.5})
+
+        # Ensure covariance has not changed
+        from numpy.testing import assert_array_equal
+        mod_d = dist + 5
+        assert_array_equal(mod_d.cov, dist.cov)
+
+    def test_MultivariateNormalDist_radd_override(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        
+        mod_d = 0 + dist
+        self.assertEqual(mod_d.mean, {'a': 2, 'b': 10})
+        mod_d = 5 + dist
+        self.assertEqual(mod_d.mean, {'a': 7, 'b': 15})
+        mod_d = -5 + dist
+        self.assertEqual(mod_d.mean, {'a': -3, 'b': 5})
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = [] + dist
+            mod_d = {} + dist
+            mod_d = "test" + dist
+        # Also works with floats
+        mod_d = 5.5 + dist
+        self.assertEqual(mod_d.mean, {'a': 7.5, 'b': 15.5})
+
+        # Ensure covariance has not changed
+        from numpy.testing import assert_array_equal
+        mod_d = 5 + dist
+        assert_array_equal(mod_d.cov, dist.cov)
+
+    def test_MultivariateNormalDist_iadd_override(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        from numpy import copy
+        dist_save = copy(dist.cov)
+        
+        dist += 0
+        self.assertEqual(dist.mean, {'a': 2, 'b': 10})
+        dist += 5
+        self.assertEqual(dist.mean, {'a': 7, 'b': 15})
+        dist += -5
+        self.assertEqual(dist.mean, {'a': 2, 'b': 10})
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            dist += []
+            dist += {}
+            dist += "test"
+        # Also works with floats
+        dist += 5.5
+        self.assertEqual(dist.mean, {'a': 7.5, 'b': 15.5})
+
+        # Ensure covariance has not changed
+        from numpy.testing import assert_array_equal
+        dist += 5
+        assert_array_equal(dist.cov, dist_save)
+
+    def test_MultivariateNormalDist_sub_override(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        
+        mod_d = dist - 0
+        self.assertEqual(mod_d.mean, {'a': 2, 'b': 10})
+        mod_d = dist - -5
+        self.assertEqual(mod_d.mean, {'a': 7, 'b': 15})
+        mod_d = dist - 5
+        self.assertEqual(mod_d.mean, {'a': -3, 'b': 5})
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = dist - []
+            mod_d = dist - {}
+            mod_d = dist - "test"
+        # Also works with floats
+        mod_d = dist - 5.5
+        self.assertEqual(mod_d.mean, {'a': -3.5, 'b': 4.5})
+
+        # Ensure covariance has not changed
+        from numpy.testing import assert_array_equal
+        mod_d = dist - 5
+        assert_array_equal(mod_d.cov, dist.cov)
+
+    def test_MultivariateNormalDist_rsub_override(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        
+        mod_d = 0 - dist
+        self.assertEqual(mod_d.mean, {'a': 2, 'b': 10})
+        mod_d = -5 - dist
+        self.assertEqual(mod_d.mean, {'a': 7, 'b': 15})
+        mod_d = 5 - dist
+        self.assertEqual(mod_d.mean, {'a': -3, 'b': 5})
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            mod_d = [] - dist
+            mod_d = {} - dist
+            mod_d = "test" - dist
+        # Also works with floats
+        mod_d = 5.5 - dist
+        self.assertEqual(mod_d.mean, {'a': -3.5, 'b': 4.5})
+
+        # Ensure covariance has not changed
+        from numpy.testing import assert_array_equal
+        mod_d = 5 - dist
+        assert_array_equal(mod_d.cov, dist.cov)
+
+    def test_MultivariateNormalDist_isub_override(self):
+        dist = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        from numpy import copy
+        dist_save = copy(dist.cov)
+        
+        dist -= 0
+        self.assertEqual(dist.mean, {'a': 2, 'b': 10})
+        dist -= 5
+        self.assertEqual(dist.mean, {'a': -3, 'b': 5})
+        dist -= -5
+        self.assertEqual(dist.mean, {'a': 2, 'b': 10})
+        with self.assertRaises(TypeError):
+            # Test adding invalid type
+            dist -= []
+            dist -= {}
+            dist -= "test"
+        # Also works with floats
+        dist -= 5.5
+        self.assertEqual(dist.mean, {'a': -3.5, 'b': 4.5})
+
+        # Ensure covariance has not changed
+        from numpy.testing import assert_array_equal
+        dist -= 5
+        assert_array_equal(dist.cov, dist_save)
+
+    def test_relative_accuracy(self):
+        # Testing for ScalarData
+        d = ScalarData({'a': 12, 'b': 14})
+        gt_std = {'a': 14, 'b': 16}
+        gt_neg = {'a': -14, 'b': -16}
+        ra_std = d.relative_accuracy(gt_std)
+        ra_neg = d.relative_accuracy(gt_neg)
+        self.assertDictEqual(ra_std,  {'a': 0.8571428571428572, 'b': 0.875})
+        self.assertDictEqual(ra_neg, {'a': 2.857142857142857, 'b': 2.875})
+        with self.assertRaises(ZeroDivisionError): # Passing in ground truth of 0 leads to divide by 0 error
+            gt_zero = {'a': 0, 'b': 0}
+            ra_zero = d.relative_accuracy(gt_zero)
+        with self.assertRaises(TypeError): # Passing in non-dict arg
+            ra_err_list = d.relative_accuracy([])
+            ra_err_str = d.relative_accuracy("")
+            ra_err_int = d.relative_accuracy(1)
+            ra_err_float = d.relative_accuracy(0.1)
+            ra_err_set = d.relative_accuracy(set())
+
+        # Testing for UnweightedSamples
+        d = UnweightedSamples([{'a': 1, 'b':2}, {'a': 3, 'b':-2}])
+        gt_std = {'a': 5, 'b': 4}
+        gt_neg = {'a': -5, 'b': -3}
+        ra_std = d.relative_accuracy(gt_std)
+        ra_neg = d.relative_accuracy(gt_neg)
+        self.assertDictEqual(ra_std,  {'a': 0.4, 'b': 0.0})
+        self.assertDictEqual(ra_neg, {'a': 2.4, 'b': 2.0})
+        with self.assertRaises(ZeroDivisionError): # Hits -inf and nan; maybe because 0/0?
+            gt_zero = {'a': 0, 'b': 0}
+            ra_zero = d.relative_accuracy(gt_zero)
+        with self.assertRaises(TypeError): # Passing in non-dict arg
+            ra_err_list = d.relative_accuracy([])
+            ra_err_str = d.relative_accuracy("")
+            ra_err_int = d.relative_accuracy(1)
+            ra_err_float = d.relative_accuracy(0.1)
+            ra_err_set = d.relative_accuracy(set())
+        
+        # Testing for MultivariateNormalDist
+        d = MultivariateNormalDist(['a', 'b'], array([2, 10]), array([[1, 0], [0, 1]]))
+        gt_std = {'a': 3, 'b': 3}
+        gt_neg = {'a': -3, 'b': -3}
+        ra_std = d.relative_accuracy(gt_std)
+        ra_neg = d.relative_accuracy(gt_neg)
+        self.assertDictEqual(ra_std,  {'a': 0.6666666666666667, 'b': -1.3333333333333335})
+        self.assertDictEqual(ra_neg, {'a': 2.666666666666667, 'b': 5.333333333333333})
+        with self.assertRaises(ZeroDivisionError): # Hits -inf and nan; maybe because 0/0?
+            gt_zero = {'a': 0, 'b': 0}
+            ra_zero = d.relative_accuracy(gt_zero)
+        with self.assertRaises(TypeError): # Passing in non-dict arg
+            ra_err_list = d.relative_accuracy([])
+            ra_err_str = d.relative_accuracy("")
+            ra_err_int = d.relative_accuracy(1)
+            ra_err_float = d.relative_accuracy(0.1)
+            ra_err_set = d.relative_accuracy(set())
+
 
 # This allows the module to be executed directly    
 def run_tests():
diff --git a/tests/test_visualize.py b/tests/test_visualize.py
index 9f756452..caad35c1 100644
--- a/tests/test_visualize.py
+++ b/tests/test_visualize.py
@@ -1,3 +1,5 @@
+# Copyright © 2021 United States Government as represented by the Administrator of the National Aeronautics and Space Administration.  All Rights Reserved.
+
 import unittest
 from prog_algs.visualize import plot_scatter