Repository: lewisfogden/heavylight (language: python)

Issue:
Implement an experimental memory optimized model
Only deleted thing is pipfile, can install like `pip install -e .[dev]` for optional dev deps?

## Motivation

Why is memory optimization important? Currently when we cache the values we never clear them from the cache. As the number of timesteps grows, so too does our cache.

If we do 1000 timesteps, than each of these stays in the cache. But if we write our code carefully, we only need the previous timestep to calculate the next timestep. So we don't actually need to cache all the values. And then for our 1000 timesteps we can make memory usage 1/1000th of what it was before.

For example, 16 GB of RAM can store 2 billion 64 bit numbers. Suppose we have a complex model with 10 formulas, 240 timesteps, and don't optimize the cache. The cache size (pols * timesteps * formulas) is going to be (10 * 240 * P) = 2 billion and P = 833,333 which is pretty good. But if we do the optimization then we don't depend on timesteps and (10 * P) = 2 billion and P = 200,000,000.

So basically I'm trying to run really big models and to do this I need to clear the cache.

## How is this accomplished

First we do a warmup run to generate the structure of the call graph. This will generate information for the `CacheGraph` like `cache_graph.last_needed_by`, which we now explain.

Each function call needs to know who is the last function call to require the result. For example, both `pols_if(t)` and `pols_death(t-1)` call on `pols_if(t-1)`. So whoever is the last one to require the result of a function will be the one to clear it. This works because the call graph is the same between the warmup run and the optimized run. Some function calls have no dependencies, like 

```py
    def pv_cashflow(self, t):
        """present value of the cashflow occuring at time t"""
        return self.cashflow(t) * self.v(t)
```

And these functions clear own cache after they are called.

So basically a function either has a last person calling on it and this person will be the one clearing it, or they will have no people calling on it and they clear themselves.

## Without the cache, how do I review results

This is where you need to use the `storage_func`. This function stores results in a separate cache which is not cleared. A typical `storage_func` is like `np.sum`. So before we store any results in our permanent cache, we aggregate them down to a single number so they do not consume memory.

## Other additions

### Unit tests

Unit tests are provided for all implemented code. 

### Docs site scaffolding

I started working on a docs site, I have picked a template I like, the site would look like this - https://docs.kidger.site/equinox/.

It uses MkDocs because markdown > rst in my opinion.

Not much has been added to the docs site so far.

## In summary

Let me know if this PR is in any way not in the spirit of the heavylight project and what might need to change to make a contribution that is valued.


Discussion / hints:

