From 1c7a501532851fd33ad837a532013f186c3ddd7e Mon Sep 17 00:00:00 2001 From: "Rachael T. Sexton" Date: Wed, 4 Feb 2026 17:42:00 -0500 Subject: [PATCH 1/6] refactor away from coco (better docs) --- .gitignore | 1 + .gitlab-ci.yml | 1 + docs/api/contingent.md | 6 +- docs/api/plotting.md | 6 +- docs/css/mkdocstrings.css | 49 + examples/tutorial.ipynb | 190 +- pyproject.toml | 2 +- src/contingency/contingent.coco | 300 --- src/contingency/contingent.py | 3515 ++----------------------------- src/contingency/plots.py | 3 +- tests/test_contingency.py | 2 +- uv.lock | 60 +- zensical.toml | 20 +- 13 files changed, 452 insertions(+), 3703 deletions(-) delete mode 100644 src/contingency/contingent.coco diff --git a/.gitignore b/.gitignore index 8470962..704e112 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ wheels/ .hypothesis src/contingency/__coconut_cache__ site +examples/.ipynb_checkpoints diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ee6f44e..bdcfc8a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,3 +26,4 @@ zensical: publish: site rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH diff --git a/docs/api/contingent.md b/docs/api/contingent.md index 03c4805..3e6fbac 100644 --- a/docs/api/contingent.md +++ b/docs/api/contingent.md @@ -1,7 +1,9 @@ +--- +title: Contingent +--- - -::: contingency.Contingent +::: contingency.contingent handler: python options: show_root_heading: true diff --git a/docs/api/plotting.md b/docs/api/plotting.md index 13cd0d6..fda59cd 100644 --- a/docs/api/plotting.md +++ b/docs/api/plotting.md @@ -2,4 +2,8 @@ title: Plotting Utilities --- -::: contingency.plots +::: contingency.plots.PR_contour + handler: python + options: + show_root_heading: true + diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css index 6949f66..94f3c00 100644 --- a/docs/css/mkdocstrings.css +++ b/docs/css/mkdocstrings.css @@ -3,4 +3,53 @@ div.doc-contents:not(.first) { padding-left: 25px; border-left: 4px solid rgba(230, 230, 230); margin-bottom: 80px; +} + + +/* Tree-like output for backlinks. */ +.doc-backlink-list { + --tree-clr: var(--md-default-fg-color); + --tree-font-size: 1rem; + --tree-item-height: 1; + --tree-offset: 1rem; + --tree-thickness: 1px; + --tree-style: solid; + display: grid; + list-style: none !important; +} + +.doc-backlink-list li>span:first-child { + text-indent: .3rem; +} + +.doc-backlink-list li { + padding-inline-start: var(--tree-offset); + border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr); + position: relative; + margin-left: 0 !important; + + &:last-child { + border-color: transparent; + } + + &::before { + content: ''; + position: absolute; + top: calc(var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness)); + left: calc(var(--tree-thickness) * -1); + width: calc(var(--tree-offset) + var(--tree-thickness) * 2); + height: calc(var(--tree-item-height) * var(--tree-font-size)); + border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr); + border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr); + } + + &::after { + content: ''; + position: absolute; + border-radius: 50%; + background-color: var(--tree-clr); + top: calc(var(--tree-item-height) / 2 * 1rem); + left: var(--tree-offset); + translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1); + } } \ No newline at end of file diff --git a/examples/tutorial.ipynb b/examples/tutorial.ipynb index ada1984..2577b11 100644 --- a/examples/tutorial.ipynb +++ b/examples/tutorial.ipynb @@ -3,16 +3,53 @@ { "cell_type": "markdown", "id": "13c4169b-9964-4509-ac33-f7a67da84114", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "# `Contingent` Tutorial" ] }, { "cell_type": "code", - "execution_count": 49, - "id": "19abf967-31e0-4d7d-b604-1e79ecab104e", + "execution_count": 27, + "id": "7413f7c1-1035-4af0-acc9-67c04bf7edb4", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('F', 'F2', 'G', 'recall', 'precision', 'mcc', 'aps')" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from contingency.contingent import ScoreOptions\n", + "from typing import get_args\n", + "\n", + "get_args(ScoreOptions.__value__)\n", + "# ScoreOptions." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "19abf967-31e0-4d7d-b604-1e79ecab104e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ "import numpy as np\n", @@ -25,9 +62,15 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 22, "id": "837cfc6c-a1fc-4b17-8b0e-99ca3ffc17b2", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ "y_true = np.array([0,1,0,0,1]).astype(bool)\n", @@ -37,16 +80,28 @@ { "cell_type": "markdown", "id": "a8e531bf-bb69-4c10-aeec-6dd30629c209", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Basic Instantiation" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 7, "id": "ec520468-da30-4b2a-82a6-931e8285e822", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "data": { @@ -120,7 +175,13 @@ { "cell_type": "markdown", "id": "5b7b49c9-fd30-4bfe-9e5d-6a38cb9aff36", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "We now have access to properties that will return useful metrics from these contingency counts, such as \n", "\n", @@ -132,9 +193,15 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "id": "0a1a2fc8-2525-42f4-b911-2452a89c2d1f", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "data": { @@ -167,7 +234,13 @@ { "cell_type": "markdown", "id": "3e3ed63f-57d9-44bb-b820-9027657f4caa", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "## Contingencies from Probabilities\n", "\n", @@ -179,9 +252,15 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 9, "id": "aaa3f615-1760-46b2-8b70-a03a4ae6ab04", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [], "source": [ "y_prob = np.array([0.1,0.8,0.1,.7,.25])" @@ -189,9 +268,15 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "id": "003886cb-34e1-4023-bbac-4878daaa26b1", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "outputs": [ { "data": { @@ -224,7 +309,7 @@ "(6, 5)" ] }, - "execution_count": 6, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -239,7 +324,13 @@ { "cell_type": "markdown", "id": "c5398246-39dc-4afc-9448-5c83c292a690", - "metadata": {}, + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, "source": [ "Note how the number of positives decreases as the threshold increases. \n", "\n", @@ -248,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 11, "id": "8b5b687d-b799-4930-a502-c9d2fabf668f", "metadata": {}, "outputs": [ @@ -289,7 +380,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "id": "bf03a42f-238d-4f8d-acdd-5966c40c2109", "metadata": {}, "outputs": [ @@ -309,11 +400,11 @@ { "data": { "text/html": [ - "
0.39492652768935094\n",
+       "
0.3949265276893509\n",
        "
\n" ], "text/plain": [ - "\u001b[1;36m0.39492652768935094\u001b[0m\n" + "\u001b[1;36m0.3949265276893509\u001b[0m\n" ] }, "metadata": {}, @@ -322,11 +413,11 @@ { "data": { "text/html": [ - "
0.6481253367346939\n",
+       "
0.648125336734694\n",
        "
\n" ], "text/plain": [ - "\u001b[1;36m0.6481253367346939\u001b[0m\n" + "\u001b[1;36m0.648125336734694\u001b[0m\n" ] }, "metadata": {}, @@ -351,23 +442,23 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "id": "6c63145e-7d81-4b04-801b-999394dc3684", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[]" + "[]" ] }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -404,7 +495,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 14, "id": "6727a30a-1b52-4657-8247-fea0dc9f3ff3", "metadata": {}, "outputs": [], @@ -418,7 +509,7 @@ }, { "cell_type": "code", - "execution_count": 90, + "execution_count": 15, "id": "ba9220fb-f97c-4226-b7a9-5efdbce8a55f", "metadata": {}, "outputs": [ @@ -426,9 +517,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "1.07 ms ± 231 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n", - "2.83 ms ± 55.4 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", - "30.3 μs ± 341 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + "681 μs ± 8.29 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n", + "5.24 ms ± 190 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "38.5 μs ± 868 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" ] } ], @@ -454,7 +545,7 @@ }, { "cell_type": "code", - "execution_count": 91, + "execution_count": 16, "id": "7ca9e17b-055f-4523-9681-241b933e67c1", "metadata": {}, "outputs": [ @@ -462,14 +553,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "1.36 s ± 576 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", - "176 μs ± 10.9 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + "1.48 s ± 34.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "310 μs ± 2.8 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" ] } ], "source": [ "%timeit np.mean([matthews_corrcoef(y_true,x) for x in Mbig.y_pred])\n", - "%timeit M1000.expected('mcc')" + "%timeit Mbig.expected('mcc')" ] }, { @@ -485,13 +576,13 @@ }, { "cell_type": "code", - "execution_count": 96, + "execution_count": 17, "id": "9d3e34c1-d351-490d-afc6-1c7a103818ba", "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -524,13 +615,13 @@ }, { "cell_type": "code", - "execution_count": 97, + "execution_count": 18, "id": "db21c2c1-610a-4bc5-a292-08422141a747", "metadata": {}, "outputs": [ { "data": { - "image/png": "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/PPPx7333otoNIprr70WAHDNNddgYGAAu3fvBgDccMMNuOiii/CNb3wD73rXu/DII4/gL3/5C773ve/V820QBEEQTUrdhfKKK67A1NQUbrvtNvj9fpx99tl48sknecLO8PBw1qyxLVu24Cc/+QluvfVW3HLLLVi/fj1+8YtfYOPGjfV6CwRBEEQTU3ehBIDrr79+UVfrU089tWDbe9/7Xrz3ve8t67WsVit27dqV1x3b6tC+yQ/tl8WhfZMf2i+LY8Z9U/eGAwRBEATRyNS9hR1BEARBNDIklARBEARRABJKgiAIgigACSVBEARBFKAphZLmWy5OKfvm+9//Pi688EK0tbWhra0NW7duXXJfmpVSvzOMRx55BIIg8IYZzUip+yYYDOK6665DX18frFYrNmzY0JS/qVL3y7333otTTjkFdrsdg4ODuPHGG5FIJGq02trwb//2b7jsssvQ398PQRDwi1/8YsnHPPXUUzjnnHNgtVqxbt06PPzww1VfZ8noTcYjjzyiWywW/aGHHtIPHz6sf+QjH9F9Pp8+MTGR9/5/+tOfdEmS9K997Wv6iy++qN966626oij6Cy+8UOOVV59S982VV16p33///fpzzz2nv/TSS/oHPvAB3ev16iMjIzVeeXUpdb8wjh07pg8MDOgXXnih/t/+23+rzWJrTKn7JplM6uedd56+fft2/emnn9aPHTumP/XUU/rBgwdrvPLqUup++fGPf6xbrVb9xz/+sX7s2DH917/+td7X16ffeOONNV55ddmzZ4/+hS98Qf/5z3+uA9D/9V//teD9X3/9dd3hcOg7d+7UX3zxRf3b3/62LkmS/uSTT9ZmwUXSdEJ5/vnn69dddx2/nU6n9f7+fn337t157/++971Pf9e73pW17YILLtD//u//vqrrrAel7ptcNE3T3W63/sMf/rBaS6wL5ewXTdP0LVu26P/8z/+s79ixo2mFstR9893vfldfs2aNrqpqrZZYF0rdL9ddd53+9re/PWvbzp079be+9a1VXWc9KUYoP/e5z+lnnHFG1rYrrrhC37ZtWxVXVjpN5Xql+ZaLU86+ySUWiyGVSqG9vb1ay6w55e6XL3/5y+ju7saHPvShWiyzLpSzb375y19i8+bNuO6669DT04ONGzfirrvuQjqdrtWyq045+2XLli3Yv38/d8++/vrr2LNnD7Zv316TNTcqZjn+NkRnnkpRi/mWZqWcfZPL5z//efT39y/4YpuZcvbL008/jQcffBAHDx6swQrrRzn75vXXX8fvf/97XHXVVdizZw9ee+01fOITn0AqlcKuXbtqseyqU85+ufLKKzE9PY23ve1t0HUdmqbhYx/7GG655ZZaLLlhWez4GwqFEI/HYbfb67SybJrKoiSqx1e/+lU88sgj+Nd//VfYbLZ6L6duhMNhXH311fj+97+Pzs7Oei+n4chkMuju7sb3vvc9nHvuubjiiivwhS98AQ888EC9l1ZXnnrqKdx11134zne+gwMHDuDnP/85nnjiCdxxxx31XhpRBE1lUdZivqVZKWffML7+9a/jq1/9Kn73u9/hrLPOquYya06p++Xo0aM4fvw4LrvsMr4tk8kAAGRZxpEjR7B27drqLrpGlPOd6evrg6IokCSJbzvttNPg9/uhqiosFktV11wLytkvX/ziF3H11Vfjwx/+MADgzDPPRDQaxUc/+lF84QtfyBr80Eosdvz1eDwNY00CTWZRGudbMth8y8XmVbL5lkYKzbc0K+XsGwD42te+hjvuuANPPvkkzjvvvFostaaUul9OPfVUvPDCCzh48CC/vOc978HFF1+MgwcPYnBwsJbLryrlfGfe+ta34rXXXuMnDwDwyiuvoK+vrylEEihvv8RisQViyE4m9BZut22a42+9s4kqzSOPPKJbrVb94Ycf1l988UX9ox/9qO7z+XS/36/ruq5fffXV+k033cTv/6c//UmXZVn/+te/rr/00kv6rl27mro8pJR989WvflW3WCz6Y489po+Pj/NLOByu11uoCqXul1yaOeu11H0zPDysu91u/frrr9ePHDmiP/7443p3d7d+55131ustVIVS98uuXbt0t9ut/+///b/1119/Xf/Nb36jr127Vn/f+95Xr7dQFcLhsP7cc8/pzz33nA5Av+eee/TnnntOP3HihK7run7TTTfpV199Nb8/Kw/57Gc/q7/00kv6/fffT+UhteLb3/62vnLlSt1isejnn3++/uc//5n/76KLLtJ37NiRdf+f/vSn+oYNG3SLxaKfccYZ+hNPPFHjFdeOUvbNqlWrdAALLrt27ar9wqtMqd8ZI80slLpe+r7Zt2+ffsEFF+hWq1Vfs2aN/pWvfEXXNK3Gq64+peyXVCqlf+lLX9LXrl2r22w2fXBwUP/EJz6hBwKB2i+8ivzhD3/Ie8xg+2LHjh36RRddtOAxZ599tm6xWPQ1a9boP/jBD2q+7qWgMVsEQRAEUYCmilESBEEQRKUhoSQIgiCIApBQEgRBEEQBSCgJgiAIogAklARBEARRABJKgiAIgigACSVBEARBFICEkiCIkjBOrj9+/DgEQWj6SSpEa0NCSRAm4gMf+AAEQYAgCFAUBatXr8bnPvc5JBKJei+NIJqWppoeQhCtwDvf+U784Ac/QCqVwv79+7Fjxw4IgoC777673ksjiKaELEqCMBlWqxW9vb0YHBzE5Zdfjq1bt+K3v/0tgPkpFrt378bq1atht9uxadMmPPbYY1mPP3z4MN797nfD4/HA7XbjwgsvxNGjRwEA//mf/4lLLrkEnZ2d8Hq9uOiii3DgwIGav0eCaCRIKAnCxBw6dAj79u3jI6x2796NH/3oR3jggQdw+PBh3Hjjjfi7v/s7/PGPfwQAjI6O4q/+6q9gtVrx+9//Hvv378cHP/hBaJoGYH4w9Y4dO/D000/jz3/+M9avX4/t27cjHA7X7T0SRL0h1ytBmIzHH38cLpcLmqYhmUxCFEXcd999SCaTuOuuu/C73/2Oz/Nbs2YNnn76afzTP/0TLrroItx///3wer145JFHoCgKAGDDhg38ud/+9rdnvdb3vvc9+Hw+/PGPf8S73/3u2r1JgmggSCgJwmRcfPHF+O53v4toNIpvfvObkGUZf/u3f4vDhw8jFovhkksuybq/qqp405veBAA4ePAgLrzwQi6SuUxMTODWW2/FU089hcnJSaTTacRiMQwPD1f9fRFEo0JCSRAmw+l0Yt26dQCAhx56CJs2bcKDDz6IjRs3AgCeeOIJDAwMZD3GarUCAOx2e8Hn3rFjB2ZmZvCtb30Lq1atgtVqxebNm6GqahXeCUGYAxJKgjAxoijilltuwc6dO/HKK6/AarVieHgYF110Ud77n3XWWfjhD3+IVCqV16r805/+hO985zvYvn07AODkyZOYnp6u6nsgiEaHknkIwuS8973vhSRJ+Kd/+id85jOfwY033ogf/vCHOHr0KA4cOIBvf/vb+OEPfwgAuP766xEKhfA//sf/wF/+8he8+uqr+Jd/+RccOXIEALB+/Xr8y7/8C1566SX8x3/8B6666qolrVCCaHbIoiQIkyPLMq6//np87Wtfw7Fjx9DV1YXdu3fj9ddfh8/nwznnnINbbrkFANDR0YHf//73+OxnP4uLLroIkiTh7LPPxlvf+lYAwIMPPoiPfvSjOOecczA4OIi77roLn/nMZ+r59gii7gi6ruv1XgRBEARBNCrkeiUIgiCIApBQEgRBEEQBSCgJgiAIogAklARBEARRABJKgiAIgigACSVBEARBFICEkiAIgiAKQEJJEARBEAUgoSQIgiCIApBQEgRBEEQBSCgJgiAIogAklARBEARRgP8fXpUSO+oc1DIAAAAASUVORK5CYII=", + "image/png": "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/PPPx7333otoNIprr70WAHDNNddgYGAAu3fvBgDccMMNuOiii/CNb3wD73rXu/DII4/gL3/5C773ve/V820QBEEQTUrdhfKKK67A1NQUbrvtNvj9fpx99tl48sknecLO8PBw1qyxLVu24Cc/+QluvfVW3HLLLVi/fj1+8YtfYOPGjfV6CwRBEEQTU3ehBIDrr79+UVfrU089tWDbe9/7Xrz3ve8t67WsVit27dqV1x3b6tC+yQ/tl8WhfZMf2i+LY8Z9U/eGAwRBEATRyNS9hR1BEARBNDIklARBEARRABJKgiAIgigACSVBEARBFKAphZLmWy5OKfvm+9//Pi688EK0tbWhra0NW7duXXJfmpVSvzOMRx55BIIg8IYZzUip+yYYDOK6665DX18frFYrNmzY0JS/qVL3y7333otTTjkFdrsdg4ODuPHGG5FIJGq02trwb//2b7jsssvQ398PQRDwi1/8YsnHPPXUUzjnnHNgtVqxbt06PPzww1VfZ8noTcYjjzyiWywW/aGHHtIPHz6sf+QjH9F9Pp8+MTGR9/5/+tOfdEmS9K997Wv6iy++qN966626oij6Cy+8UOOVV59S982VV16p33///fpzzz2nv/TSS/oHPvAB3ev16iMjIzVeeXUpdb8wjh07pg8MDOgXXnih/t/+23+rzWJrTKn7JplM6uedd56+fft2/emnn9aPHTumP/XUU/rBgwdrvPLqUup++fGPf6xbrVb9xz/+sX7s2DH917/+td7X16ffeOONNV55ddmzZ4/+hS98Qf/5z3+uA9D/9V//teD9X3/9dd3hcOg7d+7UX3zxRf3b3/62LkmS/uSTT9ZmwUXSdEJ5/vnn69dddx2/nU6n9f7+fn337t157/++971Pf9e73pW17YILLtD//u//vqrrrAel7ptcNE3T3W63/sMf/rBaS6wL5ewXTdP0LVu26P/8z/+s79ixo2mFstR9893vfldfs2aNrqpqrZZYF0rdL9ddd53+9re/PWvbzp079be+9a1VXWc9KUYoP/e5z+lnnHFG1rYrrrhC37ZtWxVXVjpN5Xql+ZaLU86+ySUWiyGVSqG9vb1ay6w55e6XL3/5y+ju7saHPvShWiyzLpSzb375y19i8+bNuO6669DT04ONGzfirrvuQjqdrtWyq045+2XLli3Yv38/d8++/vrr2LNnD7Zv316TNTcqZjn+NkRnnkpRi/mWZqWcfZPL5z//efT39y/4YpuZcvbL008/jQcffBAHDx6swQrrRzn75vXXX8fvf/97XHXVVdizZw9ee+01fOITn0AqlcKuXbtqseyqU85+ufLKKzE9PY23ve1t0HUdmqbhYx/7GG655ZZaLLlhWez4GwqFEI/HYbfb67SybJrKoiSqx1e/+lU88sgj+Nd//VfYbLZ6L6duhMNhXH311fj+97+Pzs7Oei+n4chkMuju7sb3vvc9nHvuubjiiivwhS98AQ888EC9l1ZXnnrqKdx11134zne+gwMHDuDnP/85nnjiCdxxxx31XhpRBE1lUdZivqVZKWffML7+9a/jq1/9Kn73u9/hrLPOquYya06p++Xo0aM4fvw4LrvsMr4tk8kAAGRZxpEjR7B27drqLrpGlPOd6evrg6IokCSJbzvttNPg9/uhqiosFktV11wLytkvX/ziF3H11Vfjwx/+MADgzDPPRDQaxUc/+lF84QtfyBr80Eosdvz1eDwNY00CTWZRGudbMth8y8XmVbL5lkYKzbc0K+XsGwD42te+hjvuuANPPvkkzjvvvFostaaUul9OPfVUvPDCCzh48CC/vOc978HFF1+MgwcPYnBwsJbLryrlfGfe+ta34rXXXuMnDwDwyiuvoK+vrylEEihvv8RisQViyE4m9BZut22a42+9s4kqzSOPPKJbrVb94Ycf1l988UX9ox/9qO7z+XS/36/ruq5fffXV+k033cTv/6c//UmXZVn/+te/rr/00kv6rl27mro8pJR989WvflW3WCz6Y489po+Pj/NLOByu11uoCqXul1yaOeu11H0zPDysu91u/frrr9ePHDmiP/7443p3d7d+55131ustVIVS98uuXbt0t9ut/+///b/1119/Xf/Nb36jr127Vn/f+95Xr7dQFcLhsP7cc8/pzz33nA5Av+eee/TnnntOP3HihK7run7TTTfpV199Nb8/Kw/57Gc/q7/00kv6/fffT+UhteLb3/62vnLlSt1isejnn3++/uc//5n/76KLLtJ37NiRdf+f/vSn+oYNG3SLxaKfccYZ+hNPPFHjFdeOUvbNqlWrdAALLrt27ar9wqtMqd8ZI80slLpe+r7Zt2+ffsEFF+hWq1Vfs2aN/pWvfEXXNK3Gq64+peyXVCqlf+lLX9LXrl2r22w2fXBwUP/EJz6hBwKB2i+8ivzhD3/Ie8xg+2LHjh36RRddtOAxZ599tm6xWPQ1a9boP/jBD2q+7qWgMVsEQRAEUYCmilESBEEQRKUhoSQIgiCIApBQEgRBEEQBSCgJgiAIogAklARBEARRABJKgiAIgigACSVBEARBFICEkiCIkjBOrj9+/DgEQWj6SSpEa0NCSRAm4gMf+AAEQYAgCFAUBatXr8bnPvc5JBKJei+NIJqWppoeQhCtwDvf+U784Ac/QCqVwv79+7Fjxw4IgoC777673ksjiKaELEqCMBlWqxW9vb0YHBzE5Zdfjq1bt+K3v/0tgPkpFrt378bq1atht9uxadMmPPbYY1mPP3z4MN797nfD4/HA7XbjwgsvxNGjRwEA//mf/4lLLrkEnZ2d8Hq9uOiii3DgwIGav0eCaCRIKAnCxBw6dAj79u3jI6x2796NH/3oR3jggQdw+PBh3Hjjjfi7v/s7/PGPfwQAjI6O4q/+6q9gtVrx+9//Hvv378cHP/hBaJoGYH4w9Y4dO/D000/jz3/+M9avX4/t27cjHA7X7T0SRL0h1ytBmIzHH38cLpcLmqYhmUxCFEXcd999SCaTuOuuu/C73/2Oz/Nbs2YNnn76afzTP/0TLrroItx///3wer145JFHoCgKAGDDhg38ud/+9rdnvdb3vvc9+Hw+/PGPf8S73/3u2r1JgmggSCgJwmRcfPHF+O53v4toNIpvfvObkGUZf/u3f4vDhw8jFovhkksuybq/qqp405veBAA4ePAgLrzwQi6SuUxMTODWW2/FU089hcnJSaTTacRiMQwPD1f9fRFEo0JCSRAmw+l0Yt26dQCAhx56CJs2bcKDDz6IjRs3AgCeeOIJDAwMZD3GarUCAOx2e8Hn3rFjB2ZmZvCtb30Lq1atgtVqxebNm6GqahXeCUGYAxJKgjAxoijilltuwc6dO/HKK6/AarVieHgYF110Ud77n3XWWfjhD3+IVCqV16r805/+hO985zvYvn07AODkyZOYnp6u6nsgiEaHknkIwuS8973vhSRJ+Kd/+id85jOfwY033ogf/vCHOHr0KA4cOIBvf/vb+OEPfwgAuP766xEKhfA//sf/wF/+8he8+uqr+Jd/+RccOXIEALB+/Xr8y7/8C1566SX8x3/8B6666qolrVCCaHbIoiQIkyPLMq6//np87Wtfw7Fjx9DV1YXdu3fj9ddfh8/nwznnnINbbrkFANDR0YHf//73+OxnP4uLLroIkiTh7LPPxlvf+lYAwIMPPoiPfvSjOOecczA4OIi77roLn/nMZ+r59gii7gi6ruv1XgRBEARBNCrkeiUIgiCIApBQEgRBEEQBSCgJgiAIogAklARBEARRABJKgiAIgigACSVBEARBFICEkiAIgiAKQEJJEARBEAUgoSQIgiCIApBQEgRBEEQBSCgJgiAIogAklARBEARRgP8fXpUSO+oc1DIAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -559,7 +650,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 19, "id": "d16b5fb4-3639-4af0-9878-c35bc4228363", "metadata": {}, "outputs": [ @@ -567,9 +658,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "511 ms ± 21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", - "678 ms ± 203 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", - "28 μs ± 411 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + "270 ms ± 11.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "793 ms ± 34.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "35.3 μs ± 857 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" ] } ], @@ -589,18 +680,18 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 20, "id": "ef7dffb5-a90f-413b-99fb-7ba3172a4e6e", "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
0.9865 0.9858\n",
+       "
0.9867 0.986\n",
        "
\n" ], "text/plain": [ - "\u001b[1;36m0.9865\u001b[0m \u001b[1;36m0.9858\u001b[0m\n" + "\u001b[1;36m0.9867\u001b[0m \u001b[1;36m0.986\u001b[0m\n" ] }, "metadata": {}, @@ -632,6 +723,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" + }, + "pixi-kernel": { + "environment": "default" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index 7e8ae41..dc45983 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,6 @@ requires-python = ">=3.11, <3.14" dependencies = [ "jaxtyping>=0.3.3", "numpy>=2.3.5", - "scikit-learn>=1.7.2", "scipy>=1.16.3", ] @@ -33,6 +32,7 @@ dev = [ "mkdocstrings-python>=2.0.1", "pytest>=9.0.1", "rich[jupyter]>=14.2.0", + "scikit-learn>=1.8.0", "zensical>=0.0.20", ] diff --git a/src/contingency/contingent.coco b/src/contingency/contingent.coco deleted file mode 100644 index 1852f60..0000000 --- a/src/contingency/contingent.coco +++ /dev/null @@ -1,300 +0,0 @@ -import numpy as np -from numpy import ndarray as nda -# from sklearn.metrics import precision_recall_curve, fbeta_score -from scipy.stats import ecdf -from scipy.integrate import trapezoid - -from typing import Literal -from jaxtyping import Bool, Num, jaxtyped -from beartype import beartype as typechecker -from dataclasses import dataclass, field -from sklearn.preprocessing import minmax_scale -import warnings - -__all__ = [ - "Contingent", -] - -type ScoreOptions = Literal[ - 'F', - 'F2', - 'G', - 'recall', - 'precision', - 'mcc', - 'aps' -] - -type PredProb = Num[nda, 'features'] -type ProbThres = Num[nda, '*#batch'] -type PredThres = Bool[nda, '*#batch features'] - -def quantile_tf(x:PredProb)-> (ProbThres,PredProb): - cdf = ecdf(x).cdf - p = cdf.probabilities |> np.pad$(?, ((1,1)), constant_values=(0,1)) - return p, cdf.evaluate(x) - -@jaxtyped(typechecker=typechecker) -def minmax_tf( - x:Num[nda, 'feat'] -)-> (Num[nda,'*#batch'], Num[nda, 'feat']): - x_p = minmax_scale(x, feature_range=(1e-5, 1 - 1e-5)) - p = np.pad(np.unique(x_p), ((1,1)), constant_values=(0,1)) - return p, x_p - -# def _all_thres(x:PredProb, t:ProbThres)->PredThres: - # return np.less_equal.outer(t, x) - -#TODO use density (.getnnz()) for sparse via dispatching -@jaxtyped(typechecker=typechecker) -def _bool_contract( - A:Bool[nda, '*#batch feat'], - B:Bool[nda, '*#batch feat'] -)-> Num[nda, '*#batch'] = (A*B).sum(axis=-1) - -def _TP(actual,pred) = _bool_contract( pred, actual) -def _FP(actual,pred) = _bool_contract( pred,~actual) -def _FN(actual,pred) = _bool_contract(~pred, actual) -def _TN(actual,pred) = _bool_contract(~pred,~actual) - -@jaxtyped(typechecker=typechecker) -@dataclass -class Contingent: - """ dataclass to hold true and (batched) predicted values - - Parameters: - y_true: True positive and negative binary classifications - y_pred: Predicted, possible batched (tensor) - weights: weight(s) for y_pred, useful for expected values of scores - - Properties: - f_beta: beta-weighted harmonic mean of precision and recall - F: alias for f_beta(1) - recall: a.k.a. true-positive rate - precision: a.k.a. positive-predictive-value (PPV) - mcc: Matthew's Correlation Coefficient - G: Fowlkes-Mallows score (geometric mean of precision and recall) - """ - y_true: Bool[nda, 'feat'] - y_pred: Bool[nda, '*#batch feat'] - - weights: Num[nda, '*#batch']|None = None - - TP: Num[nda, "..."] = field(init=False) - FP: Num[nda, "..."] = field(init=False) - FN: Num[nda, "..."] = field(init=False) - TN: Num[nda, "..."] = field(init=False) - - - PP: Num[nda, "..."] = field(init=False) - PN: Num[nda, "..."] = field(init=False) - P: Num[nda, "..."] = field(init=False) - N: Num[nda, "..."] = field(init=False) - - - PPV: Num[nda, "..."] = field(init=False) - NPV: Num[nda, "..."] = field(init=False) - TPR: Num[nda, "..."] = field(init=False) - TNR: Num[nda, "..."] = field(init=False) - - def __post_init__(self): - self.y_true = np.atleast_2d(self.y_true) - self.y_pred = np.atleast_2d(self.y_pred) - self.TP = _TP(self.y_true, self.y_pred) - self.FP = _FP(self.y_true, self.y_pred) - self.FN = _FN(self.y_true, self.y_pred) - self.TN = _TN(self.y_true, self.y_pred) - - self.PP = self.TP + self.FP - self.PN = self.FN + self.TN - self.P = self.TP + self.FN - self.N = self.FP + self.TN - - # self.PPV = np.divide(self.TP, self.PP, out=np.ones_like(self.TP), where=self.PP!=0.) - self.PPV = np.ma.divide(self.TP, self.PP) - self.NPV = np.ma.divide(self.TN, self.PN) - self.TPR = np.ma.divide(self.TP, self.P) - self.TNR = np.ma.divide(self.TN, self.N) - - - @classmethod - def from_scalar[T]( - cls: Type[T], - y_true: PredProb, - x:PredProb?, - subsamples:int?=None - )->T?: - """ take scalar predictions and generate (batched) Contingent - - by default, x is rescaled to [0,1] and used as the weights parameter - for the Contingent constructor. Only unique values are needed, since - the thresholding only changes with each unique prediction value. - - Uses numpy's `less_equal.outer` to accomplish fast, vectorized thresholding - and enable rapid estimation of batched scores accross all thresholds. - - - Parameters: - y_true: True pos/neg binary vector - x: scalar weights for relative prediction strength (positive) - """ - # p, x_p = quantile_tf(x) - if x is None: - warnings.warn("`None` value recieved, passing the buck...") - return None - p, x_p = minmax_tf(x) - if subsamples: - p = np.interp( - np.linspace(0,1,subsamples), - np.linspace(0,1,p.shape[0]), - p - ) - y_preds = np.less_equal.outer(p,x_p) - - return cls(y_true, y_preds, weights=p) - - - - def f_beta(self, beta=1): - """Fᵦ score - - weighted harmonic mean of precision and recall, with β-times - more bias for recall. - """ - return f_beta(beta, self) - - @property - def F2(self): - """F₂ harmonic mean with recall weighted 2x over precision""" - return f_beta(2., self) - - @property - def F(self) : - """F₁ score (harmonic mean of recall, precision)""" - return F1(self) - - @property - def recall(self): - """i.e. True Positive Rate TP/(TP+FN)""" - return recall(self) - - @property - def precision(self): - """i.e. Positive Predictive Value TP/(TP+FP)""" - return precision(self) - - @property - def mcc(self): - """ Matthew's Correlation Coefficient (MCC) - - Widely considered the most fair/least bias metric for imbalanced - classification tasks. - """ - return matthews_corrcoef(self) - - @property - def G(self): - """ Fowlkes-Mallows, the geometric mean of precision and recall. - - commonly used in unsupervised cases where synthetic test-data - has been made available (e.g. MENDR, clustering validation, etc.) - """ - return fowlkes_mallows(self) - - @typechecker - def expected(self, mode: ScoreOptions='aps')->float: - """ - A convenience function to calculate the expected value of a score. - - Usually for use in tandem with `Contingent.from_scalar()`, since scores will be given over a range of weights (via self.weights) - - Expected value is approximated with numerical integration via the trapezoidal rule. - The exception is for Average Precision Score, which is calculated over the range of Recall scores and has been made to use a simple 1-st order difference so that scores match those derived by Scikit-learn. - - Parameters: - mode: available scores that can be aggregated over the y_pred probabilities - """ - if mode=='aps': - return avg_precision_score(self) - else: - return trapezoid(getattr(self, mode), x=self.weights) - -# def PPV(Yt:PredThres,Pt:PredThres) = TP/PP -# def NPV(Yt:PredThres,Pt:PredThres) = TN/PN -# def TPR(Yt:PredThres,Pt:PredThres) = TP/ -# def TNR(Yt:PredThres,Pt:PredThres) = _bool_contract(~Pt,~Yt) - -def recall(Y:Contingent)->ProbThres: - """True Positive Rate""" - return Y.TPR.filled(1.) - - -def precision(Y:Contingent)->ProbThres: - """Positive Predictive Value""" - return Y.PPV.filled(1.) - - -def f_beta(beta:float, Y:Contingent)-> ProbThres: - """F_beta score - - weighted harmonic mean of precision and recall, with beta-times - more bias for recall. - """ - top = (1+beta**2)*Y.PPV*Y.TPR - bottom = beta**2*Y.PPV + Y.TPR - - return np.ma.divide(top, bottom).filled(0.) - -def F1(Y:Contingent)->ProbThres: - """partially applied f_beta with beta=1 (equal/no bias) - """ - return f_beta(1., Y) - - -def matthews_corrcoef(Y:Contingent)->ProbThres: - """ Matthew's Correlation Coefficient (MCC) - - Widely considered the most fair/least bias metric for imbalanced - classification tasks. - """ - return (l - r).filled(0) where: - m = np.vstack([Y.TPR,Y.TNR,Y.PPV,Y.NPV]) - l = np.sqrt(m).prod(axis=0) - r = np.sqrt(1-m).prod(axis=0) - # return 1-cdist(Y.y_pred, Y.y_true, "correlation")[:,0] - -def fowlkes_mallows(Y:Contingent)->ProbThres: - return np.sqrt(recall(Y)*precision(Y)) - -def avg_precision_score(Y:Contingent)->float: - """ """ - return np.sum(np.diff(Y.recall[::-1], prepend=0) * Y.precision[::-1]) - -# def precision(y_true, y_pred): -# TP,FP,TN,FN = _retrieval_square(y_true, p_pred) - -# def _wasserstein_gaussian(C1, C2): -# a = np.trace(C1+C2) -# sqrtC1 = sqrtm(C1) -# b = np.trace(sqrtm(sqrtC1@C2@sqrtC1)) - -# X = rw.to_array() -# # print(a,b) -# return a - 2*b - -# @jaxtyped(typechecker=beartype) -# def bhattacharyya(a:PredProb,b:PredProb): -# """non-metric distance between distributions""" -# return np.sqrt(a*b).sum(axis=0) - - -# @jaxtyped(typechecker=beartype) -# def hellinger(a:PredProb,b:PredProb): -# """distance metric between binary distributions""" -# return np.sqrt(1-bhattacharyya(a,b)) - -# @jaxtyped(typechecker=beartype) -# def thres_expect(x_thres:Num[nda,'t'], score:Num[nda, 't'])->float: -# # return 0.5*thres_expect(stats.beta(0.5,0.5),x_thres, score)+0.5*thres_expect(stats.beta(2.5,1.7),x_thres,score) -# # return thres_expect(stats.beta(2.5,1.7), x_thres,score) -# return trapezoid(score, x=x_thres) diff --git a/src/contingency/contingent.py b/src/contingency/contingent.py index 1686690..3ebeb46 100644 --- a/src/contingency/contingent.py +++ b/src/contingency/contingent.py @@ -1,3110 +1,74 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# __coconut_hash__ = 0x845b27b9 - -# Compiled with Coconut version 3.1.2-post_dev7 - -# Coconut Header: ------------------------------------------------------------- - -from __future__ import print_function, absolute_import, unicode_literals, division -import sys as _coconut_sys -import os as _coconut_os -try: - __file__ = _coconut_os.path.abspath(__file__) if __file__ else __file__ -except NameError: - pass -else: - if __file__ and str('__coconut_cache__') in __file__: - _coconut_file_comps = [] - while __file__: - __file__, _coconut_file_comp = _coconut_os.path.split(__file__) - if not _coconut_file_comp: - _coconut_file_comps.append(__file__) - break - if _coconut_file_comp != str('__coconut_cache__'): - _coconut_file_comps.append(_coconut_file_comp) - __file__ = _coconut_os.path.join(*reversed(_coconut_file_comps)) -_coconut_cached__coconut__ = _coconut_sys.modules.get(str('_coconut_cached__coconut__'), _coconut_sys.modules.get(str('__coconut__'))) -if _coconut_sys.version_info < (3,): - - import functools as _coconut_functools - _coconut_getattr = getattr - def _coconut_wraps(base_func): - def wrap(new_func): - new_func_module = _coconut_getattr(new_func, "__module__") - _coconut_functools.update_wrapper(new_func, base_func) - if new_func_module is not None: - new_func.__module__ = new_func_module - return new_func - return wrap - from __builtin__ import chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, raw_input, xrange, repr, long - py_bytes, py_chr, py_dict, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_super, py_zip, py_filter, py_reversed, py_enumerate, py_raw_input, py_xrange, py_repr, py_min, py_max = bytes, chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, raw_input, xrange, repr, min, max - _coconut_py_raw_input, _coconut_py_xrange, _coconut_py_int, _coconut_py_long, _coconut_py_print, _coconut_py_str, _coconut_py_super, _coconut_py_unicode, _coconut_py_repr, _coconut_py_dict, _coconut_py_bytes, _coconut_py_min, _coconut_py_max = raw_input, xrange, int, long, print, str, super, unicode, repr, dict, bytes, min, max - from collections import Sequence as _coconut_Sequence - from future_builtins import * - chr, str = unichr, unicode - from io import open - class object(object): - __slots__ = () - def __ne__(self, other): - eq = self == other - return _coconut.NotImplemented if eq is _coconut.NotImplemented else not eq - def __nonzero__(self): - if _coconut.hasattr(self, "__bool__"): - got = self.__bool__() - if not _coconut.isinstance(got, _coconut.bool): - raise _coconut.TypeError("__bool__ should return bool, returned " + _coconut.type(got).__name__) - return got - return True - class int(_coconut_py_int): - __slots__ = () - __doc__ = getattr(_coconut_py_int, "__doc__", "") - class __metaclass__(type): - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, (_coconut_py_int, _coconut_py_long)) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, (_coconut_py_int, _coconut_py_long)) - class bytes(_coconut_py_bytes): - __slots__ = () - __doc__ = getattr(_coconut_py_bytes, "__doc__", "") - class __metaclass__(type): - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_py_bytes) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, _coconut_py_bytes) - def __new__(self, *args): - if not args: - return b"" - elif _coconut.len(args) == 1: - if _coconut.isinstance(args[0], _coconut.int): - return b"\x00" * args[0] - elif _coconut.isinstance(args[0], _coconut.bytes): - return _coconut_py_bytes(args[0]) - else: - return b"".join(_coconut.chr(x) for x in args[0]) - else: - return args[0].encode(*args[1:]) - class range(object): - __slots__ = ("_xrange",) - __doc__ = getattr(_coconut_py_xrange, "__doc__", "") - def __init__(self, *args): - self._xrange = _coconut_py_xrange(*args) - def __iter__(self): - return _coconut.iter(self._xrange) - def __reversed__(self): - return _coconut.reversed(self._xrange) - def __len__(self): - return _coconut.len(self._xrange) - def __bool__(self): - return _coconut.bool(self._xrange) - def __contains__(self, elem): - return elem in self._xrange - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - args = _coconut.slice(*self._args) - start, stop, step = (args.start if args.start is not None else 0), args.stop, (args.step if args.step is not None else 1) - if index.start is None: - new_start = start if index.step is None or index.step >= 0 else stop - step - elif index.start >= 0: - new_start = start + step * index.start - if (step >= 0 and new_start >= stop) or (step < 0 and new_start <= stop): - new_start = stop - else: - new_start = stop + step * index.start - if (step >= 0 and new_start <= start) or (step < 0 and new_start >= start): - new_start = start - if index.stop is None: - new_stop = stop if index.step is None or index.step >= 0 else start - step - elif index.stop >= 0: - new_stop = start + step * index.stop - if (step >= 0 and new_stop >= stop) or (step < 0 and new_stop <= stop): - new_stop = stop - else: - new_stop = stop + step * index.stop - if (step >= 0 and new_stop <= start) or (step < 0 and new_stop >= start): - new_stop = start - new_step = step if index.step is None else step * index.step - return self.__class__(new_start, new_stop, new_step) - else: - return self._xrange[index] - def count(self, elem): - """Count the number of times elem appears in the range.""" - return _coconut_py_int(elem in self._xrange) - def index(self, elem): - """Find the index of elem in the range.""" - if elem not in self._xrange: raise _coconut.ValueError(_coconut.repr(elem) + " is not in range") - start, _, step = self._xrange.__reduce_ex__(2)[1] - return (elem - start) // step - def __repr__(self): - return _coconut.repr(self._xrange)[1:] - @property - def _args(self): - return self._xrange.__reduce__()[1] - def __reduce_ex__(self, protocol): - return (self.__class__, self._xrange.__reduce_ex__(protocol)[1]) - def __reduce__(self): - return self.__reduce_ex__(_coconut.pickle.DEFAULT_PROTOCOL) - def __hash__(self): - return _coconut.hash(self._args) - def __copy__(self): - return self.__class__(*self._args) - def __eq__(self, other): - return self.__class__ is other.__class__ and self._args == other._args - _coconut_Sequence.register(range) - @_coconut_wraps(_coconut_py_print) - def print(*args, **kwargs): - file = kwargs.get("file", _coconut_sys.stdout) - if "flush" in kwargs: - flush = kwargs["flush"] - del kwargs["flush"] - else: - flush = False - if _coconut.getattr(file, "encoding", None) is not None: - _coconut_py_print(*(_coconut_py_unicode(x).encode(file.encoding) for x in args), **kwargs) - else: - _coconut_py_print(*args, **kwargs) - if flush: - file.flush() - @_coconut_wraps(_coconut_py_raw_input) - def input(*args, **kwargs): - if _coconut.getattr(_coconut_sys.stdout, "encoding", None) is not None: - return _coconut_py_raw_input(*args, **kwargs).decode(_coconut_sys.stdout.encoding) - return _coconut_py_raw_input(*args, **kwargs).decode() - @_coconut_wraps(_coconut_py_repr) - def repr(obj): - import __builtin__ - try: - __builtin__.repr = _coconut_repr - if isinstance(obj, _coconut_py_unicode): - return _coconut_py_unicode(_coconut_py_repr(obj)[1:]) - if isinstance(obj, _coconut_py_str): - return "b" + _coconut_py_unicode(_coconut_py_repr(obj)) - return _coconut_py_unicode(_coconut_py_repr(obj)) - finally: - __builtin__.repr = _coconut_py_repr - ascii = _coconut_repr = repr - def raw_input(*args): - """Coconut uses Python 3 'input' instead of Python 2 'raw_input'.""" - raise _coconut.NameError("Coconut uses Python 3 'input' instead of Python 2 'raw_input'") - def xrange(*args): - """Coconut uses Python 3 'range' instead of Python 2 'xrange'.""" - raise _coconut.NameError("Coconut uses Python 3 'range' instead of Python 2 'xrange'") - def _coconut_exec(obj, globals=None, locals=None): - """Execute the given source in the context of globals and locals.""" - if locals is None: - locals = _coconut_sys._getframe(1).f_locals if globals is None else globals - if globals is None: - globals = _coconut_sys._getframe(1).f_globals - exec(obj, globals, locals) - import operator as _coconut_operator - class _coconut_attrgetter(object): - __slots__ = ("attrs",) - def __init__(self, *attrs): - self.attrs = attrs - def __reduce_ex__(self, _): - return self.__reduce__() - def __reduce__(self): - return (self.__class__, self.attrs) - @staticmethod - def _getattr(obj, attr): - for name in attr.split("."): - obj = _coconut.getattr(obj, name) - return obj - def __call__(self, obj): - if len(self.attrs) == 1: - return self._getattr(obj, self.attrs[0]) - return _coconut.tuple(self._getattr(obj, attr) for attr in self.attrs) - _coconut_operator.attrgetter = _coconut_attrgetter - class _coconut_itemgetter(object): - __slots__ = ("items",) - def __init__(self, *items): - self.items = items - def __reduce_ex__(self, _): - return self.__reduce__() - def __reduce__(self): - return (self.__class__, self.items) - def __call__(self, obj): - if len(self.items) == 1: - return obj[self.items[0]] - return _coconut.tuple(obj[item] for item in self.items) - _coconut_operator.itemgetter = _coconut_itemgetter - class _coconut_methodcaller(object): - __slots__ = ("name", "args", "kwargs") - def __init__(self, name, *args, **kwargs): - self.name = name - self.args = args - self.kwargs = kwargs - def __reduce_ex__(self, _): - return self.__reduce__() - def __reduce__(self): - return (self.__class__, (self.name,) + self.args, {"kwargs": self.kwargs}) - def __setstate__(self, setvars): - for k, v in setvars.items(): - _coconut.setattr(self, k, v) - def __call__(self, obj): - return _coconut.getattr(obj, self.name)(*self.args, **self.kwargs) - _coconut_operator.methodcaller = _coconut_methodcaller - if _coconut_sys.version_info < (2, 7): - import copy_reg as _coconut_copy_reg - def _coconut_new_partial(func, args, keywords): - return _coconut_functools.partial(func, *(args if args is not None else ()), **(keywords if keywords is not None else {})) - _coconut_copy_reg.constructor(_coconut_new_partial) - def _coconut_reduce_partial(self): - return (_coconut_new_partial, (self.func, self.args, self.keywords)) - _coconut_copy_reg.pickle(_coconut_functools.partial, _coconut_reduce_partial) - def min(*args, **kwargs): - if len(args) == 1 and "default" in kwargs: - obj = tuple(args[0]) - default = kwargs.pop("default") - if len(obj): - return _coconut_py_min(obj, **kwargs) - else: - return default - else: - return _coconut_py_min(*args, **kwargs) - def max(*args, **kwargs): - if len(args) == 1 and "default" in kwargs: - obj = tuple(args[0]) - default = kwargs.pop("default") - if len(obj): - return _coconut_py_max(obj, **kwargs) - else: - return default - else: - return _coconut_py_max(*args, **kwargs) - from collections import OrderedDict as _coconut_OrderedDict - def _coconut_default_breakpointhook(*args, **kwargs): - hookname = _coconut.os.getenv("PYTHONBREAKPOINT") - if hookname != "0": - if not hookname: - hookname = "pdb.set_trace" - modname, dot, funcname = hookname.rpartition(".") - if not dot: - modname = "builtins" if _coconut_sys.version_info >= (3,) else "__builtin__" - if _coconut_sys.version_info >= (2, 7): - import importlib - module = importlib.import_module(modname) - else: - import imp - module = imp.load_module(modname, *imp.find_module(modname)) - hook = _coconut.getattr(module, funcname) - return hook(*args, **kwargs) - if not hasattr(_coconut_sys, "__breakpointhook__"): - _coconut_sys.__breakpointhook__ = _coconut_default_breakpointhook - def breakpoint(*args, **kwargs): - return _coconut.getattr(_coconut_sys, "breakpointhook", _coconut_default_breakpointhook)(*args, **kwargs) - class _coconut_dict_base(_coconut_OrderedDict): - __slots__ = () - __doc__ = getattr(_coconut_OrderedDict, "__doc__", "") - __eq__ = _coconut_py_dict.__eq__ - def __repr__(self): - return "{" + ", ".join("{k!r}: {v!r}".format(k=k, v=v) for k, v in self.items()) + "}" - def __or__(self, other): - out = self.copy() - out.update(other) - return out - def __ror__(self, other): - out = self.__class__(other) - out.update(self) - return out - def __ior__(self, other): - self.update(other) - return self - class _coconut_dict_meta(type): - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_py_dict) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, _coconut_py_dict) - dict = _coconut_dict_meta(py_str("dict"), _coconut_dict_base.__bases__, _coconut_dict_base.__dict__.copy()) - dict.keys = _coconut_OrderedDict.viewkeys - dict.values = _coconut_OrderedDict.viewvalues - dict.items = _coconut_OrderedDict.viewitems -else: - - import functools as _coconut_functools - _coconut_getattr = getattr - def _coconut_wraps(base_func): - def wrap(new_func): - new_func_module = _coconut_getattr(new_func, "__module__") - _coconut_functools.update_wrapper(new_func, base_func) - if new_func_module is not None: - new_func.__module__ = new_func_module - return new_func - return wrap - from builtins import chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, repr - py_bytes, py_chr, py_dict, py_hex, py_input, py_int, py_map, py_object, py_oct, py_open, py_print, py_range, py_str, py_super, py_zip, py_filter, py_reversed, py_enumerate, py_repr, py_min, py_max = bytes, chr, dict, hex, input, int, map, object, oct, open, print, range, str, super, zip, filter, reversed, enumerate, repr, min, max - _coconut_py_str, _coconut_py_super, _coconut_py_dict, _coconut_py_min, _coconut_py_max = str, super, dict, min, max - exec("_coconut_exec = exec") - if _coconut_sys.version_info >= (3, 7): - py_breakpoint = breakpoint - if _coconut_sys.version_info < (3, 4): - def min(*args, **kwargs): - if len(args) == 1 and "default" in kwargs: - obj = tuple(args[0]) - default = kwargs.pop("default") - if len(obj): - return _coconut_py_min(obj, **kwargs) - else: - return default - else: - return _coconut_py_min(*args, **kwargs) - def max(*args, **kwargs): - if len(args) == 1 and "default" in kwargs: - obj = tuple(args[0]) - default = kwargs.pop("default") - if len(obj): - return _coconut_py_max(obj, **kwargs) - else: - return default - else: - return _coconut_py_max(*args, **kwargs) - if _coconut_sys.version_info < (3, 7): - from collections import OrderedDict as _coconut_OrderedDict - def _coconut_default_breakpointhook(*args, **kwargs): - hookname = _coconut.os.getenv("PYTHONBREAKPOINT") - if hookname != "0": - if not hookname: - hookname = "pdb.set_trace" - modname, dot, funcname = hookname.rpartition(".") - if not dot: - modname = "builtins" if _coconut_sys.version_info >= (3,) else "__builtin__" - if _coconut_sys.version_info >= (2, 7): - import importlib - module = importlib.import_module(modname) - else: - import imp - module = imp.load_module(modname, *imp.find_module(modname)) - hook = _coconut.getattr(module, funcname) - return hook(*args, **kwargs) - if not hasattr(_coconut_sys, "__breakpointhook__"): - _coconut_sys.__breakpointhook__ = _coconut_default_breakpointhook - def breakpoint(*args, **kwargs): - return _coconut.getattr(_coconut_sys, "breakpointhook", _coconut_default_breakpointhook)(*args, **kwargs) - class _coconut_dict_base(_coconut_OrderedDict): - __slots__ = () - __doc__ = getattr(_coconut_OrderedDict, "__doc__", "") - __eq__ = _coconut_py_dict.__eq__ - def __repr__(self): - return "{" + ", ".join("{k!r}: {v!r}".format(k=k, v=v) for k, v in self.items()) + "}" - def __or__(self, other): - out = self.copy() - out.update(other) - return out - def __ror__(self, other): - out = self.__class__(other) - out.update(self) - return out - def __ior__(self, other): - self.update(other) - return self - class _coconut_dict_meta(type): - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_py_dict) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, _coconut_py_dict) - dict = _coconut_dict_meta(py_str("dict"), _coconut_dict_base.__bases__, _coconut_dict_base.__dict__.copy()) - elif _coconut_sys.version_info < (3, 9): - class _coconut_dict_base(_coconut_py_dict): - __slots__ = () - __doc__ = getattr(_coconut_py_dict, "__doc__", "") - def __or__(self, other): - out = self.copy() - out.update(other) - return out - def __ror__(self, other): - out = self.__class__(other) - out.update(self) - return out - def __ior__(self, other): - self.update(other) - return self - class _coconut_dict_meta(type): - def __instancecheck__(cls, inst): - return _coconut.isinstance(inst, _coconut_py_dict) - def __subclasscheck__(cls, subcls): - return _coconut.issubclass(subcls, _coconut_py_dict) - dict = _coconut_dict_meta(py_str("dict"), _coconut_dict_base.__bases__, _coconut_dict_base.__dict__.copy()) - if _coconut_sys.version_info < (3, 11): - try: - from exceptiongroup import ExceptionGroup, BaseExceptionGroup - except ImportError: - class you_need_to_install_exceptiongroup(object): - __slots__ = () - ExceptionGroup = BaseExceptionGroup = you_need_to_install_exceptiongroup() -class _coconut_missing_module(object): - __slots__ = ("_import_err",) - def __init__(self, error): - self._import_err = error - def __getattr__(self, name): - raise self._import_err -@_coconut_wraps(_coconut_py_super) -def _coconut_super(type=None, object_or_type=None): - if type is None: - if object_or_type is not None: - raise _coconut.TypeError("invalid use of super()") - frame = _coconut_sys._getframe(1) - try: - cls = frame.f_locals["__class__"] - except _coconut.AttributeError: - raise _coconut.RuntimeError("super(): __class__ cell not found") - self = frame.f_locals[frame.f_code.co_varnames[0]] - return _coconut_py_super(cls, self) - return _coconut_py_super(type, object_or_type) -super = _coconut_super -class _coconut(object): - import collections, copy, functools, types, itertools, operator, threading, os, warnings, contextlib, traceback, weakref, multiprocessing, inspect - from multiprocessing import dummy as multiprocessing_dummy - if _coconut_sys.version_info < (3, 2): - try: - from backports.functools_lru_cache import lru_cache - functools.lru_cache = lru_cache - except ImportError as lru_cache_import_err: - functools.lru_cache = _coconut_missing_module(lru_cache_import_err) - if _coconut_sys.version_info < (3,): - import copy_reg as copyreg - else: - import copyreg - if _coconut_sys.version_info < (3, 4): - try: - import trollius as asyncio - except ImportError as trollius_import_err: - class you_need_to_install_trollius(_coconut_missing_module): - __slots__ = () - def coroutine(self, func): - def raise_import_error(*args, **kwargs): - raise self._import_err - return raise_import_error - def Return(self, obj): - raise self._import_err - asyncio = you_need_to_install_trollius(trollius_import_err) - asyncio_Return = asyncio.Return - else: - import asyncio - asyncio_Return = StopIteration - try: - import async_generator - except ImportError as async_generator_import_err: - async_generator = _coconut_missing_module(async_generator_import_err) - if _coconut_sys.version_info < (3,): - import cPickle as pickle - else: - import pickle - OrderedDict = collections.OrderedDict if _coconut_sys.version_info >= (2, 7) else dict - if _coconut_sys.version_info < (3, 3): - abc = collections - else: - import collections.abc as abc - typing = types.ModuleType(_coconut_py_str("typing")) - try: - import typing_extensions - except ImportError: - typing_extensions = None - else: - for _name in dir(typing_extensions): - if not _name.startswith("__"): - setattr(typing, _name, getattr(typing_extensions, _name)) - typing.__doc__ = "Coconut version of typing that makes use of typing.typing_extensions when possible.\n\n" + (getattr(typing, "__doc__") or "The typing module is not available at runtime in Python 3.4 or earlier; try hiding your typedefs behind an 'if TYPE_CHECKING:' block.") - if _coconut_sys.version_info < (3, 5): - if not hasattr(typing, "TYPE_CHECKING"): - typing.TYPE_CHECKING = False - if not hasattr(typing, "Any"): - typing.Any = Ellipsis - if not hasattr(typing, "cast"): - def cast(t, x): - """typing.cast[T](t: Type[T], x: Any) -> T = x""" - return x - typing.cast = cast - cast = staticmethod(cast) - if not hasattr(typing, "TypeVar"): - def TypeVar(name, *args, **kwargs): - """Runtime mock of typing.TypeVar for Python 3.4 and earlier.""" - return name - typing.TypeVar = TypeVar - TypeVar = staticmethod(TypeVar) - if not hasattr(typing, "Generic"): - class Generic_mock(object): - """Runtime mock of typing.Generic for Python 3.4 and earlier.""" - __slots__ = () - def __getitem__(self, vars): - return _coconut.object - typing.Generic = Generic_mock() - else: - import typing as _typing - for _name in dir(_typing): - if not hasattr(typing, _name): - setattr(typing, _name, getattr(_typing, _name)) - if _coconut_sys.version_info < (3, 6): - if not hasattr(typing, "NamedTuple"): - def NamedTuple(name, fields): - return _coconut.collections.namedtuple(name, [x for x, t in fields]) - typing.NamedTuple = NamedTuple - NamedTuple = staticmethod(NamedTuple) - if _coconut_sys.version_info < (3, 8): - if not hasattr(typing, "Protocol"): - class YouNeedToInstallTypingExtensions(object): - __slots__ = () - def __init__(self): - raise _coconut.TypeError('Protocols cannot be instantiated') - typing.Protocol = YouNeedToInstallTypingExtensions - if _coconut_sys.version_info < (3, 10): - if not hasattr(typing, "ParamSpec"): - def ParamSpec(name, *args, **kwargs): - """Runtime mock of typing.ParamSpec for Python 3.9 and earlier.""" - return _coconut.typing.TypeVar(name) - typing.ParamSpec = ParamSpec - if not hasattr(typing, "TypeAlias") or not hasattr(typing, "Concatenate"): - class you_need_to_install_typing_extensions(object): - __slots__ = () - typing.TypeAlias = typing.Concatenate = you_need_to_install_typing_extensions() - if _coconut_sys.version_info < (3, 11): - if not hasattr(typing, "TypeVarTuple"): - def TypeVarTuple(name, *args, **kwargs): - """Runtime mock of typing.TypeVarTuple for Python 3.10 and earlier.""" - return _coconut.typing.TypeVar(name) - typing.TypeVarTuple = TypeVarTuple - if not hasattr(typing, "Unpack"): - class you_need_to_install_typing_extensions(object): - __slots__ = () - typing.Unpack = you_need_to_install_typing_extensions() - - def _typing_getattr(name): - raise _coconut.AttributeError("typing.%s is not available on the current Python version and couldn't be looked up in typing_extensions; try hiding your typedefs behind an 'if TYPE_CHECKING:' block" % (name,)) - typing.__getattr__ = _typing_getattr - _typing_getattr = staticmethod(_typing_getattr) - zip_longest = itertools.zip_longest if _coconut_sys.version_info >= (3,) else itertools.izip_longest - try: - import numpy - except ImportError as numpy_import_err: - numpy = _coconut_missing_module(numpy_import_err) - else: - abc.Sequence.register(numpy.ndarray) - numpy_modules = ('numpy', 'torch', 'jaxlib', 'pandas', 'xarray') - xarray_modules = ('xarray',) - pandas_modules = ('pandas',) - jax_numpy_modules = ('jaxlib',) - tee_type = type(itertools.tee((), 1)[0]) - reiterables = abc.Sequence, abc.Mapping, abc.Set - fmappables = list, tuple, dict, set, frozenset, bytes, bytearray - abc.Sequence.register(collections.deque) - Ellipsis, NotImplemented, NotImplementedError, Exception, AttributeError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, StopIteration, RuntimeError, all, any, bool, bytes, callable, chr, classmethod, complex, dict, enumerate, filter, float, frozenset, getattr, hasattr, hash, id, int, isinstance, issubclass, iter, len, list, locals, globals, map, min, max, next, object, ord, property, range, reversed, set, setattr, slice, str, sum, super, tuple, type, vars, zip, repr, print, bytearray = Ellipsis, NotImplemented, NotImplementedError, Exception, AttributeError, ImportError, IndexError, KeyError, NameError, TypeError, ValueError, StopIteration, RuntimeError, all, any, bool, bytes, callable, chr, classmethod, complex, dict, enumerate, filter, float, frozenset, getattr, hasattr, hash, id, int, isinstance, issubclass, iter, len, list, locals, globals, map, staticmethod(min), staticmethod(max), next, object, ord, property, range, reversed, set, setattr, slice, str, sum, staticmethod(super), tuple, type, vars, zip, staticmethod(repr), staticmethod(print), bytearray -@_coconut_wraps(_coconut.functools.partial) -def _coconut_partial(_coconut_func, *args, **kwargs): - partial_func = _coconut.functools.partial(_coconut_func, *args, **kwargs) - partial_func.__name__ = _coconut.getattr(_coconut_func, "__name__", None) - return partial_func -def _coconut_handle_cls_kwargs(**kwargs): - """Some code taken from six under the terms of its MIT license.""" - metaclass = kwargs.pop("metaclass", None) - if kwargs and metaclass is None: - raise _coconut.TypeError("unexpected keyword argument(s) in class definition: %r" % (kwargs,)) - def coconut_handle_cls_kwargs_wrapper(cls): - if metaclass is None: - return cls - orig_vars = cls.__dict__.copy() - slots = orig_vars.get("__slots__") - if slots is not None: - if _coconut.isinstance(slots, _coconut.str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop("__dict__", None) - orig_vars.pop("__weakref__", None) - if _coconut.hasattr(cls, "__qualname__"): - orig_vars["__qualname__"] = cls.__qualname__ - return metaclass(cls.__name__, cls.__bases__, orig_vars, **kwargs) - return coconut_handle_cls_kwargs_wrapper -def _coconut_handle_cls_stargs(*args): - temp_names = ["_coconut_base_cls_%s" % (i,) for i in _coconut.range(_coconut.len(args))] - ns = _coconut_py_dict(_coconut.zip(temp_names, args)) - _coconut_exec("class _coconut_cls_stargs_base(" + ", ".join(temp_names) + "): pass", ns) - return ns["_coconut_cls_stargs_base"] -class _coconut_baseclass(object): - __slots__ = ("__weakref__",) - def __reduce_ex__(self, _): - return self.__reduce__() - def __eq__(self, other): - return self.__class__ is other.__class__ and self.__reduce__() == other.__reduce__() - def __hash__(self): - return _coconut.hash(self.__reduce__()) - def __setstate__(self, setvars): - for k, v in setvars.items(): - _coconut.setattr(self, k, v) - def __iter_getitem__(self, index): - getitem = _coconut.getattr(self, "__getitem__", None) - if getitem is None: - raise _coconut.NotImplementedError - return getitem(index) -class _coconut_base_callable(_coconut_baseclass): - __slots__ = () - def __get__(self, obj, objtype=None): - if obj is None: - return self - if _coconut_sys.version_info < (3,): - return _coconut.types.MethodType(self, obj, objtype) - else: - return _coconut.types.MethodType(self, obj) -class _coconut_Sentinel(_coconut_baseclass): - __slots__ = () - def __reduce__(self): - return (self.__class__, ()) -_coconut_sentinel = _coconut_Sentinel() -def _coconut_get_base_module(obj): - return obj.__class__.__module__.split(".", 1)[0] -def _coconut_xarray_to_pandas(obj): - import xarray - if isinstance(obj, xarray.Dataset): - return obj.to_dataframe() - elif isinstance(obj, xarray.DataArray): - return obj.to_series() - else: - return obj.to_pandas() -def _coconut_xarray_to_numpy(obj): - import xarray - if isinstance(obj, xarray.Dataset): - return obj.to_dataframe().to_numpy() - else: - return obj.to_numpy() -class CoconutWarning(Warning, object): - """Exception class used for all Coconut warnings.""" - __slots__ = () -_coconut_CoconutWarning = CoconutWarning -class MatchError(_coconut_baseclass, Exception): - """Pattern-matching error. Has attributes .pattern, .value, and .message.""" - max_val_repr_len = 500 - def __init__(self, pattern=None, value=None): - self.pattern = pattern - self.value = value - self._message = None - @property - def message(self): - if self._message is None: - val_repr = _coconut.repr(self.value) - self._message = "pattern-matching failed for %s in %s" % (_coconut.repr(self.pattern), val_repr if _coconut.len(val_repr) <= self.max_val_repr_len else val_repr[:self.max_val_repr_len] + "...") - Exception.__init__(self, self._message) - return self._message - def __repr__(self): - self.message - return Exception.__repr__(self) - def __str__(self): - self.message - return Exception.__str__(self) - def __unicode__(self): - self.message - return Exception.__unicode__(self) - def __reduce__(self): - return (self.__class__, (self.pattern, self.value), {"_message": self._message}) - def __setstate__(self, state): - _coconut_baseclass.__setstate__(self, state) - if self._message is not None: - Exception.__init__(self, self._message) -_coconut_cached_MatchError = None if _coconut_cached__coconut__ is None else getattr(_coconut_cached__coconut__, "MatchError", None) -if _coconut_cached_MatchError is not None: - if _coconut_sys.version_info >= (3,): - for _coconut_varname in dir(MatchError): - try: - setattr(_coconut_cached_MatchError, _coconut_varname, getattr(MatchError, _coconut_varname)) - except (AttributeError, TypeError): - pass - MatchError = _coconut_cached_MatchError -class _coconut_tail_call(_coconut_baseclass): - __slots__ = ("func", "args", "kwargs") - def __init__(self, _coconut_func, *args, **kwargs): - self.func = _coconut_func - self.args = args - self.kwargs = kwargs - def __reduce__(self): - return (self.__class__, (self.func, self.args, self.kwargs)) -_coconut_tco_func_dict = _coconut.weakref.WeakValueDictionary() -def _coconut_tco(func): - @_coconut_wraps(func) - def tail_call_optimized_func(*args, **kwargs): - call_func = func - while True: - if _coconut.isinstance(call_func, _coconut_base_pattern_func): - call_func = call_func._coconut_tco_func - elif _coconut.isinstance(call_func, _coconut.types.MethodType): - wkref_func = _coconut_tco_func_dict.get(_coconut.id(call_func.__func__)) - if wkref_func is call_func.__func__: - if call_func.__self__ is None: - call_func = call_func._coconut_tco_func - else: - call_func = _coconut_partial(call_func._coconut_tco_func, call_func.__self__) - else: - wkref_func = _coconut_tco_func_dict.get(_coconut.id(call_func)) - if wkref_func is call_func: - call_func = call_func._coconut_tco_func - result = call_func(*args, **kwargs) # use 'coconut --no-tco' to clean up your traceback - if not isinstance(result, _coconut_tail_call): - return result - call_func, args, kwargs = result.func, result.args, result.kwargs - tail_call_optimized_func._coconut_tco_func = func - tail_call_optimized_func.__module__ = _coconut.getattr(func, "__module__", None) - tail_call_optimized_func.__name__ = _coconut.getattr(func, "__name__", None) - tail_call_optimized_func.__qualname__ = _coconut.getattr(func, "__qualname__", None) - _coconut_tco_func_dict[_coconut.id(tail_call_optimized_func)] = tail_call_optimized_func - return tail_call_optimized_func -@_coconut_wraps(_coconut.itertools.tee) -def tee(iterable, n=2): - if n < 0: - raise _coconut.ValueError("tee: n cannot be negative") - elif n == 0: - return () - elif n == 1: - return (iterable,) - elif _coconut.isinstance(iterable, _coconut.reiterables): - return (iterable,) * n - else: - if _coconut.getattr(iterable, "__getitem__", None) is not None or _coconut.isinstance(iterable, (_coconut.tee_type, _coconut.abc.Sized, _coconut.abc.Container)): - existing_copies = [iterable] - while _coconut.len(existing_copies) < n: - try: - copy = _coconut.copy.copy(iterable) - except _coconut.TypeError: - break - else: - existing_copies.append(copy) - else: - return _coconut.tuple(existing_copies) - return _coconut.itertools.tee(iterable, n) -class _coconut_has_iter(_coconut_baseclass): - __slots__ = ("iter",) - def __new__(cls, iterable): - self = _coconut.super(_coconut_has_iter, cls).__new__(cls) - self.iter = iterable - return self - def get_new_iter(self): - """Tee the underlying iterator.""" - self.iter = _coconut_reiterable(self.iter) - return self.iter - def __fmap__(self, func): - return _coconut_map(func, self) -class reiterable(_coconut_has_iter): - """Allow an iterator to be iterated over multiple times with the same results.""" - __slots__ = () - def __new__(cls, iterable): - if _coconut.isinstance(iterable, _coconut.reiterables): - return iterable - return _coconut.super(_coconut_reiterable, cls).__new__(cls, iterable) - def get_new_iter(self): - """Tee the underlying iterator.""" - self.iter, new_iter = _coconut_tee(self.iter) - return new_iter - def __iter__(self): - return _coconut.iter(self.get_new_iter()) - def __repr__(self): - return "reiterable(%s)" % (_coconut.repr(self.get_new_iter()),) - def __reduce__(self): - return (self.__class__, (self.iter,)) - def __copy__(self): - return self.__class__(self.get_new_iter()) - def __getitem__(self, index): - return _coconut_iter_getitem(self.get_new_iter(), index) - def __reversed__(self): - return _coconut_reversed(self.get_new_iter()) - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return _coconut.len(self.get_new_iter()) - def __contains__(self, elem): - return elem in self.get_new_iter() - def count(self, elem): - """Count the number of times elem appears in the iterable.""" - return self.get_new_iter().count(elem) - def index(self, elem): - """Find the index of elem in the iterable.""" - return self.get_new_iter().index(elem) -_coconut.reiterables = (reiterable,) + _coconut.reiterables -def _coconut_iter_getitem_special_case(iterable, start, stop, step): - iterable = _coconut.itertools.islice(iterable, start, None) - cache = _coconut.collections.deque(_coconut.itertools.islice(iterable, -stop), maxlen=-stop) - for index, item in _coconut.enumerate(iterable): - cached_item = cache.popleft() - if index % step == 0: - yield cached_item - cache.append(item) -def _coconut_iter_getitem(iterable, index): - """Iterator slicing works just like sequence slicing, including support for negative indices and slices, and support for `slice` objects in the same way as can be done with normal slicing. - - Coconut's iterator slicing is very similar to Python's `itertools.islice`, but unlike `itertools.islice`, Coconut's iterator slicing supports negative indices, and will preferentially call an object's `__iter_getitem__` (always used if available) or `__getitem__` (only used if the object is a collections.abc.Sequence). Coconut's iterator slicing is also optimized to work well with all of Coconut's built-in objects, only computing the elements of each that are actually necessary to extract the desired slice. - - Some code taken from more_itertools under the terms of its MIT license. - """ - obj_iter_getitem = _coconut.getattr(iterable, "__iter_getitem__", None) - if obj_iter_getitem is None and _coconut.isinstance(iterable, _coconut.abc.Sequence): - obj_iter_getitem = _coconut.getattr(iterable, "__getitem__", None) - if obj_iter_getitem is not None: - try: - result = obj_iter_getitem(index) - except _coconut.NotImplementedError: - pass - else: - return result - if not _coconut.isinstance(index, _coconut.slice): - index = _coconut.operator.index(index) - if index < 0: - return _coconut.collections.deque(iterable, maxlen=-index)[0] - result = _coconut.next(_coconut.itertools.islice(iterable, index, index + 1), _coconut_sentinel) - if result is _coconut_sentinel: - raise _coconut.IndexError(".$[] index out of range") - return result - start = _coconut.operator.index(index.start) if index.start is not None else None - stop = _coconut.operator.index(index.stop) if index.stop is not None else None - step = _coconut.operator.index(index.step) if index.step is not None else 1 - if step == 0: - raise _coconut.ValueError("slice step cannot be zero") - if start is None and stop is None and step == -1: - obj_reversed = _coconut.getattr(iterable, "__reversed__", None) - if obj_reversed is not None: - try: - result = obj_reversed() - except _coconut.NotImplementedError: - pass - else: - if result is not _coconut.NotImplemented: - return result - if step >= 0: - start = 0 if start is None else start - if start < 0: - cache = _coconut.collections.deque(_coconut.enumerate(iterable, 1), maxlen=-start) - len_iter = cache[-1][0] if cache else 0 - i = _coconut.max(len_iter + start, 0) - if stop is None: - j = len_iter - elif stop >= 0: - j = _coconut.min(stop, len_iter) - else: - j = _coconut.max(len_iter + stop, 0) - n = j - i - if n <= 0: - return () - if n < -start or step != 1: - cache = _coconut.itertools.islice(cache, 0, n, step) - return _coconut_map(_coconut.operator.itemgetter(1), cache) - elif stop is None or stop >= 0: - return _coconut.itertools.islice(iterable, start, stop, step) - else: - return _coconut_iter_getitem_special_case(iterable, start, stop, step) - else: - start = -1 if start is None else start - if stop is not None and stop < 0: - n = -stop - 1 - cache = _coconut.collections.deque(_coconut.enumerate(iterable, 1), maxlen=n) - len_iter = cache[-1][0] if cache else 0 - if start < 0: - i, j = start, stop - else: - i, j = _coconut.min(start - len_iter, -1), None - return _coconut_map(_coconut.operator.itemgetter(1), _coconut.tuple(cache)[i:j:step]) - else: - if stop is not None: - m = stop + 1 - iterable = _coconut.itertools.islice(iterable, m, None) - if start < 0: - i = start - n = None - elif stop is None: - i = None - n = start + 1 - else: - i = None - n = start - stop - if n is not None: - if n <= 0: - return () - iterable = _coconut.itertools.islice(iterable, 0, n) - return _coconut.tuple(iterable)[i::step] -class _coconut_attritemgetter(_coconut_base_callable): - __slots__ = ("attr", "is_iter_and_items") - def __init__(self, attr, *is_iter_and_items): - self.attr = attr - self.is_iter_and_items = is_iter_and_items - def __call__(self, obj): - out = obj - if self.attr is not None: - out = _coconut.getattr(out, self.attr) - for is_iter, item in self.is_iter_and_items: - if is_iter: - out = _coconut_iter_getitem(out, item) - else: - out = out[item] - return out - def __repr__(self): - return "." + (self.attr or "") + "".join(("$" if is_iter else "") + "[" + _coconut.repr(item) + "]" for is_iter, item in self.is_iter_and_items) - def __reduce__(self): - return (self.__class__, (self.attr,) + self.is_iter_and_items) -class _coconut_compostion_baseclass(_coconut_base_callable): - def __init__(self, func, *func_infos): - try: - _coconut.functools.update_wrapper(self, func) - except _coconut.AttributeError: - pass - if _coconut.isinstance(func, self.__class__): - self._coconut_func = func._coconut_func - func_infos = func._coconut_func_infos + func_infos - else: - self._coconut_func = func - self._coconut_func_infos = [] - for f_info in func_infos: - f = f_info[0] - if _coconut.isinstance(f, self.__class__): - self._coconut_func_infos.append((f._coconut_func,) + f_info[1:]) - self._coconut_func_infos += f._coconut_func_infos - else: - self._coconut_func_infos.append(f_info) - self._coconut_func_infos = _coconut.tuple(self._coconut_func_infos) - def __reduce__(self): - return (self.__class__, (self._coconut_func,) + self._coconut_func_infos) -class _coconut_base_compose(_coconut_compostion_baseclass): - __slots__ = () - def __call__(self, *args, **kwargs): - arg = self._coconut_func(*args, **kwargs) - for f, stars, none_aware in self._coconut_func_infos: - if none_aware and arg is None: - return arg - if stars == 0: - arg = f(arg) - elif stars == 1: - arg = f(*arg) - elif stars == 2: - arg = f(**arg) - else: - raise _coconut.RuntimeError("invalid internal stars value " + _coconut.repr(stars) + " in " + _coconut.repr(self) + " (you should report this at https://github.com/evhub/coconut/issues/new)") - return arg - def __repr__(self): - return _coconut.repr(self._coconut_func) + " " + " ".join(".." + "?"*none_aware + "*"*stars + "> " + _coconut.repr(f) for f, stars, none_aware in self._coconut_func_infos) -class _coconut_async_compose(_coconut_compostion_baseclass): - __slots__ = () - if _coconut_sys.version_info < (3, 5): - if _coconut_sys.version_info < (3, 4): - @_coconut.asyncio.coroutine - def __call__(self, *args, **kwargs): - arg = yield _coconut.asyncio.From(self._coconut_func(*args, **kwargs)) - for f, await_f in self._coconut_func_infos: - arg = f(arg) - if await_f: - arg = yield _coconut.asyncio.From(arg) - raise _coconut.asyncio.Return(arg) - else: - _coconut___call___ns = {"_coconut": _coconut} - _coconut_exec('def __call__(self, *args, **kwargs):\n arg = yield from self._coconut_func(*args, **kwargs)\n for f, await_f in self._coconut_func_infos:\n arg = f(arg)\n if await_f:\n arg = yield from arg\n raise _coconut.StopIteration(arg)', _coconut___call___ns) - __call__ = _coconut.asyncio.coroutine(_coconut___call___ns["__call__"]) - else: - _coconut___call___ns = {"_coconut": _coconut} - _coconut_exec('async def __call__(self, *args, **kwargs):\n arg = await self._coconut_func(*args, **kwargs)\n for f, await_f in self._coconut_func_infos:\n arg = f(arg)\n if await_f:\n arg = await arg\n return arg', _coconut___call___ns) - __call__ = _coconut___call___ns["__call__"] - def __repr__(self): - return _coconut.repr(self._coconut_func) + " " + " ".join("`and_then" + "_await"*await_f + "` " + _coconut.repr(f) for f, await_f in self._coconut_func_infos) -def and_then(first_async_func, second_func): - """Compose an async function with a normal function. - - Effectively equivalent to: - def and_then[**T, U, V]( - first_async_func: async (**T) -> U, - second_func: U -> V, - ) -> async (**T) -> V = - async def (*args, **kwargs) => ( - first_async_func(*args, **kwargs) - |> await - |> second_func - ) - """ - return _coconut_async_compose(first_async_func, (second_func, False)) -def and_then_await(first_async_func, second_async_func): - """Compose two async functions. - - Effectively equivalent to: - def and_then_await[**T, U, V]( - first_async_func: async (**T) -> U, - second_async_func: async U -> V, - ) -> async (**T) -> V = - async def (*args, **kwargs) => ( - first_async_func(*args, **kwargs) - |> await - |> second_async_func - |> await - ) - """ - return _coconut_async_compose(first_async_func, (second_async_func, True)) -def _coconut_forward_compose(func, *funcs): - """Forward composition operator (..>). - - (..>)(f, g) is effectively equivalent to (*args, **kwargs) => g(f(*args, **kwargs)).""" - return _coconut_base_compose(func, *((f, 0, False) for f in funcs)) -def _coconut_back_compose(*funcs): - """Backward composition operator (<..). - - (<..)(f, g) is effectively equivalent to (*args, **kwargs) => f(g(*args, **kwargs)).""" - return _coconut_forward_compose(*_coconut.reversed(funcs)) -def _coconut_forward_none_compose(func, *funcs): - """Forward none-aware composition operator (..?>). - - (..?>)(f, g) is effectively equivalent to (*args, **kwargs) => g?(f(*args, **kwargs)).""" - return _coconut_base_compose(func, *((f, 0, True) for f in funcs)) -def _coconut_back_none_compose(*funcs): - """Backward none-aware composition operator (<..?). - - (<..?)(f, g) is effectively equivalent to (*args, **kwargs) => f?(g(*args, **kwargs)).""" - return _coconut_forward_none_compose(*_coconut.reversed(funcs)) -def _coconut_forward_star_compose(func, *funcs): - """Forward star composition operator (..*>). - - (..*>)(f, g) is effectively equivalent to (*args, **kwargs) => g(*f(*args, **kwargs)).""" - return _coconut_base_compose(func, *((f, 1, False) for f in funcs)) -def _coconut_back_star_compose(*funcs): - """Backward star composition operator (<*..). - - (<*..)(f, g) is effectively equivalent to (*args, **kwargs) => f(*g(*args, **kwargs)).""" - return _coconut_forward_star_compose(*_coconut.reversed(funcs)) -def _coconut_forward_none_star_compose(func, *funcs): - """Forward none-aware star composition operator (..?*>). - - (..?*>)(f, g) is effectively equivalent to (*args, **kwargs) => g?(*f(*args, **kwargs)).""" - return _coconut_base_compose(func, *((f, 1, True) for f in funcs)) -def _coconut_back_none_star_compose(*funcs): - """Backward none-aware star composition operator (<*?..). - - (<*?..)(f, g) is effectively equivalent to (*args, **kwargs) => f?(*g(*args, **kwargs)).""" - return _coconut_forward_none_star_compose(*_coconut.reversed(funcs)) -def _coconut_forward_dubstar_compose(func, *funcs): - """Forward double star composition operator (..**>). - - (..**>)(f, g) is effectively equivalent to (*args, **kwargs) => g(**f(*args, **kwargs)).""" - return _coconut_base_compose(func, *((f, 2, False) for f in funcs)) -def _coconut_back_dubstar_compose(*funcs): - """Backward double star composition operator (<**..). - - (<**..)(f, g) is effectively equivalent to (*args, **kwargs) => f(**g(*args, **kwargs)).""" - return _coconut_forward_dubstar_compose(*_coconut.reversed(funcs)) -def _coconut_forward_none_dubstar_compose(func, *funcs): - """Forward none-aware double star composition operator (..?**>). - - (..?**>)(f, g) is effectively equivalent to (*args, **kwargs) => g?(**f(*args, **kwargs)).""" - return _coconut_base_compose(func, *((f, 2, True) for f in funcs)) -def _coconut_back_none_dubstar_compose(*funcs): - """Backward none-aware double star composition operator (<**?..). - - (<**?..)(f, g) is effectively equivalent to (*args, **kwargs) => f?(**g(*args, **kwargs)).""" - return _coconut_forward_none_dubstar_compose(*_coconut.reversed(funcs)) -def _coconut_pipe(x, f): - """Pipe operator (|>). Equivalent to (x, f) => f(x).""" - return f(x) -def _coconut_star_pipe(xs, f): - """Star pipe operator (*|>). Equivalent to (xs, f) => f(*xs).""" - return f(*xs) -def _coconut_dubstar_pipe(kws, f): - """Double star pipe operator (**|>). Equivalent to (kws, f) => f(**kws).""" - return f(**kws) -def _coconut_back_pipe(f, x): - """Backward pipe operator (<|). Equivalent to (f, x) => f(x).""" - return f(x) -def _coconut_back_star_pipe(f, xs): - """Backward star pipe operator (<*|). Equivalent to (f, xs) => f(*xs).""" - return f(*xs) -def _coconut_back_dubstar_pipe(f, kws): - """Backward double star pipe operator (<**|). Equivalent to (f, kws) => f(**kws).""" - return f(**kws) -def _coconut_none_pipe(x, f): - """Nullable pipe operator (|?>). Equivalent to (x, f) => f(x) if x is not None else None.""" - return None if x is None else f(x) -def _coconut_none_star_pipe(xs, f): - """Nullable star pipe operator (|?*>). Equivalent to (xs, f) => f(*xs) if xs is not None else None.""" - return None if xs is None else f(*xs) -def _coconut_none_dubstar_pipe(kws, f): - """Nullable double star pipe operator (|?**>). Equivalent to (kws, f) => f(**kws) if kws is not None else None.""" - return None if kws is None else f(**kws) -def _coconut_back_none_pipe(f, x): - """Nullable backward pipe operator ( f(x) if x is not None else None.""" - return None if x is None else f(x) -def _coconut_back_none_star_pipe(f, xs): - """Nullable backward star pipe operator (<*?|). Equivalent to (f, xs) => f(*xs) if xs is not None else None.""" - return None if xs is None else f(*xs) -def _coconut_back_none_dubstar_pipe(f, kws): - """Nullable backward double star pipe operator (<**?|). Equivalent to (kws, f) => f(**kws) if kws is not None else None.""" - return None if kws is None else f(**kws) -def _coconut_assert(cond, msg=None): - """Assert operator (assert). Asserts condition with optional message.""" - if not cond: - assert False, msg if msg is not None else "(assert) got falsey value " + _coconut.repr(cond) -def _coconut_raise(exc=None, from_exc=None): - """Raise operator (raise). Raises exception with optional cause.""" - if exc is None: - raise - if from_exc is not None: - exc.__cause__ = from_exc - raise exc -def _coconut_bool_and(a, b): - """Boolean and operator (and). Equivalent to (a, b) => a and b.""" - return a and b -def _coconut_bool_or(a, b): - """Boolean or operator (or). Equivalent to (a, b) => a or b.""" - return a or b -def _coconut_in(a, b): - """Containment operator (in). Equivalent to (a, b) => a in b.""" - return a in b -def _coconut_not_in(a, b): - """Negative containment operator (not in). Equivalent to (a, b) => a not in b.""" - return a not in b -def _coconut_none_coalesce(a, b): - """None coalescing operator (??). Equivalent to (a, b) => a if a is not None else b.""" - return b if a is None else a -def _coconut_minus(a, b=_coconut_sentinel): - """Minus operator (-). Effectively equivalent to (a, b=None) => a - b if b is not None else -a.""" - if b is _coconut_sentinel: - return -a - return a - b -def _coconut_comma_op(*args): - """Comma operator (,). Equivalent to (*args) => args.""" - return args -def _coconut_if_op(cond, if_true, if_false): - """If operator (if). Equivalent to (cond, if_true, if_false) => if_true if cond else if_false.""" - return if_true if cond else if_false -if _coconut_sys.version_info < (3, 5): - def _coconut_matmul(a, b, **kwargs): - """Matrix multiplication operator (@). Implements operator.matmul on any Python version.""" - in_place = kwargs.pop("in_place", False) - if kwargs: - raise _coconut.TypeError("_coconut_matmul() got unexpected keyword arguments " + _coconut.repr(kwargs)) - if in_place and _coconut.hasattr(a, "__imatmul__"): - try: - result = a.__imatmul__(b) - except _coconut.NotImplementedError: - pass - else: - if result is not _coconut.NotImplemented: - return result - if _coconut.hasattr(a, "__matmul__"): - try: - result = a.__matmul__(b) - except _coconut.NotImplementedError: - pass - else: - if result is not _coconut.NotImplemented: - return result - if _coconut.hasattr(b, "__rmatmul__"): - try: - result = b.__rmatmul__(a) - except _coconut.NotImplementedError: - pass - else: - if result is not _coconut.NotImplemented: - return result - if "numpy" in (_coconut_get_base_module(a), _coconut_get_base_module(b)): - from numpy import matmul - return matmul(a, b) - raise _coconut.TypeError("unsupported operand type(s) for @: " + _coconut.repr(_coconut.type(a)) + " and " + _coconut.repr(_coconut.type(b))) -else: - _coconut_matmul = _coconut.operator.matmul -class scan(_coconut_has_iter): - """Reduce func over iterable, yielding intermediate results, - optionally starting from initial.""" - __slots__ = ("func", "initial") - def __new__(cls, function, iterable, initial=_coconut_sentinel): - self = _coconut.super(_coconut_scan, cls).__new__(cls, iterable) - self.func = function - self.initial = initial - return self - def __repr__(self): - return "scan(%r, %s%s)" % (self.func, _coconut.repr(self.iter), "" if self.initial is _coconut_sentinel else ", " + _coconut.repr(self.initial)) - def __reduce__(self): - return (self.__class__, (self.func, self.iter, self.initial)) - def __copy__(self): - return self.__class__(self.func, self.get_new_iter(), self.initial) - def __iter__(self): - acc = self.initial - if acc is not _coconut_sentinel: - yield acc - for item in self.iter: - if acc is _coconut_sentinel: - acc = item - else: - acc = self.func(acc, item) - yield acc - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return _coconut.len(self.iter) -class reversed(_coconut_has_iter): - __slots__ = () - __doc__ = getattr(_coconut.reversed, "__doc__", "") - def __new__(cls, iterable): - if _coconut.isinstance(iterable, _coconut.range): - return iterable[::-1] - if _coconut.getattr(iterable, "__reversed__", None) is None or _coconut.isinstance(iterable, (_coconut.list, _coconut.tuple)): - return _coconut.super(_coconut_reversed, cls).__new__(cls, iterable) - return _coconut.reversed(iterable) - def __repr__(self): - return "reversed(%s)" % (_coconut.repr(self.iter),) - def __reduce__(self): - return (self.__class__, (self.iter,)) - def __copy__(self): - return self.__class__(self.get_new_iter()) - def __iter__(self): - return _coconut.iter(_coconut.reversed(self.iter)) - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - return _coconut_iter_getitem(self.iter, _coconut.slice(-(index.start + 1) if index.start is not None else None, -(index.stop + 1) if index.stop else None, -(index.step if index.step is not None else 1))) - return _coconut_iter_getitem(self.iter, -(index + 1)) - def __reversed__(self): - return self.iter - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return _coconut.len(self.iter) - def __contains__(self, elem): - return elem in self.iter - def count(self, elem): - """Count the number of times elem appears in the reversed iterable.""" - return self.iter.count(elem) - def index(self, elem): - """Find the index of elem in the reversed iterable.""" - return _coconut.len(self.iter) - self.iter.index(elem) - 1 - def __fmap__(self, func): - return self.__class__(_coconut_map(func, self.iter)) -class flatten(_coconut_has_iter): - """Flatten an iterable of iterables into a single iterable. - Only flattens the top level of the iterable.""" - __slots__ = ("levels", "_made_reit") - def __new__(cls, iterable, levels=1): - if levels is not None: - levels = _coconut.operator.index(levels) - if levels < 0: - raise _coconut.ValueError("flatten: levels cannot be negative") - if levels == 0: - return iterable - self = _coconut.super(_coconut_flatten, cls).__new__(cls, iterable) - self.levels = levels - self._made_reit = False - return self - def get_new_iter(self): - """Tee the underlying iterator.""" - if not self._made_reit: - for i in _coconut.reversed(_coconut.range(0 if self.levels is None else self.levels + 1)): - mapper = _coconut_reiterable - for _ in _coconut.range(i): - mapper = _coconut.functools.partial(_coconut_map, mapper) - self.iter = mapper(self.iter) - self._made_reit = True - return self.iter - def __iter__(self): - if self.levels is None: - return self._iter_all_levels() - new_iter = self.iter - for _ in _coconut.range(self.levels): - new_iter = _coconut.itertools.chain.from_iterable(new_iter) - return new_iter - def _iter_all_levels(self, new=False): - """Iterate over all levels of the iterable.""" - for item in (self.get_new_iter() if new else self.iter): - if _coconut.isinstance(item, _coconut.abc.Iterable): - for subitem in self.__class__(item, None): - yield subitem - else: - yield item - def __reversed__(self): - if self.levels is None: - return _coconut.reversed(_coconut.tuple(self._iter_all_levels(new=True))) - reversed_iter = self.get_new_iter() - for i in _coconut.reversed(_coconut.range(self.levels + 1)): - reverser = _coconut_reversed - for _ in _coconut.range(i): - reverser = _coconut.functools.partial(_coconut_map, reverser) - reversed_iter = reverser(reversed_iter) - return self.__class__(reversed_iter, self.levels) - def __repr__(self): - return "flatten(" + _coconut.repr(self.iter) + (", " + _coconut.repr(self.levels) if self.levels is not None else "") + ")" - def __reduce__(self): - return (self.__class__, (self.iter, self.levels)) - def __copy__(self): - return self.__class__(self.get_new_iter(), self.levels) - def __contains__(self, elem): - if self.levels == 1: - return _coconut.any(elem in it for it in self.get_new_iter()) - raise _coconut.TypeError("flatten.__contains__ only supported for levels=1") - def count(self, elem): - """Count the number of times elem appears in the flattened iterable.""" - if self.levels != 1: - raise _coconut.ValueError("flatten.count only supported for levels=1") - return _coconut.sum(it.count(elem) for it in self.get_new_iter()) - def index(self, elem): - """Find the index of elem in the flattened iterable.""" - if self.levels != 1: - raise _coconut.ValueError("flatten.index only supported for levels=1") - ind = 0 - for it in self.get_new_iter(): - try: - return ind + it.index(elem) - except _coconut.ValueError: - ind += _coconut.len(it) - raise _coconut.ValueError("%r not in %r" % (elem, self)) - def __fmap__(self, func): - if self.levels == 1: - return self.__class__(_coconut_map(_coconut_partial(_coconut_map, func), self.get_new_iter())) - return _coconut_map(func, self) -class cartesian_product(_coconut_baseclass): - __slots__ = ("iters", "repeat") - __doc__ = getattr(_coconut.itertools.product, "__doc__", "Cartesian product of input iterables.") + """ - -Additionally supports Cartesian products of numpy arrays.""" - def __new__(cls, *iterables, **kwargs): - repeat = _coconut.operator.index(kwargs.pop("repeat", 1)) - if kwargs: - raise _coconut.TypeError("cartesian_product() got unexpected keyword arguments " + _coconut.repr(kwargs)) - if repeat == 0: - iterables = () - repeat = 1 - if repeat < 0: - raise _coconut.ValueError("cartesian_product: repeat cannot be negative") - if iterables: - it_modules = [_coconut_get_base_module(it) for it in iterables] - if _coconut.all(mod in _coconut.numpy_modules for mod in it_modules): - iterables = tuple((it.to_numpy() if mod in _coconut.pandas_modules else _coconut_xarray_to_numpy(it) if mod in _coconut.xarray_modules else it) for it, mod in _coconut.zip(iterables, it_modules)) - if _coconut.any(mod in _coconut.jax_numpy_modules for mod in it_modules): - from jax import numpy - else: - numpy = _coconut.numpy - iterables *= repeat - dtype = numpy.result_type(*iterables) - arr = numpy.empty([_coconut.len(a) for a in iterables] + [_coconut.len(iterables)], dtype=dtype) - for i, a in _coconut.enumerate(numpy.ix_(*iterables)): - arr[..., i] = a - return arr.reshape(-1, _coconut.len(iterables)) - self = _coconut.super(_coconut_cartesian_product, cls).__new__(cls) - self.iters = iterables - self.repeat = repeat - return self - def __iter__(self): - return _coconut.itertools.product(*self.iters, repeat=self.repeat) - def __repr__(self): - return "cartesian_product(" + ", ".join(_coconut.repr(it) for it in self.iters) + (", repeat=" + _coconut.repr(self.repeat) if self.repeat != 1 else "") + ")" - def __reduce__(self): - return (self.__class__, self.iters, {"repeat": self.repeat}) - def __copy__(self): - self.iters = _coconut.tuple(_coconut_reiterable(it) for it in self.iters) - return self.__class__(*self.iters, repeat=self.repeat) - @property - def all_iters(self): - return _coconut.itertools.chain.from_iterable(_coconut.itertools.repeat(self.iters, self.repeat)) - def __len__(self): - total_len = 1 - for it in self.iters: - if not _coconut.isinstance(it, _coconut.abc.Sized): - return _coconut.NotImplemented - total_len *= _coconut.len(it) - return total_len ** self.repeat - def __contains__(self, elem): - for e, it in _coconut.zip_longest(elem, self.all_iters, fillvalue=_coconut_sentinel): - if e is _coconut_sentinel or it is _coconut_sentinel or e not in it: - return False - return True - def count(self, elem): - """Count the number of times elem appears in the product.""" - total_count = 1 - for e, it in _coconut.zip_longest(elem, self.all_iters, fillvalue=_coconut_sentinel): - if e is _coconut_sentinel or it is _coconut_sentinel: - return 0 - total_count *= it.count(e) - if not total_count: - return total_count - return total_count - def __fmap__(self, func): - return _coconut_map(func, self) -class map(_coconut_baseclass, _coconut.map): - __slots__ = ("func", "iters") - __doc__ = getattr(_coconut.map, "__doc__", "") - def __new__(cls, function, *iterables, **kwargs): - strict = kwargs.pop("strict", False) - if kwargs: - raise _coconut.TypeError(cls.__name__ + "() got unexpected keyword arguments " + _coconut.repr(kwargs)) - if strict and _coconut.len(iterables) > 1: - return _coconut_starmap(function, _coconut_zip(*iterables, strict=True)) - self = _coconut.map.__new__(cls, function, *iterables) - self.func = function - self.iters = iterables - return self - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - return self.__class__(self.func, *(_coconut_iter_getitem(it, index) for it in self.iters)) - return self.func(*(_coconut_iter_getitem(it, index) for it in self.iters)) - def __reversed__(self): - return self.__class__(self.func, *(_coconut_reversed(it) for it in self.iters)) - def __len__(self): - if not _coconut.all(_coconut.isinstance(it, _coconut.abc.Sized) for it in self.iters): - return _coconut.NotImplemented - return _coconut.min((_coconut.len(it) for it in self.iters), default=0) - def __repr__(self): - return "%s(%r, %s)" % (self.__class__.__name__, self.func, ", ".join((_coconut.repr(it) for it in self.iters))) - def __reduce__(self): - return (self.__class__, (self.func,) + self.iters) - def __copy__(self): - self.iters = _coconut.tuple(_coconut_reiterable(it) for it in self.iters) - return self.__class__(self.func, *self.iters) - def __iter__(self): - return _coconut.map(self.func, *self.iters) - def __fmap__(self, func): - return self.__class__(_coconut_forward_compose(self.func, func), *self.iters) -class _coconut_parallel_map_func_wrapper(_coconut_baseclass): - __slots__ = ("map_cls", "func", "star") - def __init__(self, map_cls, func, star): - self.map_cls = map_cls - self.func = func - self.star = star - def __reduce__(self): - return (self.__class__, (self.map_cls, self.func, self.star)) - def __call__(self, *args, **kwargs): - self.map_cls._get_pool_stack().append(None) - try: - if self.star: - assert _coconut.len(args) == 1, "internal process_map/thread_map error (you should report this at https://github.com/evhub/coconut/issues/new)" - return self.func(*args[0], **kwargs) - else: - return self.func(*args, **kwargs) - except: - _coconut.print(self.map_cls.__name__ + " error:") - _coconut.traceback.print_exc() - raise - finally: - assert self.map_cls._get_pool_stack().pop() is None, "internal process_map/thread_map error (you should report this at https://github.com/evhub/coconut/issues/new)" -class _coconut_base_parallel_map(map): - __slots__ = ("result", "chunksize", "strict", "stream", "ordered") - @classmethod - def _get_pool_stack(cls): - return cls._threadlocal_ns.__dict__.setdefault("pool_stack", [None]) - def __new__(cls, function, *iterables, **kwargs): - self = _coconut.super(_coconut_base_parallel_map, cls).__new__(cls, function, *iterables) - self.result = None - self.chunksize = kwargs.pop("chunksize", 1) - self.strict = kwargs.pop("strict", False) - self.stream = kwargs.pop("stream", False) - self.ordered = kwargs.pop("ordered", True) - if kwargs: - raise _coconut.TypeError(cls.__name__ + "() got unexpected keyword arguments " + _coconut.repr(kwargs)) - if not self.stream and cls._get_pool_stack()[-1] is not None: - return self.to_tuple() - return self - def __reduce__(self): - return (self.__class__, (self.func,) + self.iters, {"chunksize": self.chunksize, "strict": self.strict, "stream": self.stream, "ordered": self.ordered}) - @classmethod - @_coconut.contextlib.contextmanager - def multiple_sequential_calls(cls, max_workers=None): - """Context manager that causes nested calls to use the same pool.""" - if cls._get_pool_stack()[-1] is None: - cls._get_pool_stack()[-1] = cls._make_pool(max_workers) - try: - yield - finally: - cls._get_pool_stack()[-1].terminate() - cls._get_pool_stack()[-1] = None - elif max_workers is not None: - self.map_cls._get_pool_stack().append(cls._make_pool(max_workers)) - try: - yield - finally: - cls._get_pool_stack()[-1].terminate() - cls._get_pool_stack().pop() - else: - yield - def _execute_map(self): - map_func = self._get_pool_stack()[-1].imap if self.ordered else self._get_pool_stack()[-1].imap_unordered - if _coconut.len(self.iters) == 1: - return map_func(_coconut_parallel_map_func_wrapper(self.__class__, self.func, False), self.iters[0], self.chunksize) - elif self.strict: - return map_func(_coconut_parallel_map_func_wrapper(self.__class__, self.func, True), _coconut_zip(*self.iters, strict=True), self.chunksize) - else: - return map_func(_coconut_parallel_map_func_wrapper(self.__class__, self.func, True), _coconut.zip(*self.iters), self.chunksize) - def to_tuple(self): - """Execute the map operation and return the results as a tuple.""" - if self.result is None: - with self.multiple_sequential_calls(): - self.result = _coconut.tuple(self._execute_map()) - self.func = _coconut_ident - self.iters = (self.result,) - return self.result - def to_stream(self): - """Stream the map operation, yielding results one at a time.""" - if self._get_pool_stack()[-1] is None: - raise _coconut.RuntimeError("cannot stream outside of " + cls.__name__ + ".multiple_sequential_calls context") - return self._execute_map() - def __iter__(self): - if self.stream: - return self.to_stream() - else: - return _coconut.iter(self.to_tuple()) -class process_map(_coconut_base_parallel_map): - """Multi-process implementation of map. Requires arguments to be pickleable. - - For multiple sequential calls, use: - with process_map.multiple_sequential_calls(): - ... - """ - __slots__ = () - _threadlocal_ns = _coconut.threading.local() - @staticmethod - def _make_pool(max_workers=None): - return _coconut.multiprocessing.Pool(max_workers) -class thread_map(_coconut_base_parallel_map): - """Multi-thread implementation of map. - - For multiple sequential calls, use: - with thread_map.multiple_sequential_calls(): - ... - """ - __slots__ = () - _threadlocal_ns = _coconut.threading.local() - @staticmethod - def _make_pool(max_workers=None): - return _coconut.multiprocessing_dummy.Pool(_coconut.multiprocessing.cpu_count() * 5 if max_workers is None else max_workers) -class zip(_coconut_baseclass, _coconut.zip): - __slots__ = ("iters", "strict") - __doc__ = getattr(_coconut.zip, "__doc__", "") - def __new__(cls, *iterables, **kwargs): - self = _coconut.zip.__new__(cls, *iterables) - self.iters = iterables - self.strict = kwargs.pop("strict", False) - if kwargs: - raise _coconut.TypeError(cls.__name__ + "() got unexpected keyword arguments " + _coconut.repr(kwargs)) - return self - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - return self.__class__(*(_coconut_iter_getitem(it, index) for it in self.iters), strict=self.strict) - return _coconut.tuple(_coconut_iter_getitem(it, index) for it in self.iters) - def __reversed__(self): - return self.__class__(*(_coconut_reversed(it) for it in self.iters), strict=self.strict) - def __len__(self): - if not _coconut.all(_coconut.isinstance(it, _coconut.abc.Sized) for it in self.iters): - return _coconut.NotImplemented - return _coconut.min((_coconut.len(it) for it in self.iters), default=0) - def __repr__(self): - return "zip(%s%s)" % (", ".join((_coconut.repr(it) for it in self.iters)), ", strict=True" if self.strict else "") - def __reduce__(self): - return (self.__class__, self.iters, {"strict": self.strict}) - def __copy__(self): - self.iters = _coconut.tuple(_coconut_reiterable(it) for it in self.iters) - return self.__class__(*self.iters, strict=self.strict) - def __iter__(self): - for items in _coconut.iter(_coconut.zip(*self.iters, strict=self.strict) if _coconut_sys.version_info >= (3, 10) else _coconut.zip_longest(*self.iters, fillvalue=_coconut_sentinel) if self.strict else _coconut.zip(*self.iters)): - if self.strict and _coconut_sys.version_info < (3, 10) and _coconut.any(x is _coconut_sentinel for x in items): - raise _coconut.ValueError("zip(..., strict=True) arguments have mismatched lengths") - yield items - def __fmap__(self, func): - return _coconut_map(func, self) -class zip_longest(zip): - __slots__ = ("fillvalue",) - __doc__ = getattr(_coconut.zip_longest, "__doc__", "Version of zip that fills in missing values with fillvalue.") - def __new__(cls, *iterables, **kwargs): - self = _coconut.super(_coconut_zip_longest, cls).__new__(cls, *iterables, strict=False) - self.fillvalue = kwargs.pop("fillvalue", None) - if kwargs: - raise _coconut.TypeError(cls.__name__ + "() got unexpected keyword arguments " + _coconut.repr(kwargs)) - return self - def __getitem__(self, index): - self_len = None - if _coconut.isinstance(index, _coconut.slice): - if self_len is None: - self_len = self.__len__() - if self_len is _coconut.NotImplemented: - return self_len - new_ind = _coconut.slice(index.start + self_len if index.start is not None and index.start < 0 else index.start, index.stop + self_len if index.stop is not None and index.stop < 0 else index.stop, index.step) - return self.__class__(*(_coconut_iter_getitem(it, new_ind) for it in self.iters)) - if index < 0: - if self_len is None: - self_len = self.__len__() - if self_len is _coconut.NotImplemented: - return self_len - index += self_len - result = [] - got_non_default = False - for it in self.iters: - try: - result.append(_coconut_iter_getitem(it, index)) - except _coconut.IndexError: - result.append(self.fillvalue) - else: - got_non_default = True - if not got_non_default: - raise _coconut.IndexError("zip_longest index out of range") - return _coconut.tuple(result) - def __len__(self): - if not _coconut.all(_coconut.isinstance(it, _coconut.abc.Sized) for it in self.iters): - return _coconut.NotImplemented - return _coconut.max((_coconut.len(it) for it in self.iters), default=0) - def __repr__(self): - return "zip_longest(%s, fillvalue=%s)" % (", ".join((_coconut.repr(it) for it in self.iters)), _coconut.repr(self.fillvalue)) - def __reduce__(self): - return (self.__class__, self.iters, {"fillvalue": self.fillvalue}) - def __copy__(self): - self.iters = _coconut.tuple(_coconut_reiterable(it) for it in self.iters) - return self.__class__(*self.iters, fillvalue=self.fillvalue) - def __iter__(self): - return _coconut.iter(_coconut.zip_longest(*self.iters, fillvalue=self.fillvalue)) -class filter(_coconut_baseclass, _coconut.filter): - __slots__ = ("func", "iter") - __doc__ = getattr(_coconut.filter, "__doc__", "") - def __new__(cls, function, iterable): - self = _coconut.filter.__new__(cls, function, iterable) - self.func = function - self.iter = iterable - return self - def __reversed__(self): - return self.__class__(self.func, _coconut_reversed(self.iter)) - def __repr__(self): - return "filter(%r, %s)" % (self.func, _coconut.repr(self.iter)) - def __reduce__(self): - return (self.__class__, (self.func, self.iter)) - def __copy__(self): - self.iter = _coconut_reiterable(self.iter) - return self.__class__(self.func, self.iter) - def __iter__(self): - return _coconut.iter(_coconut.filter(self.func, self.iter)) - def __fmap__(self, func): - return _coconut_map(func, self) -class enumerate(_coconut_baseclass, _coconut.enumerate): - __slots__ = ("iter", "start") - __doc__ = getattr(_coconut.enumerate, "__doc__", "") - def __new__(cls, iterable, start=0): - start = _coconut.operator.index(start) - self = _coconut.enumerate.__new__(cls, iterable, start) - self.iter = iterable - self.start = start - return self - def __repr__(self): - return "enumerate(%s, %r)" % (_coconut.repr(self.iter), self.start) - def __fmap__(self, func): - return _coconut_map(func, self) - def __reduce__(self): - return (self.__class__, (self.iter, self.start)) - def __copy__(self): - self.iter = _coconut_reiterable(self.iter) - return self.__class__(self.iter, self.start) - def __iter__(self): - return _coconut.iter(_coconut.enumerate(self.iter, self.start)) - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - return self.__class__(_coconut_iter_getitem(self.iter, index), self.start + (0 if index.start is None else index.start if index.start >= 0 else _coconut.len(self.iter) + index.start)) - return (self.start + index, _coconut_iter_getitem(self.iter, index)) - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return _coconut.len(self.iter) -class multi_enumerate(_coconut_has_iter): - """Enumerate an iterable of iterables. Works like enumerate, but indexes - through inner iterables and produces a tuple index representing the index - in each inner iterable. Supports indexing. - - For numpy arrays, uses np.nditer under the hood and supports len. - """ - __slots__ = () - def __repr__(self): - return "multi_enumerate(%s)" % (_coconut.repr(self.iter),) - def __reduce__(self): - return (self.__class__, (self.iter,)) - def __copy__(self): - return self.__class__(self.get_new_iter()) - @property - def is_numpy(self): - return _coconut_get_base_module(self.iter) in _coconut.numpy_modules - def __iter__(self): - if self.is_numpy: - it = _coconut.numpy.nditer(self.iter, ["multi_index", "refs_ok"], [["readonly"]]) - for x in it: - x, = x.flatten() - yield it.multi_index, x - else: - ind = [-1] - its = [_coconut.iter(self.iter)] - while its: - ind[-1] += 1 - try: - x = _coconut.next(its[-1]) - except _coconut.StopIteration: - ind.pop() - its.pop() - else: - if _coconut.isinstance(x, _coconut.abc.Iterable): - ind.append(-1) - its.append(_coconut.iter(x)) - else: - yield _coconut.tuple(ind), x - def __getitem__(self, index): - if self.is_numpy and not _coconut.isinstance(index, _coconut.slice): - multi_ind = [] - for i in _coconut.reversed(self.iter.shape): - multi_ind.append(index % i) - index //= i - multi_ind = _coconut.tuple(_coconut.reversed(multi_ind)) - return multi_ind, self.iter[multi_ind] - return _coconut_iter_getitem(_coconut.iter(self), index) - def __len__(self): - if self.is_numpy: - return self.iter.size - return _coconut.NotImplemented -class count(_coconut_baseclass): - __slots__ = ("start", "step") - __doc__ = getattr(_coconut.itertools.count, "__doc__", "count(start, step) returns an infinite iterator starting at start and increasing by step.") - def __init__(self, start=0, step=1): - self.start = start - self.step = step - def __reduce__(self): - return (self.__class__, (self.start, self.step)) - def __repr__(self): - return "count(%s, %s)" % (_coconut.repr(self.start), _coconut.repr(self.step)) - def __iter__(self): - while True: - yield self.start - if self.step: - self.start += self.step - def __fmap__(self, func): - return _coconut_map(func, self) - def __contains__(self, elem): - if not self.step: - return elem == self.start - if self.step > 0 and elem < self.start or self.step < 0 and elem > self.start: - return False - return (elem - self.start) % self.step == 0 - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - if (index.start is None or index.start >= 0) and (index.stop is None or index.stop >= 0): - new_start, new_step = self.start, self.step - if self.step and index.start is not None: - new_start += self.step * index.start - if self.step and index.step is not None: - new_step *= index.step - if index.stop is None: - return self.__class__(new_start, new_step) - if self.step and _coconut.isinstance(self.start, _coconut.int) and _coconut.isinstance(self.step, _coconut.int): - return _coconut.range(new_start, self.start + self.step * index.stop, new_step) - return _coconut_map(self.__getitem__, _coconut.range(index.start if index.start is not None else 0, index.stop, index.step if index.step is not None else 1)) - raise _coconut.IndexError("count() indices cannot be negative") - if index < 0: - raise _coconut.IndexError("count() indices cannot be negative") - return self.start + self.step * index if self.step else self.start - def count(self, elem): - """Count the number of times elem appears in the count.""" - if not self.step: - return _coconut.float("inf") if elem == self.start else 0 - return _coconut.int(elem in self) - def index(self, elem): - """Find the index of elem in the count.""" - if elem not in self: - raise _coconut.ValueError(_coconut.repr(elem) + " not in " + _coconut.repr(self)) - return (elem - self.start) // self.step if self.step else 0 - def __reversed__(self): - if not self.step: - return self - raise _coconut.TypeError(_coconut.repr(self) + " object is not reversible") -class cycle(_coconut_has_iter): - """cycle is a modified version of itertools.cycle with a times parameter - that controls the number of times to cycle through the given iterable - before stopping.""" - __slots__ = ("times",) - def __new__(cls, iterable, times=None): - self = _coconut.super(_coconut_cycle, cls).__new__(cls, iterable) - if times is None: - self.times = None - else: - self.times = _coconut.operator.index(times) - if self.times < 0: - raise _coconut.ValueError("cycle: times cannot be negative") - return self - def __reduce__(self): - return (self.__class__, (self.iter, self.times)) - def __copy__(self): - return self.__class__(self.get_new_iter(), self.times) - def __repr__(self): - return "cycle(%s, %r)" % (_coconut.repr(self.iter), self.times) - def __iter__(self): - i = 0 - while self.times is None or i < self.times: - for x in self.get_new_iter(): - yield x - i += 1 - def __contains__(self, elem): - return elem in self.iter - def __getitem__(self, index): - if not _coconut.isinstance(index, _coconut.slice): - if self.times is not None and index // _coconut.len(self.iter) >= self.times: - raise _coconut.IndexError("cycle index out of range") - return self.iter[index % _coconut.len(self.iter)] - if self.times is None: - return _coconut_map(self.__getitem__, _coconut_count()[index]) - else: - return _coconut_map(self.__getitem__, _coconut_range(0, _coconut.len(self))[index]) - def __len__(self): - if self.times is None or not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return _coconut.len(self.iter) * self.times - def __reversed__(self): - if self.times is None: - raise _coconut.TypeError(_coconut.repr(self) + " object is not reversible") - return self.__class__(_coconut_reversed(self.get_new_iter()), self.times) - def count(self, elem): - """Count the number of times elem appears in the cycle.""" - return self.iter.count(elem) * (float("inf") if self.times is None else self.times) - def index(self, elem): - """Find the index of elem in the cycle.""" - if elem not in self.iter: - raise _coconut.ValueError(_coconut.repr(elem) + " not in " + _coconut.repr(self)) - return self.iter.index(elem) -class windowsof(_coconut_has_iter): - """Produces an iterable that effectively mimics a sliding window over iterable of the given size. - The step determines the spacing between windowsof. - - If the size is larger than the iterable, windowsof will produce an empty iterable. - If that is not the desired behavior, fillvalue can be passed and will be used in place of missing values.""" - __slots__ = ("size", "fillvalue", "step") - def __new__(cls, size, iterable, fillvalue=_coconut_sentinel, step=1): - self = _coconut.super(_coconut_windowsof, cls).__new__(cls, iterable) - self.size = _coconut.operator.index(size) - if self.size < 1: - raise _coconut.ValueError("windowsof: size must be >= 1; not %r" % (self.size,)) - self.fillvalue = fillvalue - self.step = _coconut.operator.index(step) - if self.step < 1: - raise _coconut.ValueError("windowsof: step must be >= 1; not %r" % (self.step,)) - return self - def __reduce__(self): - return (self.__class__, (self.size, self.iter, self.fillvalue, self.step)) - def __copy__(self): - return self.__class__(self.size, self.get_new_iter(), self.fillvalue, self.step) - def __repr__(self): - return "windowsof(" + _coconut.repr(self.size) + ", " + _coconut.repr(self.iter) + (", fillvalue=" + _coconut.repr(self.fillvalue) if self.fillvalue is not _coconut_sentinel else "") + (", step=" + _coconut.repr(self.step) if self.step != 1 else "") + ")" - def __iter__(self): - cache = _coconut.collections.deque() - i = 0 - for x in self.iter: - i += 1 - cache.append(x) - if _coconut.len(cache) == self.size: - yield _coconut.tuple(cache) - for _ in _coconut.range(self.step): - cache.popleft() - if self.fillvalue is not _coconut_sentinel and (i < self.size or i % self.step != 0): - while _coconut.len(cache) < self.size: - cache.append(self.fillvalue) - yield _coconut.tuple(cache) - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - if _coconut.len(self.iter) < self.size: - return 0 if self.fillvalue is _coconut_sentinel else 1 - return (_coconut.len(self.iter) - self.size + self.step) // self.step + _coconut.int(_coconut.len(self.iter) % self.step != 0 if self.fillvalue is not _coconut_sentinel else 0) -class groupsof(_coconut_has_iter): - """groupsof(n, iterable) splits iterable into groups of size n. - - If the length of the iterable is not divisible by n, the last group will be of size < n. - """ - __slots__ = ("group_size", "fillvalue") - def __new__(cls, n, iterable, fillvalue=_coconut_sentinel): - self = _coconut.super(_coconut_groupsof, cls).__new__(cls, iterable) - self.group_size = _coconut.operator.index(n) - if self.group_size < 1: - raise _coconut.ValueError("group size must be >= 1; not %r" % (self.group_size,)) - self.fillvalue = fillvalue - return self - def __iter__(self): - iterator = _coconut.iter(self.iter) - loop = True - while loop: - group = [] - for _ in _coconut.range(self.group_size): - try: - group.append(_coconut.next(iterator)) - except _coconut.StopIteration: - loop = False - break - if group: - if not loop and self.fillvalue is not _coconut_sentinel: - while _coconut.len(group) < self.group_size: - group.append(self.fillvalue) - yield _coconut.tuple(group) - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return (_coconut.len(self.iter) + self.group_size - 1) // self.group_size - def __repr__(self): - return "groupsof(" + _coconut.repr(self.group_size) + ", " + _coconut.repr(self.iter) + (", fillvalue=" + _coconut.repr(self.fillvalue) if self.fillvalue is not _coconut_sentinel else "") + ")" - def __reduce__(self): - return (self.__class__, (self.group_size, self.iter)) - def __copy__(self): - return self.__class__(self.group_size, self.get_new_iter()) -class recursive_generator(_coconut_base_callable): - """Decorator that memoizes a generator (or any function that returns an iterator). - Particularly useful for recursive generators, which may require recursive_generator to function properly.""" - __slots__ = ("func", "reit_store") - def __init__(self, func): - self.func = func - self.reit_store = _coconut.dict() - def __call__(self, *args, **kwargs): - key = (0, args, _coconut.frozenset(kwargs.items())) - try: - _coconut.hash(key) - except _coconut.TypeError: - try: - key = (1, _coconut.pickle.dumps(key, -1)) - except _coconut.Exception: - raise _coconut.TypeError("recursive_generator() requires function arguments to be hashable or pickleable") - reit = self.reit_store.get(key) - if reit is None: - reit = _coconut_reiterable(self.func(*args, **kwargs)) - self.reit_store[key] = reit - return reit - def __repr__(self): - return "recursive_generator(%r)" % (self.func,) - def __reduce__(self): - return (self.__class__, (self.func,)) -class _coconut_FunctionMatchErrorContext(_coconut_baseclass): - __slots__ = ("exc_class", "taken") - _threadlocal_ns = _coconut.threading.local() - def __init__(self, exc_class): - self.exc_class = exc_class - self.taken = False - @classmethod - def get_contexts(cls): - return cls._threadlocal_ns.__dict__.setdefault("contexts", []) - def __enter__(self): - self.get_contexts().append(self) - def __exit__(self, type, value, traceback): - self.get_contexts().pop() - def __reduce__(self): - return (self.__class__, (self.exc_class,)) -def _coconut_get_function_match_error(): - contexts = _coconut_FunctionMatchErrorContext.get_contexts() - if not contexts: - return _coconut_MatchError - ctx = contexts[-1] - if ctx.taken: - return _coconut_MatchError - ctx.taken = True - return ctx.exc_class -class _coconut_base_pattern_func(_coconut_base_callable): - _coconut_is_match = True - def __init__(self, *funcs): - self.FunctionMatchError = _coconut.type(_coconut_py_str("MatchError"), (_coconut_MatchError,), _coconut_py_dict()) - self.patterns = [] - self.__doc__ = None - self.__name__ = None - if _coconut_sys.version_info >= (3, 7): - self.__qualname__ = None - for func in funcs: - self.add_pattern(func) - def add_pattern(self, func): - if _coconut.isinstance(func, _coconut_base_pattern_func): - self.patterns += func.patterns - else: - self.patterns.append(func) - self.__doc__ = _coconut.getattr(func, "__doc__", self.__doc__) - self.__name__ = _coconut.getattr(func, "__name__", self.__name__) - if _coconut_sys.version_info >= (3, 7): - self.__qualname__ = _coconut.getattr(func, "__qualname__", self.__qualname__) - def __call__(self, *args, **kwargs): - for func in self.patterns[:-1]: - try: - with _coconut_FunctionMatchErrorContext(self.FunctionMatchError): - return func(*args, **kwargs) - except self.FunctionMatchError: - pass - return self.patterns[-1](*args, **kwargs) - def _coconut_tco_func(self, *args, **kwargs): - for func in self.patterns[:-1]: - try: - with _coconut_FunctionMatchErrorContext(self.FunctionMatchError): - return func(*args, **kwargs) - except self.FunctionMatchError: - pass - return _coconut_tail_call(self.patterns[-1], *args, **kwargs) - def __repr__(self): - return "addpattern(%r)(*%r)" % (self.patterns[0], self.patterns[1:]) - def __reduce__(self): - return (self.__class__, _coconut.tuple(self.patterns)) -def _coconut_mark_as_match(base_func): - base_func._coconut_is_match = True - return base_func -def addpattern(base_func, *add_funcs, **kwargs): - """Decorator to add new cases to a pattern-matching function (where the new case is checked last). - - Pass allow_any_func=True to allow any object as the base_func rather than just pattern-matching functions. - If add_funcs are passed, addpattern(base_func, add_func) is equivalent to addpattern(base_func)(add_func). - """ - allow_any_func = kwargs.pop("allow_any_func", False) - if not allow_any_func and not _coconut.getattr(base_func, "_coconut_is_match", False): - _coconut.warnings.warn("Possible misuse of addpattern with non-pattern-matching function " + _coconut.repr(base_func) + " (pass allow_any_func=True to dismiss)", _coconut_CoconutWarning, 2) - if kwargs: - raise _coconut.TypeError("addpattern() got unexpected keyword arguments " + _coconut.repr(kwargs)) - if add_funcs: - return _coconut_base_pattern_func(base_func, *add_funcs) - return _coconut_partial(_coconut_base_pattern_func, base_func) -_coconut_addpattern = addpattern -class _coconut_complex_partial(_coconut_base_callable): - __slots__ = ("func", "_argdict", "_arglen", "_pos_kwargs", "_stargs", "keywords", "__name__") - def __init__(self, _coconut_func, _coconut_argdict, _coconut_arglen, _coconut_pos_kwargs, *args, **kwargs): - self.func = _coconut_func - self._argdict = _coconut_argdict - self._arglen = _coconut_arglen - self._pos_kwargs = _coconut_pos_kwargs - self._stargs = args - self.keywords = kwargs - self.__name__ = _coconut.getattr(_coconut_func, "__name__", None) - def __reduce__(self): - return (self.__class__, (self.func, self._argdict, self._arglen, self._pos_kwargs) + self._stargs, {"keywords": self.keywords}) - @property - def args(self): - return _coconut.tuple(self._argdict.get(i) for i in _coconut.range(self._arglen)) + self._stargs - @property - def required_nargs(self): - return self._arglen - _coconut.len(self._argdict) + len(self._pos_kwargs) - def __call__(self, *args, **kwargs): - callargs = [] - argind = 0 - for i in _coconut.range(self._arglen): - if i in self._argdict: - callargs.append(self._argdict[i]) - elif argind >= _coconut.len(args): - raise _coconut.TypeError("expected at least " + _coconut.str(self.required_nargs) + " argument(s) to " + _coconut.repr(self)) - else: - callargs.append(args[argind]) - argind += 1 - for k in self._pos_kwargs: - if k in kwargs: - raise _coconut.TypeError(_coconut.repr(k) + " is an invalid keyword argument for " + _coconut.repr(self)) - elif argind >= _coconut.len(args): - raise _coconut.TypeError("expected at least " + _coconut.str(self.required_nargs) + " argument(s) to " + _coconut.repr(self)) - else: - kwargs[k] = args[argind] - argind += 1 - callargs += self._stargs - callargs += args[argind:] - callkwargs = self.keywords.copy() - callkwargs.update(kwargs) - return self.func(*callargs, **callkwargs) - def __repr__(self): - args = [] - for i in _coconut.range(self._arglen): - if i in self._argdict: - args.append(_coconut.repr(self._argdict[i])) - else: - args.append("?") - for arg in self._stargs: - args.append(_coconut.repr(arg)) - for k in self._pos_kwargs: - args.append(k + "=?") - for k, v in self.keywords.items(): - args.append(k + "=" + _coconut.repr(v)) - return "%r$(%s)" % (self.func, ", ".join(args)) -def consume(iterable, keep_last=0): - """consume(iterable, keep_last) fully exhausts iterable and returns the last keep_last elements.""" - return _coconut.collections.deque(iterable, maxlen=keep_last) -class starmap(_coconut_baseclass, _coconut.itertools.starmap): - __slots__ = ("func", "iter") - __doc__ = getattr(_coconut.itertools.starmap, "__doc__", "starmap(func, iterable) = (func(*args) for args in iterable)") - def __new__(cls, function, iterable): - self = _coconut.itertools.starmap.__new__(cls, function, iterable) - self.func = function - self.iter = iterable - return self - def __getitem__(self, index): - if _coconut.isinstance(index, _coconut.slice): - return self.__class__(self.func, _coconut_iter_getitem(self.iter, index)) - return self.func(*_coconut_iter_getitem(self.iter, index)) - def __reversed__(self): - return self.__class__(self.func, *_coconut_reversed(self.iter)) - def __len__(self): - if not _coconut.isinstance(self.iter, _coconut.abc.Sized): - return _coconut.NotImplemented - return _coconut.len(self.iter) - def __repr__(self): - return "starmap(%r, %s)" % (self.func, _coconut.repr(self.iter)) - def __reduce__(self): - return (self.__class__, (self.func, self.iter)) - def __copy__(self): - self.iter = _coconut_reiterable(self.iter) - return self.__class__(self.func, self.iter) - def __iter__(self): - return _coconut.iter(_coconut.itertools.starmap(self.func, self.iter)) - def __fmap__(self, func): - return self.__class__(_coconut_forward_compose(self.func, func), self.iter) -class multiset(_coconut.collections.Counter, object): - __slots__ = () - __doc__ = getattr(_coconut.collections.Counter, "__doc__", "multiset is a version of set that counts the number of times each element is added.") - def add(self, item): - """Add an element to a multiset.""" - self[item] += 1 - def remove(self, item, **kwargs): - """Remove an element from a multiset; it must be a member.""" - allow_missing = kwargs.pop("allow_missing", False) - if kwargs: - raise _coconut.TypeError("multiset.remove() got unexpected keyword arguments " + _coconut.repr(kwargs)) - item_count = self[item] - if item_count > 0: - self[item] = item_count - 1 - if item_count - 1 <= 0: - del self[item] - elif not allow_missing: - raise _coconut.KeyError(item) - def discard(self, item): - """Remove an element from a multiset if it is a member.""" - return self.remove(item, allow_missing=True) - def isdisjoint(self, other): - """Return True if two multisets have a null intersection.""" - return not self & other - def __xor__(self, other): - return self - other | other - self - def __ixor__(self, other): - right = other - self - self -= other - self |= right - return self - def count(self, item): - """Return the number of times an element occurs in a multiset. - Equivalent to multiset[item], but additionally verifies the count is non-negative.""" - result = self[item] - if result < 0: - raise _coconut.ValueError("multiset has negative count for " + _coconut.repr(item)) - return result - def __fmap__(self, func): - return self.__class__(_coconut.dict((func(obj), num) for obj, num in self.items())) - if _coconut_sys.version_info < (3,): - def __add__(self, other): - return self.__class__(_coconut.super(_coconut_multiset, self).__add__(other)) - def __and__(self, other): - return self.__class__(_coconut.super(_coconut_multiset, self).__and__(other)) - def __or__(self, other): - return self.__class__(_coconut.super(_coconut_multiset, self).__or__(other)) - def __sub__(self, other): - return self.__class__(_coconut.super(_coconut_multiset, self).__sub__(other)) - def __pos__(self): - return self + _coconut_multiset() - def __neg__(self): - return _coconut_multiset() - self - else: - def __add__(self, other): - out = self.copy() - out += other - return out - def __and__(self, other): - out = self.copy() - out &= other - return out - def __or__(self, other): - out = self.copy() - out |= other - return out - def __sub__(self, other): - out = self.copy() - out -= other - return out - def __pos__(self): - return self.__class__(_coconut.super(_coconut_multiset, self).__pos__()) - def __neg__(self): - return self.__class__(_coconut.super(_coconut_multiset, self).__neg__()) - if _coconut_sys.version_info < (3, 10): - def total(self): - """Compute the sum of the counts in a multiset. - Note that total_size is different from len(multiset), which only counts the unique elements.""" - return _coconut.sum(self.values()) - def __eq__(self, other): - if not _coconut.isinstance(other, _coconut.dict): - return False - if not _coconut.isinstance(other, _coconut.collections.Counter): - return _coconut.NotImplemented - for k, v in self.items(): - if other[k] != v: - return False - for k, v in other.items(): - if self[k] != v: - return False - return True - __ne__ = _coconut.object.__ne__ - def __le__(self, other): - if not _coconut.isinstance(other, _coconut.collections.Counter): - return _coconut.NotImplemented - for k, v in self.items(): - if not (v <= other[k]): - return False - for k, v in other.items(): - if not (self[k] <= v): - return False - return True - def __lt__(self, other): - if not _coconut.isinstance(other, _coconut.collections.Counter): - return _coconut.NotImplemented - found_diff = False - for k, v in self.items(): - if not (v <= other[k]): - return False - found_diff = found_diff or v != other[k] - for k, v in other.items(): - if not (self[k] <= v): - return False - found_diff = found_diff or self[k] != v - return found_diff - if _coconut_sys.version_info < (3,): - def __bool__(self): - return _coconut.bool(_coconut.len(self)) - keys = _coconut.collections.Counter.viewkeys - values = _coconut.collections.Counter.viewvalues - items = _coconut.collections.Counter.viewitems -_coconut.abc.MutableSet.register(multiset) -def _coconut_base_makedata(data_type, args, from_fmap=False, fallback_to_init=False): - if _coconut.hasattr(data_type, "_make") and _coconut.issubclass(data_type, _coconut.tuple): - return data_type._make(args) - if _coconut.issubclass(data_type, (_coconut.range, _coconut.abc.Iterator)): - return args - if _coconut.issubclass(data_type, _coconut.str): - return "".join(args) - if fallback_to_init or _coconut.issubclass(data_type, _coconut.fmappables): - return data_type(args) - if from_fmap: - raise _coconut.TypeError("no known __fmap__ implementation for " + _coconut.repr(data_type) + " (pass fallback_to_init=True to fall back on __init__ and __iter__)") - raise _coconut.TypeError("no known makedata implementation for " + _coconut.repr(data_type) + " (pass fallback_to_init=True to fall back on __init__)") -def makedata(data_type, *args, **kwargs): - """Construct an object of the given data_type containing the given arguments.""" - fallback_to_init = kwargs.pop("fallback_to_init", False) - if kwargs: - raise _coconut.TypeError("makedata() got unexpected keyword arguments " + _coconut.repr(kwargs)) - return _coconut_base_makedata(data_type, args, fallback_to_init=fallback_to_init) -if _coconut_sys.version_info < (3, 3): - _coconut_amap = None -else: - class _coconut_amap(_coconut_baseclass): - __slots__ = ("func", "aiter") - def __init__(self, func, aiter): - self.func = func - self.aiter = aiter - def __reduce__(self): - return (self.__class__, (self.func, self.aiter)) - def __repr__(self): - return "fmap(" + _coconut.repr(self.func) + ", " + _coconut.repr(self.aiter) + ")" - def __aiter__(self): - return self - if _coconut_sys.version_info < (3, 5): - _coconut___anext___ns = {"_coconut": _coconut} - _coconut_exec('def __anext__(self):\n result = yield from self.aiter.__anext__()\n return self.func(result)', _coconut___anext___ns) - __anext__ = _coconut.asyncio.coroutine(_coconut___anext___ns["__anext__"]) - else: - _coconut___anext___ns = {"_coconut": _coconut} - _coconut_exec('async def __anext__(self):\n return self.func(await self.aiter.__anext__())', _coconut___anext___ns) - __anext__ = _coconut___anext___ns["__anext__"] -def fmap(func, obj, **kwargs): - """fmap(func, obj) creates a copy of obj with func applied to its contents. - - Supports: - * Coconut data types - * `str`, `dict`, `list`, `tuple`, `set`, `frozenset`, `bytes`, `bytearray` - * `dict` (maps over .items()) - * asynchronous iterables - * numpy arrays (uses np.vectorize) - * pandas objects (uses .apply) - - Override by defining obj.__fmap__(func). - """ - starmap_over_mappings = kwargs.pop("starmap_over_mappings", False) - fallback_to_init = kwargs.pop("fallback_to_init", False) - if kwargs: - raise _coconut.TypeError("fmap() got unexpected keyword arguments " + _coconut.repr(kwargs)) - obj_fmap = _coconut.getattr(obj, "__fmap__", None) - if obj_fmap is not None: - try: - result = obj_fmap(func) - except _coconut.NotImplementedError: - pass - else: - if result is not _coconut.NotImplemented: - return result - obj_module = _coconut_get_base_module(obj) - if obj_module in _coconut.xarray_modules: - return _coconut_fmap(func, _coconut_xarray_to_pandas(obj)).to_xarray() - if obj_module in _coconut.pandas_modules: - if obj.ndim <= 1: - return obj.apply(func) - return obj.apply(func, axis=obj.ndim-1) - if obj_module in _coconut.jax_numpy_modules: - import jax.numpy as jnp - return jnp.vectorize(func)(obj) - if obj_module in _coconut.numpy_modules: - return _coconut.numpy.vectorize(func)(obj) - obj_aiter = _coconut.getattr(obj, "__aiter__", None) - if obj_aiter is not None and _coconut_amap is not None: - try: - aiter = obj_aiter() - except _coconut.NotImplementedError: - pass - else: - if aiter is not _coconut.NotImplemented: - return _coconut_amap(func, aiter) - if _coconut_sys.version_info < (3,): - if _coconut.isinstance(obj, _coconut.bytes): - return _coconut_base_makedata(_coconut.bytes, [func(_coconut.ord(x)) for x in obj], from_fmap=True, fallback_to_init=fallback_to_init) - if _coconut.isinstance(obj, _coconut.abc.Mapping): - mapped_obj = (_coconut_starmap if starmap_over_mappings else _coconut_map)(func, obj.items()) - else: - mapped_obj = _coconut_map(func, obj) - return _coconut_base_makedata(obj.__class__, mapped_obj, from_fmap=True, fallback_to_init=fallback_to_init) -def memoize(*args, **kwargs): - """Decorator that memoizes a function, preventing it from being recomputed - if it is called multiple times with the same arguments.""" - if not kwargs and _coconut.len(args) == 1 and _coconut.callable(args[0]): - return _coconut_memoize_helper()(args[0]) - if _coconut.len(kwargs) == 1 and "user_function" in kwargs and _coconut.callable(kwargs["user_function"]): - return _coconut_memoize_helper()(kwargs["user_function"]) - return _coconut_memoize_helper(*args, **kwargs) -memoize.RECURSIVE = _coconut_Sentinel() -def _coconut_memoize_helper(maxsize=None, typed=False): - if maxsize is memoize.RECURSIVE: - def memoizer(func): - """memoize(...)""" - inside = [False] - cache = _coconut.dict() - @_coconut_wraps(func) - def memoized_func(*args, **kwargs): - if typed: - key = (_coconut.tuple((x, _coconut.type(x)) for x in args), _coconut.tuple((k, _coconut.type(k), v, _coconut.type(v)) for k, v in kwargs.items())) - else: - key = (args, _coconut.tuple(kwargs.items())) - got = cache.get(key, _coconut_sentinel) - if got is not _coconut_sentinel: - return got - outer_inside, inside[0] = inside[0], True - try: - got = func(*args, **kwargs) - cache[key] = got - return got - finally: - inside[0] = outer_inside - if not inside[0]: - cache.clear() - memoized_func.__module__ = _coconut.getattr(func, "__module__", None) - memoized_func.__name__ = _coconut.getattr(func, "__name__", None) - memoized_func.__qualname__ = _coconut.getattr(func, "__qualname__", None) - return memoized_func - return memoizer - else: - return _coconut.functools.lru_cache(maxsize, typed) -def _coconut_call_set_names(cls): - if _coconut_sys.version_info < (3, 6): - for k, v in _coconut.vars(cls).items(): - set_name = _coconut.getattr(v, "__set_name__", None) - if set_name is not None: - set_name(cls, k) -class override(_coconut_baseclass): - """Declare a method in a subclass as an override of a parent class method. - Enforces at runtime that the parent class has such a method to be overwritten.""" - __slots__ = ("func",) - def __init__(self, func): - self.func = func - def __get__(self, obj, objtype=None): - self_func_get = _coconut.getattr(self.func, "__get__", None) - if self_func_get is not None: - if objtype is None: - return self_func_get(obj) - else: - return self_func_get(obj, objtype) - if obj is None: - return self.func - if _coconut_sys.version_info < (3,): - return _coconut.types.MethodType(self.func, obj, objtype) - else: - return _coconut.types.MethodType(self.func, obj) - def __set_name__(self, obj, name): - if not _coconut.hasattr(_coconut.super(obj, obj), name): - raise _coconut.RuntimeError(obj.__name__ + "." + name + " marked with @override but not overriding anything") - def __reduce__(self): - return (self.__class__, (self.func,)) -def reveal_type(obj): - """Special function to get MyPy to print the type of the given expression. - At runtime, reveal_type is the identity function.""" - return obj -def reveal_locals(): - """Special function to get MyPy to print the type of the current locals. - At runtime, reveal_locals always returns None.""" - pass -def _coconut_dict_merge(*dicts, **kwargs): - for_func = kwargs.pop("for_func", False) - assert not kwargs, "error with internal Coconut function _coconut_dict_merge (you should report this at https://github.com/evhub/coconut/issues/new)" - newdict = _coconut.dict() - prevlen = 0 - for d in dicts: - newdict.update(d) - if for_func: - if _coconut.len(newdict) != prevlen + _coconut.len(d): - raise _coconut.TypeError("multiple values for the same keyword argument") - prevlen = _coconut.len(newdict) - return newdict -def ident(x, **kwargs): - """The identity function. Generally equivalent to x => x. Useful in point-free programming. - Accepts one keyword-only argument, side_effect, which specifies a function to call on the argument before it is returned.""" - side_effect = kwargs.pop("side_effect", None) - if kwargs: - raise _coconut.TypeError("ident() got unexpected keyword arguments " + _coconut.repr(kwargs)) - if side_effect is not None: - side_effect(x) - return x -if _coconut_sys.version_info < (3, 11): - def call(_coconut_f, *args, **kwargs): - """Function application operator function. - - Equivalent to: - def call(f, /, *args, **kwargs) = f(*args, **kwargs). - """ - return _coconut_f(*args, **kwargs) -else: - call = _coconut.operator.call -def safe_call(_coconut_f, *args, **kwargs): - """safe_call is a version of call that catches any Exceptions and - returns an Expected containing either the result or the error. - - Equivalent to: - def safe_call(f, /, *args, **kwargs): - try: - return Expected(f(*args, **kwargs)) - except Exception as err: - return Expected(error=err) - """ - try: - return _coconut_Expected(_coconut_f(*args, **kwargs)) - except _coconut.Exception as err: - return _coconut_Expected(error=err) -class Expected(_coconut.collections.namedtuple("Expected", ("result", "error")), object): - '''Coconut's Expected built-in is a Coconut data that represents a value - that may or may not be an error, similar to Haskell's Either. - - Effectively equivalent to: - data Expected[T](result: T? = None, error: BaseException? = None): - def __bool__(self) -> bool: - return self.error is None - def __fmap__[U](self, func: T -> U) -> Expected[U]: - """Maps func over the result if it exists. - - __fmap__ should be used directly only when fmap is not available (e.g. when consuming an Expected in vanilla Python). - """ - return self.__class__(func(self.result)) if self else self - def and_then[U](self, func: T -> Expected[U]) -> Expected[U]: - """Maps a T -> Expected[U] over an Expected[T] to produce an Expected[U]. - Implements a monadic bind. Equivalent to fmap ..> .join().""" - return self |> fmap$(func) |> .join() - def join(self: Expected[Expected[T]]) -> Expected[T]: - """Monadic join. Converts Expected[Expected[T]] to Expected[T].""" - if not self: - return self - if not self.result `isinstance` Expected: - raise TypeError("Expected.join() requires an Expected[Expected[_]]") - return self.result - def map_error(self, func: BaseException -> BaseException) -> Expected[T]: - """Maps func over the error if it exists.""" - return self if self else self.__class__(error=func(self.error)) - def handle(self, err_type, handler: BaseException -> T) -> Expected[T]: - """Recover from the given err_type by calling handler on the error to determine the result.""" - if not self and isinstance(self.error, err_type): - return self.__class__(handler(self.error)) - return self - def expect_error(self, *err_types: BaseException) -> Expected[T]: - """Raise any errors that do not match the given error types.""" - if not self and not isinstance(self.error, err_types): - raise self.error - return self - def unwrap(self) -> T: - """Unwrap the result or raise the error.""" - if not self: - raise self.error - return self.result - def or_else[U](self, func: BaseException -> Expected[U]) -> Expected[T | U]: - """Return self if no error, otherwise return the result of evaluating func on the error.""" - return self if self else func(self.error) - def result_or_else[U](self, func: BaseException -> U) -> T | U: - """Return the result if it exists, otherwise return the result of evaluating func on the error.""" - return self.result if self else func(self.error) - def result_or[U](self, default: U) -> T | U: - """Return the result if it exists, otherwise return the default. - - Since .result_or() completely silences errors, it is highly recommended that you - call .expect_error() first to explicitly declare what errors you are okay silencing. - """ - return self.result if self else default - ''' - __slots__ = () - _coconut_is_data = True - __match_args__ = ("result", "error") - _coconut_data_defaults = {0: None, 1: None} - def __add__(self, other): return _coconut.NotImplemented - def __mul__(self, other): return _coconut.NotImplemented - def __rmul__(self, other): return _coconut.NotImplemented - __ne__ = _coconut.object.__ne__ - def __eq__(self, other): - return self.__class__ is other.__class__ and _coconut.tuple.__eq__(self, other) - def __hash__(self): - return _coconut.tuple.__hash__(self) ^ hash(self.__class__) - def __new__(cls, result=_coconut_sentinel, error=None): - if result is not _coconut_sentinel and error is not None: - raise _coconut.TypeError("Expected cannot have both a result and an error") - if result is _coconut_sentinel and error is None: - raise _coconut.TypeError("Expected must have either a result or an error") - if result is _coconut_sentinel: - result = None - return _coconut.tuple.__new__(cls, (result, error)) - def __bool__(self): - return self.error is None - def __fmap__(self, func): - """Maps func over the result if it exists. - - __fmap__ should be used directly only when fmap is not available (e.g. when consuming an Expected in vanilla Python). - """ - return self.__class__(func(self.result)) if self else self - def and_then(self, func): - """Maps a T -> Expected[U] over an Expected[T] to produce an Expected[U]. - Implements a monadic bind. Equivalent to fmap ..> .join().""" - return self.__fmap__(func).join() - def join(self): - """Monadic join. Converts Expected[Expected[T]] to Expected[T].""" - if not self: - return self - if not _coconut.isinstance(self.result, _coconut_Expected): - raise _coconut.TypeError("Expected.join() requires an Expected[Expected[_]]") - return self.result - def map_error(self, func): - """Maps func over the error if it exists.""" - return self if self else self.__class__(error=func(self.error)) - def handle(self, err_type, handler): - """Recover from the given err_type by calling handler on the error to determine the result.""" - if not self and _coconut.isinstance(self.error, err_type): - return self.__class__(handler(self.error)) - return self - def expect_error(self, *err_types): - """Raise any errors that do not match the given error types.""" - if not self and not _coconut.isinstance(self.error, err_types): - raise self.error - return self - def unwrap(self): - """Unwrap the result or raise the error.""" - if not self: - raise self.error - return self.result - def or_else(self, func): - """Return self if no error, otherwise return the result of evaluating func on the error.""" - if self: - return self - got = func(self.error) - if not _coconut.isinstance(got, _coconut_Expected): - raise _coconut.TypeError("Expected.or_else() requires a function that returns an Expected") - return got - def result_or_else(self, func): - """Return the result if it exists, otherwise return the result of evaluating func on the error.""" - return self.result if self else func(self.error) - def result_or(self, default): - """Return the result if it exists, otherwise return the default. - - Since .result_or() completely silences errors, it is highly recommended that you - call .expect_error() first to explicitly declare what errors you are okay silencing. - """ - return self.result if self else default -class flip(_coconut_base_callable): - """Given a function, return a new function with inverse argument order. - If nargs is passed, only the first nargs arguments are reversed.""" - __slots__ = ("func", "nargs") - def __init__(self, func, nargs=None): - self.func = func - if nargs is None: - self.nargs = None - else: - self.nargs = _coconut.operator.index(nargs) - if self.nargs < 0: - raise _coconut.ValueError("flip: nargs cannot be negative") - def __reduce__(self): - return (self.__class__, (self.func, self.nargs)) - def __call__(self, *args, **kwargs): - if self.nargs is None: - return self.func(*args[::-1], **kwargs) - if self.nargs == 0: - return self.func(*args, **kwargs) - return self.func(*(args[self.nargs-1::-1] + args[self.nargs:]), **kwargs) - def __repr__(self): - return "flip(%r%s)" % (self.func, "" if self.nargs is None else ", " + _coconut.repr(self.nargs)) -class const(_coconut_base_callable): - """Create a function that, whatever its arguments, just returns the given value.""" - __slots__ = ("value",) - def __init__(self, value): - self.value = value - def __reduce__(self): - return (self.__class__, (self.value,)) - def __call__(self, *args, **kwargs): - return self.value - def __repr__(self): - return "const(%s)" % (_coconut.repr(self.value),) -class _coconut_lifted(_coconut_base_callable): - __slots__ = ("apart", "func", "func_args", "func_kwargs") - def __init__(self, apart, func, func_args, func_kwargs): - self.apart = apart - self.func = func - self.func_args = func_args - self.func_kwargs = func_kwargs - def __reduce__(self): - return (self.__class__, (self.apart, self.func, self.func_args, self.func_kwargs)) - def __call__(self, *args, **kwargs): - if self.apart: - return self.func(*(f(x) for f, x in _coconut_zip(self.func_args, args, strict=True)), **_coconut_py_dict((k, self.func_kwargs[k](kwargs[k])) for k in _coconut.set(self.func_kwargs.keys()) | _coconut.set(kwargs.keys()))) - else: - return self.func(*(g(*args, **kwargs) for g in self.func_args), **_coconut_py_dict((k, h(*args, **kwargs)) for k, h in self.func_kwargs.items())) - def __repr__(self): - return "lift%s(%r)(%s%s)" % (self.func, ("_apart" if self.apart else ""), ", ".join(_coconut.repr(g) for g in self.func_args), ", ".join(k + "=" + _coconut.repr(h) for k, h in self.func_kwargs.items())) -class lift(_coconut_base_callable): - """Lift a function up so that all of its arguments are functions that all take the same arguments. - - For a binary function f(x, y) and two unary functions g(z) and h(z), lift works as the S' combinator: - lift(f)(g, h)(z) == f(g(z), h(z)) - - In general, lift is equivalent to: - def lift(f) = ((*func_args, **func_kwargs) => (*args, **kwargs) => ( - f(*(g(*args, **kwargs) for g in func_args), **{k: h(*args, **kwargs) for k, h in func_kwargs.items()})) - ) - - lift also supports a shortcut form such that lift(f, *func_args, **func_kwargs) is equivalent to lift(f)(*func_args, **func_kwargs). - """ - __slots__ = ("func",) - _apart = False - def __new__(cls, func, *func_args, **func_kwargs): - self = _coconut.super(_coconut_lift, cls).__new__(cls) - self.func = func - if func_args or func_kwargs: - self = self(*func_args, **func_kwargs) - return self - def __reduce__(self): - return (self.__class__, (self.func,)) - def __repr__(self): - return "lift%s(%r)" % (("_apart" if self._apart else ""), self.func) - def __call__(self, *func_args, **func_kwargs): - return _coconut_lifted(self._apart, self.func, func_args, func_kwargs) -class lift_apart(lift): - """Lift a function up so that all of its arguments are functions that each take separate arguments. - - For a binary function f(x, y) and two unary functions g(z) and h(z), lift_apart works as the D2 combinator: - lift_apart(f)(g, h)(z, w) == f(g(z), h(w)) - - In general, lift_apart is equivalent to: - def lift_apart(func) = (*func_args, **func_kwargs) => (*args, **kwargs) => func( - *(f(x) for f, x in zip(func_args, args, strict=True)), - **{k: func_kwargs[k](kwargs[k]) for k in func_kwargs.keys() | kwargs.keys()}, - ) - - lift_apart also supports a shortcut form such that lift_apart(f, *func_args, **func_kwargs) is equivalent to lift_apart(f)(*func_args, **func_kwargs). - """ - _apart = True -def all_equal(iterable, to=_coconut_sentinel): - """For a given iterable, check whether all elements in that iterable are equal to each other. - If 'to' is passed, check that all the elements are equal to that value. - - Supports numpy arrays. Assumes transitivity and 'x != y' being equivalent to 'not (x == y)'. - """ - iterable_module = _coconut_get_base_module(iterable) - if iterable_module in _coconut.numpy_modules: - if iterable_module in _coconut.pandas_modules: - iterable = iterable.to_numpy() - elif iterable_module in _coconut.xarray_modules: - iterable = _coconut_xarray_to_numpy(iterable) - return not _coconut.len(iterable) or (iterable == (iterable[0] if to is _coconut_sentinel else to)).all() - first_item = to - for item in iterable: - if first_item is _coconut_sentinel: - first_item = item - elif first_item != item: - return False - return True -def mapreduce(key_value_func, iterable, **kwargs): - """Map key_value_func over iterable, then collect the values into a dictionary of lists keyed by the keys. - - If reduce_func is passed, instead of collecting the values into lists, reduce over - the values for each key with reduce_func, effectively implementing a MapReduce operation. - - If collect_in is passed, initialize the collection from . - """ - collect_in = kwargs.pop("collect_in", None) - reduce_func = kwargs.pop("reduce_func", None if collect_in is None else False) - reduce_func_init = kwargs.pop("reduce_func_init", _coconut_sentinel) - if reduce_func_init is not _coconut_sentinel and not reduce_func: - raise _coconut.TypeError("reduce_func_init requires reduce_func") - map_using = kwargs.pop("map_using", _coconut.map) - if kwargs: - raise _coconut.TypeError("mapreduce()/collectby() got unexpected keyword arguments " + _coconut.repr(kwargs)) - collection = collect_in if collect_in is not None else _coconut.collections.defaultdict(_coconut.list) if reduce_func is None else _coconut.dict() - for key, val in map_using(key_value_func, iterable): - if reduce_func is None: - collection[key].append(val) - else: - old_val = collection.get(key, reduce_func_init) - if old_val is not _coconut_sentinel: - if reduce_func is False: - raise _coconut.ValueError("mapreduce()/collectby() got duplicate key " + repr(key) + " with reduce_func=False") - val = reduce_func(old_val, val) - collection[key] = val - return collection -def _coconut_parallel_mapreduce(mapreduce_func, map_cls, *args, **kwargs): - if "map_using" in kwargs: - raise _coconut.TypeError("redundant map_using argument to process/thread mapreduce/collectby") - kwargs["map_using"] = _coconut.functools.partial(map_cls, stream=True, ordered=kwargs.pop("ordered", False), chunksize=kwargs.pop("chunksize", 1)) - with map_cls.multiple_sequential_calls(max_workers=kwargs.pop("max_workers", None)): - return mapreduce_func(*args, **kwargs) -mapreduce.using_processes = _coconut_partial(_coconut_parallel_mapreduce, mapreduce, process_map) -mapreduce.using_threads = _coconut_partial(_coconut_parallel_mapreduce, mapreduce, thread_map) -def collectby(key_func, iterable, value_func=None, **kwargs): - """Collect the items in iterable into a dictionary of lists keyed by key_func(item). - - If value_func is passed, collect value_func(item) into each list instead of item. - - If reduce_func is passed, instead of collecting the items into lists, reduce over - the items for each key with reduce_func, effectively implementing a MapReduce operation. - - If map_using is passed, calculate key_func and value_func by mapping them over - the iterable using map_using as map. Useful with process_map/thread_map. - """ - return _coconut_mapreduce(_coconut_lifted(False, _coconut_comma_op, (key_func, _coconut_ident if value_func is None else value_func), _coconut.dict()), iterable, **kwargs) -collectby.using_processes = _coconut_partial(_coconut_parallel_mapreduce, collectby, process_map) -collectby.using_threads = _coconut_partial(_coconut_parallel_mapreduce, collectby, thread_map) -def _namedtuple_of(**kwargs): - """Construct an anonymous namedtuple of the given keyword arguments.""" - if _coconut_sys.version_info < (3, 6): - raise _coconut.RuntimeError("_namedtuple_of is not available on Python < 3.6 (use anonymous namedtuple literals instead)") - else: - return _coconut_mk_anon_namedtuple(kwargs.keys(), of_kwargs=kwargs) -def _coconut_mk_anon_namedtuple(fields, types=None, of_kwargs=_coconut.dict(), of_args=()): - if types is None: - NT = _coconut.collections.namedtuple("_namedtuple_of", fields) - else: - NT = _coconut.typing.NamedTuple("_namedtuple_of", [(f, t) for f, t in _coconut.zip(fields, types)]) - _coconut.copyreg.pickle(NT, lambda nt: (_coconut_mk_anon_namedtuple, (nt._fields, types, nt._asdict()))) - if _coconut_sys.version_info < (3, 10): - NT.__match_args__ = _coconut.property(lambda self: self._fields) - if of_kwargs or of_args: - return NT(*of_args, **of_kwargs) - else: - return NT -def _coconut_ndim(arr): - arr_mod = _coconut_get_base_module(arr) - if (arr_mod in _coconut.numpy_modules or _coconut.hasattr(arr.__class__, "__matconcat__")) and _coconut.hasattr(arr, "ndim"): - return arr.ndim - if arr_mod in _coconut.xarray_modules: - return 2 - if not _coconut.isinstance(arr, _coconut.abc.Sequence) or _coconut.isinstance(arr, (_coconut.str, _coconut.bytes)): - return 0 - if _coconut.len(arr) == 0: - return 1 - arr_dim = 1 - inner_arr = arr[0] - if inner_arr == arr: - return 0 - while _coconut.isinstance(inner_arr, _coconut.abc.Sequence): - arr_dim += 1 - if _coconut.len(inner_arr) < 1: - break - new_inner_arr = inner_arr[0] - if new_inner_arr == inner_arr: - break - inner_arr = new_inner_arr - return arr_dim -def _coconut_expand_arr(arr, new_dims): - if (_coconut_get_base_module(arr) in _coconut.numpy_modules or _coconut.hasattr(arr.__class__, "__matconcat__")) and _coconut.hasattr(arr, "reshape"): - return arr.reshape((1,) * new_dims + arr.shape) - for _ in _coconut.range(new_dims): - arr = [arr] - return arr -def _coconut_concatenate(arrs, axis): - for a in arrs: - if _coconut.hasattr(a.__class__, "__matconcat__"): - return a.__class__.__matconcat__(arrs, axis=axis) - arr_modules = [_coconut_get_base_module(a) for a in arrs] - if any(mod in _coconut.xarray_modules for mod in arr_modules): - return _coconut_concatenate([(_coconut_xarray_to_pandas(a) if mod in _coconut.xarray_modules else a) for a, mod in _coconut.zip(arrs, arr_modules)], axis).to_xarray() - if any(mod in _coconut.pandas_modules for mod in arr_modules): - import pandas - return pandas.concat(arrs, axis=axis) - if any(mod in _coconut.jax_numpy_modules for mod in arr_modules): - import jax.numpy - return jax.numpy.concatenate(arrs, axis=axis) - if any(mod in _coconut.numpy_modules for mod in arr_modules): - return _coconut.numpy.concatenate(arrs, axis=axis) - if not axis: - return _coconut.list(_coconut.itertools.chain.from_iterable(arrs)) - return [_coconut_concatenate(rows, axis - 1) for rows in _coconut.zip(*arrs)] -def _coconut_arr_concat_op(dim, *arrs): - """Coconut multi-dimensional array concatenation operator.""" - arr_dims = [_coconut_ndim(a) for a in arrs] - arrs = [_coconut_expand_arr(a, dim - d) if d < dim else a for a, d in _coconut.zip(arrs, arr_dims)] - arr_dims.append(dim) - max_arr_dim = _coconut.max(arr_dims) - return _coconut_concatenate(arrs, max_arr_dim - dim) -def _coconut_call_or_coefficient(func, *args): - if _coconut.callable(func): - return func(*args) - if not _coconut.isinstance(func, (_coconut.int, _coconut.float, _coconut.complex)) and _coconut_get_base_module(func) not in _coconut.numpy_modules: - raise _coconut.TypeError("first object in implicit function application and coefficient syntax must be Callable, int, float, complex, or numpy") - func = func - for x in args: - func = func * x - return func -class _coconut_SupportsAdd(_coconut.typing.Protocol): - """Coconut (+) Protocol. Equivalent to: - - class SupportsAdd[T, U, V](Protocol): - def __add__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __add__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((+) in a typing context is a Protocol)") -class _coconut_SupportsMinus(_coconut.typing.Protocol): - """Coconut (-) Protocol. Equivalent to: - - class SupportsMinus[T, U, V](Protocol): - def __sub__(self: T, other: U) -> V: - raise NotImplementedError - def __neg__(self: T) -> V: - raise NotImplementedError - """ - def __sub__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((-) in a typing context is a Protocol)") - def __neg__(self): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((-) in a typing context is a Protocol)") -class _coconut_SupportsMul(_coconut.typing.Protocol): - """Coconut (*) Protocol. Equivalent to: - - class SupportsMul[T, U, V](Protocol): - def __mul__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __mul__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((*) in a typing context is a Protocol)") -class _coconut_SupportsPow(_coconut.typing.Protocol): - """Coconut (**) Protocol. Equivalent to: - - class SupportsPow[T, U, V](Protocol): - def __pow__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __pow__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((**) in a typing context is a Protocol)") -class _coconut_SupportsTruediv(_coconut.typing.Protocol): - """Coconut (/) Protocol. Equivalent to: - - class SupportsTruediv[T, U, V](Protocol): - def __truediv__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __truediv__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((/) in a typing context is a Protocol)") -class _coconut_SupportsFloordiv(_coconut.typing.Protocol): - """Coconut (//) Protocol. Equivalent to: - - class SupportsFloordiv[T, U, V](Protocol): - def __floordiv__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __floordiv__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((//) in a typing context is a Protocol)") -class _coconut_SupportsMod(_coconut.typing.Protocol): - """Coconut (%) Protocol. Equivalent to: - - class SupportsMod[T, U, V](Protocol): - def __mod__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __mod__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((%) in a typing context is a Protocol)") -class _coconut_SupportsAnd(_coconut.typing.Protocol): - """Coconut (&) Protocol. Equivalent to: - - class SupportsAnd[T, U, V](Protocol): - def __and__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __and__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((&) in a typing context is a Protocol)") -class _coconut_SupportsXor(_coconut.typing.Protocol): - """Coconut (^) Protocol. Equivalent to: - - class SupportsXor[T, U, V](Protocol): - def __xor__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __xor__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((^) in a typing context is a Protocol)") -class _coconut_SupportsOr(_coconut.typing.Protocol): - """Coconut (|) Protocol. Equivalent to: - - class SupportsOr[T, U, V](Protocol): - def __or__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __or__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((|) in a typing context is a Protocol)") -class _coconut_SupportsLshift(_coconut.typing.Protocol): - """Coconut (<<) Protocol. Equivalent to: - - class SupportsLshift[T, U, V](Protocol): - def __lshift__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __lshift__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((<<) in a typing context is a Protocol)") -class _coconut_SupportsRshift(_coconut.typing.Protocol): - """Coconut (>>) Protocol. Equivalent to: - - class SupportsRshift[T, U, V](Protocol): - def __rshift__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __rshift__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((>>) in a typing context is a Protocol)") -class _coconut_SupportsMatmul(_coconut.typing.Protocol): - """Coconut (@) Protocol. Equivalent to: - - class SupportsMatmul[T, U, V](Protocol): - def __matmul__(self: T, other: U) -> V: - raise NotImplementedError(...) - """ - def __matmul__(self, other): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((@) in a typing context is a Protocol)") -class _coconut_SupportsInv(_coconut.typing.Protocol): - """Coconut (~) Protocol. Equivalent to: - - class SupportsInv[T, V](Protocol): - def __invert__(self: T) -> V: - raise NotImplementedError(...) - """ - def __invert__(self): - raise _coconut.NotImplementedError("Protocol methods cannot be called at runtime ((~) in a typing context is a Protocol)") -@_coconut_wraps(_coconut.functools.reduce) -def reduce(function, iterable, initial=_coconut_sentinel): - if initial is _coconut_sentinel: - return _coconut.functools.reduce(function, iterable) - return _coconut.functools.reduce(function, iterable, initial) -class takewhile(_coconut.itertools.takewhile, object): - __slots__ = () - __doc__ = _coconut.itertools.takewhile.__doc__ - def __new__(cls, predicate, iterable): - return _coconut.itertools.takewhile.__new__(cls, predicate, iterable) -class dropwhile(_coconut.itertools.dropwhile, object): - __slots__ = () - __doc__ = _coconut.itertools.dropwhile.__doc__ - def __new__(cls, predicate, iterable): - return _coconut.itertools.dropwhile.__new__(cls, predicate, iterable) -if _coconut_sys.version_info < (3, 5): - def async_map(*args, **kwargs): - """async_map not available on Python < 3.5""" - raise _coconut.NameError("async_map not available on Python < 3.5") -else: - _coconut_async_map_ns = {"_coconut": _coconut, '_coconut_zip': zip} - _coconut_exec('async def async_map(async_func, *iters, strict=False):\n """Map async_func over iters asynchronously using anyio."""\n import anyio\n results = []\n async def store_func_in_of(i, args):\n got = await async_func(*args)\n results.extend([None] * (1 + i - _coconut.len(results)))\n results[i] = got\n async with anyio.create_task_group() as nursery:\n for i, args in _coconut.enumerate(_coconut_zip(*iters, strict=strict)):\n nursery.start_soon(store_func_in_of, i, args)\n return results', _coconut_async_map_ns) - async_map = _coconut_async_map_ns["async_map"] -def prepattern(base_func, **kwargs): - """DEPRECATED: use addpattern instead.""" - def pattern_prepender(func): - return addpattern(func, base_func, **kwargs) - return pattern_prepender -def datamaker(data_type): - """DEPRECATED: use makedata instead.""" - return _coconut_partial(makedata, data_type) -of, parallel_map, concurrent_map, recursive_iterator = call, process_map, thread_map, recursive_generator -_coconut_self_match_types = (bool, bytearray, bytes, dict, float, frozenset, int, py_int, list, set, str, py_str, tuple) -TYPE_CHECKING, _coconut_Expected, _coconut_MatchError, _coconut_cartesian_product, _coconut_count, _coconut_cycle, _coconut_enumerate, _coconut_flatten, _coconut_fmap, _coconut_filter, _coconut_groupsof, _coconut_ident, _coconut_lift, _coconut_map, _coconut_mapreduce, _coconut_multiset, _coconut_range, _coconut_reiterable, _coconut_reversed, _coconut_scan, _coconut_starmap, _coconut_tee, _coconut_windowsof, _coconut_zip, _coconut_zip_longest = False, Expected, MatchError, cartesian_product, count, cycle, enumerate, flatten, fmap, filter, groupsof, ident, lift, map, mapreduce, multiset, range, reiterable, reversed, scan, starmap, tee, windowsof, zip, zip_longest - -# Compiled Coconut: ----------------------------------------------------------- - -import numpy as np #1 (line in Coconut source) -from numpy import ndarray as nda #2 (line in Coconut source) +import numpy as np +from numpy import ndarray as nda # from sklearn.metrics import precision_recall_curve, fbeta_score -from scipy.stats import ecdf #4 (line in Coconut source) -from scipy.integrate import trapezoid #5 (line in Coconut source) - -try: #7 (line in Coconut source) - _coconut_sys_0 = sys # type: ignore #7 (line in Coconut source) -except _coconut.NameError: #7 (line in Coconut source) - _coconut_sys_0 = _coconut_sentinel #7 (line in Coconut source) -sys = _coconut_sys #7 (line in Coconut source) -if sys.version_info >= (3, 8): #7 (line in Coconut source) - if _coconut.typing.TYPE_CHECKING: #7 (line in Coconut source) - from typing import Literal #7 (line in Coconut source) - else: #7 (line in Coconut source) - try: #7 (line in Coconut source) - Literal = _coconut.typing.Literal #7 (line in Coconut source) - except _coconut.AttributeError as _coconut_imp_err: #7 (line in Coconut source) - raise _coconut.ImportError(_coconut.str(_coconut_imp_err)) #7 (line in Coconut source) -else: #7 (line in Coconut source) - from typing_extensions import Literal #7 (line in Coconut source) -if _coconut_sys_0 is not _coconut_sentinel: #7 (line in Coconut source) - sys = _coconut_sys_0 #7 (line in Coconut source) -from jaxtyping import Bool #8 (line in Coconut source) -from jaxtyping import Num #8 (line in Coconut source) -from jaxtyping import jaxtyped #8 (line in Coconut source) -from beartype import beartype as typechecker #9 (line in Coconut source) -from dataclasses import dataclass #10 (line in Coconut source) -from dataclasses import field #10 (line in Coconut source) -from sklearn.preprocessing import minmax_scale #11 (line in Coconut source) -import warnings #12 (line in Coconut source) - -__all__ = ["Contingent",] #14 (line in Coconut source) - -ScoreOptions = Literal['F', 'F2', 'G', 'recall', 'precision', 'mcc', 'aps'] # type: _coconut.typing.TypeAlias #18 (line in Coconut source) -if "__annotations__" not in _coconut.locals(): #18 (line in Coconut source) - __annotations__ = {} # type: ignore #18 (line in Coconut source) -__annotations__["ScoreOptions"] = _coconut.typing.TypeAlias #18 (line in Coconut source) - -PredProb = Num[nda, 'features'] # type: _coconut.typing.TypeAlias #28 (line in Coconut source) -if "__annotations__" not in _coconut.locals(): #28 (line in Coconut source) - __annotations__ = {} # type: ignore #28 (line in Coconut source) -__annotations__["PredProb"] = _coconut.typing.TypeAlias #28 (line in Coconut source) -ProbThres = Num[nda, '*#batch'] # type: _coconut.typing.TypeAlias #29 (line in Coconut source) -if "__annotations__" not in _coconut.locals(): #29 (line in Coconut source) - __annotations__ = {} # type: ignore #29 (line in Coconut source) -__annotations__["ProbThres"] = _coconut.typing.TypeAlias #29 (line in Coconut source) -PredThres = Bool[nda, '*#batch features'] # type: _coconut.typing.TypeAlias #30 (line in Coconut source) -if "__annotations__" not in _coconut.locals(): #30 (line in Coconut source) - __annotations__ = {} # type: ignore #30 (line in Coconut source) -__annotations__["PredThres"] = _coconut.typing.TypeAlias #30 (line in Coconut source) - -def quantile_tf(x # type: PredProb #32 (line in Coconut source) - ): #32 (line in Coconut source) -# type: (...) -> (ProbThres, PredProb) - cdf = ecdf(x).cdf #33 (line in Coconut source) - p = (_coconut_complex_partial(np.pad, {1: ((1, 1))}, 2, (), constant_values=(0, 1)))(cdf.probabilities) #34 (line in Coconut source) - return p, cdf.evaluate(x) #35 (line in Coconut source) - - -@jaxtyped(typechecker=typechecker) #37 (line in Coconut source) -def minmax_tf(x # type: Num[nda, 'feat'] #38 (line in Coconut source) - ): #38 (line in Coconut source) -# type: (...) -> (Num[nda, '*#batch'], Num[nda, 'feat']) - x_p = minmax_scale(x, feature_range=(1e-5, 1 - 1e-5)) #41 (line in Coconut source) - p = np.pad(np.unique(x_p), ((1, 1)), constant_values=(0, 1)) #42 (line in Coconut source) - return p, x_p #43 (line in Coconut source) +from scipy.stats import ecdf +from scipy.integrate import trapezoid + +from typing import Literal, Type +from jaxtyping import Bool, Num, jaxtyped +from beartype import beartype as typechecker +from dataclasses import dataclass, field +# from sklearn.preprocessing import minmax_scale +import warnings + +# __all__ = [ +# "Contingent", + +# ] + +type ScoreOptions = Literal[ + 'F', + 'F2', + 'G', + 'recall', + 'precision', + 'mcc', + 'aps' +] + +type PredProb = Num[nda, 'features'] +type ProbThres = Num[nda, '*#batch'] +type PredThres = Bool[nda, '*#batch features'] + +# def _quantile_tf(x:PredProb)-> (ProbThres,PredProb): +# cdf = ecdf(x).cdf +# p = cdf.probabilities |> np.pad$(?, ((1,1)), constant_values=(0,1)) +# return p, cdf.evaluate(x) + +@jaxtyped(typechecker=typechecker) +def _minmax_tf( + x:Num[nda, 'feat'], tol:float=1e-5 +)-> tuple[Num[nda,'*#batch'], Num[nda, 'feat']]: + xmin, xmax =tol, 1-tol + scale = (xmax-xmin)/(x.max(axis=0) - x.min(axis=0)) + x_p = scale*x + xmin - x.min(axis=0)*scale + # x_p = minmax_scale(x, feature_range=(tol, 1 - tol)) + p = np.pad(np.unique(x_p), ((1,1)), constant_values=(0,1)) + return p, x_p # def _all_thres(x:PredProb, t:ProbThres)->PredThres: -# return np.less_equal.outer(t, x) + # return np.less_equal.outer(t, x) #TODO use density (.getnnz()) for sparse via dispatching - -@jaxtyped(typechecker=typechecker) #49 (line in Coconut source) -@_coconut_tco #50 (line in Coconut source) -def _bool_contract(A, # type: Bool[nda, '*#batch feat'] #50 (line in Coconut source) - B # type: Bool[nda, '*#batch feat'] #50 (line in Coconut source) - ): #50 (line in Coconut source) -# type: (...) -> Num[nda, '*#batch'] - return _coconut_tail_call((A * B).sum, axis=-1) #50 (line in Coconut source) - - -@_coconut_tco #55 (line in Coconut source) -def _TP(actual, pred): #55 (line in Coconut source) - return _coconut_tail_call(_bool_contract, pred, actual) #55 (line in Coconut source) - -@_coconut_tco #56 (line in Coconut source) -def _FP(actual, pred): #56 (line in Coconut source) - return _coconut_tail_call(_bool_contract, pred, ~actual) #56 (line in Coconut source) - -@_coconut_tco #57 (line in Coconut source) -def _FN(actual, pred): #57 (line in Coconut source) - return _coconut_tail_call(_bool_contract, ~pred, actual) #57 (line in Coconut source) - -@_coconut_tco #58 (line in Coconut source) -def _TN(actual, pred): #58 (line in Coconut source) - return _coconut_tail_call(_bool_contract, ~pred, ~actual) #58 (line in Coconut source) - - -@jaxtyped(typechecker=typechecker) #60 (line in Coconut source) -@dataclass #61 (line in Coconut source) -class Contingent(_coconut.object): #62 (line in Coconut source) +@jaxtyped(typechecker=typechecker) +def _bool_contract( + A:Bool[nda, '*#batch feat'], + B:Bool[nda, '*#batch feat'] +)-> Num[nda, '*#batch']: + return (A*B).sum(axis=-1) + +def _TP(actual,pred): + return _bool_contract( pred, actual) +def _FP(actual,pred): + return _bool_contract( pred,~actual) +def _FN(actual,pred): + return _bool_contract(~pred, actual) +def _TN(actual,pred): + return _bool_contract(~pred,~actual) + +@jaxtyped(typechecker=typechecker) +@dataclass +class Contingent: """ dataclass to hold true and (batched) predicted values Parameters: @@ -3119,105 +83,56 @@ class Contingent(_coconut.object): #62 (line in Coconut source) precision: a.k.a. positive-predictive-value (PPV) mcc: Matthew's Correlation Coefficient G: Fowlkes-Mallows score (geometric mean of precision and recall) - """ #77 (line in Coconut source) - y_true = _coconut.typing.cast(_coconut.typing.Any, _coconut.Ellipsis) # type: Bool[nda, 'feat'] #78 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #78 (line in Coconut source) - __annotations__ = {} # type: ignore #78 (line in Coconut source) - __annotations__["y_true"] = Bool[nda, 'feat'] #78 (line in Coconut source) - y_pred = _coconut.typing.cast(_coconut.typing.Any, _coconut.Ellipsis) # type: Bool[nda, '*#batch feat'] #79 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #79 (line in Coconut source) - __annotations__ = {} # type: ignore #79 (line in Coconut source) - __annotations__["y_pred"] = Bool[nda, '*#batch feat'] #79 (line in Coconut source) - - weights = None # type: _coconut.typing.Union[Num[nda, '*#batch'], None] #81 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #81 (line in Coconut source) - __annotations__ = {} # type: ignore #81 (line in Coconut source) - __annotations__["weights"] = _coconut.typing.Union[Num[nda, '*#batch'], None] #81 (line in Coconut source) - - TP = field(init=False) # type: Num[nda, "..."] #83 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #83 (line in Coconut source) - __annotations__ = {} # type: ignore #83 (line in Coconut source) - __annotations__["TP"] = Num[nda, "..."] #83 (line in Coconut source) - FP = field(init=False) # type: Num[nda, "..."] #84 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #84 (line in Coconut source) - __annotations__ = {} # type: ignore #84 (line in Coconut source) - __annotations__["FP"] = Num[nda, "..."] #84 (line in Coconut source) - FN = field(init=False) # type: Num[nda, "..."] #85 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #85 (line in Coconut source) - __annotations__ = {} # type: ignore #85 (line in Coconut source) - __annotations__["FN"] = Num[nda, "..."] #85 (line in Coconut source) - TN = field(init=False) # type: Num[nda, "..."] #86 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #86 (line in Coconut source) - __annotations__ = {} # type: ignore #86 (line in Coconut source) - __annotations__["TN"] = Num[nda, "..."] #86 (line in Coconut source) - - - PP = field(init=False) # type: Num[nda, "..."] #89 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #89 (line in Coconut source) - __annotations__ = {} # type: ignore #89 (line in Coconut source) - __annotations__["PP"] = Num[nda, "..."] #89 (line in Coconut source) - PN = field(init=False) # type: Num[nda, "..."] #90 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #90 (line in Coconut source) - __annotations__ = {} # type: ignore #90 (line in Coconut source) - __annotations__["PN"] = Num[nda, "..."] #90 (line in Coconut source) - P = field(init=False) # type: Num[nda, "..."] #91 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #91 (line in Coconut source) - __annotations__ = {} # type: ignore #91 (line in Coconut source) - __annotations__["P"] = Num[nda, "..."] #91 (line in Coconut source) - N = field(init=False) # type: Num[nda, "..."] #92 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #92 (line in Coconut source) - __annotations__ = {} # type: ignore #92 (line in Coconut source) - __annotations__["N"] = Num[nda, "..."] #92 (line in Coconut source) - - - PPV = field(init=False) # type: Num[nda, "..."] #95 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #95 (line in Coconut source) - __annotations__ = {} # type: ignore #95 (line in Coconut source) - __annotations__["PPV"] = Num[nda, "..."] #95 (line in Coconut source) - NPV = field(init=False) # type: Num[nda, "..."] #96 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #96 (line in Coconut source) - __annotations__ = {} # type: ignore #96 (line in Coconut source) - __annotations__["NPV"] = Num[nda, "..."] #96 (line in Coconut source) - TPR = field(init=False) # type: Num[nda, "..."] #97 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #97 (line in Coconut source) - __annotations__ = {} # type: ignore #97 (line in Coconut source) - __annotations__["TPR"] = Num[nda, "..."] #97 (line in Coconut source) - TNR = field(init=False) # type: Num[nda, "..."] #98 (line in Coconut source) - if "__annotations__" not in _coconut.locals(): #98 (line in Coconut source) - __annotations__ = {} # type: ignore #98 (line in Coconut source) - __annotations__["TNR"] = Num[nda, "..."] #98 (line in Coconut source) - - def __post_init__(self): #100 (line in Coconut source) - self.y_true = np.atleast_2d(self.y_true) #101 (line in Coconut source) - self.y_pred = np.atleast_2d(self.y_pred) #102 (line in Coconut source) - self.TP = _TP(self.y_true, self.y_pred) #103 (line in Coconut source) - self.FP = _FP(self.y_true, self.y_pred) #104 (line in Coconut source) - self.FN = _FN(self.y_true, self.y_pred) #105 (line in Coconut source) - self.TN = _TN(self.y_true, self.y_pred) #106 (line in Coconut source) - - self.PP = self.TP + self.FP #108 (line in Coconut source) - self.PN = self.FN + self.TN #109 (line in Coconut source) - self.P = self.TP + self.FN #110 (line in Coconut source) - self.N = self.FP + self.TN #111 (line in Coconut source) - -# self.PPV = np.divide(self.TP, self.PP, out=np.ones_like(self.TP), where=self.PP!=0.) - self.PPV = np.ma.divide(self.TP, self.PP) #114 (line in Coconut source) - self.NPV = np.ma.divide(self.TN, self.PN) #115 (line in Coconut source) - self.TPR = np.ma.divide(self.TP, self.P) #116 (line in Coconut source) - self.TNR = np.ma.divide(self.TN, self.N) #117 (line in Coconut source) - - - - _coconut_typevar_T_0 = _coconut.typing.TypeVar("_coconut_typevar_T_0") #120 (line in Coconut source) - - @classmethod #120 (line in Coconut source) - @_coconut_tco #121 (line in Coconut source) - def from_scalar(cls, # type: Type[_coconut_typevar_T_0] #121 (line in Coconut source) - y_true, # type: PredProb #121 (line in Coconut source) - x, # type: _coconut.typing.Optional[PredProb] #121 (line in Coconut source) - subsamples=None # type: _coconut.typing.Optional[int] #121 (line in Coconut source) - ): #121 (line in Coconut source) -# type: (...) -> _coconut.typing.Optional[_coconut_typevar_T_0] + """ + y_true: Bool[nda, 'feat'] + y_pred: Bool[nda, '*#batch feat'] + + weights: Num[nda, '*#batch']|None = None + + TP: Num[nda, "..."] = field(init=False) + FP: Num[nda, "..."] = field(init=False) + FN: Num[nda, "..."] = field(init=False) + TN: Num[nda, "..."] = field(init=False) + + + PP: Num[nda, "..."] = field(init=False) + PN: Num[nda, "..."] = field(init=False) + P: Num[nda, "..."] = field(init=False) + N: Num[nda, "..."] = field(init=False) + + + PPV: Num[nda, "..."] = field(init=False) + NPV: Num[nda, "..."] = field(init=False) + TPR: Num[nda, "..."] = field(init=False) + TNR: Num[nda, "..."] = field(init=False) + + def __post_init__(self): + self.y_true = np.atleast_2d(self.y_true) + self.y_pred = np.atleast_2d(self.y_pred) + self.TP = _TP(self.y_true, self.y_pred) + self.FP = _FP(self.y_true, self.y_pred) + self.FN = _FN(self.y_true, self.y_pred) + self.TN = _TN(self.y_true, self.y_pred) + + self.PP = self.TP + self.FP + self.PN = self.FN + self.TN + self.P = self.TP + self.FN + self.N = self.FP + self.TN + + # self.PPV = np.divide(self.TP, self.PP, out=np.ones_like(self.TP), where=self.PP!=0.) + self.PPV = np.ma.divide(self.TP, self.PP) + self.NPV = np.ma.divide(self.TN, self.PN) + self.TPR = np.ma.divide(self.TP, self.P) + self.TNR = np.ma.divide(self.TN, self.N) + + + @classmethod + def from_scalar[T]( + cls: Type[T], + y_true: PredProb, + x:PredProb|None, + subsamples:int|None=None + )->T|None: """ take scalar predictions and generate (batched) Contingent by default, x is rescaled to [0,1] and used as the weights parameter @@ -3231,86 +146,75 @@ def from_scalar(cls, # type: Type[_coconut_typevar_T_0] #121 (line in Coconut Parameters: y_true: True pos/neg binary vector x: scalar weights for relative prediction strength (positive) - """ #140 (line in Coconut source) -# p, x_p = quantile_tf(x) - if x is None: #142 (line in Coconut source) - warnings.warn("`None` value recieved, passing the buck...") #143 (line in Coconut source) - return None #144 (line in Coconut source) - p, x_p = minmax_tf(x) #145 (line in Coconut source) - if subsamples: #146 (line in Coconut source) - p = np.interp(np.linspace(0, 1, subsamples), np.linspace(0, 1, p.shape[0]), p) #147 (line in Coconut source) - y_preds = np.less_equal.outer(p, x_p) #152 (line in Coconut source) - - return _coconut_tail_call(cls, y_true, y_preds, weights=p) #154 (line in Coconut source) + """ + # p, x_p = _quantile_tf(x) + if x is None: + warnings.warn("`None` value recieved, passing the buck...") + return None + p, x_p = _minmax_tf(x) + if subsamples: + p = np.interp( + np.linspace(0,1,subsamples), + np.linspace(0,1,p.shape[0]), + p + ) + y_preds = np.less_equal.outer(p,x_p) + return cls(y_true, y_preds, weights=p) - @_coconut_tco #158 (line in Coconut source) - def f_beta(self, beta=1): #158 (line in Coconut source) + def f_beta(self, beta=1): """Fᵦ score weighted harmonic mean of precision and recall, with β-times more bias for recall. - """ #163 (line in Coconut source) - return _coconut_tail_call(f_beta, beta, self) #164 (line in Coconut source) - - - @property #166 (line in Coconut source) - @_coconut_tco #167 (line in Coconut source) - def F2(self): #167 (line in Coconut source) - """F₂ harmonic mean with recall weighted 2x over precision""" #168 (line in Coconut source) - return _coconut_tail_call(f_beta, 2., self) #169 (line in Coconut source) - - - @property #171 (line in Coconut source) - @_coconut_tco #172 (line in Coconut source) - def F(self): #172 (line in Coconut source) - """F₁ score (harmonic mean of recall, precision)""" #173 (line in Coconut source) - return _coconut_tail_call(F1, self) #174 (line in Coconut source) - + """ + return f_beta(beta, self) - @property #176 (line in Coconut source) - @_coconut_tco #177 (line in Coconut source) - def recall(self): #177 (line in Coconut source) - """i.e. True Positive Rate TP/(TP+FN)""" #178 (line in Coconut source) - return _coconut_tail_call(recall, self) #179 (line in Coconut source) + @property + def F2(self): + """F₂ harmonic mean with recall weighted 2x over precision""" + return f_beta(2., self) + + @property + def F(self) : + """F₁ score (harmonic mean of recall, precision)""" + return F1(self) + @property + def recall(self): + """i.e. True Positive Rate TP/(TP+FN) - @property #181 (line in Coconut source) - @_coconut_tco #182 (line in Coconut source) - def precision(self): #182 (line in Coconut source) - """i.e. Positive Predictive Value TP/(TP+FP)""" #183 (line in Coconut source) - return _coconut_tail_call(precision, self) #184 (line in Coconut source) + see [recall][contingency.contingent.recall] + """ + return recall(self) + @property + def precision(self): + """i.e. Positive Predictive Value TP/(TP+FP)""" + return precision(self) - @property #186 (line in Coconut source) - @_coconut_tco #187 (line in Coconut source) - def mcc(self): #187 (line in Coconut source) + @property + def mcc(self): """ Matthew's Correlation Coefficient (MCC) Widely considered the most fair/least bias metric for imbalanced classification tasks. - """ #192 (line in Coconut source) - return _coconut_tail_call(matthews_corrcoef, self) #193 (line in Coconut source) - + """ + return matthews_corrcoef(self) - @property #195 (line in Coconut source) - @_coconut_tco #196 (line in Coconut source) - def G(self): #196 (line in Coconut source) + @property + def G(self): """ Fowlkes-Mallows, the geometric mean of precision and recall. commonly used in unsupervised cases where synthetic test-data has been made available (e.g. MENDR, clustering validation, etc.) - """ #201 (line in Coconut source) - return _coconut_tail_call(fowlkes_mallows, self) #202 (line in Coconut source) - + """ + return fowlkes_mallows(self) - @typechecker #204 (line in Coconut source) - @_coconut_tco #205 (line in Coconut source) - def expected(self, mode='aps' # type: ScoreOptions #205 (line in Coconut source) - ): #205 (line in Coconut source) -# type: (...) -> float + @typechecker + def expected(self, mode: ScoreOptions='aps')->float: """ A convenience function to calculate the expected value of a score. @@ -3321,91 +225,62 @@ def expected(self, mode='aps' # type: ScoreOptions #205 (line in Coconut sourc Parameters: mode: available scores that can be aggregated over the y_pred probabilities - """ #216 (line in Coconut source) - if mode == 'aps': #217 (line in Coconut source) - return _coconut_tail_call(avg_precision_score, self) #218 (line in Coconut source) - else: #219 (line in Coconut source) - return _coconut_tail_call(trapezoid, getattr(self, mode), x=self.weights) #220 (line in Coconut source) + """ + if mode=='aps': + return avg_precision_score(self) + else: + return trapezoid(getattr(self, mode), x=self.weights) # def PPV(Yt:PredThres,Pt:PredThres) = TP/PP # def NPV(Yt:PredThres,Pt:PredThres) = TN/PN # def TPR(Yt:PredThres,Pt:PredThres) = TP/ # def TNR(Yt:PredThres,Pt:PredThres) = _bool_contract(~Pt,~Yt) - -_coconut_call_set_names(Contingent) #227 (line in Coconut source) -@_coconut_tco #227 (line in Coconut source) -def recall(Y # type: Contingent #227 (line in Coconut source) - ): #227 (line in Coconut source) -# type: (...) -> ProbThres - """True Positive Rate""" #228 (line in Coconut source) - return _coconut_tail_call(Y.TPR.filled, 1.) #229 (line in Coconut source) +def recall(Y:Contingent)->ProbThres: + """True Positive Rate""" + return Y.TPR.filled(1.) +def precision(Y:Contingent)->ProbThres: + """Positive Predictive Value""" + return Y.PPV.filled(1.) -@_coconut_tco #232 (line in Coconut source) -def precision(Y # type: Contingent #232 (line in Coconut source) - ): #232 (line in Coconut source) -# type: (...) -> ProbThres - """Positive Predictive Value""" #233 (line in Coconut source) - return _coconut_tail_call(Y.PPV.filled, 1.) #234 (line in Coconut source) - - -@_coconut_tco #237 (line in Coconut source) -def f_beta(beta, # type: float #237 (line in Coconut source) - Y # type: Contingent #237 (line in Coconut source) - ): #237 (line in Coconut source) -# type: (...) -> ProbThres +def f_beta(beta:float, Y:Contingent)-> ProbThres: """F_beta score weighted harmonic mean of precision and recall, with beta-times more bias for recall. - """ #242 (line in Coconut source) - top = (1 + beta**2) * Y.PPV * Y.TPR #243 (line in Coconut source) - bottom = beta**2 * Y.PPV + Y.TPR #244 (line in Coconut source) - - return _coconut_tail_call(np.ma.divide(top, bottom).filled, 0.) #246 (line in Coconut source) + """ + top = (1+beta**2)*Y.PPV*Y.TPR + bottom = beta**2*Y.PPV + Y.TPR + return np.ma.divide(top, bottom).filled(0.) -@_coconut_tco #248 (line in Coconut source) -def F1(Y # type: Contingent #248 (line in Coconut source) - ): #248 (line in Coconut source) -# type: (...) -> ProbThres +def F1(Y:Contingent)->ProbThres: """partially applied f_beta with beta=1 (equal/no bias) - """ #250 (line in Coconut source) - return _coconut_tail_call(f_beta, 1., Y) #251 (line in Coconut source) - + """ + return f_beta(1., Y) -def matthews_corrcoef(Y # type: Contingent #254 (line in Coconut source) - ): #254 (line in Coconut source) -# type: (...) -> ProbThres +def matthews_corrcoef(Y:Contingent)->ProbThres: """ Matthew's Correlation Coefficient (MCC) Widely considered the most fair/least bias metric for imbalanced classification tasks. - """ #259 (line in Coconut source) - _coconut_where_m_0 = np.vstack([Y.TPR, Y.TNR, Y.PPV, Y.NPV]) #261 (line in Coconut source) - _coconut_where_l_0 = np.sqrt(_coconut_where_m_0).prod(axis=0) #262 (line in Coconut source) - _coconut_where_r_0 = np.sqrt(1 - _coconut_where_m_0).prod(axis=0) #263 (line in Coconut source) -# return 1-cdist(Y.y_pred, Y.y_true, "correlation")[:,0] - - return (_coconut_where_l_0 - _coconut_where_r_0).filled(0) #266 (line in Coconut source) - -@_coconut_tco #266 (line in Coconut source) -def fowlkes_mallows(Y # type: Contingent #266 (line in Coconut source) - ): #266 (line in Coconut source) -# type: (...) -> ProbThres - return _coconut_tail_call(np.sqrt, recall(Y) * precision(Y)) #267 (line in Coconut source) - - -@_coconut_tco #269 (line in Coconut source) -def avg_precision_score(Y # type: Contingent #269 (line in Coconut source) - ): #269 (line in Coconut source) -# type: (...) -> float - """ """ #270 (line in Coconut source) - return _coconut_tail_call(np.sum, np.diff(Y.recall[::-1], prepend=0) * Y.precision[::-1]) #271 (line in Coconut source) + """ + m = np.vstack([Y.TPR,Y.TNR,Y.PPV,Y.NPV]) + l = np.sqrt(m).prod(axis=0) + r = np.sqrt(1-m).prod(axis=0) + return (l - r).filled(0) + # return 1-cdist(Y.y_pred, Y.y_true, "correlation")[:,0] + +def fowlkes_mallows(Y:Contingent)->ProbThres: + return np.sqrt(recall(Y)*precision(Y)) + +def avg_precision_score(Y:Contingent)->float: + """ """ + return np.sum(np.diff(Y.recall[::-1], prepend=0) * Y.precision[::-1]) # def precision(y_true, y_pred): # TP,FP,TN,FN = _retrieval_square(y_true, p_pred) diff --git a/src/contingency/plots.py b/src/contingency/plots.py index e729bf1..4efe32c 100644 --- a/src/contingency/plots.py +++ b/src/contingency/plots.py @@ -2,12 +2,13 @@ try: import matplotlib.pyplot as plt + import matplotlib.axes except ImportError: _has_plot = False else: _has_plot = True -def PR_contour(ax=None): +def PR_contour(ax:[matplotlib.axes.Axes|None]=None): """Generate a nice-looking contour plot for Precision vs. Recall REQUIRES optional [plot] dependencies! diff --git a/tests/test_contingency.py b/tests/test_contingency.py index 5ef99d9..4fe2cb3 100644 --- a/tests/test_contingency.py +++ b/tests/test_contingency.py @@ -75,7 +75,7 @@ def test_from_scalar(y_Y): @given( make_true_prob(), - st.sampled_from(get_args(ScoreOptions)) + st.sampled_from(get_args(ScoreOptions.__value__)) ) def test_expected(y_Y, mode): y_true, y_pred = y_Y diff --git a/uv.lock b/uv.lock index 2297397..bedb3b7 100644 --- a/uv.lock +++ b/uv.lock @@ -165,7 +165,6 @@ source = { editable = "." } dependencies = [ { name = "jaxtyping" }, { name = "numpy" }, - { name = "scikit-learn" }, { name = "scipy" }, ] @@ -184,6 +183,7 @@ dev = [ { name = "mkdocstrings-python" }, { name = "pytest" }, { name = "rich", extra = ["jupyter"] }, + { name = "scikit-learn" }, { name = "zensical" }, ] @@ -192,7 +192,6 @@ requires-dist = [ { name = "jaxtyping", specifier = ">=0.3.3" }, { name = "matplotlib", marker = "extra == 'plot'", specifier = ">=3.10.6" }, { name = "numpy", specifier = ">=2.3.5" }, - { name = "scikit-learn", specifier = ">=1.7.2" }, { name = "scipy", specifier = ">=1.16.3" }, ] provides-extras = ["plot"] @@ -207,6 +206,7 @@ dev = [ { name = "mkdocstrings-python", specifier = ">=2.0.1" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "rich", extras = ["jupyter"], specifier = ">=14.2.0" }, + { name = "scikit-learn", specifier = ">=1.8.0" }, { name = "zensical", specifier = ">=0.0.20" }, ] @@ -540,11 +540,11 @@ wheels = [ [[package]] name = "joblib" -version = "1.5.2" +version = "1.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, ] [[package]] @@ -1307,7 +1307,7 @@ jupyter = [ [[package]] name = "scikit-learn" -version = "1.7.2" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, @@ -1315,28 +1315,32 @@ dependencies = [ { name = "scipy" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, - { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, - { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, - { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, - { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, - { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, - { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, - { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, - { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, - { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, - { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, - { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, - { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, - { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, - { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, - { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835, upload-time = "2025-12-10T07:07:39.385Z" }, + { url = "https://files.pythonhosted.org/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381, upload-time = "2025-12-10T07:07:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632, upload-time = "2025-12-10T07:07:43.899Z" }, + { url = "https://files.pythonhosted.org/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788, upload-time = "2025-12-10T07:07:45.982Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706, upload-time = "2025-12-10T07:07:48.111Z" }, + { url = "https://files.pythonhosted.org/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451, upload-time = "2025-12-10T07:07:49.873Z" }, + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, ] [[package]] diff --git a/zensical.toml b/zensical.toml index c206c78..26647ea 100644 --- a/zensical.toml +++ b/zensical.toml @@ -332,4 +332,22 @@ paths = ["src/contingency/"] [project.plugins.mkdocstrings.handlers.python.options] docstring_style = "google" inherited_members = true -show_source = false +# show_source = false +backlinks = "tree" +docstring_options = {ignore_init_summary = true} +docstring_section_style = "list" +# extensions = ["griffe_typingdoc",] +filters = ["!^_", "^__"] +heading_level = 1 +# merge_init_into_class = true +parameter_headings = true +separate_signature = true +show_root_heading = true +show_root_full_path = false +show_signature_annotations = true +show_source = true +show_symbol_type_heading = true +show_symbol_type_toc = true +signature_crossrefs = true +summary = true +unwrap_annotated = true From 9fccb4bfe30bab474397d0bad289e921fee94f2a Mon Sep 17 00:00:00 2001 From: "Rachael T. Sexton" Date: Thu, 5 Feb 2026 10:36:33 -0500 Subject: [PATCH 2/6] unit test ci and better property doc backlinks. --- .gitlab-ci.yml | 17 ++++++++-- examples/tutorial.ipynb | 25 --------------- src/contingency/contingent.py | 58 ++++++++++++++++++----------------- 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bdcfc8a..9bfa950 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,6 @@ stages: + - prep + - test - pages variables: @@ -9,13 +11,24 @@ variables: # so we need to copy instead of using hard links. UV_LINK_MODE: copy -zensical: +uv-setup: + stage: prep image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER - stage: pages before_script: - apk add g++ build-base linux-headers script: - uv sync + +pytest: + stage: test + needs: ["uv-setup"] + script: + - uv run pytest "tests/test_contingency.py" + +zensical: + stage: pages + needs: ["uv-setup"] + script: - uv run zensical build # - mv site public artifacts: diff --git a/examples/tutorial.ipynb b/examples/tutorial.ipynb index 2577b11..61f3835 100644 --- a/examples/tutorial.ipynb +++ b/examples/tutorial.ipynb @@ -14,31 +14,6 @@ "# `Contingent` Tutorial" ] }, - { - "cell_type": "code", - "execution_count": 27, - "id": "7413f7c1-1035-4af0-acc9-67c04bf7edb4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "('F', 'F2', 'G', 'recall', 'precision', 'mcc', 'aps')" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from contingency.contingent import ScoreOptions\n", - "from typing import get_args\n", - "\n", - "get_args(ScoreOptions.__value__)\n", - "# ScoreOptions." - ] - }, { "cell_type": "code", "execution_count": 21, diff --git a/src/contingency/contingent.py b/src/contingency/contingent.py index 3ebeb46..e96307a 100644 --- a/src/contingency/contingent.py +++ b/src/contingency/contingent.py @@ -40,7 +40,8 @@ def _minmax_tf( x:Num[nda, 'feat'], tol:float=1e-5 )-> tuple[Num[nda,'*#batch'], Num[nda, 'feat']]: xmin, xmax =tol, 1-tol - scale = (xmax-xmin)/(x.max(axis=0) - x.min(axis=0)) + with np.errstate(divide='ignore', invalid='ignore'): + scale = np.nan_to_num((xmax-xmin)/np.ptp(x,axis=0)) x_p = scale*x + xmin - x.min(axis=0)*scale # x_p = minmax_scale(x, feature_range=(tol, 1 - tol)) p = np.pad(np.unique(x_p), ((1,1)), constant_values=(0,1)) @@ -165,52 +166,40 @@ def from_scalar[T]( def f_beta(self, beta=1): - """Fᵦ score - - weighted harmonic mean of precision and recall, with β-times - more bias for recall. - """ + """Fᵦ score (see [`f_beta`][contingency.contingent.f_beta])""" return f_beta(beta, self) @property def F2(self): - """F₂ harmonic mean with recall weighted 2x over precision""" + """F₂ score (see [`f_beta`][contingency.contingent.f_beta]) + + """ return f_beta(2., self) @property def F(self) : - """F₁ score (harmonic mean of recall, precision)""" + """F₁ score (see [`f_beta`][contingency.contingent.f_beta])""" return F1(self) @property def recall(self): - """i.e. True Positive Rate TP/(TP+FN) - - see [recall][contingency.contingent.recall] - """ + """see [`recall`][contingency.contingent.recall]""" return recall(self) @property def precision(self): - """i.e. Positive Predictive Value TP/(TP+FP)""" + """see [`precision`][contingency.contingent.precision]""" return precision(self) @property def mcc(self): - """ Matthew's Correlation Coefficient (MCC) - - Widely considered the most fair/least bias metric for imbalanced - classification tasks. + """Matthew's Correlation Coefficient (see [`matthews_corrcoef`][contingency.contingent.matthews_corrcoef]) """ return matthews_corrcoef(self) @property def G(self): - """ Fowlkes-Mallows, the geometric mean of precision and recall. - - commonly used in unsupervised cases where synthetic test-data - has been made available (e.g. MENDR, clustering validation, etc.) - """ + """Fowlkes-Mallowes score, see [`fowlkes_mallows`][contingency.contingent.fowlkes_mallows]""" return fowlkes_mallows(self) @typechecker @@ -237,19 +226,19 @@ def expected(self, mode: ScoreOptions='aps')->float: # def TNR(Yt:PredThres,Pt:PredThres) = _bool_contract(~Pt,~Yt) def recall(Y:Contingent)->ProbThres: - """True Positive Rate""" + """TP/(TP+FN) i.e. True Positive Rate""" return Y.TPR.filled(1.) def precision(Y:Contingent)->ProbThres: - """Positive Predictive Value""" + """TP/(TP+FP) i.e. Positive Predictive Value""" return Y.PPV.filled(1.) def f_beta(beta:float, Y:Contingent)-> ProbThres: - """F_beta score + """Fᵦ score - weighted harmonic mean of precision and recall, with beta-times + Weighted harmonic mean of precision and recall, with β-times more bias for recall. """ top = (1+beta**2)*Y.PPV*Y.TPR @@ -258,7 +247,7 @@ def f_beta(beta:float, Y:Contingent)-> ProbThres: return np.ma.divide(top, bottom).filled(0.) def F1(Y:Contingent)->ProbThres: - """partially applied f_beta with beta=1 (equal/no bias) + """Partially applied [`f_beta`][contingency.contingent.f_beta] with beta=1 (equal/no bias) """ return f_beta(1., Y) @@ -266,8 +255,11 @@ def F1(Y:Contingent)->ProbThres: def matthews_corrcoef(Y:Contingent)->ProbThres: """ Matthew's Correlation Coefficient (MCC) + Also called the φ coeffient, it is similar to a Pearson correlation + for binary variables. + Widely considered the most fair/least bias metric for imbalanced - classification tasks. + classification tasks. """ m = np.vstack([Y.TPR,Y.TNR,Y.PPV,Y.NPV]) l = np.sqrt(m).prod(axis=0) @@ -276,6 +268,16 @@ def matthews_corrcoef(Y:Contingent)->ProbThres: # return 1-cdist(Y.y_pred, Y.y_true, "correlation")[:,0] def fowlkes_mallows(Y:Contingent)->ProbThres: + """ Fowlkes-Mallows (G), the geometric mean of precision and recall. + + Commonly used in unsupervised cases where synthetic test-data + has been made available (e.g. MENDR, clustering validation, etc.) + + [Recently shown](https://arxiv.org/pdf/2305.00594) to be the limit + of [MCC][contingency.contingent.matthews_corrcoef] as the number of + True Negatives goes to infinity, making it useful for imbalanced, + needle-in-haystack problems, like multi-cluster assignment. + """ return np.sqrt(recall(Y)*precision(Y)) def avg_precision_score(Y:Contingent)->float: From d8790237efe3301f19074b19cd3a4774b716936c Mon Sep 17 00:00:00 2001 From: "Rachael T. Sexton" Date: Thu, 5 Feb 2026 10:51:12 -0500 Subject: [PATCH 3/6] try debian? --- .gitlab-ci.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9bfa950..65549e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,32 +1,30 @@ stages: - - prep - - test - - pages + - install_and_deploy variables: UV_VERSION: "0.9.28" PYTHON_VERSION: "3.12" - BASE_LAYER: alpine + BASE_LAYER: debian # GitLab CI creates a separate mountpoint for the build directory, # so we need to copy instead of using hard links. UV_LINK_MODE: copy uv-setup: - stage: prep + stage: install_and_deploy image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER - before_script: - - apk add g++ build-base linux-headers + # before_script: + # - apk add g++ build-base linux-headers script: - uv sync pytest: - stage: test + stage: install_and_deploy needs: ["uv-setup"] script: - uv run pytest "tests/test_contingency.py" zensical: - stage: pages + stage: install_and_deploy needs: ["uv-setup"] script: - uv run zensical build From f25d1c1a78a7266ba6d7e2ffa0a9dbf846ce670c Mon Sep 17 00:00:00 2001 From: "Rachael T. Sexton" Date: Thu, 5 Feb 2026 10:57:22 -0500 Subject: [PATCH 4/6] one big stage --- .gitlab-ci.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 65549e9..868f870 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ stages: variables: UV_VERSION: "0.9.28" PYTHON_VERSION: "3.12" - BASE_LAYER: debian + BASE_LAYER: alpine # GitLab CI creates a separate mountpoint for the build directory, # so we need to copy instead of using hard links. UV_LINK_MODE: copy @@ -12,21 +12,27 @@ variables: uv-setup: stage: install_and_deploy image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER - # before_script: - # - apk add g++ build-base linux-headers + variables: + UV_CACHE_DIR: .uv-cache + cache: + - key: + files: + - uv.lock + paths: + - $UV_CACHE_DIR script: - uv sync - -pytest: - stage: install_and_deploy - needs: ["uv-setup"] - script: + - uv cache prune --ci + # pytest: + # stage: install_and_deploy + # needs: ["uv-setup"] + # script: - uv run pytest "tests/test_contingency.py" -zensical: - stage: install_and_deploy - needs: ["uv-setup"] - script: + # zensical: + # stage: install_and_deploy + # needs: ["uv-setup"] + # script: - uv run zensical build # - mv site public artifacts: From 9a772ab31099b54a2f2aa1a03e5de2df3be3602f Mon Sep 17 00:00:00 2001 From: "Rachael T. Sexton" Date: Thu, 5 Feb 2026 11:00:35 -0500 Subject: [PATCH 5/6] oops need gcc again --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 868f870..772cf2d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,9 @@ uv-setup: - uv.lock paths: - $UV_CACHE_DIR + + before_script: + - apk add g++ build-base linux-headers script: - uv sync - uv cache prune --ci From d09e5b24993931a95432cc410c0e8f089927d45c Mon Sep 17 00:00:00 2001 From: "Rachael T. Sexton" Date: Thu, 5 Feb 2026 14:03:53 -0500 Subject: [PATCH 6/6] finish draft --- docs/api/plotting.md | 2 +- docs/getting-started/02-tutorial.md | 121 ++++++++++++------------- docs/getting-started/03-performance.md | 19 +++- docs/index.md | 23 ++--- src/contingency/contingent.py | 22 +++-- src/contingency/plots.py | 23 ++--- zensical.toml | 16 ++-- 7 files changed, 116 insertions(+), 110 deletions(-) diff --git a/docs/api/plotting.md b/docs/api/plotting.md index fda59cd..7b20fc5 100644 --- a/docs/api/plotting.md +++ b/docs/api/plotting.md @@ -2,7 +2,7 @@ title: Plotting Utilities --- -::: contingency.plots.PR_contour +::: contingency.plots handler: python options: show_root_heading: true diff --git a/docs/getting-started/02-tutorial.md b/docs/getting-started/02-tutorial.md index 7b127a0..bb11137 100644 --- a/docs/getting-started/02-tutorial.md +++ b/docs/getting-started/02-tutorial.md @@ -3,16 +3,20 @@ icon: lucide/graduation-cap --- # Tutorial +Binary classification metrics are all about testing the quality of your +predicted labels against the _actual_ labels you observed. +To start out, we will need example _predictions_ (`y_pred`) and _targets_ (`y_true`) -```ipython -import numpy as np -from rich import print -from contingency import Contingent -import matplotlib.pyplot as plt +??? info "python imports & setup" -np.set_printoptions(formatter={'float_kind':"{:.5g}".format}) -``` + ```ipython + import numpy as np + from rich import print + import matplotlib.pyplot as plt + + np.set_printoptions(formatter={'float_kind':"{:.5g}".format}) + ``` ```ipython @@ -22,41 +26,42 @@ y_pred = np.array([0,1,0,1,0]).astype(bool) ## Basic Instantiation +Now, just instantiate the [`Contingent`][contingency.contingent.Contingent] dataclass with your true and predicted target values. ```ipython -M = Contingent(y_pred = y_pred, y_true=y_true) +from contingency import Contingent +M = Contingent(y_pred=y_pred, y_true=y_true) -# M.precision print(M) ``` - -
Contingent(
-    y_true=array([[False,  True, False, False,  True]]),
-    y_pred=array([[False,  True, False,  True, False]]),
-    weights=None,
-    TP=array([1]),
-    FP=array([1]),
-    FN=array([1]),
-    TN=array([2]),
-    PP=array([2]),
-    PN=array([3]),
-    P=array([2]),
-    N=array([3]),
-    PPV=masked_array(data=[0.5],
-             mask=[False],
-       fill_value=1e+20),
-    NPV=masked_array(data=[0.6666666666666666],
-             mask=[False],
-       fill_value=1e+20),
-    TPR=masked_array(data=[0.5],
-             mask=[False],
-       fill_value=1e+20),
-    TNR=masked_array(data=[0.6666666666666666],
-             mask=[False],
-       fill_value=1e+20)
-)
-
+??? example "output" +
Contingent(
+        y_true=array([[False,  True, False, False,  True]]),
+        y_pred=array([[False,  True, False,  True, False]]),
+        weights=None,
+        TP=array([1]),
+        FP=array([1]),
+        FN=array([1]),
+        TN=array([2]),
+        PP=array([2]),
+        PN=array([3]),
+        P=array([2]),
+        N=array([3]),
+        PPV=masked_array(data=[0.5],
+                 mask=[False],
+           fill_value=1e+20),
+        NPV=masked_array(data=[0.6666666666666666],
+                 mask=[False],
+           fill_value=1e+20),
+        TPR=masked_array(data=[0.5],
+                 mask=[False],
+           fill_value=1e+20),
+        TNR=masked_array(data=[0.6666666666666666],
+                 mask=[False],
+           fill_value=1e+20)
+    )
+    
@@ -70,16 +75,9 @@ We now have access to properties that will return useful metrics from these cont ```ipython print(M.mcc, M.F, M.G, sep='\n') -# m = np.vstack([M.TPR,M.TNR,M.PPV,M.NPV]).T#.filled(0) -# l = np.sqrt(m).prod(axis=0) -# r = np.sqrt(1-m).prod(axis=0) -# M_batch.TPR -# np.sqrt(m)#.prod(axis=1) -# (l-r).filled(0) ``` -
[0.16667]
 [0.5]
 [0.5]
@@ -90,15 +88,18 @@ print(M.mcc, M.F, M.G, sep='\n')
 ## Contingencies from Probabilities
 
 Most ML systems do not output binary classifications directly, but instead output probabilities or weights. 
-Thresholding these will create an entire "family" of predictions, as the threshold increases or lowers. 
-
-`Contingent` easily handles this as a simple broadcasting operation, using the `from_scalar()` constructor: 
-
 
 ```ipython
 y_prob = np.array([0.1,0.8,0.1,.7,.25])
 ```
 
+Thresholding these will create an entire "family" of predictions, as the threshold increases or lowers. 
+
+`Contingent` easily handles this as a simple broadcasting operation, using numpy.
+To access this functionality, procuce a `Contingent` instance  using the `from_scalar()` constructor with you scalar predictions: 
+
+
+
 
 ```ipython
 M_batch = Contingent.from_scalar(y_true, y_prob)
@@ -119,16 +120,13 @@ M_batch.y_pred.shape
 
 
 
-
-
-
     (6, 5)
 
 
 
-Note how the number of positives decreases as the threshold increases. 
+Note how the number of positives decreases as the threshold increases (downward, increasing with each row). 
 
-Likewise, we can see the set of metrics is now vectorized as well: 
+Likewise, we can see the set of metrics is now vectorized as well, since each threshold implies a different set of TP,FP, FN, and TN counts: 
 
 
 ```ipython
@@ -176,9 +174,10 @@ for score in ('aps', 'mcc', 'F'):
 
 ## Optional Plotting Utilities
 
-There is an included plot utility for making nicely formatted P-R curve axes to plot your `Contingent` metrics on. 
-While this does not automatically plot the P-R curves themselves, this functionality will be added at a later time. 
+For those of us that are consistently performing threshold sensitivity analyses, a _Precision-Recall_ (P-R) curve probably feels like an old friend.
+Communicating these, with respect to the aggregate scores like [`F`][contingency.contingent.f_beta] and [`G`][contingency.contingent.fowlkes_mallows], can be tricky, so we've provided a simple template `matplotlib.axes.Axes` object to
 
+There is an included plot utility [`PR_contour`][contingency.plots.PR_contour] for making nicely formatted P-R curve axes to plot your `Contingent` metrics on. 
 
 ```ipython
 from contingency.plots import PR_contour
@@ -189,19 +188,13 @@ PR_contour()
 plt.step(M_batch.recall, M_batch.precision, color='k', ls='--', where='post')
 # plt.plot(M_batch.recall, M_batch.precision, color='k', ls='--')
 ```
-
-
-
-
-    []
-
-
-
-
     
 ![png](output_15_1.png)
     
 
+!!! tip
+    While the [`Contingent`][contingency.contingent.Contingent] class does not have a method to automatically plot its own P-R curves on a contour like this, such functionality is planned to be added at a later time. 
+
+
 
-## Performance
 
diff --git a/docs/getting-started/03-performance.md b/docs/getting-started/03-performance.md
index 9a9c6c5..104e6cf 100644
--- a/docs/getting-started/03-performance.md
+++ b/docs/getting-started/03-performance.md
@@ -6,22 +6,23 @@ icon: lucide/trending-up
 When datasets become increasingly large, the number of unique thresholds can grow significantly. 
 
 ## Vectorize & Memoize
-Because looping in python is slow, we rely on boolean matrix operations to calculate the contingency counts. At the core of `Contingent.from_scalar` is a call to `numpy.less_equal.outer()`, which broadcasts the thresholding operation over all possible levels simultaneously. 
+Because looping in python is slow, we rely on boolean matrix operations to calculate the contingency counts. At the core of [`Contingent.from_scalar`][contingency.contingent.Contingent.from_scalar] is a call to [`numpy.less_equal.outer`](https://numpy.org/doc/stable/reference/generated/numpy.ufunc.outer.html), which broadcasts the thresholding operation over all possible levels simultaneously. 
 
 This is reasonably fast, able to calculate e.g. APS only marginally slower than the scikit-learn implementation.
 In addition, the one-time cacluation of the "full" contingency set has the added benefit of amortizing the cost of subsequent metric calculations significantly. 
 
 
-
+Let'smake a much larger test case than before, by adding white noise to a known ground-truth. 
 
 ```ipython
-rng = np.random.default_rng(24) ## mph, the avg cruising airspeed velocity of an unladen (european) swallow
+rng = np.random.default_rng(24) # (1)! 
 y_src = rng.random(1000)
 y_true = y_src>0.7
 
 y_pred = y_src + 0.05*rng.normal(size=1000)
 ```
 
+1. Did you know? 24mph is the cruising airspeed velocity of an unladen (european) swallow
 
 ```ipython
 from sklearn.metrics import average_precision_score, matthews_corrcoef
@@ -51,10 +52,20 @@ Say you wish to find the expected value of the MCC score over all thresholds:
     1.36 s ± 576 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
     176 μs ± 10.9 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
 
+!!! tip
+
+    This is one of the key features of `contingency`!
+    
+    If you have many individual datasets or runs, and you want to compare cross-threshold metrics (like APS) over many experiments, needing over a second per-run can add up quickly!
+    This is a common problem in feature engineering and model selection pipelines.
+
+    For an example, see the [MENDR benchmark](https://github.com/usnistgov/mendr), where tens of thousands of individual prediction arrays need to be systematically compared via APS and expected MCC.
+    Using the mean of many `matthews_corrcoef` calls would take a very long time, if not for the optimizations made by `contingency`!
 
 ## Subsampling Approximation
 
-The limit to this amortization comes from your RAM: the outer-product matrix can get huge. 
+The limit to this amortization comes from your RAM: the outer-product matrix we use to vectorize contingency counting can get _huge_. 
+
 To mitigate this, `Contingent.from_scalar` has a `subsamples` option, wich allows you to approximate the threshold values with an interpolated subset, distributed according to the originals. 
 
 With only a few subsamples, the score curves quickly converge to their "true" values. 
diff --git a/docs/index.md b/docs/index.md
index 6a70061..2427939 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -4,23 +4,20 @@ icon: lucide/house
 
 # Contingency Documentation
 
-## Welcome
-
-
 ![Image title](./images/logo.svg){ align=right }
 
-> Fast, vectorized metrology with binary contingency counts. 
+> _Fast, vectorized metrology with binary contingency counts._ 
 
 Rapidly calculate binary classifier metrics like MCC, F-Scores, and Average Precision Scores from scalar and binary predictions.
 
-For an overview of features, usage, and performance, see the [tutorial](./getting-started/02-tutorial.md). 
+For an overview of features and usage, see the [tutorial](getting-started/02-tutorial).  
+For more details about Contingency's performance and intended use-cases, see [Performance](getting-started/03-performance)
+
+!!! example "Contact the PI"
 
-## Contact the PI
+    [Rachael Sexton](https://www.nist.gov/people/rachael-t-sexton)  
+    Email: [`rachael.sexton@nist.gov`](mailto:rachael.sexton@nist.gov)  
 
-[Rachael Sexton](https://www.nist.gov/people/rachael-t-sexton)
-> [`rachael.sexton@nist.gov`](mailto:rachael.sexton@nist.gov)
-```
-NIST Engineering Laboratory
-Systems Integration Division
-Information Modeling & Testing Group 
-```
+    NIST Engineering Laboratory  
+    Systems Integration Division  
+    Information Modeling & Testing Group   
diff --git a/src/contingency/contingent.py b/src/contingency/contingent.py
index e96307a..836b10c 100644
--- a/src/contingency/contingent.py
+++ b/src/contingency/contingent.py
@@ -72,18 +72,24 @@ def _TN(actual,pred):
 class Contingent:
     """ dataclass to hold true and (batched) predicted values
 
+    Being a contingency library, this class is built around the idea
+    of calculating which predictions are:
+
+    - True
+        - Predicted Negative (TN)
+        - Predicted Positive (TP)
+    - False
+        - Predicted Negative (FN)
+        - Predicted Positive (FP)
+
+    From these counts (TN, TP, FN, FP), all other contingency metrics
+    are found.  
+    
     Parameters:
         y_true: True positive and negative binary classifications
         y_pred: Predicted, possible batched (tensor)
         weights: weight(s) for y_pred, useful for expected values of scores
 
-    Properties:
-        f_beta: beta-weighted harmonic mean of precision and recall
-        F:  alias for f_beta(1)
-        recall: a.k.a. true-positive rate
-        precision: a.k.a. positive-predictive-value (PPV)
-        mcc: Matthew's Correlation Coefficient
-        G: Fowlkes-Mallows score (geometric mean of precision and recall)
     """
     y_true: Bool[nda, 'feat']
     y_pred: Bool[nda, '*#batch feat']
@@ -259,7 +265,7 @@ def matthews_corrcoef(Y:Contingent)->ProbThres:
     for binary variables.
     
     Widely considered the most fair/least bias metric for imbalanced
-    classification tasks. 
+    classification tasks. [(Chico & Jurman, 2023)](https://doi.org/10.1186/s13040-023-00322-4)
     """
     m = np.vstack([Y.TPR,Y.TNR,Y.PPV,Y.NPV])
     l = np.sqrt(m).prod(axis=0)
diff --git a/src/contingency/plots.py b/src/contingency/plots.py
index 4efe32c..3cb5031 100644
--- a/src/contingency/plots.py
+++ b/src/contingency/plots.py
@@ -11,7 +11,9 @@
 def PR_contour(ax:[matplotlib.axes.Axes|None]=None):
     """Generate a nice-looking contour plot for Precision vs. Recall
 
-    REQUIRES optional [plot] dependencies!
+    For an example, see the [Tutorial](getting-started/02-tutorial/#optional-plotting-utilities)
+
+    REQUIRES optional `contingency[plot]` dependencies! See [Installation](getting-started/01-installation).
     """
     if not _has_plot:
         raise ImportError("Optional contingiency[plot] dependencies required.")
@@ -21,27 +23,20 @@ def PR_contour(ax:[matplotlib.axes.Axes|None]=None):
     thres = np.linspace(0.2, 0.8, num=4)
     lines, labels = [], []
     for t in thres:
-        # recall = np.linspace(0.00001, 1., num=100)
-        # recall = np.logspace(-5, 0., num=100)
         recall_f1 = np.linspace(t/(2-t), 1.)
         recall_fm = np.linspace(t**2,1.)
         prec_f1 = t * recall_f1 / (2 * recall_f1 - t)
-        # prec_f1 = 1/(2/t - 1/recall)
-        # f1_bound = (recall>t/2)&(1.1>=prec_f1)# (0<=prec_f1)&(1>=prec_f1)
         prec_fm = t**2/recall_fm
-        # fm_bound = (0<=y_fm)&(1>=y_fm)
 
-        # (l,) = ax.plot(recall[f1_bound], prec_f1[f1_bound], color="0.8")
-        # (l,) = ax.plot(x[fm_bound], y_fm[fm_bound], color="0.9")
         (l,) = ax.plot(recall_f1, prec_f1, color="0.8")
         (l,) = ax.plot(recall_fm, prec_fm, color="0.95")
-        # midpt = y_fm[25]-0.03
-        ax.annotate(f"{t:0.1f}", xy=(t-.02, t-0.02), color='0.8', bbox=dict(facecolor='white', linewidth=0, alpha=0.5))
-        # print(y_f1[24])
+        ax.annotate(
+            f"{t:0.1f}",
+            xy=(t-.02, t-0.02),
+            color='0.8',
+            bbox=dict(facecolor='white', linewidth=0, alpha=0.5)
+        )
 
-        # plt.annotate("f1={0:0.1f}".format(f_score), xy=(1.1, y_f1[48]-0.01), color='xkcd:orange')
-        # return plt.gca()
-    # ax.legend()
     ax.annotate(r"$F_1$", xy=(1.01, 0.2/(2-0.2)-0.01), color='0.8')
     ax.annotate(r"F-M", xy=(1.01, 0.2**2-0.01), color='0.9')
     ax.set(
diff --git a/zensical.toml b/zensical.toml
index 26647ea..5f9caf8 100644
--- a/zensical.toml
+++ b/zensical.toml
@@ -213,7 +213,7 @@ features = [
     # In order to provide a better user experience on slow connections when
     # using instant navigation, a progress indicator can be enabled.
     # https://zensical.org/docs/setup/navigation/#progress-indicator
-    #"navigation.instant.progress",
+    "navigation.instant.progress",
 
     # When navigation paths are activated, a breadcrumb navigation is rendered
     # above the title of each page
@@ -223,7 +223,7 @@ features = [
     # When pruning is enabled, only the visible navigation items are included
     # in the rendered HTML, reducing the size of the built site by 33% or more.
     # https://zensical.org/docs/setup/navigation/#navigation-pruning
-    #"navigation.prune",
+    "navigation.prune",
 
     # When sections are enabled, top-level sections are rendered as groups in
     # the sidebar for viewports above 1220px, but remain as-is on mobile.
@@ -233,7 +233,7 @@ features = [
     # When tabs are enabled, top-level sections are rendered in a menu layer
     # below the header for viewports above 1220px, but remain as-is on mobile.
     # https://zensical.org/docs/setup/navigation/#navigation-tabs
-    #"navigation.tabs",
+    # "navigation.tabs",
 
     # When sticky tabs are enabled, navigation tabs will lock below the header
     # and always remain visible when scrolling down.
@@ -259,12 +259,12 @@ features = [
     # When anchor following for the table of contents is enabled, the sidebar
     # is automatically scrolled so that the active anchor is always visible.
     # https://zensical.org/docs/setup/navigation/#anchor-following
-    # "toc.follow",
+    "toc.follow",
 
     # When navigation integration for the table of contents is enabled, it is
     # always rendered as part of the navigation sidebar on the left.
     # https://zensical.org/docs/setup/navigation/#navigation-integration
-    #"toc.integrate",
+    # "toc.integrate",
 ]
 
 # ----------------------------------------------------------------------------
@@ -324,6 +324,10 @@ code = "Fira Code"
 #icon = "fontawesome/brands/github"
 #link = "https://github.com/user/repo"
 
+# markdown_extensions = [
+#   "pymdownx.superfences",
+#   "pymdownx.details"
+# ]
 
 [project.plugins.mkdocstrings.handlers.python]
 inventories = ["https://docs.python.org/3/objects.inv"]
@@ -333,7 +337,7 @@ paths = ["src/contingency/"]
 docstring_style = "google"
 inherited_members = true
 # show_source = false
-backlinks = "tree"
+# backlinks = "tree"
 docstring_options = {ignore_init_summary = true}
 docstring_section_style = "list"
 # extensions = ["griffe_typingdoc",]