Fix patch:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..ac92a38
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,37 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/python
+{
+	"name": "Python 3",
+	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+	"image": "mcr.microsoft.com/devcontainers/python:1-3.8-bookworm",
+	"features": {
+		"ghcr.io/devcontainers/features/docker-in-docker:2": {},
+		"ghcr.io/dhoeric/features/act:1": {},
+		"ghcr.io/hspaans/devcontainer-features/pytest:1": {},
+		"ghcr.io/meaningful-ooo/devcontainer-features/fish:1": {},
+		"ghcr.io/stuartleeks/dev-container-features/shell-history:0": {}
+	},
+
+	// Use 'forwardPorts' to make a list of ports inside the container available locally.
+	// "forwardPorts": [],
+
+	// Use 'postCreateCommand' to pip install the editable
+	// "postCreateCommand": "pip install -e .[dev]",
+	"customizations": {
+		"vscode": {
+			"extensions": [
+				"ms-python.python",
+				"github.vscode-github-actions",
+				"ms-toolsai.jupyter",
+				"ms-python.black-formatter",
+				"tamasfe.even-better-toml"
+			]
+		}
+	},
+
+	// Configure tool-specific properties.
+	// "customizations": {},
+
+	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+	// "remoteUser": "root"
+}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..f33a02c
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for more information:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+# https://containers.dev/guide/dependabot
+
+version: 2
+updates:
+ - package-ecosystem: "devcontainers"
+   directory: "/"
+   schedule:
+     interval: weekly
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
new file mode 100644
index 0000000..e2ba301
--- /dev/null
+++ b/.github/workflows/python-package.yml
@@ -0,0 +1,40 @@
+name: Python package
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
+    env:
+      PYTHON: ${{ matrix.python-version }}
+
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v3
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install pytest
+        python -m pip install -e .[dev]
+    - name: Test with pytest
+      run: |
+        pytest --cov=src tests/
+    - run: ls
+    - name: Upload coverage to codecov
+      uses: codecov/codecov-action@v3
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}
+        fail_ci_if_error: true
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..af7446d
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+    "python.analysis.extraPaths": [
+        "./src"
+    ]
+}
\ No newline at end of file
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index a42da85..0000000
--- a/Pipfile
+++ /dev/null
@@ -1,18 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[packages]
-jupyterlab = "*"
-pandas = "*"
-numpy = "*"
-seaborn = "*"
-matplotlib = "*"
-solara = "*"
-heavylight = "*"
-
-[dev-packages]
-
-[requires]
-python_version = "3.11"
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index 946f62d..0000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,1945 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "3fc7606c1b6b8bc3a81ccd497c0bfa685177872c54df2ab7baf021e0a89c4b21"
-        },
-        "pipfile-spec": 6,
-        "requires": {
-            "python_version": "3.11"
-        },
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "annotated-types": {
-            "hashes": [
-                "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802",
-                "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.5.0"
-        },
-        "anyio": {
-            "hashes": [
-                "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780",
-                "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.7.1"
-        },
-        "argon2-cffi": {
-            "hashes": [
-                "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80",
-                "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==21.3.0"
-        },
-        "argon2-cffi-bindings": {
-            "hashes": [
-                "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670",
-                "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f",
-                "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583",
-                "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194",
-                "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c",
-                "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a",
-                "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082",
-                "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5",
-                "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f",
-                "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7",
-                "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d",
-                "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f",
-                "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae",
-                "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3",
-                "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86",
-                "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367",
-                "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d",
-                "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93",
-                "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb",
-                "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e",
-                "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==21.2.0"
-        },
-        "arrow": {
-            "hashes": [
-                "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1",
-                "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==1.2.3"
-        },
-        "asttokens": {
-            "hashes": [
-                "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3",
-                "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"
-            ],
-            "version": "==2.2.1"
-        },
-        "async-lru": {
-            "hashes": [
-                "sha256:00c0a8899c20b9c88663a47732689ff98189c9fa08ad9f734d7722f934d250b1",
-                "sha256:b714c9d1415fca4e264da72a9e2abc66880ce7430e03a973341f88ea4c0d4869"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.0.3"
-        },
-        "attrs": {
-            "hashes": [
-                "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04",
-                "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==23.1.0"
-        },
-        "babel": {
-            "hashes": [
-                "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610",
-                "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.12.1"
-        },
-        "backcall": {
-            "hashes": [
-                "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e",
-                "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"
-            ],
-            "version": "==0.2.0"
-        },
-        "beautifulsoup4": {
-            "hashes": [
-                "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da",
-                "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"
-            ],
-            "markers": "python_full_version >= '3.6.0'",
-            "version": "==4.12.2"
-        },
-        "bleach": {
-            "hashes": [
-                "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414",
-                "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==6.0.0"
-        },
-        "cachetools": {
-            "hashes": [
-                "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590",
-                "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==5.3.1"
-        },
-        "certifi": {
-            "hashes": [
-                "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7",
-                "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2023.5.7"
-        },
-        "cffi": {
-            "hashes": [
-                "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
-                "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
-                "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
-                "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
-                "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
-                "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
-                "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
-                "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
-                "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
-                "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
-                "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
-                "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
-                "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
-                "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
-                "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
-                "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
-                "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
-                "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
-                "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
-                "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
-                "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
-                "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
-                "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
-                "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
-                "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
-                "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
-                "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
-                "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
-                "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
-                "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
-                "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
-                "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
-                "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
-                "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
-                "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
-                "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
-                "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
-                "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
-                "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
-                "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
-                "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
-                "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
-                "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
-                "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
-                "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
-                "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
-                "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
-                "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
-                "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
-                "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
-                "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
-                "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
-                "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
-                "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
-                "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
-                "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
-                "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
-                "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
-                "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
-                "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
-                "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
-                "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
-                "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
-                "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
-            ],
-            "version": "==1.15.1"
-        },
-        "charset-normalizer": {
-            "hashes": [
-                "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96",
-                "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c",
-                "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710",
-                "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706",
-                "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020",
-                "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252",
-                "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad",
-                "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329",
-                "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a",
-                "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f",
-                "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6",
-                "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4",
-                "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a",
-                "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46",
-                "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2",
-                "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23",
-                "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace",
-                "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd",
-                "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982",
-                "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10",
-                "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2",
-                "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea",
-                "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09",
-                "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5",
-                "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149",
-                "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489",
-                "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9",
-                "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80",
-                "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592",
-                "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3",
-                "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6",
-                "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed",
-                "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c",
-                "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200",
-                "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a",
-                "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e",
-                "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d",
-                "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6",
-                "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623",
-                "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669",
-                "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3",
-                "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa",
-                "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9",
-                "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2",
-                "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f",
-                "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1",
-                "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4",
-                "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a",
-                "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8",
-                "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3",
-                "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029",
-                "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f",
-                "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959",
-                "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22",
-                "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7",
-                "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952",
-                "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346",
-                "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e",
-                "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d",
-                "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299",
-                "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd",
-                "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a",
-                "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3",
-                "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037",
-                "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94",
-                "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c",
-                "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858",
-                "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a",
-                "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449",
-                "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c",
-                "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918",
-                "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1",
-                "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c",
-                "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac",
-                "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"
-            ],
-            "markers": "python_full_version >= '3.7.0'",
-            "version": "==3.2.0"
-        },
-        "click": {
-            "hashes": [
-                "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3",
-                "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==8.1.4"
-        },
-        "colorama": {
-            "hashes": [
-                "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
-                "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
-            ],
-            "markers": "platform_system == 'Windows'",
-            "version": "==0.4.6"
-        },
-        "comm": {
-            "hashes": [
-                "sha256:16613c6211e20223f215fc6d3b266a247b6e2641bf4e0a3ad34cb1aff2aa3f37",
-                "sha256:a61efa9daffcfbe66fd643ba966f846a624e4e6d6767eda9cf6e993aadaab93e"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.1.3"
-        },
-        "contourpy": {
-            "hashes": [
-                "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e",
-                "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104",
-                "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70",
-                "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882",
-                "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f",
-                "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48",
-                "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e",
-                "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a",
-                "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37",
-                "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a",
-                "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2",
-                "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655",
-                "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545",
-                "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027",
-                "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15",
-                "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94",
-                "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439",
-                "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d",
-                "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa",
-                "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae",
-                "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103",
-                "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc",
-                "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa",
-                "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f",
-                "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18",
-                "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9",
-                "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76",
-                "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493",
-                "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9",
-                "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed",
-                "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4",
-                "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f",
-                "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3",
-                "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21",
-                "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e",
-                "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1",
-                "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a",
-                "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002",
-                "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.1.0"
-        },
-        "cycler": {
-            "hashes": [
-                "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3",
-                "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.11.0"
-        },
-        "debugpy": {
-            "hashes": [
-                "sha256:0679b7e1e3523bd7d7869447ec67b59728675aadfc038550a63a362b63029d2c",
-                "sha256:279d64c408c60431c8ee832dfd9ace7c396984fd7341fa3116aee414e7dcd88d",
-                "sha256:33edb4afa85c098c24cc361d72ba7c21bb92f501104514d4ffec1fb36e09c01a",
-                "sha256:38ed626353e7c63f4b11efad659be04c23de2b0d15efff77b60e4740ea685d07",
-                "sha256:5224eabbbeddcf1943d4e2821876f3e5d7d383f27390b82da5d9558fd4eb30a9",
-                "sha256:53f7a456bc50706a0eaabecf2d3ce44c4d5010e46dfc65b6b81a518b42866267",
-                "sha256:9cd10cf338e0907fdcf9eac9087faa30f150ef5445af5a545d307055141dd7a4",
-                "sha256:aaf6da50377ff4056c8ed470da24632b42e4087bc826845daad7af211e00faad",
-                "sha256:b3e7ac809b991006ad7f857f016fa92014445085711ef111fdc3f74f66144096",
-                "sha256:bae1123dff5bfe548ba1683eb972329ba6d646c3a80e6b4c06cd1b1dd0205e9b",
-                "sha256:c0ff93ae90a03b06d85b2c529eca51ab15457868a377c4cc40a23ab0e4e552a3",
-                "sha256:c4c2f0810fa25323abfdfa36cbbbb24e5c3b1a42cb762782de64439c575d67f2",
-                "sha256:d71b31117779d9a90b745720c0eab54ae1da76d5b38c8026c654f4a066b0130a",
-                "sha256:dbe04e7568aa69361a5b4c47b4493d5680bfa3a911d1e105fbea1b1f23f3eb45",
-                "sha256:de86029696e1b3b4d0d49076b9eba606c226e33ae312a57a46dca14ff370894d",
-                "sha256:e3876611d114a18aafef6383695dfc3f1217c98a9168c1aaf1a02b01ec7d8d1e",
-                "sha256:ed6d5413474e209ba50b1a75b2d9eecf64d41e6e4501977991cdc755dc83ab0f",
-                "sha256:f90a2d4ad9a035cee7331c06a4cf2245e38bd7c89554fe3b616d90ab8aab89cc"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.6.7"
-        },
-        "decorator": {
-            "hashes": [
-                "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330",
-                "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==5.1.1"
-        },
-        "defusedxml": {
-            "hashes": [
-                "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
-                "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.7.1"
-        },
-        "executing": {
-            "hashes": [
-                "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc",
-                "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"
-            ],
-            "version": "==1.2.0"
-        },
-        "fastjsonschema": {
-            "hashes": [
-                "sha256:4b90b252628ca695280924d863fe37234eebadc29c5360d322571233dc9746e0",
-                "sha256:f4eeb8a77cef54861dbf7424ac8ce71306f12cbb086c45131bcba2c6a4f726e3"
-            ],
-            "version": "==2.17.1"
-        },
-        "filelock": {
-            "hashes": [
-                "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81",
-                "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.12.2"
-        },
-        "fonttools": {
-            "hashes": [
-                "sha256:00ab569b2a3e591e00425023ade87e8fef90380c1dde61be7691cb524ca5f743",
-                "sha256:022c4a16b412293e7f1ce21b8bab7a6f9d12c4ffdf171fdc67122baddb973069",
-                "sha256:05171f3c546f64d78569f10adc0de72561882352cac39ec7439af12304d8d8c0",
-                "sha256:14037c31138fbd21847ad5e5441dfdde003e0a8f3feb5812a1a21fd1c255ffbd",
-                "sha256:15abb3d055c1b2dff9ce376b6c3db10777cb74b37b52b78f61657634fd348a0d",
-                "sha256:18ea64ac43e94c9e0c23d7a9475f1026be0e25b10dda8f236fc956188761df97",
-                "sha256:1a003608400dd1cca3e089e8c94973c6b51a4fb1ef00ff6d7641617b9242e637",
-                "sha256:1bc4c5b147be8dbc5df9cc8ac5e93ee914ad030fe2a201cc8f02f499db71011d",
-                "sha256:200729d12461e2038700d31f0d49ad5a7b55855dec7525074979a06b46f88505",
-                "sha256:337b6e83d7ee73c40ea62407f2ce03b07c3459e213b6f332b94a69923b9e1cb9",
-                "sha256:37467cee0f32cada2ec08bc16c9c31f9b53ea54b2f5604bf25a1246b5f50593a",
-                "sha256:425b74a608427499b0e45e433c34ddc350820b6f25b7c8761963a08145157a66",
-                "sha256:530c5d35109f3e0cea2535742d6a3bc99c0786cf0cbd7bb2dc9212387f0d908c",
-                "sha256:56d4d85f5374b45b08d2f928517d1e313ea71b4847240398decd0ab3ebbca885",
-                "sha256:5e00334c66f4e83535384cb5339526d01d02d77f142c23b2f97bd6a4f585497a",
-                "sha256:5fdf60f8a5c6bcce7d024a33f7e4bc7921f5b74e8ea13bccd204f2c8b86f3470",
-                "sha256:6a8d71b9a5c884c72741868e845c0e563c5d83dcaf10bb0ceeec3b4b2eb14c67",
-                "sha256:6d5adf4ba114f028fc3f5317a221fd8b0f4ef7a2e5524a2b1e0fd891b093791a",
-                "sha256:7449e5e306f3a930a8944c85d0cbc8429cba13503372a1a40f23124d6fb09b58",
-                "sha256:7961575221e3da0841c75da53833272c520000d76f7f71274dbf43370f8a1065",
-                "sha256:7f6e3fa3da923063c286320e728ba2270e49c73386e3a711aa680f4b0747d692",
-                "sha256:882983279bf39afe4e945109772c2ffad2be2c90983d6559af8b75c19845a80a",
-                "sha256:8a917828dbfdb1cbe50cf40eeae6fbf9c41aef9e535649ed8f4982b2ef65c091",
-                "sha256:8c4305b171b61040b1ee75d18f9baafe58bd3b798d1670078efe2c92436bfb63",
-                "sha256:91784e21a1a085fac07c6a407564f4a77feb471b5954c9ee55a4f9165151f6c1",
-                "sha256:94c915f6716589f78bc00fbc14c5b8de65cfd11ee335d32504f1ef234524cb24",
-                "sha256:97d95b8301b62bdece1af943b88bcb3680fd385f88346a4a899ee145913b414a",
-                "sha256:a954b90d1473c85a22ecf305761d9fd89da93bbd31dae86e7dea436ad2cb5dc9",
-                "sha256:aa83b3f151bc63970f39b2b42a06097c5a22fd7ed9f7ba008e618de4503d3895",
-                "sha256:b802dcbf9bcff74672f292b2466f6589ab8736ce4dcf36f48eb994c2847c4b30",
-                "sha256:bae8c13abbc2511e9a855d2142c0ab01178dd66b1a665798f357da0d06253e0d",
-                "sha256:c55f1b4109dbc3aeb496677b3e636d55ef46dc078c2a5e3f3db4e90f1c6d2907",
-                "sha256:eb52c10fda31159c22c7ed85074e05f8b97da8773ea461706c273e31bcbea836",
-                "sha256:ec468c022d09f1817c691cf884feb1030ef6f1e93e3ea6831b0d8144c06480d1"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.40.0"
-        },
-        "fqdn": {
-            "hashes": [
-                "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f",
-                "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"
-            ],
-            "version": "==1.5.1"
-        },
-        "h11": {
-            "hashes": [
-                "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
-                "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.14.0"
-        },
-        "heavylight": {
-            "hashes": [
-                "sha256:62778f345b0cbbdcfd0baaea28c5b9b74755bf8b4a2da0a1b620b6d42373f8d3",
-                "sha256:c455a41a37188dc5ba6d7d490883bcf3bcc401341383159a78bd90b952246fb9"
-            ],
-            "index": "pypi",
-            "version": "==1.0.4"
-        },
-        "humanize": {
-            "hashes": [
-                "sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a",
-                "sha256:df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.7.0"
-        },
-        "idna": {
-            "hashes": [
-                "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
-                "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==3.4"
-        },
-        "ipykernel": {
-            "hashes": [
-                "sha256:29cea0a716b1176d002a61d0b0c851f34536495bc4ef7dd0222c88b41b816123",
-                "sha256:2f5fffc7ad8f1fd5aadb4e171ba9129d9668dbafa374732cf9511ada52d6547f"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==6.24.0"
-        },
-        "ipython": {
-            "hashes": [
-                "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1",
-                "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf"
-            ],
-            "markers": "python_version >= '3.9'",
-            "version": "==8.14.0"
-        },
-        "ipyvue": {
-            "hashes": [
-                "sha256:da3f2a6115d1f27995e7efa0e0e267eb976ade5ca982aa3da3106c70d6f878db",
-                "sha256:e35979c2ca980c227c681179d9c52fb5c04468c419ff50a23fc719c9239671ca"
-            ],
-            "version": "==1.9.2"
-        },
-        "ipyvuetify": {
-            "hashes": [
-                "sha256:11bdfe9490b4533a91b141c40763bea4e73d71c764e8d1aa9969b5a348a886b5",
-                "sha256:9ba44279479f33f5cb83af406aa813050ee96201955c2cba087fd23a843d5b4e"
-            ],
-            "version": "==1.8.10"
-        },
-        "ipywidgets": {
-            "hashes": [
-                "sha256:50ace0a8886e9a0d68b980db82f94c25d55d21ff2340ed36f802dd9365e94acf",
-                "sha256:e0aed0c95a1e55b6a123f64305245578bdc09e52965a34941c2b6a578b8c64a0"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==8.0.7"
-        },
-        "isoduration": {
-            "hashes": [
-                "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9",
-                "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"
-            ],
-            "version": "==20.11.0"
-        },
-        "jedi": {
-            "hashes": [
-                "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e",
-                "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.18.2"
-        },
-        "jinja2": {
-            "hashes": [
-                "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
-                "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.1.2"
-        },
-        "json5": {
-            "hashes": [
-                "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f",
-                "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"
-            ],
-            "version": "==0.9.14"
-        },
-        "jsonpointer": {
-            "hashes": [
-                "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a",
-                "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"
-            ],
-            "version": "==2.4"
-        },
-        "jsonschema": {
-            "hashes": [
-                "sha256:8caf5b57a990a98e9b39832ef3cb35c176fe331414252b6e1b26fd5866f891a4",
-                "sha256:b508dd6142bd03f4c3670534c80af68cd7bbff9ea830b9cf2625d4a3c49ddf60"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==4.18.0"
-        },
-        "jsonschema-specifications": {
-            "hashes": [
-                "sha256:3d2b82663aff01815f744bb5c7887e2121a63399b49b104a3c96145474d091d7",
-                "sha256:ca1c4dd059a9e7b34101cf5b3ab7ff1d18b139f35950d598d629837ef66e8f28"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2023.6.1"
-        },
-        "jupyter-client": {
-            "hashes": [
-                "sha256:3af69921fe99617be1670399a0b857ad67275eefcfa291e2c81a160b7b650f5f",
-                "sha256:7441af0c0672edc5d28035e92ba5e32fadcfa8a4e608a434c228836a89df6158"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==8.3.0"
-        },
-        "jupyter-core": {
-            "hashes": [
-                "sha256:5ba5c7938a7f97a6b0481463f7ff0dbac7c15ba48cf46fa4035ca6e838aa1aba",
-                "sha256:ae9036db959a71ec1cac33081eeb040a79e681f08ab68b0883e9a676c7a90dce"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.3.1"
-        },
-        "jupyter-events": {
-            "hashes": [
-                "sha256:57a2749f87ba387cd1bfd9b22a0875b889237dbf2edc2121ebb22bde47036c17",
-                "sha256:9a6e9995f75d1b7146b436ea24d696ce3a35bfa8bfe45e0c33c334c79464d0b3"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.6.3"
-        },
-        "jupyter-lsp": {
-            "hashes": [
-                "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1",
-                "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.2.0"
-        },
-        "jupyter-server": {
-            "hashes": [
-                "sha256:36da0a266d31a41ac335a366c88933c17dfa5bb817a48f5c02c16d303bc9477f",
-                "sha256:6a77912aff643e53fa14bdb2634884b52b784a4be77ce8e93f7283faed0f0849"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.7.0"
-        },
-        "jupyter-server-terminals": {
-            "hashes": [
-                "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d",
-                "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.4.4"
-        },
-        "jupyterlab": {
-            "hashes": [
-                "sha256:0a77898aebb55da391e5f57022774c089fb075e98803ff3d514a79b727dc934d",
-                "sha256:201b4f729a7dc5e22ca6c4dd8944cde792f1cb008d7c6b821e0a48d2502205c8"
-            ],
-            "index": "pypi",
-            "version": "==4.0.2"
-        },
-        "jupyterlab-pygments": {
-            "hashes": [
-                "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f",
-                "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.2.2"
-        },
-        "jupyterlab-server": {
-            "hashes": [
-                "sha256:83c01aa4ad9451cd61b383e634d939ff713850f4640c0056b2cdb2b6211a74c7",
-                "sha256:a5ea2c839336a8ba7c38c8e7b2f24cedf919f0d439f4d2e606d9322013a95788"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.23.0"
-        },
-        "jupyterlab-widgets": {
-            "hashes": [
-                "sha256:4715912d6ceab839c9db35953c764b3214ebbc9161c809f6e0510168845dfdf5",
-                "sha256:d428ab97b8d87cc7c54cbf37644d6e0f0e662f23876e05fa460a73ec3257252a"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.0.8"
-        },
-        "kiwisolver": {
-            "hashes": [
-                "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b",
-                "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166",
-                "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c",
-                "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c",
-                "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0",
-                "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4",
-                "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9",
-                "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286",
-                "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767",
-                "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c",
-                "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6",
-                "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b",
-                "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004",
-                "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf",
-                "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494",
-                "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac",
-                "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626",
-                "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766",
-                "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514",
-                "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6",
-                "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f",
-                "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d",
-                "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191",
-                "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d",
-                "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51",
-                "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f",
-                "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8",
-                "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454",
-                "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb",
-                "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da",
-                "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8",
-                "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de",
-                "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a",
-                "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9",
-                "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008",
-                "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3",
-                "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32",
-                "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938",
-                "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1",
-                "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9",
-                "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d",
-                "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824",
-                "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b",
-                "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd",
-                "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2",
-                "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5",
-                "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69",
-                "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3",
-                "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae",
-                "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597",
-                "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e",
-                "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955",
-                "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca",
-                "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a",
-                "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea",
-                "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede",
-                "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4",
-                "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6",
-                "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686",
-                "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408",
-                "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871",
-                "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29",
-                "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750",
-                "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897",
-                "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0",
-                "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2",
-                "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09",
-                "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.4.4"
-        },
-        "markdown": {
-            "hashes": [
-                "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2",
-                "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.4.3"
-        },
-        "markdown-it-py": {
-            "hashes": [
-                "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1",
-                "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==3.0.0"
-        },
-        "markupsafe": {
-            "hashes": [
-                "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e",
-                "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e",
-                "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431",
-                "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686",
-                "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559",
-                "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc",
-                "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c",
-                "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0",
-                "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4",
-                "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9",
-                "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575",
-                "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba",
-                "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d",
-                "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3",
-                "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00",
-                "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155",
-                "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac",
-                "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52",
-                "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f",
-                "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8",
-                "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b",
-                "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24",
-                "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea",
-                "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198",
-                "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0",
-                "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee",
-                "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be",
-                "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2",
-                "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707",
-                "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6",
-                "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58",
-                "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779",
-                "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636",
-                "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c",
-                "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad",
-                "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee",
-                "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc",
-                "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2",
-                "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48",
-                "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7",
-                "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e",
-                "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b",
-                "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa",
-                "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5",
-                "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e",
-                "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb",
-                "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9",
-                "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57",
-                "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc",
-                "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.1.3"
-        },
-        "matplotlib": {
-            "hashes": [
-                "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8",
-                "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b",
-                "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676",
-                "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f",
-                "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201",
-                "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d",
-                "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9",
-                "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11",
-                "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1",
-                "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de",
-                "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc",
-                "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e",
-                "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1",
-                "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24",
-                "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544",
-                "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f",
-                "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07",
-                "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e",
-                "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13",
-                "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4",
-                "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608",
-                "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117",
-                "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603",
-                "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d",
-                "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256",
-                "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2",
-                "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7",
-                "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273",
-                "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b",
-                "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d",
-                "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b",
-                "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64",
-                "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e",
-                "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd",
-                "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20",
-                "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391",
-                "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e",
-                "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c",
-                "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca",
-                "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a",
-                "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"
-            ],
-            "index": "pypi",
-            "version": "==3.7.2"
-        },
-        "matplotlib-inline": {
-            "hashes": [
-                "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311",
-                "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==0.1.6"
-        },
-        "mdurl": {
-            "hashes": [
-                "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
-                "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.1.2"
-        },
-        "mistune": {
-            "hashes": [
-                "sha256:b9b3e438efbb57c62b5beb5e134dab664800bdf1284a7ee09e8b12b13eb1aac6",
-                "sha256:e912116c13aa0944f9dc530db38eb88f6a77087ab128f49f84a48f4c05ea163c"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.0.1"
-        },
-        "nbclient": {
-            "hashes": [
-                "sha256:25e861299e5303a0477568557c4045eccc7a34c17fc08e7959558707b9ebe548",
-                "sha256:f9b179cd4b2d7bca965f900a2ebf0db4a12ebff2f36a711cb66861e4ae158e55"
-            ],
-            "markers": "python_full_version >= '3.8.0'",
-            "version": "==0.8.0"
-        },
-        "nbconvert": {
-            "hashes": [
-                "sha256:24fcf27efdef2b51d7f090cc5ce5a9b178766a55be513c4ebab08c91899ab550",
-                "sha256:5a445c6794b0791984bc5436608fe2c066cb43c83920c7bc91bde3b765e9a264"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==7.6.0"
-        },
-        "nbformat": {
-            "hashes": [
-                "sha256:8c8fa16d6d05062c26177754bfbfac22de644888e2ef69d27ad2a334cf2576e5",
-                "sha256:e98ebb6120c3efbafdee2a40af2a140cadee90bb06dd69a2a63d9551fcc7f976"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==5.9.0"
-        },
-        "nest-asyncio": {
-            "hashes": [
-                "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8",
-                "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.5.6"
-        },
-        "notebook-shim": {
-            "hashes": [
-                "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7",
-                "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.2.3"
-        },
-        "numpy": {
-            "hashes": [
-                "sha256:012097b5b0d00a11070e8f2e261128c44157a8689f7dedcf35576e525893f4fe",
-                "sha256:0d3fe3dd0506a28493d82dc3cf254be8cd0d26f4008a417385cbf1ae95b54004",
-                "sha256:0def91f8af6ec4bb94c370e38c575855bf1d0be8a8fbfba42ef9c073faf2cf19",
-                "sha256:1a180429394f81c7933634ae49b37b472d343cccb5bb0c4a575ac8bbc433722f",
-                "sha256:1d5d3c68e443c90b38fdf8ef40e60e2538a27548b39b12b73132456847f4b631",
-                "sha256:20e1266411120a4f16fad8efa8e0454d21d00b8c7cee5b5ccad7565d95eb42dd",
-                "sha256:247d3ffdd7775bdf191f848be8d49100495114c82c2bd134e8d5d075fb386a1c",
-                "sha256:35a9527c977b924042170a0887de727cd84ff179e478481404c5dc66b4170009",
-                "sha256:38eb6548bb91c421261b4805dc44def9ca1a6eef6444ce35ad1669c0f1a3fc5d",
-                "sha256:3d7abcdd85aea3e6cdddb59af2350c7ab1ed764397f8eec97a038ad244d2d105",
-                "sha256:41a56b70e8139884eccb2f733c2f7378af06c82304959e174f8e7370af112e09",
-                "sha256:4a90725800caeaa160732d6b31f3f843ebd45d6b5f3eec9e8cc287e30f2805bf",
-                "sha256:6b82655dd8efeea69dbf85d00fca40013d7f503212bc5259056244961268b66e",
-                "sha256:6c6c9261d21e617c6dc5eacba35cb68ec36bb72adcff0dee63f8fbc899362588",
-                "sha256:77d339465dff3eb33c701430bcb9c325b60354698340229e1dff97745e6b3efa",
-                "sha256:791f409064d0a69dd20579345d852c59822c6aa087f23b07b1b4e28ff5880fcb",
-                "sha256:9a3a9f3a61480cc086117b426a8bd86869c213fc4072e606f01c4e4b66eb92bf",
-                "sha256:c1516db588987450b85595586605742879e50dcce923e8973f79529651545b57",
-                "sha256:c40571fe966393b212689aa17e32ed905924120737194b5d5c1b20b9ed0fb171",
-                "sha256:d412c1697c3853c6fc3cb9751b4915859c7afe6a277c2bf00acf287d56c4e625",
-                "sha256:d5154b1a25ec796b1aee12ac1b22f414f94752c5f94832f14d8d6c9ac40bcca6",
-                "sha256:d736b75c3f2cb96843a5c7f8d8ccc414768d34b0a75f466c05f3a739b406f10b",
-                "sha256:e8f6049c4878cb16960fbbfb22105e49d13d752d4d8371b55110941fb3b17800",
-                "sha256:f76aebc3358ade9eacf9bc2bb8ae589863a4f911611694103af05346637df1b7",
-                "sha256:fd67b306320dcadea700a8f79b9e671e607f8696e98ec255915c0c6d6b818503"
-            ],
-            "index": "pypi",
-            "version": "==1.25.1"
-        },
-        "overrides": {
-            "hashes": [
-                "sha256:6187d8710a935d09b0bcef8238301d6ee2569d2ac1ae0ec39a8c7924e27f58ca",
-                "sha256:8b97c6c1e1681b78cbc9424b138d880f0803c2254c5ebaabdde57bb6c62093f2"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==7.3.1"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
-                "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==23.1"
-        },
-        "pandas": {
-            "hashes": [
-                "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682",
-                "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc",
-                "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b",
-                "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089",
-                "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5",
-                "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26",
-                "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210",
-                "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b",
-                "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641",
-                "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd",
-                "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78",
-                "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b",
-                "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e",
-                "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061",
-                "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0",
-                "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e",
-                "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8",
-                "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d",
-                "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0",
-                "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c",
-                "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183",
-                "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df",
-                "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8",
-                "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f",
-                "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"
-            ],
-            "index": "pypi",
-            "version": "==2.0.3"
-        },
-        "pandocfilters": {
-            "hashes": [
-                "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38",
-                "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.5.0"
-        },
-        "parso": {
-            "hashes": [
-                "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0",
-                "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.8.3"
-        },
-        "pickleshare": {
-            "hashes": [
-                "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca",
-                "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"
-            ],
-            "version": "==0.7.5"
-        },
-        "pillow": {
-            "hashes": [
-                "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5",
-                "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530",
-                "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d",
-                "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca",
-                "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891",
-                "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992",
-                "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7",
-                "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3",
-                "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba",
-                "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3",
-                "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3",
-                "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f",
-                "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538",
-                "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3",
-                "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d",
-                "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c",
-                "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017",
-                "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3",
-                "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223",
-                "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e",
-                "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3",
-                "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6",
-                "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640",
-                "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334",
-                "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1",
-                "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba",
-                "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa",
-                "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0",
-                "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396",
-                "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d",
-                "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485",
-                "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf",
-                "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43",
-                "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37",
-                "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2",
-                "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd",
-                "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86",
-                "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967",
-                "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629",
-                "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568",
-                "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed",
-                "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f",
-                "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551",
-                "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3",
-                "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614",
-                "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff",
-                "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d",
-                "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883",
-                "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684",
-                "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0",
-                "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de",
-                "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b",
-                "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3",
-                "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199",
-                "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51",
-                "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==10.0.0"
-        },
-        "platformdirs": {
-            "hashes": [
-                "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c",
-                "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.8.1"
-        },
-        "prometheus-client": {
-            "hashes": [
-                "sha256:9c3b26f1535945e85b8934fb374678d263137b78ef85f305b1156c7c881cd11b",
-                "sha256:a77b708cf083f4d1a3fb3ce5c95b4afa32b9c521ae363354a4a910204ea095ce"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.17.0"
-        },
-        "prompt-toolkit": {
-            "hashes": [
-                "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac",
-                "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"
-            ],
-            "markers": "python_full_version >= '3.7.0'",
-            "version": "==3.0.39"
-        },
-        "psutil": {
-            "hashes": [
-                "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d",
-                "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217",
-                "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4",
-                "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c",
-                "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f",
-                "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da",
-                "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4",
-                "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42",
-                "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5",
-                "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4",
-                "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9",
-                "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f",
-                "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30",
-                "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==5.9.5"
-        },
-        "pure-eval": {
-            "hashes": [
-                "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350",
-                "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"
-            ],
-            "version": "==0.2.2"
-        },
-        "pycparser": {
-            "hashes": [
-                "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
-                "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
-            ],
-            "version": "==2.21"
-        },
-        "pydantic": {
-            "hashes": [
-                "sha256:b802f5245b8576315fe619e5989fd083448fa1258638ef9dac301ca60878396d",
-                "sha256:f5581e0c79b2ec2fa25a9d30d766629811cdda022107fa73d022ab5578873ae3"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.0.2"
-        },
-        "pydantic-core": {
-            "hashes": [
-                "sha256:017700236ea2e7afbef5d3803559c80bd8720306778ebd49268de7ce9972e83e",
-                "sha256:047e782b9918f35ef534ced36f1fd2064f5581229b7a15e4d3177387a6b53134",
-                "sha256:0681472245ef182554208a25d16884c84f1c5a69f14e6169b88932e5da739a1c",
-                "sha256:06ae67547251135a1b3f8dd465797b13146295a3866bc12ddd73f7512787bb7c",
-                "sha256:080a7af828388284a68ad7d3d3eac3bcfff6a580292849aff087e7d556ec42d4",
-                "sha256:0855cf8b760fb40f97f0226cb527c8a94a2ab9d8179628beae20d6939aaeacb0",
-                "sha256:087ddbb754575618a8832ee4ab52fe7eb332f502e2a56088b53dbeb5c4efdf9f",
-                "sha256:0b5d37aedea5963f2097bddbcdb255483191646a52d40d8bb66d61c190fcac91",
-                "sha256:0be2e2812a43205728a06c9d0fd090432cd76a9bb5bff2bfcfdf8b0e27d51851",
-                "sha256:0e5761ce986ec709897b1b965fad9743f301500434bea3cbab2b6e662571580f",
-                "sha256:15eb4cb543ed36f6a4f16e3bee7aa7ed1c3757be95a3f3bbb2b82b9887131e0f",
-                "sha256:1635a37137fafbc6ee0a8c879857e05b30b1aabaa927e653872b71f1501b1502",
-                "sha256:1a5c4475510d1a9cc1458a26cfc21442223e52ce9adb640775c38739315d03c7",
-                "sha256:1c917f7a41d9d09b8b024a5d65cf37e5588ccdb6e610d2df565fb7186b1f3b1c",
-                "sha256:2278ca0b0dfbcfb1e12fa58570916dc260dc72bee5e6e342debf5329d8204688",
-                "sha256:24c3c9180a2d19d640bacc2d00f497a9a1f2abadb2a9ee201b56bb03bc5343bd",
-                "sha256:2575664f0a559a7b951a518f6f34c23cab7190f34f8220b8c8218c4f403147ee",
-                "sha256:2ca2d2d5ab65fb40dd05259965006edcc62a9d9b30102737c0a6f45bcbd254e8",
-                "sha256:2ee3ae58f271851362f6c9b33e4c9f9e866557ec7d8c03dc091e9b5aa5566cec",
-                "sha256:3747a4178139ebf3f19541285b2eb7c886890ca4eb7eec851578c02a13cc1385",
-                "sha256:4663293a36a851a860b1299c50837914269fca127434911297dd39fea9667a01",
-                "sha256:46cd323371aa7e4053010ccdb94063a4273aa9e5dbe97f8a1147faa769de8d8d",
-                "sha256:4938b32c09dbcecbeb652327cb4a449b1ef1a1bf6c8fc2c8241aa6b8f6d63b54",
-                "sha256:4ac140d54da366672f6b91f9a1e8e2d4e7e72720143353501ae886d3fca03272",
-                "sha256:4aff436c23c68449601b3fba7075b4f37ef8fbb893c8c1ed3ef898f090332b1e",
-                "sha256:4e67f9b9dfda2e42b39459cbf99d319ccb90da151e35cead3521975b2afbf673",
-                "sha256:5056afea59651c4e47ec6dadbb77ccae4742c059a3d12bc1c0e393d189d2970d",
-                "sha256:51968887d6bd1eaa7fc7759701ea8ccb470c04654beaa8ede6835b0533f206a9",
-                "sha256:5948af62f323252d56acaec8ebfca5f15933f6b72f8dbe3bf21ee97b2d10e3f0",
-                "sha256:5a014ee88980013d192a718cbb88e8cea20acd3afad69bc6d15672d05a49cdb6",
-                "sha256:60b7239206a2f61ad89c7518adfacb3ccd6662eaa07c5e437317aea2615a1f18",
-                "sha256:682ff9228c838018c47dfa89b3d84cca45f88cacde28807ab8296ec221862af4",
-                "sha256:68a2a767953c707d9575dcf14d8edee7930527ee0141a8bb612c22d1f1059f9a",
-                "sha256:6bf00f56a4468f5b03dadb672a5f1d24aea303d4ccffe8a0f548c9e36017edd3",
-                "sha256:6e3bcb4a9bc209a61ea2aceb7433ce2ece32c7e670b0c06848bf870c9b3e7d87",
-                "sha256:7345b1741bf66a9d8ed0ec291c3eabd534444e139e1ea6db5742ac9fd3be2530",
-                "sha256:74a33aa69d476773230396396afb8e11908f8dafdcfd422e746770599a3f889d",
-                "sha256:7648e48ba263ca0a8a2dc55a60a219c9133fb101ba52c89a14a29fb3d4322ca3",
-                "sha256:7684b5fb906b37e940c5df3f57118f32e033af5e4770e5ae2ae56fbd2fe1a30a",
-                "sha256:76c9c55462740d728b344e3a087775846516c3fee31ec56e2075faa7cfcafcbf",
-                "sha256:7c7ad8958aadfbcd664078002246796ecd5566b64b22f6af4fd1bbcec6bf8f60",
-                "sha256:7ff6bfe63f447a509ed4d368a7f4ba6a7abc03bc4744fc3fb30f2ffab73f3821",
-                "sha256:804cf8f6a859620f8eb754c02f7770f61c3e9c519f8338c331d555b3d6976e3c",
-                "sha256:8125152b03dd91deca5afe5b933a1994b39405adf6be2fe8dce3632319283f85",
-                "sha256:817681d111cb65f07d46496eafec815f48e1aff37713b73135a0a9eb4d3610ab",
-                "sha256:818f5cb1b209ab1295087c45717178f4bbbd2bd7eda421f7a119e7b9b736a3cb",
-                "sha256:82e09f27edab289187dd924d4d93f2a35f21aa969699b2504aa643da7fbfeff9",
-                "sha256:840238c845b0f80777151fef0003088ab91c6f7b3467edaff4932b425c4e3c3f",
-                "sha256:87cff210af3258ca0c829e3ebc849d7981bfde23a99d6cb7a3c17a163b3dbad2",
-                "sha256:8884a1dbfc5cb8c54b48446ca916d4577c1f4d901126091e4ab25d00194e065f",
-                "sha256:88a56f0f6d020b4d17641f4b4d1f9540a536d4146768d059c430e97bdb485fc1",
-                "sha256:8b9a5fc4058d64c9c826684dcdb43891c1b474a4a88dcf8dfc3e1fb5889496f8",
-                "sha256:8c0213891898fa5b404cf3edf4797e3ac7819a0708ea5473fc6432a2aa27c189",
-                "sha256:8e6ce261ccb9a986953c4dce070327e4954f9dd4cd214746dfc70efbc713b6a1",
-                "sha256:8eb4e2b71562375609c66a79f89acd4fe95c5cba23473d04952c8b14b6f908f5",
-                "sha256:90b06bb47e60173d24c7cb79670aa8dd6081797290353b9d3c66d3a23e88eb34",
-                "sha256:94d368af9e6563de6e7170a74710a2cbace7a1e9c8e507d9e3ac34c7065d7ae3",
-                "sha256:9a5fba9168fc27805553760fa8198db46eef83bf52b4e87ebbe1333b823d0e70",
-                "sha256:9b9f8bf1d7008a58fbb6eb334dc6e2f2905400cced8dadb46c4ca28f005a8562",
-                "sha256:a4ae46769d9a7138d58cd190441cac14ce954010a0081f28462ed916c8e55a4f",
-                "sha256:a772c652603855d7180015849d483a1f539351a263bb9b81bfe85193a33ce124",
-                "sha256:a8b9c2cc4c5f8169b943d24be4bd1548fe81c016d704126e3a3124a2fc164885",
-                "sha256:aa39499625239da4ec960cf4fc66b023929b24cc77fb8520289cfdb3c1986428",
-                "sha256:aa54902fa51f7d921ba80923cf1c7ff3dce796a7903300bd8824deb90e357744",
-                "sha256:ac462a28218ea7d592c7ad51b517558f4ac6565a4e53db7a4811eeaf9c9660b0",
-                "sha256:af832edd384755826e494ffdcf1fdda86e4babc42a0b18d342943fb18181040e",
-                "sha256:b1fad38db1744d27061df516e59c5025b09b0a50a337c04e6eebdbddc18951bc",
-                "sha256:b4038869ba1d8fa33863b4b1286ab07e6075a641ae269b865f94d7e10b3e800e",
-                "sha256:b4673d1f29487608d613ebcc5caa99ba15eb58450a7449fb6d800f29d90bebc1",
-                "sha256:b4815720c266e832b20e27a7a5f3772bb09fdedb31a9a34bab7b49d98967ef5a",
-                "sha256:b59a64c367f350873c40a126ffe9184d903d2126c701380b4b55753484df5948",
-                "sha256:b74906e01c7fc938ac889588ef438de812989817095c3c4904721f647d64a4d1",
-                "sha256:b815a769b019dd96be6571096f246b74f63330547e9b30244c51b4a2eb0277fc",
-                "sha256:bad7029fb2251c1ac7d3acdd607e540d40d137a7d43a5e5acdcfdbd38db3fc0a",
-                "sha256:bb471ea8650796060afc99909d9b75da583d317e52f660faf64c45f70b3bf1e2",
-                "sha256:bd95d223de5162811a7b36c73d48eac4fee03b075132f3a1b73c132ce157a60c",
-                "sha256:be3419204952bbe9b72b90008977379c52f99ae1c6e640488de4be783c345d71",
-                "sha256:c0f481aaf0119f77b200e5a5e2799b3e14c015a317eaa948f42263908735cc9f",
-                "sha256:c2d00a96fdf26295c6f25eaf9e4a233f353146a73713cd97a5f5dc6090c3aef2",
-                "sha256:c720e55cef609d50418bdfdfb5c44a76efc020ae7455505788d0113c54c7df55",
-                "sha256:cb854ec52e6e2e05b83d647695f4d913452fdd45a3dfa8233d7dab5967b3908f",
-                "sha256:cbba32fb14e199d0493c6b9c44870dab0a9c37af9f0f729068459d1849279ffd",
-                "sha256:cd62f73830d4715bc643ae39de0bd4fb9c81d6d743530074da91e77a2cccfe67",
-                "sha256:cf92dccca8f66e987f6c4378700447f82b79e86407912ab1ee06b16b82f05120",
-                "sha256:d281a10837d98db997c0247f45d138522c91ce30cf3ae7a6afdb5e709707d360",
-                "sha256:d2c790f0d928b672484eac4f5696dd0b78f3d6d148a641ea196eb49c0875e30a",
-                "sha256:d35d634d9d1ed280c87bc2a7a6217b8787eedc86f368fc2fa1c0c8c78f7d3c93",
-                "sha256:db4564aea8b3cb6cf1e5f3fd80f1ced73a255d492396d1bd8abd688795b34d63",
-                "sha256:dc737506b4a0ba2922a2626fc6d620ce50a46aebd0fe2fbcad1b93bbdd8c7e78",
-                "sha256:e17056390068afd4583d88dcf4d4495764e4e2c7d756464468e0d21abcb8931e",
-                "sha256:e68a404fad8493989d6f07b7b9e066f1d2524d7cb64db2d4e9a84c920032c67f",
-                "sha256:e7fd334b40c5e13a97becfcaba314de0dcc6f7fe21ec8f992139bcc64700e9dc",
-                "sha256:eb4301f009a44bb5db5edfe4e51a8175a4112b566baec07f4af8b1f8cb4649a2",
-                "sha256:ebf583f4d9b52abd15cc59e5f6eeca7e3e9741c6ea62d8711c00ac3acb067875",
-                "sha256:ef71e73a81a4cd7e87c93e8ff0170140fd93ba33b0f61e83da3f55f6e0a84fb4",
-                "sha256:f0eb54b11cd4fe0c6404611eef77086ade03eb1457e92910bbb4f3479efa3f79",
-                "sha256:f2de65752fff248319bcd3b29da24e205fa505607539fcd4acc4037355175b63",
-                "sha256:f5de2d4167fd4bc5ad205fb7297e25867b8e335ca08d64ed7a561d2955a2c32d",
-                "sha256:f7bcdf70c8b6e70be11c78d3c00b80a24cccfb408128f23e91ec3019bed1ecc1",
-                "sha256:fa38a76e832743866aed6b715869757074b06357d1a260163ec26d84974245fe",
-                "sha256:fc909f62325a631e1401dd07dfc386986dbcac15f98c9ff2145d930678a9d25a"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.1.2"
-        },
-        "pygments": {
-            "hashes": [
-                "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c",
-                "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.15.1"
-        },
-        "pymdown-extensions": {
-            "hashes": [
-                "sha256:ae66d84013c5d027ce055693e09a4628b67e9dec5bce05727e45b0918e36f274",
-                "sha256:b44e1093a43b8a975eae17b03c3a77aad4681b3b56fce60ce746dbef1944c8cb"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==10.0.1"
-        },
-        "pyparsing": {
-            "hashes": [
-                "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
-                "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
-            ],
-            "markers": "python_full_version >= '3.6.8'",
-            "version": "==3.0.9"
-        },
-        "python-dateutil": {
-            "hashes": [
-                "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
-                "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==2.8.2"
-        },
-        "python-json-logger": {
-            "hashes": [
-                "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c",
-                "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2.0.7"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
-                "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
-            ],
-            "version": "==2023.3"
-        },
-        "pywin32": {
-            "hashes": [
-                "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d",
-                "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65",
-                "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e",
-                "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b",
-                "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4",
-                "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040",
-                "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a",
-                "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36",
-                "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8",
-                "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e",
-                "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802",
-                "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a",
-                "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407",
-                "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"
-            ],
-            "markers": "sys_platform == 'win32' and platform_python_implementation != 'PyPy'",
-            "version": "==306"
-        },
-        "pywinpty": {
-            "hashes": [
-                "sha256:38cb924f2778b5751ef91a75febd114776b3af0ae411bc667be45dd84fc881d3",
-                "sha256:3c46aef80dd50979aff93de199e4a00a8ee033ba7a03cadf0a91fed45f0c39d7",
-                "sha256:4c7d06ad10f6e92bc850a467f26d98f4f30e73d2fe5926536308c6ae0566bc16",
-                "sha256:7ffbd66310b83e42028fc9df7746118978d94fba8c1ebf15a7c1275fdd80b28a",
-                "sha256:902d79444b29ad1833b8d5c3c9aabdfd428f4f068504430df18074007c8c0de8",
-                "sha256:cdbb5694cf8c7242c2ecfaca35c545d31fa5d5814c3d67a4e628f803f680ebea"
-            ],
-            "markers": "os_name == 'nt'",
-            "version": "==2.0.10"
-        },
-        "pyyaml": {
-            "hashes": [
-                "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
-                "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
-                "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
-                "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
-                "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
-                "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
-                "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
-                "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
-                "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
-                "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
-                "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
-                "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
-                "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
-                "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
-                "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
-                "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
-                "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
-                "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
-                "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
-                "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
-                "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
-                "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
-                "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
-                "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
-                "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
-                "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
-                "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
-                "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
-                "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
-                "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
-                "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
-                "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
-                "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
-                "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
-                "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
-                "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
-                "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
-                "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
-                "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
-                "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==6.0"
-        },
-        "pyzmq": {
-            "hashes": [
-                "sha256:01f06f33e12497dca86353c354461f75275a5ad9eaea181ac0dc1662da8074fa",
-                "sha256:0b6b42f7055bbc562f63f3df3b63e3dd1ebe9727ff0f124c3aa7bcea7b3a00f9",
-                "sha256:0c4fc2741e0513b5d5a12fe200d6785bbcc621f6f2278893a9ca7bed7f2efb7d",
-                "sha256:108c96ebbd573d929740d66e4c3d1bdf31d5cde003b8dc7811a3c8c5b0fc173b",
-                "sha256:13bbe36da3f8aaf2b7ec12696253c0bf6ffe05f4507985a8844a1081db6ec22d",
-                "sha256:154bddda2a351161474b36dba03bf1463377ec226a13458725183e508840df89",
-                "sha256:19d0383b1f18411d137d891cab567de9afa609b214de68b86e20173dc624c101",
-                "sha256:1a6169e69034eaa06823da6a93a7739ff38716142b3596c180363dee729d713d",
-                "sha256:1fc56a0221bdf67cfa94ef2d6ce5513a3d209c3dfd21fed4d4e87eca1822e3a3",
-                "sha256:2a21fec5c3cea45421a19ccbe6250c82f97af4175bc09de4d6dd78fb0cb4c200",
-                "sha256:2b15247c49d8cbea695b321ae5478d47cffd496a2ec5ef47131a9e79ddd7e46c",
-                "sha256:2f5efcc29056dfe95e9c9db0dfbb12b62db9c4ad302f812931b6d21dd04a9119",
-                "sha256:2f666ae327a6899ff560d741681fdcdf4506f990595201ed39b44278c471ad98",
-                "sha256:332616f95eb400492103ab9d542b69d5f0ff628b23129a4bc0a2fd48da6e4e0b",
-                "sha256:33d5c8391a34d56224bccf74f458d82fc6e24b3213fc68165c98b708c7a69325",
-                "sha256:3575699d7fd7c9b2108bc1c6128641a9a825a58577775ada26c02eb29e09c517",
-                "sha256:3830be8826639d801de9053cf86350ed6742c4321ba4236e4b5568528d7bfed7",
-                "sha256:3a522510e3434e12aff80187144c6df556bb06fe6b9d01b2ecfbd2b5bfa5c60c",
-                "sha256:3bed53f7218490c68f0e82a29c92335daa9606216e51c64f37b48eb78f1281f4",
-                "sha256:414b8beec76521358b49170db7b9967d6974bdfc3297f47f7d23edec37329b00",
-                "sha256:442d3efc77ca4d35bee3547a8e08e8d4bb88dadb54a8377014938ba98d2e074a",
-                "sha256:47b915ba666c51391836d7ed9a745926b22c434efa76c119f77bcffa64d2c50c",
-                "sha256:48e5e59e77c1a83162ab3c163fc01cd2eebc5b34560341a67421b09be0891287",
-                "sha256:4a82faae00d1eed4809c2f18b37f15ce39a10a1c58fe48b60ad02875d6e13d80",
-                "sha256:4a983c8694667fd76d793ada77fd36c8317e76aa66eec75be2653cef2ea72883",
-                "sha256:4c2fc7aad520a97d64ffc98190fce6b64152bde57a10c704b337082679e74f67",
-                "sha256:4cb27ef9d3bdc0c195b2dc54fcb8720e18b741624686a81942e14c8b67cc61a6",
-                "sha256:4d67609b37204acad3d566bb7391e0ecc25ef8bae22ff72ebe2ad7ffb7847158",
-                "sha256:5482f08d2c3c42b920e8771ae8932fbaa0a67dff925fc476996ddd8155a170f3",
-                "sha256:5489738a692bc7ee9a0a7765979c8a572520d616d12d949eaffc6e061b82b4d1",
-                "sha256:5693dcc4f163481cf79e98cf2d7995c60e43809e325b77a7748d8024b1b7bcba",
-                "sha256:58416db767787aedbfd57116714aad6c9ce57215ffa1c3758a52403f7c68cff5",
-                "sha256:5873d6a60b778848ce23b6c0ac26c39e48969823882f607516b91fb323ce80e5",
-                "sha256:5af31493663cf76dd36b00dafbc839e83bbca8a0662931e11816d75f36155897",
-                "sha256:5e7fbcafa3ea16d1de1f213c226005fea21ee16ed56134b75b2dede5a2129e62",
-                "sha256:65346f507a815a731092421d0d7d60ed551a80d9b75e8b684307d435a5597425",
-                "sha256:6581e886aec3135964a302a0f5eb68f964869b9efd1dbafdebceaaf2934f8a68",
-                "sha256:69511d604368f3dc58d4be1b0bad99b61ee92b44afe1cd9b7bd8c5e34ea8248a",
-                "sha256:7018289b402ebf2b2c06992813523de61d4ce17bd514c4339d8f27a6f6809492",
-                "sha256:71c7b5896e40720d30cd77a81e62b433b981005bbff0cb2f739e0f8d059b5d99",
-                "sha256:75217e83faea9edbc29516fc90c817bc40c6b21a5771ecb53e868e45594826b0",
-                "sha256:7e23a8c3b6c06de40bdb9e06288180d630b562db8ac199e8cc535af81f90e64b",
-                "sha256:80c41023465d36280e801564a69cbfce8ae85ff79b080e1913f6e90481fb8957",
-                "sha256:831ba20b660b39e39e5ac8603e8193f8fce1ee03a42c84ade89c36a251449d80",
-                "sha256:851fb2fe14036cfc1960d806628b80276af5424db09fe5c91c726890c8e6d943",
-                "sha256:8751f9c1442624da391bbd92bd4b072def6d7702a9390e4479f45c182392ff78",
-                "sha256:8b45d722046fea5a5694cba5d86f21f78f0052b40a4bbbbf60128ac55bfcc7b6",
-                "sha256:8b697774ea8273e3c0460cf0bba16cd85ca6c46dfe8b303211816d68c492e132",
-                "sha256:90146ab578931e0e2826ee39d0c948d0ea72734378f1898939d18bc9c823fcf9",
-                "sha256:9301cf1d7fc1ddf668d0abbe3e227fc9ab15bc036a31c247276012abb921b5ff",
-                "sha256:95bd3a998d8c68b76679f6b18f520904af5204f089beebb7b0301d97704634dd",
-                "sha256:968b0c737797c1809ec602e082cb63e9824ff2329275336bb88bd71591e94a90",
-                "sha256:97d984b1b2f574bc1bb58296d3c0b64b10e95e7026f8716ed6c0b86d4679843f",
-                "sha256:9e68ae9864d260b18f311b68d29134d8776d82e7f5d75ce898b40a88df9db30f",
-                "sha256:adecf6d02b1beab8d7c04bc36f22bb0e4c65a35eb0b4750b91693631d4081c70",
-                "sha256:af56229ea6527a849ac9fb154a059d7e32e77a8cba27e3e62a1e38d8808cb1a5",
-                "sha256:b324fa769577fc2c8f5efcd429cef5acbc17d63fe15ed16d6dcbac2c5eb00849",
-                "sha256:b5a07c4f29bf7cb0164664ef87e4aa25435dcc1f818d29842118b0ac1eb8e2b5",
-                "sha256:bad172aba822444b32eae54c2d5ab18cd7dee9814fd5c7ed026603b8cae2d05f",
-                "sha256:bdca18b94c404af6ae5533cd1bc310c4931f7ac97c148bbfd2cd4bdd62b96253",
-                "sha256:be24a5867b8e3b9dd5c241de359a9a5217698ff616ac2daa47713ba2ebe30ad1",
-                "sha256:be86a26415a8b6af02cd8d782e3a9ae3872140a057f1cadf0133de685185c02b",
-                "sha256:c66b7ff2527e18554030319b1376d81560ca0742c6e0b17ff1ee96624a5f1afd",
-                "sha256:c8398a1b1951aaa330269c35335ae69744be166e67e0ebd9869bdc09426f3871",
-                "sha256:cad9545f5801a125f162d09ec9b724b7ad9b6440151b89645241d0120e119dcc",
-                "sha256:cb6d161ae94fb35bb518b74bb06b7293299c15ba3bc099dccd6a5b7ae589aee3",
-                "sha256:d40682ac60b2a613d36d8d3a0cd14fbdf8e7e0618fbb40aa9fa7b796c9081584",
-                "sha256:d6128d431b8dfa888bf51c22a04d48bcb3d64431caf02b3cb943269f17fd2994",
-                "sha256:dbc466744a2db4b7ca05589f21ae1a35066afada2f803f92369f5877c100ef62",
-                "sha256:ddbef8b53cd16467fdbfa92a712eae46dd066aa19780681a2ce266e88fbc7165",
-                "sha256:e21cc00e4debe8f54c3ed7b9fcca540f46eee12762a9fa56feb8512fd9057161",
-                "sha256:eb52e826d16c09ef87132c6e360e1879c984f19a4f62d8a935345deac43f3c12",
-                "sha256:f0d9e7ba6a815a12c8575ba7887da4b72483e4cfc57179af10c9b937f3f9308f",
-                "sha256:f1e931d9a92f628858a50f5bdffdfcf839aebe388b82f9d2ccd5d22a38a789dc",
-                "sha256:f45808eda8b1d71308c5416ef3abe958f033fdbb356984fabbfc7887bed76b3f",
-                "sha256:f6d39e42a0aa888122d1beb8ec0d4ddfb6c6b45aecb5ba4013c27e2f28657765",
-                "sha256:fc34fdd458ff77a2a00e3c86f899911f6f269d393ca5675842a6e92eea565bae"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==25.1.0"
-        },
-        "reacton": {
-            "hashes": [
-                "sha256:234feb2ea9cd2954a03555bfc1ec32b4d4d75ded12d89a44a72b838a513b1138",
-                "sha256:d23ad5cb0e2a24e197e28839e67f2d63b307a7d672864038c8dc849bafd6f767"
-            ],
-            "version": "==1.7.1"
-        },
-        "referencing": {
-            "hashes": [
-                "sha256:90cb53782d550ba28d2166ef3f55731f38397def8832baac5d45235f1995e35e",
-                "sha256:d3c8f323ee1480095da44d55917cfb8278d73d6b4d5f677e3e40eb21314ac67f"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.29.1"
-        },
-        "requests": {
-            "hashes": [
-                "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
-                "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.31.0"
-        },
-        "rfc3339-validator": {
-            "hashes": [
-                "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b",
-                "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.1.4"
-        },
-        "rfc3986-validator": {
-            "hashes": [
-                "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9",
-                "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.1.1"
-        },
-        "rich": {
-            "hashes": [
-                "sha256:8f87bc7ee54675732fa66a05ebfe489e27264caeeff3728c945d25971b6485ec",
-                "sha256:d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898"
-            ],
-            "markers": "python_full_version >= '3.7.0'",
-            "version": "==13.4.2"
-        },
-        "rich-click": {
-            "hashes": [
-                "sha256:0fcf4d1a09029d79322dd814ab0b2e66ac183633037561881d45abae8a161d95",
-                "sha256:f8ff96693ec6e261d1544e9f7d9a5811c5ef5d74c8adb4978430fc0dac16777e"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.6.1"
-        },
-        "rpds-py": {
-            "hashes": [
-                "sha256:08166467258fd0240a1256fce272f689f2360227ee41c72aeea103e9e4f63d2b",
-                "sha256:083df0fafe199371206111583c686c985dddaf95ab3ee8e7b24f1fda54515d09",
-                "sha256:0da53292edafecba5e1d8c1218f99babf2ed0bf1c791d83c0ab5c29b57223068",
-                "sha256:0eeb2731708207d0fe2619afe6c4dc8cb9798f7de052da891de5f19c0006c315",
-                "sha256:134ec8f14ca7dbc6d9ae34dac632cdd60939fe3734b5d287a69683c037c51acb",
-                "sha256:13e643ce8ad502a0263397362fb887594b49cf84bf518d6038c16f235f2bcea4",
-                "sha256:148b0b38d719c0760e31ce9285a9872972bdd7774969a4154f40c980e5beaca7",
-                "sha256:14f1c356712f66653b777ecd8819804781b23dbbac4eade4366b94944c9e78ad",
-                "sha256:15a90d0ac11b4499171067ae40a220d1ca3cb685ec0acc356d8f3800e07e4cb8",
-                "sha256:1a2edf8173ac0c7a19da21bc68818be1321998528b5e3f748d6ee90c0ba2a1fd",
-                "sha256:1b21575031478609db6dbd1f0465e739fe0e7f424a8e7e87610a6c7f68b4eb16",
-                "sha256:1ee45cd1d84beed6cbebc839fd85c2e70a3a1325c8cfd16b62c96e2ffb565eca",
-                "sha256:220bdcad2d2936f674650d304e20ac480a3ce88a40fe56cd084b5780f1d104d9",
-                "sha256:2418cf17d653d24ffb8b75e81f9f60b7ba1b009a23298a433a4720b2a0a17017",
-                "sha256:2614c2732bf45de5c7f9e9e54e18bc78693fa2f635ae58d2895b7965e470378c",
-                "sha256:2cd3045e7f6375dda64ed7db1c5136826facb0159ea982f77d9cf6125025bd34",
-                "sha256:2eb4b08c45f8f8d8254cdbfacd3fc5d6b415d64487fb30d7380b0d0569837bf1",
-                "sha256:300eb606e6b94a7a26f11c8cc8ee59e295c6649bd927f91e1dbd37a4c89430b6",
-                "sha256:376b8de737401050bd12810003d207e824380be58810c031f10ec563ff6aef3d",
-                "sha256:3793c21494bad1373da517001d0849eea322e9a049a0e4789e50d8d1329df8e7",
-                "sha256:37f7ee4dc86db7af3bac6d2a2cedbecb8e57ce4ed081f6464510e537589f8b1e",
-                "sha256:3816a890a6a9e9f1de250afa12ca71c9a7a62f2b715a29af6aaee3aea112c181",
-                "sha256:3c490204e16bca4f835dba8467869fe7295cdeaa096e4c5a7af97f3454a97991",
-                "sha256:3cc5e5b5514796f45f03a568981971b12a3570f3de2e76114f7dc18d4b60a3c4",
-                "sha256:41c89a366eae49ad9e65ed443a8f94aee762931a1e3723749d72aeac80f5ef2f",
-                "sha256:4a8ca409f1252e1220bf09c57290b76cae2f14723746215a1e0506472ebd7bdf",
-                "sha256:4b519bac7c09444dd85280fd60f28c6dde4389c88dddf4279ba9b630aca3bbbe",
-                "sha256:521fc8861a86ae54359edf53a15a05fabc10593cea7b3357574132f8427a5e5a",
-                "sha256:574868858a7ff6011192c023a5289158ed20e3f3b94b54f97210a773f2f22921",
-                "sha256:5a665f6f1a87614d1c3039baf44109094926dedf785e346d8b0a728e9cabd27a",
-                "sha256:5d1c2bc319428d50b3e0fa6b673ab8cc7fa2755a92898db3a594cbc4eeb6d1f7",
-                "sha256:60e0e86e870350e03b3e25f9b1dd2c6cc72d2b5f24e070249418320a6f9097b7",
-                "sha256:695f642a3a5dbd4ad2ffbbacf784716ecd87f1b7a460843b9ddf965ccaeafff4",
-                "sha256:69d089c026f6a8b9d64a06ff67dc3be196707b699d7f6ca930c25f00cf5e30d8",
-                "sha256:6c6a0225b8501d881b32ebf3f5807a08ad3685b5eb5f0a6bfffd3a6e039b2055",
-                "sha256:70bb9c8004b97b4ef7ae56a2aa56dfaa74734a0987c78e7e85f00004ab9bf2d0",
-                "sha256:73a1e48430f418f0ac3dfd87860e4cc0d33ad6c0f589099a298cb53724db1169",
-                "sha256:7495010b658ec5b52835f21d8c8b1a7e52e194c50f095d4223c0b96c3da704b1",
-                "sha256:7947e6e2c2ad68b1c12ee797d15e5f8d0db36331200b0346871492784083b0c6",
-                "sha256:7b38a9ac96eeb6613e7f312cd0014de64c3f07000e8bf0004ad6ec153bac46f8",
-                "sha256:7d20a8ed227683401cc508e7be58cba90cc97f784ea8b039c8cd01111e6043e0",
-                "sha256:7f29b8c55fd3a2bc48e485e37c4e2df3317f43b5cc6c4b6631c33726f52ffbb3",
-                "sha256:802f42200d8caf7f25bbb2a6464cbd83e69d600151b7e3b49f49a47fa56b0a38",
-                "sha256:805a5f3f05d186c5d50de2e26f765ba7896d0cc1ac5b14ffc36fae36df5d2f10",
-                "sha256:82bb361cae4d0a627006dadd69dc2f36b7ad5dc1367af9d02e296ec565248b5b",
-                "sha256:84eb541a44f7a18f07a6bfc48b95240739e93defe1fdfb4f2a295f37837945d7",
-                "sha256:89c92b74e8bf6f53a6f4995fd52f4bd510c12f103ee62c99e22bc9e05d45583c",
-                "sha256:8c398fda6df361a30935ab4c4bccb7f7a3daef2964ca237f607c90e9f3fdf66f",
-                "sha256:915031002c86a5add7c6fd4beb601b2415e8a1c956590a5f91d825858e92fe6e",
-                "sha256:927d784648211447201d4c6f1babddb7971abad922b32257ab74de2f2750fad0",
-                "sha256:92cf5b3ee60eef41f41e1a2cabca466846fb22f37fc580ffbcb934d1bcab225a",
-                "sha256:93d06cccae15b3836247319eee7b6f1fdcd6c10dabb4e6d350d27bd0bdca2711",
-                "sha256:93d99f957a300d7a4ced41615c45aeb0343bb8f067c42b770b505de67a132346",
-                "sha256:96b293c0498c70162effb13100624c5863797d99df75f2f647438bd10cbf73e4",
-                "sha256:97cab733d303252f7c2f7052bf021a3469d764fc2b65e6dbef5af3cbf89d4892",
-                "sha256:996cc95830de9bc22b183661d95559ec6b3cd900ad7bc9154c4cbf5be0c9b734",
-                "sha256:9a7d20c1cf8d7b3960c5072c265ec47b3f72a0c608a9a6ee0103189b4f28d531",
-                "sha256:9cd57981d9fab04fc74438d82460f057a2419974d69a96b06a440822d693b3c0",
-                "sha256:a11ab0d97be374efd04f640c04fe5c2d3dabc6dfb998954ea946ee3aec97056d",
-                "sha256:a13c8e56c46474cd5958d525ce6a9996727a83d9335684e41f5192c83deb6c58",
-                "sha256:a38b9f526d0d6cbdaa37808c400e3d9f9473ac4ff64d33d9163fd05d243dbd9b",
-                "sha256:a7c6304b894546b5a6bdc0fe15761fa53fe87d28527a7142dae8de3c663853e1",
-                "sha256:ad3bfb44c8840fb4be719dc58e229f435e227fbfbe133dc33f34981ff622a8f8",
-                "sha256:ae40f4a70a1f40939d66ecbaf8e7edc144fded190c4a45898a8cfe19d8fc85ea",
-                "sha256:b01b39ad5411563031ea3977bbbc7324d82b088e802339e6296f082f78f6115c",
-                "sha256:b2e3c4f2a8e3da47f850d7ea0d7d56720f0f091d66add889056098c4b2fd576c",
-                "sha256:b41941583adce4242af003d2a8337b066ba6148ca435f295f31ac6d9e4ea2722",
-                "sha256:b4627520a02fccbd324b33c7a83e5d7906ec746e1083a9ac93c41ac7d15548c7",
-                "sha256:ba9f1d1ebe4b63801977cec7401f2d41e888128ae40b5441270d43140efcad52",
-                "sha256:c03a435d26c3999c2a8642cecad5d1c4d10c961817536af52035f6f4ee2f5dd0",
-                "sha256:c200b30dd573afa83847bed7e3041aa36a8145221bf0cfdfaa62d974d720805c",
-                "sha256:c493365d3fad241d52f096e4995475a60a80f4eba4d3ff89b713bc65c2ca9615",
-                "sha256:c4d42e83ddbf3445e6514f0aff96dca511421ed0392d9977d3990d9f1ba6753c",
-                "sha256:c60528671d9d467009a6ec284582179f6b88651e83367d0ab54cb739021cd7de",
-                "sha256:c72ebc22e70e04126158c46ba56b85372bc4d54d00d296be060b0db1671638a4",
-                "sha256:ccbbd276642788c4376fbe8d4e6c50f0fb4972ce09ecb051509062915891cbf0",
-                "sha256:ceaac0c603bf5ac2f505a78b2dcab78d3e6b706be6596c8364b64cc613d208d2",
-                "sha256:d19db6ba816e7f59fc806c690918da80a7d186f00247048cd833acdab9b4847b",
-                "sha256:d5c191713e98e7c28800233f039a32a42c1a4f9a001a8a0f2448b07391881036",
-                "sha256:d64f9f88d5203274a002b54442cafc9c7a1abff2a238f3e767b70aadf919b451",
-                "sha256:d77dff3a5aa5eedcc3da0ebd10ff8e4969bc9541aa3333a8d41715b429e99f47",
-                "sha256:dd4f16e57c12c0ae17606c53d1b57d8d1c8792efe3f065a37cb3341340599d49",
-                "sha256:e39d7ab0c18ac99955b36cd19f43926450baba21e3250f053e0704d6ffd76873",
-                "sha256:e3d0cd3dff0e7638a7b5390f3a53057c4e347f4ef122ee84ed93fc2fb7ea4aa2",
-                "sha256:e7dfb1cbb895810fa2b892b68153c17716c6abaa22c7dc2b2f6dcf3364932a1c",
-                "sha256:e8e24b210a4deb5a7744971f8f77393005bae7f873568e37dfd9effe808be7f7",
-                "sha256:e9c0683cb35a9b5881b41bc01d5568ffc667910d9dbc632a1fba4e7d59e98773",
-                "sha256:ed41f3f49507936a6fe7003985ea2574daccfef999775525d79eb67344e23767",
-                "sha256:ee744fca8d1ea822480a2a4e7c5f2e1950745477143668f0b523769426060f29",
-                "sha256:f3f1e860be21f3e83011116a65e7310486300e08d9a3028e73e8d13bb6c77292",
-                "sha256:f43ab4cb04bde6109eb2555528a64dfd8a265cc6a9920a67dcbde13ef53a46c8",
-                "sha256:f53f55a8852f0e49b0fc76f2412045d6ad9d5772251dea8f55ea45021616e7d5",
-                "sha256:f59996d0550894affaad8743e97b9b9c98f638b221fac12909210ec3d9294786",
-                "sha256:f96f3f98fbff7af29e9edf9a6584f3c1382e7788783d07ba3721790625caa43e",
-                "sha256:f9adb5664b78fcfcd830000416c8cc69853ef43cb084d645b3f1f0296edd9bae",
-                "sha256:fa326b3505d5784436d9433b7980171ab2375535d93dd63fbcd20af2b5ca1bb6",
-                "sha256:fafc0049add8043ad07ab5382ee80d80ed7e3699847f26c9a5cf4d3714d96a84"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==0.8.10"
-        },
-        "seaborn": {
-            "hashes": [
-                "sha256:374645f36509d0dcab895cba5b47daf0586f77bfe3b36c97c607db7da5be0139",
-                "sha256:ebf15355a4dba46037dfd65b7350f014ceb1f13c05e814eda2c9f5fd731afc08"
-            ],
-            "index": "pypi",
-            "version": "==0.12.2"
-        },
-        "send2trash": {
-            "hashes": [
-                "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679",
-                "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==1.8.2"
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.16.0"
-        },
-        "sniffio": {
-            "hashes": [
-                "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
-                "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.3.0"
-        },
-        "solara": {
-            "hashes": [
-                "sha256:31fe77e3ef14fc5b071a3d5299448b2f8affda96189616813d43a079e8eed60a",
-                "sha256:a74e2439c1ccc5c1cea02158cce4b8af16ea30835ec8f43ab013b6444f5c5af1"
-            ],
-            "index": "pypi",
-            "version": "==1.17.5"
-        },
-        "soupsieve": {
-            "hashes": [
-                "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8",
-                "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.4.1"
-        },
-        "stack-data": {
-            "hashes": [
-                "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815",
-                "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"
-            ],
-            "version": "==0.6.2"
-        },
-        "starlette": {
-            "hashes": [
-                "sha256:7bf3da5e997e796cc202cef2bd3f96a7d9b1e1943203c2fe2b42e020bc658482",
-                "sha256:e58b9fc402c579950260fbb6d57173395c4e62804c40d3ede7e9ef1074f0c579"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.28.0"
-        },
-        "terminado": {
-            "hashes": [
-                "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333",
-                "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.17.1"
-        },
-        "tinycss2": {
-            "hashes": [
-                "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847",
-                "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.2.1"
-        },
-        "tornado": {
-            "hashes": [
-                "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4",
-                "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf",
-                "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d",
-                "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba",
-                "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe",
-                "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411",
-                "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2",
-                "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0",
-                "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c",
-                "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f",
-                "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==6.3.2"
-        },
-        "traitlets": {
-            "hashes": [
-                "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8",
-                "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==5.9.0"
-        },
-        "typing-extensions": {
-            "hashes": [
-                "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
-                "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==4.7.1"
-        },
-        "tzdata": {
-            "hashes": [
-                "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a",
-                "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"
-            ],
-            "markers": "python_version >= '2'",
-            "version": "==2023.3"
-        },
-        "uri-template": {
-            "hashes": [
-                "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7",
-                "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"
-            ],
-            "version": "==1.3.0"
-        },
-        "urllib3": {
-            "hashes": [
-                "sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1",
-                "sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.0.3"
-        },
-        "uvicorn": {
-            "hashes": [
-                "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8",
-                "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.22.0"
-        },
-        "watchdog": {
-            "hashes": [
-                "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a",
-                "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100",
-                "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8",
-                "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc",
-                "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae",
-                "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41",
-                "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0",
-                "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f",
-                "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c",
-                "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9",
-                "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3",
-                "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709",
-                "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83",
-                "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759",
-                "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9",
-                "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3",
-                "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7",
-                "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f",
-                "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346",
-                "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674",
-                "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397",
-                "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96",
-                "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d",
-                "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a",
-                "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64",
-                "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44",
-                "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.0.0"
-        },
-        "watchgod": {
-            "hashes": [
-                "sha256:2f3e8137d98f493ff58af54ea00f4d1433a6afe2ed08ab331a657df468c6bfce",
-                "sha256:cb11ff66657befba94d828e3b622d5fb76f22fbda1376f355f3e6e51e97d9450"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.8.2"
-        },
-        "wcwidth": {
-            "hashes": [
-                "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e",
-                "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"
-            ],
-            "version": "==0.2.6"
-        },
-        "webcolors": {
-            "hashes": [
-                "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf",
-                "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a"
-            ],
-            "version": "==1.13"
-        },
-        "webencodings": {
-            "hashes": [
-                "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
-                "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
-            ],
-            "version": "==0.5.1"
-        },
-        "websocket-client": {
-            "hashes": [
-                "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd",
-                "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.6.1"
-        },
-        "websockets": {
-            "hashes": [
-                "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd",
-                "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f",
-                "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998",
-                "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82",
-                "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788",
-                "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa",
-                "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f",
-                "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4",
-                "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7",
-                "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f",
-                "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd",
-                "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69",
-                "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb",
-                "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b",
-                "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016",
-                "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac",
-                "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4",
-                "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb",
-                "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99",
-                "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e",
-                "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54",
-                "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf",
-                "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007",
-                "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3",
-                "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6",
-                "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86",
-                "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1",
-                "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61",
-                "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11",
-                "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8",
-                "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f",
-                "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931",
-                "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526",
-                "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016",
-                "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae",
-                "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd",
-                "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b",
-                "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311",
-                "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af",
-                "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152",
-                "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288",
-                "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de",
-                "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97",
-                "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d",
-                "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d",
-                "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca",
-                "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0",
-                "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9",
-                "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b",
-                "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e",
-                "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128",
-                "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d",
-                "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c",
-                "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5",
-                "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6",
-                "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b",
-                "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b",
-                "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280",
-                "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c",
-                "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c",
-                "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f",
-                "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20",
-                "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8",
-                "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb",
-                "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602",
-                "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf",
-                "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0",
-                "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74",
-                "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0",
-                "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==11.0.3"
-        },
-        "widgetsnbextension": {
-            "hashes": [
-                "sha256:2e37f0ce9da11651056280c7efe96f2db052fe8fc269508e3724f5cbd6c93018",
-                "sha256:9ec291ba87c2dfad42c3d5b6f68713fa18be1acd7476569516b2431682315c17"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==4.0.8"
-        }
-    },
-    "develop": {}
-}
diff --git a/README.md b/README.md
index 9a85906..66b952e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+[![codecov](https://codecov.io/gh/actuarialopensource/heavylight/graph/badge.svg?token=40623XGSZS)](https://codecov.io/gh/actuarialopensource/heavylight)
+
 # heavylight
 
 A lightweight actuarial modelling framework for Python
diff --git a/developer-setup/environment-notes.md b/developer-setup/environment-notes.md
new file mode 100644
index 0000000..60cb683
--- /dev/null
+++ b/developer-setup/environment-notes.md
@@ -0,0 +1,24 @@
+## Installation
+
+We set up a devcontainer, so that when people open the repo in GitHub codespaces everything is set up. Can run in local devcontainer as well.
+
+Otherwise, you should run this command
+```
+pip install -e .[dev]
+```
+
+## Running tests
+
+The previous install should install optional dependencies `pytest` and `pytest-cov`
+
+```sh
+# test
+pytest
+# with coverage reporting
+pytest --cov=src tests/
+```
+
+In the devcontainer we have act installed, allowing us to verify that pytest runs in the CI/CD pipeline as well.
+```
+act -j build -s "CODECOV_TOKEN=your-codecov-token-abc555-5555"
+```
\ No newline at end of file
diff --git a/docs/_static/custom_css.css b/docs/_static/custom_css.css
new file mode 100644
index 0000000..323eec7
--- /dev/null
+++ b/docs/_static/custom_css.css
@@ -0,0 +1,167 @@
+/* Fix /page#foo going to the top of the viewport and being hidden by the navbar */
+html {
+    scroll-padding-top: 50px;
+}
+
+/* Fit the Twitter handle alongside the GitHub one in the top right. */
+
+div.md-header__source {
+    width: revert;
+    max-width: revert;
+}
+
+a.md-source {
+    display: inline-block;
+}
+
+.md-source__repository {
+    max-width: 100%;
+}
+
+/* Emphasise sections of nav on left hand side */
+
+nav.md-nav {
+  padding-left: 5px;
+}
+
+nav.md-nav--secondary {
+    border-left: revert !important;
+}
+
+.md-nav__title {
+  font-size: 0.9rem;
+}
+
+.md-nav__item--section > .md-nav__link {
+  font-size: 0.9rem;
+}
+
+/* Indent autogenerated documentation */
+
+div.doc-contents {
+  padding-left: 25px;
+  border-left: 4px solid rgba(230, 230, 230);
+}
+
+/* Increase visibility of splitters "---" */
+
+[data-md-color-scheme="default"] .md-typeset hr {
+    border-bottom-color: rgb(0, 0, 0);
+    border-bottom-width: 1pt;
+}
+
+[data-md-color-scheme="slate"] .md-typeset hr {
+    border-bottom-color: rgb(230, 230, 230);
+}
+
+/* More space at the bottom of the page */
+
+.md-main__inner {
+  margin-bottom: 1.5rem;
+}
+
+/* Remove prev/next footer buttons */
+
+.md-footer__inner {
+    display: none;
+}
+
+/* Change font sizes */
+
+html {
+    /* Decrease font size for overall webpage
+       Down from 137.5% which is the Material default */
+    font-size: 110%;
+}
+
+.md-typeset .admonition {
+    /* Increase font size in admonitions */
+    font-size: 100% !important;
+}
+
+.md-typeset details {
+    /* Increase font size in details */
+    font-size: 100% !important;
+}
+
+.md-typeset h1 {
+    font-size: 1.6rem;
+}
+
+.md-typeset h2 {
+    font-size: 1.5rem;
+}
+
+.md-typeset h3 {
+    font-size: 1.3rem;
+}
+
+.md-typeset h4 {
+    font-size: 1.1rem;
+}
+
+.md-typeset h5 {
+    font-size: 0.9rem;
+}
+
+.md-typeset h6 {
+    font-size: 0.8rem;
+}
+
+/* Bugfix: remove the superfluous parts generated when doing:
+
+??? Blah
+
+    ::: library.something
+*/
+
+.md-typeset details .mkdocstrings > h4 {
+    display: none;
+}
+
+.md-typeset details .mkdocstrings > h5 {
+    display: none;
+}
+
+/* Change default colours for <a> tags */
+
+[data-md-color-scheme="default"] {
+    --md-typeset-a-color: rgb(0, 189, 164) !important;
+}
+[data-md-color-scheme="slate"] {
+    --md-typeset-a-color: rgb(0, 189, 164) !important;
+}
+
+/* Highlight functions, classes etc. type signatures. Really helps to make clear where
+   one item ends and another begins. */
+
+[data-md-color-scheme="default"] {
+    --doc-heading-color: #DDD;
+    --doc-heading-border-color: #CCC;
+    --doc-heading-color-alt: #F0F0F0;
+}
+[data-md-color-scheme="slate"] {
+    --doc-heading-color: rgb(25,25,33);
+    --doc-heading-border-color: rgb(25,25,33);
+    --doc-heading-color-alt: rgb(33,33,44);
+    --md-code-bg-color: rgb(38,38,50);
+}
+
+h4.doc-heading {
+    /* NOT var(--md-code-bg-color) as that's not visually distinct from other code blocks.*/
+    background-color: var(--doc-heading-color);
+    border: solid var(--doc-heading-border-color);
+    border-width: 1.5pt;
+    border-radius: 2pt;
+    padding: 0pt 5pt 2pt 5pt;
+}
+h5.doc-heading, h6.heading {
+    background-color: var(--doc-heading-color-alt);
+    border-radius: 2pt;
+    padding: 0pt 5pt 2pt 5pt;
+}
+
+/* Make errors in notebooks have scrolling */
+.output_error > pre {
+    overflow: auto;
+}
\ No newline at end of file
diff --git a/docs/_static/mathjax.js b/docs/_static/mathjax.js
new file mode 100644
index 0000000..e648674
--- /dev/null
+++ b/docs/_static/mathjax.js
@@ -0,0 +1,16 @@
+window.MathJax = {
+    tex: {
+      inlineMath: [["\\(", "\\)"]],
+      displayMath: [["\\[", "\\]"]],
+      processEscapes: true,
+      processEnvironments: true
+    },
+    options: {
+      ignoreHtmlClass: ".*|",
+      processHtmlClass: "arithmatex"
+    }
+  };
+  
+  document$.subscribe(() => { 
+    MathJax.typesetPromise()
+  })
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..7ecb438
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,72 @@
+# Getting started
+
+Heavylight is a lightweight Python library that allows you to run heavy modeling workloads using a familiar recursive syntax.
+
+## Installation
+
+```bash
+pip install heavylight
+```
+
+Requires Python 3.8+
+
+## Quick example
+
+Models are defined by subclassing from `heavylight.Model`
+
+```python
+import numpy as np
+import heavylight
+
+class SimpleModel(heavylight.Model):
+
+    def __init__(self, initial_pols_if: np.ndarray, mortality_rate: float):
+        # storage_function determines what gets stored in the results DataFrame
+        super().__init__(storage_function=lambda results: np.round(np.sum(results), 3)) # customize how you aggregate results
+        self.initial_pols_if = initial_pols_if
+        self.mortality_rate = mortality_rate
+
+    def t(self, t):
+        return t
+
+    def num_pols_if(self, t):
+        if t == 0:
+            return self.initial_pols_if
+        return self.num_pols_if(t - 1) - self.pols_death(t - 1) # causes exponential time complexity if uncached
+        
+    def pols_death(self, t):
+        return self.num_pols_if(t) * self.mortality_rate
+
+    def cashflow(self, t):
+        return self.num_pols_if(t) * 100
+    
+    def v(self, t):
+        if t == 0:
+            return 1
+        return self.v(t - 1) / (1 + self.forward_rate(t))
+    
+    def forward_rate(self, t):
+        return 0.04
+    
+    def pv_cashflow(self, t):
+        return self.cashflow(t) * self.v(t)
+    
+# start with 10 policies and constant mortality rate of .01
+simple_model = SimpleModel(initial_pols_if=np.ones((10,)), mortality_rate=.01)
+# run the model for 5 timesteps
+simple_model.RunModel(proj_len=5)
+# create a dataframe to store results
+results = simple_model.ToDataFrame()
+```
+
+Results are a Pandas dataframe:
+
+|   t |   num_pols_if |   cashflow |   forward_rate |   pols_death |     v |   pv_cashflow |
+|----:|--------------:|-----------:|---------------:|-------------:|------:|--------------:|
+|   0 |        10     |   1000     |           0.04 |        0.1   | 1     |      1000     |
+|   1 |         9.9   |    990     |           0.04 |        0.099 | 0.962 |       951.923 |
+|   2 |         9.801 |    980.1   |           0.04 |        0.098 | 0.925 |       906.158 |
+|   3 |         9.703 |    970.299 |           0.04 |        0.097 | 0.889 |       862.592 |
+|   4 |         9.606 |    960.596 |           0.04 |        0.096 | 0.855 |       821.121 |
+|   5 |         9.51  |    950.99  |           0.04 |        0.095 | 0.822 |       781.645 |
+
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..bdfbeb0
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,55 @@
+# mkdocs.yml
+site_name: heavylight
+
+theme:
+    name: material
+    features:
+        - navigation.sections  # Sections are included in the navigation on the left.
+        - toc.integrate  # Table of contents is integrated on the left; does not appear separately on the right.
+        - header.autohide  # header disappears as you scroll
+    palette:
+        # Light mode / dark mode
+        # We deliberately don't automatically use `media` to check a user's preferences. We default to light mode as
+        # (a) it looks more professional, and (b) is more obvious about the fact that it offers a (dark mode) toggle.
+        - scheme: default
+          primary: white
+          accent: amber
+          toggle:
+             icon: material/weather-night
+             name: Switch to dark mode
+        - scheme: slate
+          primary: black
+          accent: amber
+          toggle:
+             icon: material/weather-sunny
+             name: Switch to light mode
+    icon:
+        repo: fontawesome/brands/github  # GitHub logo in top right
+        logo: "material/flash"  # Equinox logo in top left
+    # favicon: "_static/favicon.png"
+
+markdown_extensions:
+    - pymdownx.highlight:
+        anchor_linenums: true
+        line_spans: __span
+        pygments_lang_class: true
+    - pymdownx.inlinehilite
+    - pymdownx.snippets
+    - pymdownx.superfences
+
+repo_url: https://github.com/lewisfogden/heavylight
+
+strict: true  # Don't allow warnings during the build process
+
+extra_javascript: 
+    # The below three make MathJax work, see https://squidfunk.github.io/mkdocs-material/reference/mathjax/
+    - _static/mathjax.js
+    - https://polyfill.io/v3/polyfill.min.js?features=es6
+    - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
+
+extra_css:
+    - _static/custom_css.css
+
+nav:
+    - 'index.md'
+
diff --git a/pyproject.toml b/pyproject.toml
index 7f333c3..54b8b85 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,8 @@
 requires = ["hatchling"]
 build-backend = "hatchling.build"
 
+[tool.hatch.build.targets.wheel]
+src-dir = "src"
 
 [project]
 name = "heavylight"
@@ -17,6 +19,27 @@ dependencies = [
     "pandas>=1.2",
 ]
 
+[project.optional-dependencies]
+dev = [
+    "pytest==7.4.3",
+    "pytest-cov",
+    "pytest-timeout",
+    "numpy"
+]
+docs = [
+    "mkdocs",
+    "mkdocstrings[python]",
+    "mkdocs-material",
+]
+
+[tool.pytest.ini_options]
+addopts = [
+    "--import-mode=importlib",
+]
+testpaths = [
+    "tests",
+]
+
 classifiers = [
     "Programming Language :: Python :: 3",
     "License :: OSI Approved :: MIT License",
diff --git a/scratch.ipynb b/scratch.ipynb
new file mode 100644
index 0000000..74196be
--- /dev/null
+++ b/scratch.ipynb
@@ -0,0 +1,1195 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Iteration 1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 52,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from inspect import signature\n",
+    "from typing import Callable\n",
+    "\n",
+    "from collections import defaultdict\n",
+    "from dataclasses import dataclass\n",
+    "from typing import Any, Callable, Tuple, Union\n",
+    "\n",
+    "@dataclass(frozen=True)\n",
+    "class FunctionCall:\n",
+    "    func_name: str\n",
+    "    args: tuple\n",
+    "    kwargs: frozenset\n",
+    "\n",
+    "    def __repr__(self):\n",
+    "        if len(self.kwargs) == 0:\n",
+    "            if len(self.args) == 1:\n",
+    "                return f\"{self.func_name}({self.args[0]})\"\n",
+    "            return f\"{self.func_name}{self.args}\"\n",
+    "        return f\"{self.func_name}({self.args}, {self.kwargs})\"\n",
+    "\n",
+    "ArgsHash = Tuple[Tuple, frozenset]\n",
+    "\n",
+    "class CacheGraph:\n",
+    "    def __init__(self):\n",
+    "        self.reset()\n",
+    "\n",
+    "    def reset(self):\n",
+    "        self.stack: list[FunctionCall] = [] # what function is currently being called\n",
+    "        self.caches: defaultdict[str, dict[ArgsHash, Any]] = defaultdict(dict) # Results of function calls\n",
+    "        self.graph: defaultdict[FunctionCall, set[FunctionCall]] = defaultdict(set) # Call graph, graph[caller] = [callee1, callee2, ...]\n",
+    "        # Typically aggregated results for a function at a timestep.\n",
+    "        self.stored_results: defaultdict[str, dict[int, Any]] = defaultdict(dict)\n",
+    "        # What is the last function that needs the result of a function? Used to help in clearing the cache\n",
+    "        self.last_needed_by: dict[FunctionCall, FunctionCall] = {}\n",
+    "        # can_clear[caller] = [callee1, callee2, ...] means that caller can clear the cache of callee1 and callee2\n",
+    "        self.can_clear: dict[FunctionCall, list[FunctionCall]] = defaultdict(list)\n",
+    "\n",
+    "    def check_if_cached(self, function_call: FunctionCall):\n",
+    "        name_in_cache = function_call.func_name in self.caches\n",
+    "        return name_in_cache and (function_call.args, function_call.kwargs) in self.caches[function_call.func_name]\n",
+    "    \n",
+    "    def optimize(self):\n",
+    "        self.can_clear = defaultdict(list)\n",
+    "        for callee, caller in self.last_needed_by.items():\n",
+    "            self.can_clear[caller].append(callee)\n",
+    "\n",
+    "    def __call__(self, storage_func: Union[Callable, None] = None):\n",
+    "        def decorator_factory(func):\n",
+    "            def wrapper(*args, **kwargs):\n",
+    "                frozen_kwargs = frozenset(kwargs.items())\n",
+    "                function_call = FunctionCall(func.__name__, args, frozen_kwargs)\n",
+    "                if self.stack:\n",
+    "                    self.graph[self.stack[-1]].add(function_call)\n",
+    "                    self.last_needed_by[function_call] = self.stack[-1]\n",
+    "                if not self.check_if_cached(function_call):\n",
+    "                    self.stack.append(function_call)\n",
+    "                    result = func(*args, **kwargs)\n",
+    "                    for clearable_call in self.can_clear[function_call]:\n",
+    "                        del self.caches[clearable_call.func_name][(clearable_call.args, clearable_call.kwargs)]\n",
+    "                    self.caches[func.__name__][(args, frozen_kwargs)] = result\n",
+    "                    self._store_result(storage_func, func, args, kwargs, result)\n",
+    "                    self.stack.pop()\n",
+    "                return self.caches[func.__name__][(args, frozen_kwargs)]\n",
+    "            decorator = _Cache(self, wrapper)\n",
+    "            return decorator\n",
+    "        return decorator_factory\n",
+    "    \n",
+    "    def _store_result(self, storage_func: Union[Callable, None], func: Callable, args: tuple, kwargs: dict, raw_result: Any):\n",
+    "        \"\"\"We might want to store an intermediate result\"\"\"\n",
+    "        if storage_func is None:\n",
+    "            return\n",
+    "        # These conditions should not trigger, why we assert and not throw an exception\n",
+    "        assert len(args) == 1 and isinstance(args[0], int)\n",
+    "        assert len(kwargs) == 0\n",
+    "        # store the processed result\n",
+    "        timestep = args[0]\n",
+    "        stored_result = storage_func(raw_result)\n",
+    "        self.stored_results[func.__name__][timestep] = stored_result\n",
+    "\n",
+    "    def size(self):\n",
+    "        return sum(len(cache) for cache in self.caches.values())\n",
+    "\n",
+    "class _Cache:\n",
+    "    def __init__(self, cache_graph: CacheGraph, func: Callable):\n",
+    "        self.cache = cache_graph.caches[func.__name__]\n",
+    "        self._func = func\n",
+    "\n",
+    "    def __setitem__(self, key, value):\n",
+    "        if isinstance(key, int):\n",
+    "            self.cache[((key,), frozenset())] = value\n",
+    "        else:\n",
+    "            self.cache[(key, frozenset())] = value\n",
+    "\n",
+    "    def __repr__(self):\n",
+    "        return f\"<Cache Function: {self._func.__name__} Size: {len(self.cache)}>\"\n",
+    "    \n",
+    "    def __call__(self, *args: Any, **kwds: Any) -> Any:\n",
+    "        return self._func(*args, **kwds)\n",
+    "    \n",
+    "    \n",
+    "def check_if_single_parameter_t(func: Callable):\n",
+    "    sig = signature(func)\n",
+    "    return 't' in sig.parameters and len(sig.parameters) == 1"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 53,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "cg.can_clear=defaultdict(<class 'list'>, {fib(300): [fib(299), fib(298)], fib(299): [fib(297)], fib(298): [fib(296)], fib(297): [fib(295)], fib(296): [fib(294)], fib(295): [fib(293)], fib(294): [fib(292)], fib(293): [fib(291)], fib(292): [fib(290)], fib(291): [fib(289)], fib(290): [fib(288)], fib(289): [fib(287)], fib(288): [fib(286)], fib(287): [fib(285)], fib(286): [fib(284)], fib(285): [fib(283)], fib(284): [fib(282)], fib(283): [fib(281)], fib(282): [fib(280)], fib(281): [fib(279)], fib(280): [fib(278)], fib(279): [fib(277)], fib(278): [fib(276)], fib(277): [fib(275)], fib(276): [fib(274)], fib(275): [fib(273)], fib(274): [fib(272)], fib(273): [fib(271)], fib(272): [fib(270)], fib(271): [fib(269)], fib(270): [fib(268)], fib(269): [fib(267)], fib(268): [fib(266)], fib(267): [fib(265)], fib(266): [fib(264)], fib(265): [fib(263)], fib(264): [fib(262)], fib(263): [fib(261)], fib(262): [fib(260)], fib(261): [fib(259)], fib(260): [fib(258)], fib(259): [fib(257)], fib(258): [fib(256)], fib(257): [fib(255)], fib(256): [fib(254)], fib(255): [fib(253)], fib(254): [fib(252)], fib(253): [fib(251)], fib(252): [fib(250)], fib(251): [fib(249)], fib(250): [fib(248)], fib(249): [fib(247)], fib(248): [fib(246)], fib(247): [fib(245)], fib(246): [fib(244)], fib(245): [fib(243)], fib(244): [fib(242)], fib(243): [fib(241)], fib(242): [fib(240)], fib(241): [fib(239)], fib(240): [fib(238)], fib(239): [fib(237)], fib(238): [fib(236)], fib(237): [fib(235)], fib(236): [fib(234)], fib(235): [fib(233)], fib(234): [fib(232)], fib(233): [fib(231)], fib(232): [fib(230)], fib(231): [fib(229)], fib(230): [fib(228)], fib(229): [fib(227)], fib(228): [fib(226)], fib(227): [fib(225)], fib(226): [fib(224)], fib(225): [fib(223)], fib(224): [fib(222)], fib(223): [fib(221)], fib(222): [fib(220)], fib(221): [fib(219)], fib(220): [fib(218)], fib(219): [fib(217)], fib(218): [fib(216)], fib(217): [fib(215)], fib(216): [fib(214)], fib(215): [fib(213)], fib(214): [fib(212)], fib(213): [fib(211)], fib(212): [fib(210)], fib(211): [fib(209)], fib(210): [fib(208)], fib(209): [fib(207)], fib(208): [fib(206)], fib(207): [fib(205)], fib(206): [fib(204)], fib(205): [fib(203)], fib(204): [fib(202)], fib(203): [fib(201)], fib(202): [fib(200)], fib(201): [fib(199)], fib(200): [fib(198)], fib(199): [fib(197)], fib(198): [fib(196)], fib(197): [fib(195)], fib(196): [fib(194)], fib(195): [fib(193)], fib(194): [fib(192)], fib(193): [fib(191)], fib(192): [fib(190)], fib(191): [fib(189)], fib(190): [fib(188)], fib(189): [fib(187)], fib(188): [fib(186)], fib(187): [fib(185)], fib(186): [fib(184)], fib(185): [fib(183)], fib(184): [fib(182)], fib(183): [fib(181)], fib(182): [fib(180)], fib(181): [fib(179)], fib(180): [fib(178)], fib(179): [fib(177)], fib(178): [fib(176)], fib(177): [fib(175)], fib(176): [fib(174)], fib(175): [fib(173)], fib(174): [fib(172)], fib(173): [fib(171)], fib(172): [fib(170)], fib(171): [fib(169)], fib(170): [fib(168)], fib(169): [fib(167)], fib(168): [fib(166)], fib(167): [fib(165)], fib(166): [fib(164)], fib(165): [fib(163)], fib(164): [fib(162)], fib(163): [fib(161)], fib(162): [fib(160)], fib(161): [fib(159)], fib(160): [fib(158)], fib(159): [fib(157)], fib(158): [fib(156)], fib(157): [fib(155)], fib(156): [fib(154)], fib(155): [fib(153)], fib(154): [fib(152)], fib(153): [fib(151)], fib(152): [fib(150)], fib(151): [fib(149)], fib(150): [fib(148)], fib(149): [fib(147)], fib(148): [fib(146)], fib(147): [fib(145)], fib(146): [fib(144)], fib(145): [fib(143)], fib(144): [fib(142)], fib(143): [fib(141)], fib(142): [fib(140)], fib(141): [fib(139)], fib(140): [fib(138)], fib(139): [fib(137)], fib(138): [fib(136)], fib(137): [fib(135)], fib(136): [fib(134)], fib(135): [fib(133)], fib(134): [fib(132)], fib(133): [fib(131)], fib(132): [fib(130)], fib(131): [fib(129)], fib(130): [fib(128)], fib(129): [fib(127)], fib(128): [fib(126)], fib(127): [fib(125)], fib(126): [fib(124)], fib(125): [fib(123)], fib(124): [fib(122)], fib(123): [fib(121)], fib(122): [fib(120)], fib(121): [fib(119)], fib(120): [fib(118)], fib(119): [fib(117)], fib(118): [fib(116)], fib(117): [fib(115)], fib(116): [fib(114)], fib(115): [fib(113)], fib(114): [fib(112)], fib(113): [fib(111)], fib(112): [fib(110)], fib(111): [fib(109)], fib(110): [fib(108)], fib(109): [fib(107)], fib(108): [fib(106)], fib(107): [fib(105)], fib(106): [fib(104)], fib(105): [fib(103)], fib(104): [fib(102)], fib(103): [fib(101)], fib(102): [fib(100)], fib(101): [fib(99)], fib(100): [fib(98)], fib(99): [fib(97)], fib(98): [fib(96)], fib(97): [fib(95)], fib(96): [fib(94)], fib(95): [fib(93)], fib(94): [fib(92)], fib(93): [fib(91)], fib(92): [fib(90)], fib(91): [fib(89)], fib(90): [fib(88)], fib(89): [fib(87)], fib(88): [fib(86)], fib(87): [fib(85)], fib(86): [fib(84)], fib(85): [fib(83)], fib(84): [fib(82)], fib(83): [fib(81)], fib(82): [fib(80)], fib(81): [fib(79)], fib(80): [fib(78)], fib(79): [fib(77)], fib(78): [fib(76)], fib(77): [fib(75)], fib(76): [fib(74)], fib(75): [fib(73)], fib(74): [fib(72)], fib(73): [fib(71)], fib(72): [fib(70)], fib(71): [fib(69)], fib(70): [fib(68)], fib(69): [fib(67)], fib(68): [fib(66)], fib(67): [fib(65)], fib(66): [fib(64)], fib(65): [fib(63)], fib(64): [fib(62)], fib(63): [fib(61)], fib(62): [fib(60)], fib(61): [fib(59)], fib(60): [fib(58)], fib(59): [fib(57)], fib(58): [fib(56)], fib(57): [fib(55)], fib(56): [fib(54)], fib(55): [fib(53)], fib(54): [fib(52)], fib(53): [fib(51)], fib(52): [fib(50)], fib(51): [fib(49)], fib(50): [fib(48)], fib(49): [fib(47)], fib(48): [fib(46)], fib(47): [fib(45)], fib(46): [fib(44)], fib(45): [fib(43)], fib(44): [fib(42)], fib(43): [fib(41)], fib(42): [fib(40)], fib(41): [fib(39)], fib(40): [fib(38)], fib(39): [fib(37)], fib(38): [fib(36)], fib(37): [fib(35)], fib(36): [fib(34)], fib(35): [fib(33)], fib(34): [fib(32)], fib(33): [fib(31)], fib(32): [fib(30)], fib(31): [fib(29)], fib(30): [fib(28)], fib(29): [fib(27)], fib(28): [fib(26)], fib(27): [fib(25)], fib(26): [fib(24)], fib(25): [fib(23)], fib(24): [fib(22)], fib(23): [fib(21)], fib(22): [fib(20)], fib(21): [fib(19)], fib(20): [fib(18)], fib(19): [fib(17)], fib(18): [fib(16)], fib(17): [fib(15)], fib(16): [fib(14)], fib(15): [fib(13)], fib(14): [fib(12)], fib(13): [fib(11)], fib(12): [fib(10)], fib(11): [fib(9)], fib(10): [fib(8)], fib(9): [fib(7)], fib(8): [fib(6)], fib(7): [fib(5)], fib(6): [fib(4)], fib(5): [fib(3)], fib(4): [fib(2)], fib(3): [fib(1)], fib(2): [fib(0)]})\n",
+      "1\n"
+     ]
+    }
+   ],
+   "source": [
+    "cg = CacheGraph()\n",
+    "@cg()\n",
+    "def fib(n):\n",
+    "    if n <= 1:\n",
+    "        return n\n",
+    "    return fib(n-1) + fib(n-2)\n",
+    "fib(300)\n",
+    "cg.optimize()\n",
+    "print(f\"{cg.can_clear=}\")\n",
+    "can_clear = cg.can_clear\n",
+    "cg.reset()\n",
+    "cg.can_clear = can_clear\n",
+    "fib(300)\n",
+    "print(cg.size())\n",
+    "# cg.caches\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "defaultdict(dict,\n",
+       "            {'fib': {((1,), frozenset()): 1,\n",
+       "              ((0,), frozenset()): 0,\n",
+       "              ((2,), frozenset()): 1,\n",
+       "              ((3,), frozenset()): 2,\n",
+       "              ((4,), frozenset()): 3,\n",
+       "              ((5,), frozenset()): 5,\n",
+       "              ((6,), frozenset()): 8,\n",
+       "              ((7,), frozenset()): 13,\n",
+       "              ((8,), frozenset()): 21,\n",
+       "              ((9,), frozenset()): 34,\n",
+       "              ((10,), frozenset()): 55,\n",
+       "              ((11,), frozenset()): 89,\n",
+       "              ((12,), frozenset()): 144,\n",
+       "              ((13,), frozenset()): 233,\n",
+       "              ((14,), frozenset()): 377,\n",
+       "              ((15,), frozenset()): 610,\n",
+       "              ((16,), frozenset()): 987,\n",
+       "              ((17,), frozenset()): 1597,\n",
+       "              ((18,), frozenset()): 2584,\n",
+       "              ((19,), frozenset()): 4181,\n",
+       "              ((20,), frozenset()): 6765,\n",
+       "              ((21,), frozenset()): 10946,\n",
+       "              ((22,), frozenset()): 17711,\n",
+       "              ((23,), frozenset()): 28657,\n",
+       "              ((24,), frozenset()): 46368,\n",
+       "              ((25,), frozenset()): 75025,\n",
+       "              ((26,), frozenset()): 121393,\n",
+       "              ((27,), frozenset()): 196418,\n",
+       "              ((28,), frozenset()): 317811,\n",
+       "              ((29,), frozenset()): 514229,\n",
+       "              ((30,), frozenset()): 832040,\n",
+       "              ((31,), frozenset()): 1346269,\n",
+       "              ((32,), frozenset()): 2178309,\n",
+       "              ((33,), frozenset()): 3524578,\n",
+       "              ((34,), frozenset()): 5702887,\n",
+       "              ((35,), frozenset()): 9227465,\n",
+       "              ((36,), frozenset()): 14930352,\n",
+       "              ((37,), frozenset()): 24157817,\n",
+       "              ((38,), frozenset()): 39088169,\n",
+       "              ((39,), frozenset()): 63245986,\n",
+       "              ((40,), frozenset()): 102334155,\n",
+       "              ((41,), frozenset()): 165580141,\n",
+       "              ((42,), frozenset()): 267914296,\n",
+       "              ((43,), frozenset()): 433494437,\n",
+       "              ((44,), frozenset()): 701408733,\n",
+       "              ((45,), frozenset()): 1134903170,\n",
+       "              ((46,), frozenset()): 1836311903,\n",
+       "              ((47,), frozenset()): 2971215073,\n",
+       "              ((48,), frozenset()): 4807526976,\n",
+       "              ((49,), frozenset()): 7778742049,\n",
+       "              ((50,), frozenset()): 12586269025,\n",
+       "              ((51,), frozenset()): 20365011074,\n",
+       "              ((52,), frozenset()): 32951280099,\n",
+       "              ((53,), frozenset()): 53316291173,\n",
+       "              ((54,), frozenset()): 86267571272,\n",
+       "              ((55,), frozenset()): 139583862445,\n",
+       "              ((56,), frozenset()): 225851433717,\n",
+       "              ((57,), frozenset()): 365435296162,\n",
+       "              ((58,), frozenset()): 591286729879,\n",
+       "              ((59,), frozenset()): 956722026041,\n",
+       "              ((60,), frozenset()): 1548008755920,\n",
+       "              ((61,), frozenset()): 2504730781961,\n",
+       "              ((62,), frozenset()): 4052739537881,\n",
+       "              ((63,), frozenset()): 6557470319842,\n",
+       "              ((64,), frozenset()): 10610209857723,\n",
+       "              ((65,), frozenset()): 17167680177565,\n",
+       "              ((66,), frozenset()): 27777890035288,\n",
+       "              ((67,), frozenset()): 44945570212853,\n",
+       "              ((68,), frozenset()): 72723460248141,\n",
+       "              ((69,), frozenset()): 117669030460994,\n",
+       "              ((70,), frozenset()): 190392490709135,\n",
+       "              ((71,), frozenset()): 308061521170129,\n",
+       "              ((72,), frozenset()): 498454011879264,\n",
+       "              ((73,), frozenset()): 806515533049393,\n",
+       "              ((74,), frozenset()): 1304969544928657,\n",
+       "              ((75,), frozenset()): 2111485077978050,\n",
+       "              ((76,), frozenset()): 3416454622906707,\n",
+       "              ((77,), frozenset()): 5527939700884757,\n",
+       "              ((78,), frozenset()): 8944394323791464,\n",
+       "              ((79,), frozenset()): 14472334024676221,\n",
+       "              ((80,), frozenset()): 23416728348467685,\n",
+       "              ((81,), frozenset()): 37889062373143906,\n",
+       "              ((82,), frozenset()): 61305790721611591,\n",
+       "              ((83,), frozenset()): 99194853094755497,\n",
+       "              ((84,), frozenset()): 160500643816367088,\n",
+       "              ((85,), frozenset()): 259695496911122585,\n",
+       "              ((86,), frozenset()): 420196140727489673,\n",
+       "              ((87,), frozenset()): 679891637638612258,\n",
+       "              ((88,), frozenset()): 1100087778366101931,\n",
+       "              ((89,), frozenset()): 1779979416004714189,\n",
+       "              ((90,), frozenset()): 2880067194370816120,\n",
+       "              ((91,), frozenset()): 4660046610375530309,\n",
+       "              ((92,), frozenset()): 7540113804746346429,\n",
+       "              ((93,), frozenset()): 12200160415121876738,\n",
+       "              ((94,), frozenset()): 19740274219868223167,\n",
+       "              ((95,), frozenset()): 31940434634990099905,\n",
+       "              ((96,), frozenset()): 51680708854858323072,\n",
+       "              ((97,), frozenset()): 83621143489848422977,\n",
+       "              ((98,), frozenset()): 135301852344706746049,\n",
+       "              ((99,), frozenset()): 218922995834555169026,\n",
+       "              ((100,), frozenset()): 354224848179261915075,\n",
+       "              ((101,), frozenset()): 573147844013817084101,\n",
+       "              ((102,), frozenset()): 927372692193078999176,\n",
+       "              ((103,), frozenset()): 1500520536206896083277,\n",
+       "              ((104,), frozenset()): 2427893228399975082453,\n",
+       "              ((105,), frozenset()): 3928413764606871165730,\n",
+       "              ((106,), frozenset()): 6356306993006846248183,\n",
+       "              ((107,), frozenset()): 10284720757613717413913,\n",
+       "              ((108,), frozenset()): 16641027750620563662096,\n",
+       "              ((109,), frozenset()): 26925748508234281076009,\n",
+       "              ((110,), frozenset()): 43566776258854844738105,\n",
+       "              ((111,), frozenset()): 70492524767089125814114,\n",
+       "              ((112,), frozenset()): 114059301025943970552219,\n",
+       "              ((113,), frozenset()): 184551825793033096366333,\n",
+       "              ((114,), frozenset()): 298611126818977066918552,\n",
+       "              ((115,), frozenset()): 483162952612010163284885,\n",
+       "              ((116,), frozenset()): 781774079430987230203437,\n",
+       "              ((117,), frozenset()): 1264937032042997393488322,\n",
+       "              ((118,), frozenset()): 2046711111473984623691759,\n",
+       "              ((119,), frozenset()): 3311648143516982017180081,\n",
+       "              ((120,), frozenset()): 5358359254990966640871840,\n",
+       "              ((121,), frozenset()): 8670007398507948658051921,\n",
+       "              ((122,), frozenset()): 14028366653498915298923761,\n",
+       "              ((123,), frozenset()): 22698374052006863956975682,\n",
+       "              ((124,), frozenset()): 36726740705505779255899443,\n",
+       "              ((125,), frozenset()): 59425114757512643212875125,\n",
+       "              ((126,), frozenset()): 96151855463018422468774568,\n",
+       "              ((127,), frozenset()): 155576970220531065681649693,\n",
+       "              ((128,), frozenset()): 251728825683549488150424261,\n",
+       "              ((129,), frozenset()): 407305795904080553832073954,\n",
+       "              ((130,), frozenset()): 659034621587630041982498215,\n",
+       "              ((131,), frozenset()): 1066340417491710595814572169,\n",
+       "              ((132,), frozenset()): 1725375039079340637797070384,\n",
+       "              ((133,), frozenset()): 2791715456571051233611642553,\n",
+       "              ((134,), frozenset()): 4517090495650391871408712937,\n",
+       "              ((135,), frozenset()): 7308805952221443105020355490,\n",
+       "              ((136,), frozenset()): 11825896447871834976429068427,\n",
+       "              ((137,), frozenset()): 19134702400093278081449423917,\n",
+       "              ((138,), frozenset()): 30960598847965113057878492344,\n",
+       "              ((139,), frozenset()): 50095301248058391139327916261,\n",
+       "              ((140,), frozenset()): 81055900096023504197206408605,\n",
+       "              ((141,), frozenset()): 131151201344081895336534324866,\n",
+       "              ((142,), frozenset()): 212207101440105399533740733471,\n",
+       "              ((143,), frozenset()): 343358302784187294870275058337,\n",
+       "              ((144,), frozenset()): 555565404224292694404015791808,\n",
+       "              ((145,), frozenset()): 898923707008479989274290850145,\n",
+       "              ((146,), frozenset()): 1454489111232772683678306641953,\n",
+       "              ((147,), frozenset()): 2353412818241252672952597492098,\n",
+       "              ((148,), frozenset()): 3807901929474025356630904134051,\n",
+       "              ((149,), frozenset()): 6161314747715278029583501626149,\n",
+       "              ((150,), frozenset()): 9969216677189303386214405760200,\n",
+       "              ((151,), frozenset()): 16130531424904581415797907386349,\n",
+       "              ((152,), frozenset()): 26099748102093884802012313146549,\n",
+       "              ((153,), frozenset()): 42230279526998466217810220532898,\n",
+       "              ((154,), frozenset()): 68330027629092351019822533679447,\n",
+       "              ((155,), frozenset()): 110560307156090817237632754212345,\n",
+       "              ((156,), frozenset()): 178890334785183168257455287891792,\n",
+       "              ((157,), frozenset()): 289450641941273985495088042104137,\n",
+       "              ((158,), frozenset()): 468340976726457153752543329995929,\n",
+       "              ((159,), frozenset()): 757791618667731139247631372100066,\n",
+       "              ((160,), frozenset()): 1226132595394188293000174702095995,\n",
+       "              ((161,), frozenset()): 1983924214061919432247806074196061,\n",
+       "              ((162,), frozenset()): 3210056809456107725247980776292056,\n",
+       "              ((163,), frozenset()): 5193981023518027157495786850488117,\n",
+       "              ((164,), frozenset()): 8404037832974134882743767626780173,\n",
+       "              ((165,), frozenset()): 13598018856492162040239554477268290,\n",
+       "              ((166,), frozenset()): 22002056689466296922983322104048463,\n",
+       "              ((167,), frozenset()): 35600075545958458963222876581316753,\n",
+       "              ((168,), frozenset()): 57602132235424755886206198685365216,\n",
+       "              ((169,), frozenset()): 93202207781383214849429075266681969,\n",
+       "              ((170,), frozenset()): 150804340016807970735635273952047185,\n",
+       "              ((171,), frozenset()): 244006547798191185585064349218729154,\n",
+       "              ((172,), frozenset()): 394810887814999156320699623170776339,\n",
+       "              ((173,), frozenset()): 638817435613190341905763972389505493,\n",
+       "              ((174,), frozenset()): 1033628323428189498226463595560281832,\n",
+       "              ((175,), frozenset()): 1672445759041379840132227567949787325,\n",
+       "              ((176,), frozenset()): 2706074082469569338358691163510069157,\n",
+       "              ((177,), frozenset()): 4378519841510949178490918731459856482,\n",
+       "              ((178,), frozenset()): 7084593923980518516849609894969925639,\n",
+       "              ((179,), frozenset()): 11463113765491467695340528626429782121,\n",
+       "              ((180,), frozenset()): 18547707689471986212190138521399707760,\n",
+       "              ((181,), frozenset()): 30010821454963453907530667147829489881,\n",
+       "              ((182,), frozenset()): 48558529144435440119720805669229197641,\n",
+       "              ((183,), frozenset()): 78569350599398894027251472817058687522,\n",
+       "              ((184,), frozenset()): 127127879743834334146972278486287885163,\n",
+       "              ((185,), frozenset()): 205697230343233228174223751303346572685,\n",
+       "              ((186,), frozenset()): 332825110087067562321196029789634457848,\n",
+       "              ((187,), frozenset()): 538522340430300790495419781092981030533,\n",
+       "              ((188,), frozenset()): 871347450517368352816615810882615488381,\n",
+       "              ((189,), frozenset()): 1409869790947669143312035591975596518914,\n",
+       "              ((190,), frozenset()): 2281217241465037496128651402858212007295,\n",
+       "              ((191,), frozenset()): 3691087032412706639440686994833808526209,\n",
+       "              ((192,), frozenset()): 5972304273877744135569338397692020533504,\n",
+       "              ((193,), frozenset()): 9663391306290450775010025392525829059713,\n",
+       "              ((194,), frozenset()): 15635695580168194910579363790217849593217,\n",
+       "              ((195,), frozenset()): 25299086886458645685589389182743678652930,\n",
+       "              ((196,), frozenset()): 40934782466626840596168752972961528246147,\n",
+       "              ((197,), frozenset()): 66233869353085486281758142155705206899077,\n",
+       "              ((198,),\n",
+       "               frozenset()): 107168651819712326877926895128666735145224,\n",
+       "              ((199,),\n",
+       "               frozenset()): 173402521172797813159685037284371942044301,\n",
+       "              ((200,),\n",
+       "               frozenset()): 280571172992510140037611932413038677189525,\n",
+       "              ((201,),\n",
+       "               frozenset()): 453973694165307953197296969697410619233826,\n",
+       "              ((202,),\n",
+       "               frozenset()): 734544867157818093234908902110449296423351,\n",
+       "              ((203,),\n",
+       "               frozenset()): 1188518561323126046432205871807859915657177,\n",
+       "              ((204,),\n",
+       "               frozenset()): 1923063428480944139667114773918309212080528,\n",
+       "              ((205,),\n",
+       "               frozenset()): 3111581989804070186099320645726169127737705,\n",
+       "              ((206,),\n",
+       "               frozenset()): 5034645418285014325766435419644478339818233,\n",
+       "              ((207,),\n",
+       "               frozenset()): 8146227408089084511865756065370647467555938,\n",
+       "              ((208,),\n",
+       "               frozenset()): 13180872826374098837632191485015125807374171,\n",
+       "              ((209,),\n",
+       "               frozenset()): 21327100234463183349497947550385773274930109,\n",
+       "              ((210,),\n",
+       "               frozenset()): 34507973060837282187130139035400899082304280,\n",
+       "              ((211,),\n",
+       "               frozenset()): 55835073295300465536628086585786672357234389,\n",
+       "              ((212,),\n",
+       "               frozenset()): 90343046356137747723758225621187571439538669,\n",
+       "              ((213,),\n",
+       "               frozenset()): 146178119651438213260386312206974243796773058,\n",
+       "              ((214,),\n",
+       "               frozenset()): 236521166007575960984144537828161815236311727,\n",
+       "              ((215,),\n",
+       "               frozenset()): 382699285659014174244530850035136059033084785,\n",
+       "              ((216,),\n",
+       "               frozenset()): 619220451666590135228675387863297874269396512,\n",
+       "              ((217,),\n",
+       "               frozenset()): 1001919737325604309473206237898433933302481297,\n",
+       "              ((218,),\n",
+       "               frozenset()): 1621140188992194444701881625761731807571877809,\n",
+       "              ((219,),\n",
+       "               frozenset()): 2623059926317798754175087863660165740874359106,\n",
+       "              ((220,),\n",
+       "               frozenset()): 4244200115309993198876969489421897548446236915,\n",
+       "              ((221,),\n",
+       "               frozenset()): 6867260041627791953052057353082063289320596021,\n",
+       "              ((222,),\n",
+       "               frozenset()): 11111460156937785151929026842503960837766832936,\n",
+       "              ((223,),\n",
+       "               frozenset()): 17978720198565577104981084195586024127087428957,\n",
+       "              ((224,),\n",
+       "               frozenset()): 29090180355503362256910111038089984964854261893,\n",
+       "              ((225,),\n",
+       "               frozenset()): 47068900554068939361891195233676009091941690850,\n",
+       "              ((226,),\n",
+       "               frozenset()): 76159080909572301618801306271765994056795952743,\n",
+       "              ((227,),\n",
+       "               frozenset()): 123227981463641240980692501505442003148737643593,\n",
+       "              ((228,),\n",
+       "               frozenset()): 199387062373213542599493807777207997205533596336,\n",
+       "              ((229,),\n",
+       "               frozenset()): 322615043836854783580186309282650000354271239929,\n",
+       "              ((230,),\n",
+       "               frozenset()): 522002106210068326179680117059857997559804836265,\n",
+       "              ((231,),\n",
+       "               frozenset()): 844617150046923109759866426342507997914076076194,\n",
+       "              ((232,),\n",
+       "               frozenset()): 1366619256256991435939546543402365995473880912459,\n",
+       "              ((233,),\n",
+       "               frozenset()): 2211236406303914545699412969744873993387956988653,\n",
+       "              ((234,),\n",
+       "               frozenset()): 3577855662560905981638959513147239988861837901112,\n",
+       "              ((235,),\n",
+       "               frozenset()): 5789092068864820527338372482892113982249794889765,\n",
+       "              ((236,),\n",
+       "               frozenset()): 9366947731425726508977331996039353971111632790877,\n",
+       "              ((237,),\n",
+       "               frozenset()): 15156039800290547036315704478931467953361427680642,\n",
+       "              ((238,),\n",
+       "               frozenset()): 24522987531716273545293036474970821924473060471519,\n",
+       "              ((239,),\n",
+       "               frozenset()): 39679027332006820581608740953902289877834488152161,\n",
+       "              ((240,),\n",
+       "               frozenset()): 64202014863723094126901777428873111802307548623680,\n",
+       "              ((241,),\n",
+       "               frozenset()): 103881042195729914708510518382775401680142036775841,\n",
+       "              ((242,),\n",
+       "               frozenset()): 168083057059453008835412295811648513482449585399521,\n",
+       "              ((243,),\n",
+       "               frozenset()): 271964099255182923543922814194423915162591622175362,\n",
+       "              ((244,),\n",
+       "               frozenset()): 440047156314635932379335110006072428645041207574883,\n",
+       "              ((245,),\n",
+       "               frozenset()): 712011255569818855923257924200496343807632829750245,\n",
+       "              ((246,),\n",
+       "               frozenset()): 1152058411884454788302593034206568772452674037325128,\n",
+       "              ((247,),\n",
+       "               frozenset()): 1864069667454273644225850958407065116260306867075373,\n",
+       "              ((248,),\n",
+       "               frozenset()): 3016128079338728432528443992613633888712980904400501,\n",
+       "              ((249,),\n",
+       "               frozenset()): 4880197746793002076754294951020699004973287771475874,\n",
+       "              ((250,),\n",
+       "               frozenset()): 7896325826131730509282738943634332893686268675876375,\n",
+       "              ((251,),\n",
+       "               frozenset()): 12776523572924732586037033894655031898659556447352249,\n",
+       "              ((252,),\n",
+       "               frozenset()): 20672849399056463095319772838289364792345825123228624,\n",
+       "              ((253,),\n",
+       "               frozenset()): 33449372971981195681356806732944396691005381570580873,\n",
+       "              ((254,),\n",
+       "               frozenset()): 54122222371037658776676579571233761483351206693809497,\n",
+       "              ((255,),\n",
+       "               frozenset()): 87571595343018854458033386304178158174356588264390370,\n",
+       "              ((256,),\n",
+       "               frozenset()): 141693817714056513234709965875411919657707794958199867,\n",
+       "              ((257,),\n",
+       "               frozenset()): 229265413057075367692743352179590077832064383222590237,\n",
+       "              ((258,),\n",
+       "               frozenset()): 370959230771131880927453318055001997489772178180790104,\n",
+       "              ((259,),\n",
+       "               frozenset()): 600224643828207248620196670234592075321836561403380341,\n",
+       "              ((260,),\n",
+       "               frozenset()): 971183874599339129547649988289594072811608739584170445,\n",
+       "              ((261,),\n",
+       "               frozenset()): 1571408518427546378167846658524186148133445300987550786,\n",
+       "              ((262,),\n",
+       "               frozenset()): 2542592393026885507715496646813780220945054040571721231,\n",
+       "              ((263,),\n",
+       "               frozenset()): 4114000911454431885883343305337966369078499341559272017,\n",
+       "              ((264,),\n",
+       "               frozenset()): 6656593304481317393598839952151746590023553382130993248,\n",
+       "              ((265,),\n",
+       "               frozenset()): 10770594215935749279482183257489712959102052723690265265,\n",
+       "              ((266,),\n",
+       "               frozenset()): 17427187520417066673081023209641459549125606105821258513,\n",
+       "              ((267,),\n",
+       "               frozenset()): 28197781736352815952563206467131172508227658829511523778,\n",
+       "              ((268,),\n",
+       "               frozenset()): 45624969256769882625644229676772632057353264935332782291,\n",
+       "              ((269,),\n",
+       "               frozenset()): 73822750993122698578207436143903804565580923764844306069,\n",
+       "              ((270,),\n",
+       "               frozenset()): 119447720249892581203851665820676436622934188700177088360,\n",
+       "              ((271,),\n",
+       "               frozenset()): 193270471243015279782059101964580241188515112465021394429,\n",
+       "              ((272,),\n",
+       "               frozenset()): 312718191492907860985910767785256677811449301165198482789,\n",
+       "              ((273,),\n",
+       "               frozenset()): 505988662735923140767969869749836918999964413630219877218,\n",
+       "              ((274,),\n",
+       "               frozenset()): 818706854228831001753880637535093596811413714795418360007,\n",
+       "              ((275,),\n",
+       "               frozenset()): 1324695516964754142521850507284930515811378128425638237225,\n",
+       "              ((276,),\n",
+       "               frozenset()): 2143402371193585144275731144820024112622791843221056597232,\n",
+       "              ((277,),\n",
+       "               frozenset()): 3468097888158339286797581652104954628434169971646694834457,\n",
+       "              ((278,),\n",
+       "               frozenset()): 5611500259351924431073312796924978741056961814867751431689,\n",
+       "              ((279,),\n",
+       "               frozenset()): 9079598147510263717870894449029933369491131786514446266146,\n",
+       "              ((280,),\n",
+       "               frozenset()): 14691098406862188148944207245954912110548093601382197697835,\n",
+       "              ((281,),\n",
+       "               frozenset()): 23770696554372451866815101694984845480039225387896643963981,\n",
+       "              ((282,),\n",
+       "               frozenset()): 38461794961234640015759308940939757590587318989278841661816,\n",
+       "              ((283,),\n",
+       "               frozenset()): 62232491515607091882574410635924603070626544377175485625797,\n",
+       "              ((284,),\n",
+       "               frozenset()): 100694286476841731898333719576864360661213863366454327287613,\n",
+       "              ((285,),\n",
+       "               frozenset()): 162926777992448823780908130212788963731840407743629812913410,\n",
+       "              ((286,),\n",
+       "               frozenset()): 263621064469290555679241849789653324393054271110084140201023,\n",
+       "              ((287,),\n",
+       "               frozenset()): 426547842461739379460149980002442288124894678853713953114433,\n",
+       "              ((288,),\n",
+       "               frozenset()): 690168906931029935139391829792095612517948949963798093315456,\n",
+       "              ((289,),\n",
+       "               frozenset()): 1116716749392769314599541809794537900642843628817512046429889,\n",
+       "              ((290,),\n",
+       "               frozenset()): 1806885656323799249738933639586633513160792578781310139745345,\n",
+       "              ((291,),\n",
+       "               frozenset()): 2923602405716568564338475449381171413803636207598822186175234,\n",
+       "              ((292,),\n",
+       "               frozenset()): 4730488062040367814077409088967804926964428786380132325920579,\n",
+       "              ((293,),\n",
+       "               frozenset()): 7654090467756936378415884538348976340768064993978954512095813,\n",
+       "              ((294,),\n",
+       "               frozenset()): 12384578529797304192493293627316781267732493780359086838016392,\n",
+       "              ((295,),\n",
+       "               frozenset()): 20038668997554240570909178165665757608500558774338041350112205,\n",
+       "              ((296,),\n",
+       "               frozenset()): 32423247527351544763402471792982538876233052554697128188128597,\n",
+       "              ((297,),\n",
+       "               frozenset()): 52461916524905785334311649958648296484733611329035169538240802,\n",
+       "              ((298,),\n",
+       "               frozenset()): 84885164052257330097714121751630835360966663883732297726369399,\n",
+       "              ((299,),\n",
+       "               frozenset()): 137347080577163115432025771710279131845700275212767467264610201,\n",
+       "              ((300,),\n",
+       "               frozenset()): 222232244629420445529739893461909967206666939096499764990979600,\n",
+       "              ((301,),\n",
+       "               frozenset()): 359579325206583560961765665172189099052367214309267232255589801,\n",
+       "              ((302,),\n",
+       "               frozenset()): 581811569836004006491505558634099066259034153405766997246569401,\n",
+       "              ((303,),\n",
+       "               frozenset()): 941390895042587567453271223806288165311401367715034229502159202,\n",
+       "              ((304,),\n",
+       "               frozenset()): 1523202464878591573944776782440387231570435521120801226748728603,\n",
+       "              ((305,),\n",
+       "               frozenset()): 2464593359921179141398048006246675396881836888835835456250887805,\n",
+       "              ((306,),\n",
+       "               frozenset()): 3987795824799770715342824788687062628452272409956636682999616408,\n",
+       "              ((307,),\n",
+       "               frozenset()): 6452389184720949856740872794933738025334109298792472139250504213,\n",
+       "              ((308,),\n",
+       "               frozenset()): 10440185009520720572083697583620800653786381708749108822250120621,\n",
+       "              ((309,),\n",
+       "               frozenset()): 16892574194241670428824570378554538679120491007541580961500624834,\n",
+       "              ((310,),\n",
+       "               frozenset()): 27332759203762391000908267962175339332906872716290689783750745455,\n",
+       "              ((311,),\n",
+       "               frozenset()): 44225333398004061429732838340729878012027363723832270745251370289,\n",
+       "              ((312,),\n",
+       "               frozenset()): 71558092601766452430641106302905217344934236440122960529002115744,\n",
+       "              ((313,),\n",
+       "               frozenset()): 115783425999770513860373944643635095356961600163955231274253486033,\n",
+       "              ((314,),\n",
+       "               frozenset()): 187341518601536966291015050946540312701895836604078191803255601777,\n",
+       "              ((315,),\n",
+       "               frozenset()): 303124944601307480151388995590175408058857436768033423077509087810,\n",
+       "              ((316,),\n",
+       "               frozenset()): 490466463202844446442404046536715720760753273372111614880764689587,\n",
+       "              ((317,),\n",
+       "               frozenset()): 793591407804151926593793042126891128819610710140145037958273777397,\n",
+       "              ((318,),\n",
+       "               frozenset()): 1284057871006996373036197088663606849580363983512256652839038466984,\n",
+       "              ((319,),\n",
+       "               frozenset()): 2077649278811148299629990130790497978399974693652401690797312244381,\n",
+       "              ((320,),\n",
+       "               frozenset()): 3361707149818144672666187219454104827980338677164658343636350711365,\n",
+       "              ((321,),\n",
+       "               frozenset()): 5439356428629292972296177350244602806380313370817060034433662955746,\n",
+       "              ((322,),\n",
+       "               frozenset()): 8801063578447437644962364569698707634360652047981718378070013667111,\n",
+       "              ((323,),\n",
+       "               frozenset()): 14240420007076730617258541919943310440740965418798778412503676622857,\n",
+       "              ((324,),\n",
+       "               frozenset()): 23041483585524168262220906489642018075101617466780496790573690289968,\n",
+       "              ((325,),\n",
+       "               frozenset()): 37281903592600898879479448409585328515842582885579275203077366912825,\n",
+       "              ((326,),\n",
+       "               frozenset()): 60323387178125067141700354899227346590944200352359771993651057202793,\n",
+       "              ((327,),\n",
+       "               frozenset()): 97605290770725966021179803308812675106786783237939047196728424115618,\n",
+       "              ((328,),\n",
+       "               frozenset()): 157928677948851033162880158208040021697730983590298819190379481318411,\n",
+       "              ((329,),\n",
+       "               frozenset()): 255533968719576999184059961516852696804517766828237866387107905434029,\n",
+       "              ((330,),\n",
+       "               frozenset()): 413462646668428032346940119724892718502248750418536685577487386752440,\n",
+       "              ((331,),\n",
+       "               frozenset()): 668996615388005031531000081241745415306766517246774551964595292186469,\n",
+       "              ((332,),\n",
+       "               frozenset()): 1082459262056433063877940200966638133809015267665311237542082678938909,\n",
+       "              ((333,),\n",
+       "               frozenset()): 1751455877444438095408940282208383549115781784912085789506677971125378,\n",
+       "              ((334,),\n",
+       "               frozenset()): 2833915139500871159286880483175021682924797052577397027048760650064287,\n",
+       "              ((335,),\n",
+       "               frozenset()): 4585371016945309254695820765383405232040578837489482816555438621189665,\n",
+       "              ((336,),\n",
+       "               frozenset()): 7419286156446180413982701248558426914965375890066879843604199271253952,\n",
+       "              ((337,),\n",
+       "               frozenset()): 12004657173391489668678522013941832147005954727556362660159637892443617,\n",
+       "              ((338,),\n",
+       "               frozenset()): 19423943329837670082661223262500259061971330617623242503763837163697569,\n",
+       "              ((339,),\n",
+       "               frozenset()): 31428600503229159751339745276442091208977285345179605163923475056141186,\n",
+       "              ((340,),\n",
+       "               frozenset()): 50852543833066829834000968538942350270948615962802847667687312219838755,\n",
+       "              ((341,),\n",
+       "               frozenset()): 82281144336295989585340713815384441479925901307982452831610787275979941,\n",
+       "              ((342,),\n",
+       "               frozenset()): 133133688169362819419341682354326791750874517270785300499298099495818696,\n",
+       "              ((343,),\n",
+       "               frozenset()): 215414832505658809004682396169711233230800418578767753330908886771798637,\n",
+       "              ((344,),\n",
+       "               frozenset()): 348548520675021628424024078524038024981674935849553053830206986267617333,\n",
+       "              ((345,),\n",
+       "               frozenset()): 563963353180680437428706474693749258212475354428320807161115873039415970,\n",
+       "              ((346,),\n",
+       "               frozenset()): 912511873855702065852730553217787283194150290277873860991322859307033303,\n",
+       "              ((347,),\n",
+       "               frozenset()): 1476475227036382503281437027911536541406625644706194668152438732346449273,\n",
+       "              ((348,),\n",
+       "               frozenset()): 2388987100892084569134167581129323824600775934984068529143761591653482576,\n",
+       "              ((349,),\n",
+       "               frozenset()): 3865462327928467072415604609040860366007401579690263197296200323999931849,\n",
+       "              ((350,),\n",
+       "               frozenset()): 6254449428820551641549772190170184190608177514674331726439961915653414425,\n",
+       "              ((351,),\n",
+       "               frozenset()): 10119911756749018713965376799211044556615579094364594923736162239653346274,\n",
+       "              ((352,),\n",
+       "               frozenset()): 16374361185569570355515148989381228747223756609038926650176124155306760699,\n",
+       "              ((353,),\n",
+       "               frozenset()): 26494272942318589069480525788592273303839335703403521573912286394960106973,\n",
+       "              ((354,),\n",
+       "               frozenset()): 42868634127888159424995674777973502051063092312442448224088410550266867672,\n",
+       "              ((355,),\n",
+       "               frozenset()): 69362907070206748494476200566565775354902428015845969798000696945226974645,\n",
+       "              ((356,),\n",
+       "               frozenset()): 112231541198094907919471875344539277405965520328288418022089107495493842317,\n",
+       "              ((357,),\n",
+       "               frozenset()): 181594448268301656413948075911105052760867948344134387820089804440720816962,\n",
+       "              ((358,),\n",
+       "               frozenset()): 293825989466396564333419951255644330166833468672422805842178911936214659279,\n",
+       "              ((359,),\n",
+       "               frozenset()): 475420437734698220747368027166749382927701417016557193662268716376935476241,\n",
+       "              ((360,),\n",
+       "               frozenset()): 769246427201094785080787978422393713094534885688979999504447628313150135520,\n",
+       "              ((361,),\n",
+       "               frozenset()): 1244666864935793005828156005589143096022236302705537193166716344690085611761,\n",
+       "              ((362,),\n",
+       "               frozenset()): 2013913292136887790908943984011536809116771188394517192671163973003235747281,\n",
+       "              ((363,),\n",
+       "               frozenset()): 3258580157072680796737099989600679905139007491100054385837880317693321359042,\n",
+       "              ((364,),\n",
+       "               frozenset()): 5272493449209568587646043973612216714255778679494571578509044290696557106323,\n",
+       "              ((365,),\n",
+       "               frozenset()): 8531073606282249384383143963212896619394786170594625964346924608389878465365,\n",
+       "              ((366,),\n",
+       "               frozenset()): 13803567055491817972029187936825113333650564850089197542855968899086435571688,\n",
+       "              ((367,),\n",
+       "               frozenset()): 22334640661774067356412331900038009953045351020683823507202893507476314037053,\n",
+       "              ((368,),\n",
+       "               frozenset()): 36138207717265885328441519836863123286695915870773021050058862406562749608741,\n",
+       "              ((369,),\n",
+       "               frozenset()): 58472848379039952684853851736901133239741266891456844557261755914039063645794,\n",
+       "              ((370,),\n",
+       "               frozenset()): 94611056096305838013295371573764256526437182762229865607320618320601813254535,\n",
+       "              ((371,),\n",
+       "               frozenset()): 153083904475345790698149223310665389766178449653686710164582374234640876900329,\n",
+       "              ((372,),\n",
+       "               frozenset()): 247694960571651628711444594884429646292615632415916575771902992555242690154864,\n",
+       "              ((373,),\n",
+       "               frozenset()): 400778865046997419409593818195095036058794082069603285936485366789883567055193,\n",
+       "              ((374,),\n",
+       "               frozenset()): 648473825618649048121038413079524682351409714485519861708388359345126257210057,\n",
+       "              ((375,),\n",
+       "               frozenset()): 1049252690665646467530632231274619718410203796555123147644873726135009824265250,\n",
+       "              ((376,),\n",
+       "               frozenset()): 1697726516284295515651670644354144400761613511040643009353262085480136081475307,\n",
+       "              ((377,),\n",
+       "               frozenset()): 2746979206949941983182302875628764119171817307595766156998135811615145905740557,\n",
+       "              ((378,),\n",
+       "               frozenset()): 4444705723234237498833973519982908519933430818636409166351397897095281987215864,\n",
+       "              ((379,),\n",
+       "               frozenset()): 7191684930184179482016276395611672639105248126232175323349533708710427892956421,\n",
+       "              ((380,),\n",
+       "               frozenset()): 11636390653418416980850249915594581159038678944868584489700931605805709880172285,\n",
+       "              ((381,),\n",
+       "               frozenset()): 18828075583602596462866526311206253798143927071100759813050465314516137773128706,\n",
+       "              ((382,),\n",
+       "               frozenset()): 30464466237021013443716776226800834957182606015969344302751396920321847653300991,\n",
+       "              ((383,),\n",
+       "               frozenset()): 49292541820623609906583302538007088755326533087070104115801862234837985426429697,\n",
+       "              ((384,),\n",
+       "               frozenset()): 79757008057644623350300078764807923712509139103039448418553259155159833079730688,\n",
+       "              ((385,),\n",
+       "               frozenset()): 129049549878268233256883381302815012467835672190109552534355121389997818506160385,\n",
+       "              ((386,),\n",
+       "               frozenset()): 208806557935912856607183460067622936180344811293149000952908380545157651585891073,\n",
+       "              ((387,),\n",
+       "               frozenset()): 337856107814181089864066841370437948648180483483258553487263501935155470092051458,\n",
+       "              ((388,),\n",
+       "               frozenset()): 546662665750093946471250301438060884828525294776407554440171882480313121677942531,\n",
+       "              ((389,),\n",
+       "               frozenset()): 884518773564275036335317142808498833476705778259666107927435384415468591769993989,\n",
+       "              ((390,),\n",
+       "               frozenset()): 1431181439314368982806567444246559718305231073036073662367607266895781713447936520,\n",
+       "              ((391,),\n",
+       "               frozenset()): 2315700212878644019141884587055058551781936851295739770295042651311250305217930509,\n",
+       "              ((392,),\n",
+       "               frozenset()): 3746881652193013001948452031301618270087167924331813432662649918207032018665867029,\n",
+       "              ((393,),\n",
+       "               frozenset()): 6062581865071657021090336618356676821869104775627553202957692569518282323883797538,\n",
+       "              ((394,),\n",
+       "               frozenset()): 9809463517264670023038788649658295091956272699959366635620342487725314342549664567,\n",
+       "              ((395,),\n",
+       "               frozenset()): 15872045382336327044129125268014971913825377475586919838578035057243596666433462105,\n",
+       "              ((396,),\n",
+       "               frozenset()): 25681508899600997067167913917673267005781650175546286474198377544968911008983126672,\n",
+       "              ((397,),\n",
+       "               frozenset()): 41553554281937324111297039185688238919607027651133206312776412602212507675416588777,\n",
+       "              ((398,),\n",
+       "               frozenset()): 67235063181538321178464953103361505925388677826679492786974790147181418684399715449,\n",
+       "              ((399,),\n",
+       "               frozenset()): 108788617463475645289761992289049744844995705477812699099751202749393926359816304226,\n",
+       "              ((400,),\n",
+       "               frozenset()): 176023680645013966468226945392411250770384383304492191886725992896575345044216019675,\n",
+       "              ((401,),\n",
+       "               frozenset()): 284812298108489611757988937681460995615380088782304890986477195645969271404032323901,\n",
+       "              ((402,),\n",
+       "               frozenset()): 460835978753503578226215883073872246385764472086797082873203188542544616448248343576,\n",
+       "              ((403,),\n",
+       "               frozenset()): 745648276861993189984204820755333242001144560869101973859680384188513887852280667477,\n",
+       "              ((404,),\n",
+       "               frozenset()): 1206484255615496768210420703829205488386909032955899056732883572731058504300529011053,\n",
+       "              ((405,),\n",
+       "               frozenset()): 1952132532477489958194625524584538730388053593825001030592563956919572392152809678530,\n",
+       "              ((406,),\n",
+       "               frozenset()): 3158616788092986726405046228413744218774962626780900087325447529650630896453338689583,\n",
+       "              ((407,),\n",
+       "               frozenset()): 5110749320570476684599671752998282949163016220605901117918011486570203288606148368113,\n",
+       "              ((408,),\n",
+       "               frozenset()): 8269366108663463411004717981412027167937978847386801205243459016220834185059487057696,\n",
+       "              ((409,),\n",
+       "               frozenset()): 13380115429233940095604389734410310117100995067992702323161470502791037473665635425809,\n",
+       "              ((410,),\n",
+       "               frozenset()): 21649481537897403506609107715822337285038973915379503528404929519011871658725122483505,\n",
+       "              ((411,),\n",
+       "               frozenset()): 35029596967131343602213497450232647402139968983372205851566400021802909132390757909314,\n",
+       "              ((412,),\n",
+       "               frozenset()): 56679078505028747108822605166054984687178942898751709379971329540814780791115880392819,\n",
+       "              ((413,),\n",
+       "               frozenset()): 91708675472160090711036102616287632089318911882123915231537729562617689923506638302133,\n",
+       "              ((414,),\n",
+       "               frozenset()): 148387753977188837819858707782342616776497854780875624611509059103432470714622518694952,\n",
+       "              ((415,),\n",
+       "               frozenset()): 240096429449348928530894810398630248865816766662999539843046788666050160638129156997085,\n",
+       "              ((416,),\n",
+       "               frozenset()): 388484183426537766350753518180972865642314621443875164454555847769482631352751675692037,\n",
+       "              ((417,),\n",
+       "               frozenset()): 628580612875886694881648328579603114508131388106874704297602636435532791990880832689122,\n",
+       "              ((418,),\n",
+       "               frozenset()): 1017064796302424461232401846760575980150446009550749868752158484205015423343632508381159,\n",
+       "              ((419,),\n",
+       "               frozenset()): 1645645409178311156114050175340179094658577397657624573049761120640548215334513341070281,\n",
+       "              ((420,),\n",
+       "               frozenset()): 2662710205480735617346452022100755074809023407208374441801919604845563638678145849451440,\n",
+       "              ((421,),\n",
+       "               frozenset()): 4308355614659046773460502197440934169467600804865999014851680725486111854012659190521721,\n",
+       "              ((422,),\n",
+       "               frozenset()): 6971065820139782390806954219541689244276624212074373456653600330331675492690805039973161,\n",
+       "              ((423,),\n",
+       "               frozenset()): 11279421434798829164267456416982623413744225016940372471505281055817787346703464230494882,\n",
+       "              ((424,),\n",
+       "               frozenset()): 18250487254938611555074410636524312658020849229014745928158881386149462839394269270468043,\n",
+       "              ((425,),\n",
+       "               frozenset()): 29529908689737440719341867053506936071765074245955118399664162441967250186097733500962925,\n",
+       "              ((426,),\n",
+       "               frozenset()): 47780395944676052274416277690031248729785923474969864327823043828116713025492002771430968,\n",
+       "              ((427,),\n",
+       "               frozenset()): 77310304634413492993758144743538184801550997720924982727487206270083963211589736272393893,\n",
+       "              ((428,),\n",
+       "               frozenset()): 125090700579089545268174422433569433531336921195894847055310250098200676237081739043824861,\n",
+       "              ((429,),\n",
+       "               frozenset()): 202401005213503038261932567177107618332887918916819829782797456368284639448671475316218754,\n",
+       "              ((430,),\n",
+       "               frozenset()): 327491705792592583530106989610677051864224840112714676838107706466485315685753214360043615,\n",
+       "              ((431,),\n",
+       "               frozenset()): 529892711006095621792039556787784670197112759029534506620905162834769955134424689676262369,\n",
+       "              ((432,),\n",
+       "               frozenset()): 857384416798688205322146546398461722061337599142249183459012869301255270820177904036305984,\n",
+       "              ((433,),\n",
+       "               frozenset()): 1387277127804783827114186103186246392258450358171783690079918032136025225954602593712568353,\n",
+       "              ((434,),\n",
+       "               frozenset()): 2244661544603472032436332649584708114319787957314032873538930901437280496774780497748874337,\n",
+       "              ((435,),\n",
+       "               frozenset()): 3631938672408255859550518752770954506578238315485816563618848933573305722729383091461442690,\n",
+       "              ((436,),\n",
+       "               frozenset()): 5876600217011727891986851402355662620898026272799849437157779835010586219504163589210317027,\n",
+       "              ((437,),\n",
+       "               frozenset()): 9508538889419983751537370155126617127476264588285666000776628768583891942233546680671759717,\n",
+       "              ((438,),\n",
+       "               frozenset()): 15385139106431711643524221557482279748374290861085515437934408603594478161737710269882076744,\n",
+       "              ((439,),\n",
+       "               frozenset()): 24893677995851695395061591712608896875850555449371181438711037372178370103971256950553836461,\n",
+       "              ((440,),\n",
+       "               frozenset()): 40278817102283407038585813270091176624224846310456696876645445975772848265708967220435913205,\n",
+       "              ((441,),\n",
+       "               frozenset()): 65172495098135102433647404982700073500075401759827878315356483347951218369680224170989749666,\n",
+       "              ((442,),\n",
+       "               frozenset()): 105451312200418509472233218252791250124300248070284575192001929323724066635389191391425662871,\n",
+       "              ((443,),\n",
+       "               frozenset()): 170623807298553611905880623235491323624375649830112453507358412671675285005069415562415412537,\n",
+       "              ((444,),\n",
+       "               frozenset()): 276075119498972121378113841488282573748675897900397028699360341995399351640458606953841075408,\n",
+       "              ((445,),\n",
+       "               frozenset()): 446698926797525733283994464723773897373051547730509482206718754667074636645528022516256487945,\n",
+       "              ((446,),\n",
+       "               frozenset()): 722774046296497854662108306212056471121727445630906510906079096662473988285986629470097563353,\n",
+       "              ((447,),\n",
+       "               frozenset()): 1169472973094023587946102770935830368494778993361415993112797851329548624931514651986354051298,\n",
+       "              ((448,),\n",
+       "               frozenset()): 1892247019390521442608211077147886839616506438992322504018876947992022613217501281456451614651,\n",
+       "              ((449,),\n",
+       "               frozenset()): 3061719992484545030554313848083717208111285432353738497131674799321571238149015933442805665949,\n",
+       "              ((450,),\n",
+       "               frozenset()): 4953967011875066473162524925231604047727791871346061001150551747313593851366517214899257280600,\n",
+       "              ((451,),\n",
+       "               frozenset()): 8015687004359611503716838773315321255839077303699799498282226546635165089515533148342062946549,\n",
+       "              ((452,),\n",
+       "               frozenset()): 12969654016234677976879363698546925303566869175045860499432778293948758940882050363241320227149,\n",
+       "              ((453,),\n",
+       "               frozenset()): 20985341020594289480596202471862246559405946478745659997715004840583924030397583511583383173698,\n",
+       "              ((454,),\n",
+       "               frozenset()): 33954995036828967457475566170409171862972815653791520497147783134532682971279633874824703400847,\n",
+       "              ((455,),\n",
+       "               frozenset()): 54940336057423256938071768642271418422378762132537180494862787975116607001677217386408086574545,\n",
+       "              ((456,),\n",
+       "               frozenset()): 88895331094252224395547334812680590285351577786328700992010571109649289972956851261232789975392,\n",
+       "              ((457,),\n",
+       "               frozenset()): 143835667151675481333619103454952008707730339918865881486873359084765896974634068647640876549937,\n",
+       "              ((458,),\n",
+       "               frozenset()): 232730998245927705729166438267632598993081917705194582478883930194415186947590919908873666525329,\n",
+       "              ((459,),\n",
+       "               frozenset()): 376566665397603187062785541722584607700812257624060463965757289279181083922224988556514543075266,\n",
+       "              ((460,),\n",
+       "               frozenset()): 609297663643530892791951979990217206693894175329255046444641219473596270869815908465388209600595,\n",
+       "              ((461,),\n",
+       "               frozenset()): 985864329041134079854737521712801814394706432953315510410398508752777354792040897021902752675861,\n",
+       "              ((462,),\n",
+       "               frozenset()): 1595161992684664972646689501703019021088600608282570556855039728226373625661856805487290962276456,\n",
+       "              ((463,),\n",
+       "               frozenset()): 2581026321725799052501427023415820835483307041235886067265438236979150980453897702509193714952317,\n",
+       "              ((464,),\n",
+       "               frozenset()): 4176188314410464025148116525118839856571907649518456624120477965205524606115754507996484677228773,\n",
+       "              ((465,),\n",
+       "               frozenset()): 6757214636136263077649543548534660692055214690754342691385916202184675586569652210505678392181090,\n",
+       "              ((466,),\n",
+       "               frozenset()): 10933402950546727102797660073653500548627122340272799315506394167390200192685406718502163069409863,\n",
+       "              ((467,),\n",
+       "               frozenset()): 17690617586682990180447203622188161240682337031027142006892310369574875779255058929007841461590953,\n",
+       "              ((468,),\n",
+       "               frozenset()): 28624020537229717283244863695841661789309459371299941322398704536965075971940465647510004531000816,\n",
+       "              ((469,),\n",
+       "               frozenset()): 46314638123912707463692067318029823029991796402327083329291014906539951751195524576517845992591769,\n",
+       "              ((470,),\n",
+       "               frozenset()): 74938658661142424746936931013871484819301255773627024651689719443505027723135990224027850523592585,\n",
+       "              ((471,),\n",
+       "               frozenset()): 121253296785055132210628998331901307849293052175954107980980734350044979474331514800545696516184354,\n",
+       "              ((472,),\n",
+       "               frozenset()): 196191955446197556957565929345772792668594307949581132632670453793550007197467505024573547039776939,\n",
+       "              ((473,),\n",
+       "               frozenset()): 317445252231252689168194927677674100517887360125535240613651188143594986671799019825119243555961293,\n",
+       "              ((474,),\n",
+       "               frozenset()): 513637207677450246125760857023446893186481668075116373246321641937144993869266524849692790595738232,\n",
+       "              ((475,),\n",
+       "               frozenset()): 831082459908702935293955784701120993704369028200651613859972830080739980541065544674812034151699525,\n",
+       "              ((476,),\n",
+       "               frozenset()): 1344719667586153181419716641724567886890850696275767987106294472017884974410332069524504824747437757,\n",
+       "              ((477,),\n",
+       "               frozenset()): 2175802127494856116713672426425688880595219724476419600966267302098624954951397614199316858899137282,\n",
+       "              ((478,),\n",
+       "               frozenset()): 3520521795081009298133389068150256767486070420752187588072561774116509929361729683723821683646575039,\n",
+       "              ((479,),\n",
+       "               frozenset()): 5696323922575865414847061494575945648081290145228607189038829076215134884313127297923138542545712321,\n",
+       "              ((480,),\n",
+       "               frozenset()): 9216845717656874712980450562726202415567360565980794777111390850331644813674856981646960226192287360,\n",
+       "              ((481,),\n",
+       "               frozenset()): 14913169640232740127827512057302148063648650711209401966150219926546779697987984279570098768737999681,\n",
+       "              ((482,),\n",
+       "               frozenset()): 24130015357889614840807962620028350479216011277190196743261610776878424511662841261217058994930287041,\n",
+       "              ((483,),\n",
+       "               frozenset()): 39043184998122354968635474677330498542864661988399598709411830703425204209650825540787157763668286722,\n",
+       "              ((484,),\n",
+       "               frozenset()): 63173200356011969809443437297358849022080673265589795452673441480303628721313666802004216758598573763,\n",
+       "              ((485,),\n",
+       "               frozenset()): 102216385354134324778078911974689347564945335253989394162085272183728832930964492342791374522266860485,\n",
+       "              ((486,),\n",
+       "               frozenset()): 165389585710146294587522349272048196587026008519579189614758713664032461652278159144795591280865434248,\n",
+       "              ((487,),\n",
+       "               frozenset()): 267605971064280619365601261246737544151971343773568583776843985847761294583242651487586965803132294733,\n",
+       "              ((488,),\n",
+       "               frozenset()): 432995556774426913953123610518785740738997352293147773391602699511793756235520810632382557083997728981,\n",
+       "              ((489,),\n",
+       "               frozenset()): 700601527838707533318724871765523284890968696066716357168446685359555050818763462119969522887130023714,\n",
+       "              ((490,),\n",
+       "               frozenset()): 1133597084613134447271848482284309025629966048359864130560049384871348807054284272752352079971127752695,\n",
+       "              ((491,),\n",
+       "               frozenset()): 1834198612451841980590573354049832310520934744426580487728496070230903857873047734872321602858257776409,\n",
+       "              ((492,),\n",
+       "               frozenset()): 2967795697064976427862421836334141336150900792786444618288545455102252664927332007624673682829385529104,\n",
+       "              ((493,),\n",
+       "               frozenset()): 4801994309516818408452995190383973646671835537213025106017041525333156522800379742496995285687643305513,\n",
+       "              ((494,),\n",
+       "               frozenset()): 7769790006581794836315417026718114982822736329999469724305586980435409187727711750121668968517028834617,\n",
+       "              ((495,),\n",
+       "               frozenset()): 12571784316098613244768412217102088629494571867212494830322628505768565710528091492618664254204672140130,\n",
+       "              ((496,),\n",
+       "               frozenset()): 20341574322680408081083829243820203612317308197211964554628215486203974898255803242740333222721700974747,\n",
+       "              ((497,),\n",
+       "               frozenset()): 32913358638779021325852241460922292241811880064424459384950843991972540608783894735358997476926373114877,\n",
+       "              ((498,),\n",
+       "               frozenset()): 53254932961459429406936070704742495854129188261636423939579059478176515507039697978099330699648074089624,\n",
+       "              ((499,),\n",
+       "               frozenset()): 86168291600238450732788312165664788095941068326060883324529903470149056115823592713458328176574447204501,\n",
+       "              ((500,),\n",
+       "               frozenset()): 139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125}})"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cg.caches"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## iteration 2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from collections import defaultdict\n",
+    "from dataclasses import dataclass\n",
+    "from functools import wraps\n",
+    "from typing import Any, Callable, Tuple, Union\n",
+    "from inspect import signature\n",
+    "\n",
+    "@dataclass(frozen=True)\n",
+    "class FunctionCall:\n",
+    "    func_name: str\n",
+    "    args: tuple\n",
+    "    kwargs: frozenset\n",
+    "\n",
+    "ArgsHash = Tuple[Tuple, frozenset]\n",
+    "\n",
+    "class CacheGraph:\n",
+    "    def __init__(self):\n",
+    "        self.reset()\n",
+    "\n",
+    "    def reset(self):\n",
+    "        self.stack: list[FunctionCall] = [] # what function is currently being called\n",
+    "        self.caches: defaultdict[str, dict[ArgsHash, Any]] = defaultdict(dict) # Results of function calls\n",
+    "        self.graph: defaultdict[FunctionCall, set[FunctionCall]] = defaultdict(set) # Call graph, graph[caller] = [callee1, callee2, ...]\n",
+    "        self.indegrees: defaultdict[FunctionCall, int] = defaultdict(int) # Number of incoming edges to a node\n",
+    "        # Typically aggregated results for a function at a timestep.\n",
+    "        self.stored_results: defaultdict[str, dict[int, Any]] = defaultdict(dict) \n",
+    "\n",
+    "    def check_if_cached(self, function_call: FunctionCall):\n",
+    "        name_in_cache = function_call.func_name in self.caches\n",
+    "        return name_in_cache and (function_call.args, function_call.kwargs) in self.caches[function_call.func_name]\n",
+    "    \n",
+    "    def _store_result(self, storage_func: Union[Callable, None], func: Callable, args: tuple, kwargs: dict, raw_result: Any):\n",
+    "        \"\"\"We might want to store an intermediate result\"\"\"\n",
+    "        if storage_func is None:\n",
+    "            return\n",
+    "        # These conditions should not trigger, why we assert and not throw an exception\n",
+    "        assert len(args) == 1 and isinstance(args[0], int)\n",
+    "        assert len(kwargs) == 0\n",
+    "        # store the processed result\n",
+    "        timestep = args[0]\n",
+    "        stored_result = storage_func(raw_result)\n",
+    "        self.stored_results[func.__name__][timestep] = stored_result\n",
+    "\n",
+    "class CacheDecorator:\n",
+    "    def __init__(self, func: Callable, cache_graph: CacheGraph, storage_func: Union[Callable, None] = None):\n",
+    "        self.func = func\n",
+    "        self.graph = cache_graph.graph\n",
+    "        self.indegrees = cache_graph.indegrees.copy()\n",
+    "        self.cached_values = cache_graph.caches[func.__name__]\n",
+    "        self.storage_func = storage_func\n",
+    "        sig = signature(func)\n",
+    "        self.single_param_timesteps = 't' in sig.parameters and len(sig.parameters) == 1\n",
+    "    \n",
+    "    def __call__(self, *args, **kwargs):\n",
+    "        frozen_kwargs = frozenset(kwargs.items())\n",
+    "        function_call = FunctionCall(self.func.__name__, args, frozen_kwargs)\n",
+    "        if self.cache_graph.stack:\n",
+    "            self.cache_graph.graph[self.cache_graph.stack[-1]].add(function_call)\n",
+    "            self.cache_graph.indegrees[function_call] += 1\n",
+    "        if not self.cache_graph.check_if_cached(function_call):\n",
+    "            self.cache_graph.stack.append(function_call)\n",
+    "            result = self.func(*args, **kwargs)\n",
+    "            self.cached_values[(args, frozen_kwargs)] = result\n",
+    "            if self.single_param_timesteps:\n",
+    "                self._store_result(args, result)\n",
+    "            self.cache_graph.stack.pop()\n",
+    "        return self.cached_values[(args, frozen_kwargs)]\n",
+    "    \n",
+    "    def _store_result(self, args: tuple, raw_result: Any):\n",
+    "        \"\"\"We might want to store an intermediate result\"\"\"\n",
+    "        if self.storage_func is None:\n",
+    "            return\n",
+    "        if self.single_param_timesteps:\n",
+    "            timestep = args[0]\n",
+    "            stored_result = self.storage_func(raw_result)\n",
+    "            self.cache_graph.stored_results[self.func.__name__][timestep] = stored_result\n",
+    "\n",
+    "class CacheEvictingDecorator:\n",
+    "    def __init__(self, func: Callable, cache_graph: CacheGraph, storage_func: Union[Callable, None] = None):\n",
+    "        self.func = func\n",
+    "        self.cache_graph = cache_graph\n",
+    "        self.cached_values = cache_graph.caches[func.__name__]\n",
+    "        self.storage_func = storage_func\n",
+    "        sig = signature(func)\n",
+    "        self.single_param_timesteps = 't' in sig.parameters and len(sig.parameters) == 1\n",
+    "    \n",
+    "    def __call__(self, *args, **kwargs):\n",
+    "        frozen_kwargs = frozenset(kwargs.items())\n",
+    "        function_call = FunctionCall(self.func.__name__, args, frozen_kwargs)\n",
+    "        if self.cache_graph.stack:\n",
+    "            self.cache_graph.graph[self.cache_graph.stack[-1]].add(function_call)\n",
+    "            self.cache_graph.indegrees[function_call] += 1\n",
+    "        if not self.cache_graph.check_if_cached(function_call):\n",
+    "            self.cache_graph.stack.append(function_call)\n",
+    "            result = self.func(*args, **kwargs)\n",
+    "            self.cached_values[(args, frozen_kwargs)] = result\n",
+    "            self._store_result(self.storage_func, self.func, args, kwargs, result)\n",
+    "            self.cache_graph.stack.pop()\n",
+    "        return self.cached_values[(args, frozen_kwargs)]\n",
+    "    \n",
+    "    def _store_result(self, args: tuple, raw_result: Any):\n",
+    "        \"\"\"We might want to store an intermediate result\"\"\"\n",
+    "        if self.storage_func is None:\n",
+    "            return\n",
+    "        if self.single_param_timesteps:\n",
+    "            timestep = args[0]\n",
+    "            stored_result = self.storage_func(raw_result)\n",
+    "            self.cache_graph.stored_results[self.func.__name__][timestep] = stored_result"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# import signature\n",
+    "from inspect import signature\n",
+    "from functools import wraps\n",
+    "\n",
+    "class _Cache:\n",
+    "    \"\"\"Cache provides controllable memoization for model methods\"\"\"\n",
+    "\n",
+    "    def __init__(self, func):\n",
+    "        self.func = func\n",
+    "        self.param_len = len(signature(func).parameters)\n",
+    "        self.has_one_param = self.param_len == 1\n",
+    "        self._store = dict()\n",
+    "        self.__name__ = \"Cache: \" + func.__name__\n",
+    "\n",
+    "    def __call__(self, *arg):\n",
+    "        if arg in self._store:\n",
+    "            return self._store[arg]\n",
+    "        else:\n",
+    "            result = self.func(*arg)\n",
+    "            self._store[arg] = result\n",
+    "            return result\n",
+    "\n",
+    "    def __repr__(self):\n",
+    "        return f\"<Cache Function: {self.func.__name__} Size: {len(self._store)}>\"\n",
+    "    \n",
+    "    def sum(self):\n",
+    "        \"\"\"return the sum of all values in the Cache Function\"\"\"\n",
+    "        return sum(self._store.values())\n",
+    "    \n",
+    "    @property\n",
+    "    def values(self):\n",
+    "        return list(self._store.values())"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'Cache provides controllable memoization for model methods'"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "def fibonacci(n):\n",
+    "    \"\"\"hello\"\"\"\n",
+    "    if n <= 1:\n",
+    "        return n\n",
+    "    return fibonacci(n-1) + fibonacci(n-2)\n",
+    "\n",
+    "cached_fib = _Cache(fibonacci)\n",
+    "cached_fib.__doc__"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.8.18"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/heavylight/__init__.py b/src/heavylight/__init__.py
index ada40c6..7775911 100644
--- a/src/heavylight/__init__.py
+++ b/src/heavylight/__init__.py
@@ -1,1 +1,3 @@
-from .heavylight import Model, Table
\ No newline at end of file
+from .heavylight import Model, Table
+from .memory_optimized_model import LightModel
+from .memory_optimized_cache import CacheGraph
\ No newline at end of file
diff --git a/src/heavylight/memory_optimized_cache.py b/src/heavylight/memory_optimized_cache.py
new file mode 100644
index 0000000..9ff81c5
--- /dev/null
+++ b/src/heavylight/memory_optimized_cache.py
@@ -0,0 +1,119 @@
+from typing import Callable
+from collections import defaultdict
+from dataclasses import dataclass
+from functools import wraps
+from typing import Any, Callable, Tuple, Union, FrozenSet
+
+@dataclass(frozen=True)
+class FunctionCall:
+    func_name: str
+    args: tuple
+    kwargs: FrozenSet[Tuple[str, Any]]
+
+    def __repr__(self):
+        if len(self.kwargs) == 0:
+            if len(self.args) == 1:
+                return f"{self.func_name}({self.args[0]})"
+            return f"{self.func_name}{self.args}"
+        return f"{self.func_name}({', '.join(map(str, self.args))}, {', '.join(f'{k}={v}' for k, v in self.kwargs)})"
+
+ArgsHash = Tuple[Tuple, frozenset]
+
+class CacheGraph:
+    """
+    The cache graph maintains data structures necessary for caching and memory-optimizing collections of recursive functions.
+    It is applied to functions as a decorator
+    """
+    def __init__(self):
+        self.reset()
+
+    def reset(self):
+        """
+        Clear all internal state of the cache graph.
+        """
+        self.stack: list[FunctionCall] = [] # what function is currently being called
+        self.caches: defaultdict[str, dict[ArgsHash, Any]] = defaultdict(dict) # Results of function calls
+        self.graph: defaultdict[FunctionCall, set[FunctionCall]] = defaultdict(set) # Call graph, graph[caller] = [callee1, callee2, ...]
+        # Typically aggregated results for a function at a timestep.
+        self.stored_results: defaultdict[str, dict[int, Any]] = defaultdict(dict)
+        # What is the last function that needs the result of a function? Used to help in clearing the cache
+        self.last_needed_by: dict[FunctionCall, FunctionCall] = {}
+        # can_clear[caller] = [callee1, callee2, ...] means that caller can clear the cache of callee1 and callee2
+        self.can_clear: dict[FunctionCall, list[FunctionCall]] = defaultdict(list)
+        self.all_calls: set[FunctionCall] = set()
+        self.cache_misses: defaultdict[FunctionCall, int] = defaultdict(int)
+
+    def check_if_cached(self, function_call: FunctionCall):
+        name_in_cache = function_call.func_name in self.caches
+        return name_in_cache and (function_call.args, function_call.kwargs) in self.caches[function_call.func_name]
+    
+    def optimize(self):
+        self.can_clear = defaultdict(list)
+        for callee, caller in self.last_needed_by.items():
+            self.can_clear[caller].append(callee)
+        uncleared_calls = self.all_calls - set(self.last_needed_by.keys())
+        for call in uncleared_calls:
+            self.can_clear[call].append(call)
+
+    def optimize_and_reset(self):
+        self.optimize()
+        can_clear = self.can_clear
+        self.reset()
+        self.can_clear = can_clear
+
+    def __call__(self, storage_func: Union[Callable[[int], Any], None] = None):
+        def custom_cache_decorator(func):
+            @wraps(func)
+            def wrapper(*args, **kwargs):
+                frozen_kwargs = frozenset(kwargs.items())
+                function_call = FunctionCall(func.__name__, args, frozen_kwargs)
+                if self.stack:
+                    self.graph[self.stack[-1]].add(function_call)
+                    self.last_needed_by[function_call] = self.stack[-1]
+                if not self.check_if_cached(function_call):
+                    self.all_calls.add(function_call)
+                    self.cache_misses[function_call] += 1
+                    self.stack.append(function_call)
+                    result = func(*args, **kwargs)
+                    self.caches[func.__name__][(args, frozen_kwargs)] = result
+                    for clearable_call in self.can_clear[function_call]:
+                        del self.caches[clearable_call.func_name][(clearable_call.args, clearable_call.kwargs)]
+                    self.stack.pop()
+                    self._store_result(storage_func, func, args, kwargs, result)
+                    return result
+                return self.caches[func.__name__][(args, frozen_kwargs)]
+            decorator = _Cache(self, wrapper)
+            return decorator
+        return custom_cache_decorator
+    
+    def _store_result(self, storage_func: Union[Callable, None], func: Callable, args: tuple, kwargs: dict, raw_result: Any):
+        """We might want to store an intermediate result"""
+        if storage_func is None:
+            return
+        # These conditions should not trigger, why we assert and not throw an exception
+        assert len(args) == 1 and isinstance(args[0], int)
+        assert len(kwargs) == 0
+        # store the processed result
+        timestep = args[0]
+        stored_result = storage_func(raw_result)
+        self.stored_results[func.__name__][timestep] = stored_result
+
+    def size(self):
+        return sum(len(cache) for cache in self.caches.values())
+
+class _Cache:
+    def __init__(self, cache_graph: CacheGraph, func: Callable):
+        self.cache = cache_graph.caches[func.__name__]
+        self._func = func
+
+    def __setitem__(self, key, value):
+        if isinstance(key, int):
+            self.cache[((key,), frozenset())] = value
+        else:
+            self.cache[(key, frozenset())] = value
+
+    def __repr__(self):
+        return f"<Cache Function: {self._func.__name__}, Size: {len(self.cache)}>"
+    
+    def __call__(self, *args: Any, **kwds: Any) -> Any:
+        return self._func(*args, **kwds)
\ No newline at end of file
diff --git a/src/heavylight/memory_optimized_model.py b/src/heavylight/memory_optimized_model.py
new file mode 100644
index 0000000..7d9af63
--- /dev/null
+++ b/src/heavylight/memory_optimized_model.py
@@ -0,0 +1,93 @@
+from inspect import getmembers, signature
+from typing import Callable, List, Union
+from types import MethodType
+from heavylight.memory_optimized_cache import CacheGraph, _Cache
+import pandas as pd
+
+
+class LightModel:
+    """Base class to subclass from for recursive actuarial models.
+
+    Inheriting from this class causes functions starting with a lowercase letter to be cached.
+    A cached function will only be called once for each unique set of arguments.
+    Caching is necessary for recursive actuarial calculations to be efficient.
+
+    This means that the function will only be called once for each unique set of arguments, important for recursive actuarial models.
+
+    Parameters
+    ----------
+    storage_function: Callable[[int], Any], optional
+        This function is applied to the results of cached methods that have a single parameter `t`.
+        (e.g. `def pols_if(self, t):`).  The default is None, no values will be stored.
+        `storage_function=np.sum` will cause the sum of the results to be stored.
+        `storage_function=lambda x: x[0] if isinstance(x, np.ndarray) else x` will store the first element.
+        `storage_function=lambda x: x` will store the raw results.
+
+    Class level methods:
+        RunModel(proj_len):
+        if the model has not been auto-run at initialisation, run it for projection length.
+
+    methods/variables to avoid:
+    methods/variables starting with an underscore `_` are treated as internal.  You may break functionality if you create your own.
+    """
+    
+    def __init__(self, storage_function: Union[Callable, None] = None):
+        self.cache_graph = CacheGraph()
+        self._single_param_timestep_funcs: List[_Cache]  = []
+        self._storage_function = storage_function
+        # happens after setting up attributes
+        for method_name, method in getmembers(self):
+            if not method_name[0].islower() or method_name.startswith("_") or not isinstance(method, MethodType):
+                continue
+            is_single_param_t = check_if_single_parameter_t(method)
+            if is_single_param_t:
+                cached_method = self.cache_graph(self._storage_function)(method)
+                self._single_param_timestep_funcs.append(cached_method)
+            else:
+                cached_method = self.cache_graph()(method)
+            setattr(self, method_name, cached_method)
+
+    def RunModel(self, proj_len: int):
+        """
+        Run the model for a projection length.
+        All single parameter timestep functions will be run for each timestep.
+        """
+        for t in range(proj_len+1):
+            for func in self._single_param_timestep_funcs:
+                func(t)
+
+    def ResetCache(self):
+        """Reset the cache, useful if you want to run the model again with different parameters."""
+        self.cache_graph.reset()
+
+    def OptimizeMemoryAndReset(self):
+        """
+        Calling this clears the cache, and causes the cache to be optimized.
+        Optimizing the cache means that only needed results are stored, and intermediate results are cleared.
+        """
+        self.cache_graph.optimize_and_reset()
+
+    def TimestepFunctions(self):
+        """List the cached functions that have a single parameter `t`."""
+        return [cache._func.__name__ for cache in self._single_param_timestep_funcs]
+    
+    def StoredResults(self):
+        """
+        Return a dictionary of all stored results. 
+        The dictionary is of the form:
+        {function_name: {timestep: stored_result}}
+        """
+        return self.cache_graph.stored_results
+    
+    def ToDataFrame(self):
+        """Return a pandas dataframe of all single parameter timestep results."""
+        df = pd.DataFrame(self.StoredResults())
+        # if t is in the dataframe, move it to first position
+        if "t" in df.columns:
+            df.insert(0, "t", df.pop("t"))
+        return df
+
+
+def check_if_single_parameter_t(func: Callable):
+    sig = signature(func)
+    return 't' in sig.parameters and len(sig.parameters) == 1
\ No newline at end of file


Test patch:
diff --git a/tests/test_heavylight.py b/tests/test_heavylight.py
new file mode 100644
index 0000000..9ac2caf
--- /dev/null
+++ b/tests/test_heavylight.py
@@ -0,0 +1,33 @@
+from heavylight import Model
+
+class SimpleModel(Model):
+    def t(self, t):
+        return t
+
+    def num_pols_if(self, t):
+        if t == 0:
+            return 1
+        else:
+            return self.num_pols_if(t - 1) * 0.98
+
+    def cashflow(self, t):
+        return self.num_pols_if(t) * 100
+    
+    def v(self, t):
+        """discount factor for time t --> time 0"""
+        if t == 0:
+            return 1
+        else:
+            return self.v(t - 1) / (1 + self.forward_rate(t))
+    
+    def forward_rate(self, t):
+        return 0.04
+    
+    def pv_cashflow(self, t):
+        """present value of the cashflow occuring at time t"""
+        return self.cashflow(t) * self.v(t)
+    
+def test_heavylight():
+    """Tests be improved upon later"""
+    model = SimpleModel(do_run = True, proj_len = 10)
+    assert model.pv_cashflow.sum() > 0
\ No newline at end of file
diff --git a/tests/test_optimizations.py b/tests/test_optimizations.py
new file mode 100644
index 0000000..75a6410
--- /dev/null
+++ b/tests/test_optimizations.py
@@ -0,0 +1,66 @@
+import numpy as np
+from heavylight import LightModel
+from typing import Union, Callable
+import numpy as np
+import pytest
+
+class SimpleModel(LightModel):
+    """This class has some hard coded"""
+
+    def __init__(self, initial_pols_if: np.ndarray, storage_function: Union[Callable, None] = None, mortality_rate = .01):
+        super().__init__(storage_function=storage_function)
+        self.initial_pols_if = initial_pols_if
+        self.mortality_rate = mortality_rate
+
+    def t(self, t):
+        return np.int64(t) # to calculate nbytes for memory consumption
+
+    def num_pols_if(self, t):
+        if t == 0:
+            return self.initial_pols_if
+        else:
+            return self.num_pols_if(t - 1) - self.pols_death(t - 1) # causes exponential time complexity if uncached
+        
+    def pols_death(self, t):
+        return self.num_pols_if(t) * self.mortality_rate
+
+    def cashflow(self, t):
+        return self.num_pols_if(t) * 100
+    
+    def v(self, t):
+        if t == 0:
+            return np.int64(1)
+        else:
+            return self.v(t - 1) / (1 + self.forward_rate(t))
+    
+    def forward_rate(self, t):
+        return np.float64(0.04)
+    
+    def pv_cashflow(self, t):
+        return self.cashflow(t) * self.v(t)
+
+def calculate_cache_graph_size(model: LightModel):
+    cg = model.cache_graph
+    return sum(val.nbytes for cache in cg.caches.values() for val in cache.values())
+
+def run_model_calculate_max_cache(model: SimpleModel, max_time: int):
+    max_cache_size = 0
+    for t in range(max_time+1):
+        model.pv_cashflow(t)
+        cache_size = calculate_cache_graph_size(model)
+        max_cache_size = max(max_cache_size, cache_size)
+    return max_cache_size
+
+def get_memory_savings_ratio(model: SimpleModel, max_time: int):
+    model.ResetCache()
+    max_cache_size = run_model_calculate_max_cache(model, max_time)
+    model.OptimizeMemoryAndReset()
+    optimized_max_cache_graph_size = run_model_calculate_max_cache(model, max_time)
+    return optimized_max_cache_graph_size / max_cache_size
+
+@pytest.mark.timeout(4)
+def test_memory_savings():
+    model = SimpleModel(np.ones((1000,)))
+    assert get_memory_savings_ratio(model, 100) < .01
+    assert sum(model.cache_graph.cache_misses.values()) > 0 # verify that misses are being tracked
+    assert all(misses == 1 for misses in model.cache_graph.cache_misses.values())
diff --git a/tests/test_optimized_cache.py b/tests/test_optimized_cache.py
new file mode 100644
index 0000000..140fb72
--- /dev/null
+++ b/tests/test_optimized_cache.py
@@ -0,0 +1,63 @@
+from heavylight.memory_optimized_cache import FunctionCall, CacheGraph
+import pytest
+
+def test_function_call():
+    fc_args_kwargs = FunctionCall("func", (1, 2), frozenset([('a', 1)]))
+    assert fc_args_kwargs.func_name == "func"
+    assert fc_args_kwargs.args == (1, 2)
+    assert fc_args_kwargs.kwargs == frozenset([('a', 1)])
+    assert repr(fc_args_kwargs) == "func(1, 2, a=1)"
+    fc_single_arg_no_kwargs = FunctionCall("func", (1,), frozenset())
+    assert repr(fc_single_arg_no_kwargs) == "func(1)"
+    fc_multiple_args_no_kwargs = FunctionCall("func", (1, "hello"), frozenset())
+    assert repr(fc_multiple_args_no_kwargs) == "func(1, 'hello')"
+
+def test_cache_graph_storage_function():
+    cg = CacheGraph()
+    @cg(lambda x: x**2)
+    def fib(n):
+        if n < 2:
+            return n
+        return fib(n - 1) + fib(n - 2)
+    fib(5)
+    assert len(cg.stored_results["fib"]) == 6
+    for k, v in cg.stored_results["fib"].items():
+        assert v == fib(k)**2
+
+def test_cache_dunders():
+    cg = CacheGraph()
+    @cg()
+    def fib(n):
+        if n < 2:
+            return n
+        return fib(n - 1) + fib(n - 2)
+    fib(5)
+    assert repr(fib) == "<Cache Function: fib, Size: 6>"
+    assert len(fib.cache) == 6
+    test_key = ((5,), frozenset())
+    assert fib.cache[test_key] == cg.caches['fib'][test_key] == 5
+    fib[5] = 10
+    assert fib.cache[test_key] == cg.caches['fib'][test_key] == 10
+    fib[(5,)] = 100
+    assert fib.cache[test_key] == cg.caches['fib'][test_key] == 100
+
+@pytest.mark.timeout(4)
+def test_cache_graph_memory_optimization():
+    cg = CacheGraph()
+
+    @cg()
+    def fib(n: int):
+        if n <= 1:
+            return n
+        return fib(n-1) + fib(n-2)
+
+    def get_max_memory_fib(n: int, cg: CacheGraph):
+        max_memory_size = 0
+        for i in range(n+1):
+            fib(i)
+            max_memory_size = max(max_memory_size, cg.size())
+        return max_memory_size
+
+    assert get_max_memory_fib(200, cg) == 201
+    cg.optimize_and_reset()
+    assert get_max_memory_fib(200, cg) == 2
\ No newline at end of file
diff --git a/tests/test_optimized_model.py b/tests/test_optimized_model.py
new file mode 100644
index 0000000..69f5b6e
--- /dev/null
+++ b/tests/test_optimized_model.py
@@ -0,0 +1,99 @@
+import numpy as np
+from heavylight import LightModel
+from typing import Union, Callable
+import pytest
+
+class SimpleModel(LightModel):
+
+    def __init__(self, initial_pols_if: np.ndarray, storage_function: Union[Callable, None] = None, mortality_rate = .01):
+        super().__init__(storage_function=storage_function)
+        self.initial_pols_if = initial_pols_if
+        self.mortality_rate = mortality_rate
+
+    def t(self, t):
+        return t
+
+    def num_pols_if(self, t):
+        if t == 0:
+            return self.initial_pols_if
+        else:
+            return self.num_pols_if(t - 1) - self.pols_death(t - 1) # causes exponential time complexity if uncached
+        
+    def pols_death(self, t):
+        return self.num_pols_if(t) * self.mortality_rate
+
+    def cashflow(self, t):
+        return self.num_pols_if(t) * 100
+    
+    def v(self, t):
+        """discount factor for time t --> time 0"""
+        if t == 0:
+            return 1
+        else:
+            return self.v(t - 1) / (1 + self.forward_rate(t))
+    
+    def forward_rate(self, t):
+        return 0.04
+    
+    def pv_cashflow(self, t):
+        """present value of the cashflow occuring at time t"""
+        return self.cashflow(t) * self.v(t)
+    
+    def fib(self, t, mult_factor):
+        if t <= 1:
+            return t
+        return mult_factor * self.fib(t - 1, mult_factor) + self.fib(t - 2, mult_factor)
+
+def test_forward_rate():
+    sm = SimpleModel(np.linspace(.1, 1, 10), np.sum)
+    sm.forward_rate(0)
+    assert sm.forward_rate.cache[((0,), frozenset())] == .04
+    assert len(sm.StoredResults()) == 1
+    assert len(sm.StoredResults()['forward_rate']) == 1
+    assert sm.StoredResults()['forward_rate'][0] == .04
+
+    sm.forward_rate(5)
+    assert len(sm.forward_rate.cache) == 2
+
+
+def test_timestep_functions():
+    sm = SimpleModel(np.linspace(.1, 1, 10), np.sum)
+    timestep_functions = sm.TimestepFunctions()
+    expected_functions = ['t', 'num_pols_if', 'pols_death', 'cashflow', 'v', 'pv_cashflow', 'forward_rate']
+    assert set(timestep_functions) == set(expected_functions) # no duplicates
+    sm.RunModel(5)
+    # The timestep functions are the ones displayed in StoredResults()
+    assert set(sm.StoredResults().keys()) == set(expected_functions)
+
+@pytest.mark.timeout(4)
+def test_caching():
+    sm = SimpleModel(np.linspace(.1, 1, 10), np.sum)
+    assert len(sm.num_pols_if.cache) == 0
+    sm.RunModel(200)
+    assert len(sm.num_pols_if.cache) == 201
+    assert type(sm.StoredResults()['num_pols_if'][200]) == np.float64
+    # fib did not get called because it is not single parameter time function
+    assert len(sm.fib.cache) == 0
+    sm.fib(200, 1.1)
+    # but it is still cached
+    assert len(sm.fib.cache) == 201
+
+def test_dataframe():
+    sm = SimpleModel(np.linspace(.1, 1, 10), np.sum)
+    sm.RunModel(5)
+    df = sm.ToDataFrame()
+    assert df.shape == (6, 7)
+    assert df['t'].tolist() == [0, 1, 2, 3, 4, 5]
+
+def test_reset_cache():
+    sm = SimpleModel(np.linspace(.1, 1, 10), storage_function=np.sum, mortality_rate=.01)
+    sm.RunModel(5)
+    assert round(sm.StoredResults()['pols_death'][0], 10) == .055
+    sm.mortality_rate = .02
+    sm.RunModel(5)
+    assert round(sm.StoredResults()['pols_death'][0], 10) == .055
+    sm.ResetCache()
+    sm.RunModel(5)
+    assert round(sm.StoredResults()['pols_death'][0], 10) == .11
+    
+