From 117186ae57847d7a0b1fdbc5f3b83dea6b801f9e Mon Sep 17 00:00:00 2001 From: Yunmo Chen Date: Sat, 16 Sep 2023 05:16:43 -0400 Subject: [PATCH 01/15] Added `ruff`, `black`, `pyright`, and `pytest` to the pyproject.toml and updated dependencies. --- poetry.lock | 183 +++++++++++++++++++++++++++++++------------------ pyproject.toml | 34 ++++++--- 2 files changed, 142 insertions(+), 75 deletions(-) diff --git a/poetry.lock b/poetry.lock index 44ffc8b..0a0b84b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "colorama" @@ -36,41 +36,52 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "numpy" -version = "1.24.4" +version = "1.25.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, - {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, - {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, - {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, - {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, - {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, - {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, - {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, - {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, - {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, - {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, - {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, - {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, - {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, - {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, - {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, - {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, - {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, - {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, ] [[package]] @@ -86,28 +97,46 @@ files = [ [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pyright" +version = "1.1.327" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.327-py3-none-any.whl", hash = "sha256:3462cda239e9140276238bbdbd0b59d77406f1c2e14d8cb8c20c8e25639c6b3c"}, + {file = "pyright-1.1.327.tar.gz", hash = "sha256:ba74148ad64f22020dbbed6781c4bdb38ecb8a7ca90dc3c87a4f08d1c0e11592"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -123,41 +152,61 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "scipy" -version = "1.9.3" +version = "1.11.2" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.8" +python-versions = "<3.13,>=3.9" files = [ - {file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"}, - {file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"}, - {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"}, - {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"}, - {file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"}, - {file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"}, - {file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"}, - {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"}, - {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"}, - {file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"}, - {file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"}, - {file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"}, - {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"}, - {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"}, - {file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"}, - {file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"}, - {file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"}, - {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"}, - {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"}, - {file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"}, - {file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"}, + {file = "scipy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2b997a5369e2d30c97995dcb29d638701f8000d04df01b8e947f206e5d0ac788"}, + {file = "scipy-1.11.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:95763fbda1206bec41157582bea482f50eb3702c85fffcf6d24394b071c0e87a"}, + {file = "scipy-1.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e367904a0fec76433bf3fbf3e85bf60dae8e9e585ffd21898ab1085a29a04d16"}, + {file = "scipy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d690e1ca993c8f7ede6d22e5637541217fc6a4d3f78b3672a6fe454dbb7eb9a7"}, + {file = "scipy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d2b813bfbe8dec6a75164523de650bad41f4405d35b0fa24c2c28ae07fcefb20"}, + {file = "scipy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:afdb0d983f6135d50770dd979df50bf1c7f58b5b33e0eb8cf5c73c70600eae1d"}, + {file = "scipy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d9886f44ef8c9e776cb7527fb01455bf4f4a46c455c4682edc2c2cc8cd78562"}, + {file = "scipy-1.11.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1342ca385c673208f32472830c10110a9dcd053cf0c4b7d4cd7026d0335a6c1d"}, + {file = "scipy-1.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b133f237bd8ba73bad51bc12eb4f2d84cbec999753bf25ba58235e9fc2096d80"}, + {file = "scipy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aeb87661de987f8ec56fa6950863994cd427209158255a389fc5aea51fa7055"}, + {file = "scipy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90d3b1364e751d8214e325c371f0ee0dd38419268bf4888b2ae1040a6b266b2a"}, + {file = "scipy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:f73102f769ee06041a3aa26b5841359b1a93cc364ce45609657751795e8f4a4a"}, + {file = "scipy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa4909c6c20c3d91480533cddbc0e7c6d849e7d9ded692918c76ce5964997898"}, + {file = "scipy-1.11.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ac74b1512d38718fb6a491c439aa7b3605b96b1ed3be6599c17d49d6c60fca18"}, + {file = "scipy-1.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8425fa963a32936c9773ee3ce44a765d8ff67eed5f4ac81dc1e4a819a238ee9"}, + {file = "scipy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:542a757e2a6ec409e71df3d8fd20127afbbacb1c07990cb23c5870c13953d899"}, + {file = "scipy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea932570b1c2a30edafca922345854ff2cd20d43cd9123b6dacfdecebfc1a80b"}, + {file = "scipy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:4447ad057d7597476f9862ecbd9285bbf13ba9d73ce25acfa4e4b11c6801b4c9"}, + {file = "scipy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b0620240ef445b5ddde52460e6bc3483b7c9c750275369379e5f609a1050911c"}, + {file = "scipy-1.11.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f28f1f6cfeb48339c192efc6275749b2a25a7e49c4d8369a28b6591da02fbc9a"}, + {file = "scipy-1.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:214cdf04bbae7a54784f8431f976704ed607c4bc69ba0d5d5d6a9df84374df76"}, + {file = "scipy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10eb6af2f751aa3424762948e5352f707b0dece77288206f227864ddf675aca0"}, + {file = "scipy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0f3261f14b767b316d7137c66cc4f33a80ea05841b9c87ad83a726205b901423"}, + {file = "scipy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:2c91cf049ffb5575917f2a01da1da082fd24ed48120d08a6e7297dfcac771dcd"}, + {file = "scipy-1.11.2.tar.gz", hash = "sha256:b29318a5e39bd200ca4381d80b065cdf3076c7d7281c5e36569e99273867f61d"}, ] [package.dependencies] -numpy = ">=1.18.5,<1.26.0" +numpy = ">=1.21.6,<1.28.0" + +[package.extras] +dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "setuptools" +version = "68.2.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, +] [package.extras] -dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] -doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] -test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "tomli" @@ -172,5 +221,5 @@ files = [ [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "c3754f3a5ef2769b2a4bfe3bec30c0aa8b1b7c0264d37dc15023e02508357f8b" +python-versions = ">=3.9,<3.13" +content-hash = "dac26de6ca105acd6c73153c222d74af1526dd74b7d4e5d79c14b2c047d8da96" diff --git a/pyproject.toml b/pyproject.toml index 4e06831..770f20c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,37 @@ +[tool.ruff] +line-length = 120 + +[tool.black] +line-length = 120 + +[tool.pyright] +include = ["src", "tests"] + + +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = ["tests"] + [tool.poetry] name = "unified-metric" version = "0.1.0" -description = "" -authors = ["Tongfei Chen "] -license = "MIT" +description = "A Unified View of Evaluation Metrics for Information Extraction" +authors = ["Tongfei Chen ", "Yunmo Chen "] readme = "README.md" -packages = [{include = "unimetric"}] +packages = [{ include = "src/unimetric" }] [tool.poetry.dependencies] -python = "^3.8" -numpy = "^1.19" -scipy = "^1.9" +python = ">=3.9,<3.13" +scipy = "^1.11.2" +numpy = "^1.25.2" +pytest = "^7.4.2" + +[tool.poetry.group.dev] +optional = true [tool.poetry.group.dev.dependencies] -pytest = "^7.4.0" +pytest = "^7.4.2" +pyright = "^1.1.327" [build-system] requires = ["poetry-core"] From 507f105055826e306aa5f5ee0d0ae09ec87dd8c3 Mon Sep 17 00:00:00 2001 From: Yunmo Chen Date: Sat, 16 Sep 2023 05:17:51 -0400 Subject: [PATCH 02/15] Re-arranged the codebase. --- {unimetric => src/unimetric}/__init__.py | 0 {unimetric => src/unimetric}/alignment.py | 27 ++++++++++--------- {unimetric => src/unimetric}/amr.py | 2 +- {unimetric => src/unimetric}/decorator.py | 21 +++++++-------- {unimetric => src/unimetric}/ie.py | 1 + .../unimetric}/latent_alignment.py | 10 +++---- {unimetric => src/unimetric}/metric.py | 5 ++-- tests/__init__.py | 0 tests/test_alignment.py | 14 ---------- tests/unimetric/__init__.py | 0 tests/unimetric/test_alignment.py | 14 ++++++++++ .../{test_smatch.py => unimetric/test_amr.py} | 3 ++- 12 files changed, 50 insertions(+), 47 deletions(-) rename {unimetric => src/unimetric}/__init__.py (100%) rename {unimetric => src/unimetric}/alignment.py (69%) rename {unimetric => src/unimetric}/amr.py (100%) rename {unimetric => src/unimetric}/decorator.py (85%) rename {unimetric => src/unimetric}/ie.py (99%) rename {unimetric => src/unimetric}/latent_alignment.py (95%) rename {unimetric => src/unimetric}/metric.py (96%) create mode 100644 tests/__init__.py delete mode 100644 tests/test_alignment.py create mode 100644 tests/unimetric/__init__.py create mode 100644 tests/unimetric/test_alignment.py rename tests/{test_smatch.py => unimetric/test_amr.py} (91%) diff --git a/unimetric/__init__.py b/src/unimetric/__init__.py similarity index 100% rename from unimetric/__init__.py rename to src/unimetric/__init__.py diff --git a/unimetric/alignment.py b/src/unimetric/alignment.py similarity index 69% rename from unimetric/alignment.py rename to src/unimetric/alignment.py index 16f273d..e94ee2b 100644 --- a/unimetric/alignment.py +++ b/src/unimetric/alignment.py @@ -1,10 +1,11 @@ -from typing import Collection, Generic, Mapping, TypeVar, Iterator, Tuple +import enum from enum import Enum +from typing import Collection, TypeVar + import numpy as np import scipy.optimize as spo -from unimetric.metric import Metric, DiscreteMetric - +from unimetric.metric import Metric T = TypeVar('T') @@ -13,14 +14,14 @@ class AlignmentConstraint(Enum): """ Alignment constraints for the alignment metric. """ - ONE_TO_ONE = 0 - ONE_TO_MANY = 1 - MANY_TO_ONE = 2 - MANY_TO_MANY = 3 + OneToOne = enum.auto() + OneToMany = enum.auto() + ManyToOne = enum.auto() + ManyToMany = enum.auto() class AlignmentMetric(Metric[Collection[T]]): - def __init__(self, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.ONE_TO_ONE): + def __init__(self, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.OneToOne): self.inner = inner self.constraint = constraint @@ -32,22 +33,22 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: ) def score_self(self, x: Collection[T]) -> float: - if self.constraint == AlignmentConstraint.MANY_TO_MANY: + if self.constraint == AlignmentConstraint.ManyToMany: return self.inner.gram_matrix(x, x).sum() else: return sum(self.inner.score_self(u) for u in x) def solve_alignment(gram_matrix: np.ndarray, constraint: AlignmentConstraint) -> float: - if constraint == AlignmentConstraint.ONE_TO_ONE: + if constraint == AlignmentConstraint.OneToOne: row_idx, col_idx = spo.linear_sum_assignment( cost_matrix=gram_matrix, maximize=True, ) return gram_matrix[row_idx, col_idx].sum() - if constraint == AlignmentConstraint.ONE_TO_MANY: + if constraint == AlignmentConstraint.OneToMany: return gram_matrix.max(axis=0).sum() - if constraint == AlignmentConstraint.MANY_TO_ONE: + if constraint == AlignmentConstraint.ManyToOne: return gram_matrix.max(axis=1).sum() - if constraint == AlignmentConstraint.MANY_TO_MANY: + if constraint == AlignmentConstraint.ManyToMany: return gram_matrix.sum() diff --git a/unimetric/amr.py b/src/unimetric/amr.py similarity index 100% rename from unimetric/amr.py rename to src/unimetric/amr.py index 81ee9a8..c4b9021 100644 --- a/unimetric/amr.py +++ b/src/unimetric/amr.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from typing import Collection, Set, Union -from unimetric.latent_alignment import Variable from unimetric.decorator import unimetric +from unimetric.latent_alignment import Variable @unimetric() diff --git a/unimetric/decorator.py b/src/unimetric/decorator.py similarity index 85% rename from unimetric/decorator.py rename to src/unimetric/decorator.py index b4756d9..e7cde19 100644 --- a/unimetric/decorator.py +++ b/src/unimetric/decorator.py @@ -1,10 +1,9 @@ from dataclasses import fields, is_dataclass from typing import Literal, get_args, get_origin, Collection, Annotated, Union -from inspect import isclass -from unimetric.latent_alignment import Variable, dataclass_has_variable, LatentAlignmentMetric -from unimetric.metric import Metric, ProductMetric, DiscreteMetric, FScore, Jaccard, Precision, Recall, UnionMetric from unimetric.alignment import AlignmentConstraint, AlignmentMetric +from unimetric.latent_alignment import dataclass_has_variable, LatentAlignmentMetric +from unimetric.metric import Metric, ProductMetric, DiscreteMetric, FScore, Jaccard, Precision, Recall, UnionMetric def derive_metric(cls: type, constraint: AlignmentConstraint) -> Metric: @@ -82,14 +81,14 @@ def unimetric( """ def class_decorator(cls): alignment_constraint = { - '<->': AlignmentConstraint.ONE_TO_ONE, - '<-': AlignmentConstraint.ONE_TO_MANY, - '->': AlignmentConstraint.MANY_TO_ONE, - '~': AlignmentConstraint.MANY_TO_MANY, - '1:1': AlignmentConstraint.ONE_TO_ONE, - '1:*': AlignmentConstraint.ONE_TO_MANY, - '*:1': AlignmentConstraint.MANY_TO_ONE, - '*:*': AlignmentConstraint.MANY_TO_MANY, + '<->': AlignmentConstraint.OneToOne, + '<-': AlignmentConstraint.OneToMany, + '->': AlignmentConstraint.ManyToOne, + '~': AlignmentConstraint.ManyToMany, + '1:1': AlignmentConstraint.OneToOne, + '1:*': AlignmentConstraint.OneToMany, + '*:1': AlignmentConstraint.ManyToOne, + '*:*': AlignmentConstraint.ManyToMany, }[constraint] metric = derive_metric(cls, constraint=alignment_constraint) normalized_metric = { diff --git a/unimetric/ie.py b/src/unimetric/ie.py similarity index 99% rename from unimetric/ie.py rename to src/unimetric/ie.py index 4789117..6f8a0bc 100644 --- a/unimetric/ie.py +++ b/src/unimetric/ie.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from typing import List + from unimetric.decorator import unimetric diff --git a/unimetric/latent_alignment.py b/src/unimetric/latent_alignment.py similarity index 95% rename from unimetric/latent_alignment.py rename to src/unimetric/latent_alignment.py index a4a63e7..8149f27 100644 --- a/unimetric/latent_alignment.py +++ b/src/unimetric/latent_alignment.py @@ -21,7 +21,7 @@ def __hash__(self): class LatentAlignmentMetric(Metric[Collection[T]]): - def __init__(self, cls: type, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.ONE_TO_ONE): + def __init__(self, cls: type, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.OneToOne): if is_dataclass(cls): self.fields = fields(cls) else: @@ -71,10 +71,10 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: # each item may be mapped to some other items given the constraint item_constraint_matrix_ctor = { - AlignmentConstraint.ONE_TO_ONE: _get_one_to_one_constraint_matrix, - AlignmentConstraint.ONE_TO_MANY: _get_one_to_many_constraint_matrix, - AlignmentConstraint.MANY_TO_ONE: _get_many_to_one_constraint_matrix, - AlignmentConstraint.MANY_TO_MANY: lambda _0, _1: None, + AlignmentConstraint.OneToOne: _get_one_to_one_constraint_matrix, + AlignmentConstraint.OneToMany: _get_one_to_many_constraint_matrix, + AlignmentConstraint.ManyToOne: _get_many_to_one_constraint_matrix, + AlignmentConstraint.ManyToMany: lambda _0, _1: None, }[self.constraint] item_constraint_matrix = item_constraint_matrix_ctor(n_x, n_y) if item_constraint_matrix is not None: diff --git a/unimetric/metric.py b/src/unimetric/metric.py similarity index 96% rename from unimetric/metric.py rename to src/unimetric/metric.py index f2f4d57..2e84f82 100644 --- a/unimetric/metric.py +++ b/src/unimetric/metric.py @@ -2,7 +2,8 @@ from dataclasses import is_dataclass from functools import reduce from operator import mul -from typing import Callable, Dict, Generic, Sequence, Set, Type, TypeVar, Collection, Union, get_origin +from typing import Callable, Dict, Generic, Type, TypeVar, Collection, Union, get_origin + import numpy as np T = TypeVar('T', contravariant=True) @@ -142,7 +143,7 @@ def score(self, x: T, y: T) -> float: class UnionMetric(Metric[T]): def __init__(self, cls: type, case_metrics: Dict[type, Metric]): - if not get_origin(cls) is Union: + if get_origin(cls) is not Union: raise ValueError(f"{cls} has to be a union.") self.case_metrics = case_metrics diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_alignment.py b/tests/test_alignment.py deleted file mode 100644 index aca147e..0000000 --- a/tests/test_alignment.py +++ /dev/null @@ -1,14 +0,0 @@ -from unimetric.alignment import AlignmentConstraint, solve_alignment -from unimetric.metric import DiscreteMetric - - -def test_alignment(): - a = [1, 2, 2] - b = [1, 1, 1, 2] - - g = DiscreteMetric(int).gram_matrix(a, b) - - assert solve_alignment(g, AlignmentConstraint.ONE_TO_ONE) == 2 - assert solve_alignment(g, AlignmentConstraint.MANY_TO_ONE) == 3 - assert solve_alignment(g, AlignmentConstraint.ONE_TO_MANY) == 4 - assert solve_alignment(g, AlignmentConstraint.MANY_TO_MANY) == 5 diff --git a/tests/unimetric/__init__.py b/tests/unimetric/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unimetric/test_alignment.py b/tests/unimetric/test_alignment.py new file mode 100644 index 0000000..24533c4 --- /dev/null +++ b/tests/unimetric/test_alignment.py @@ -0,0 +1,14 @@ +from unimetric.alignment import solve_alignment, AlignmentConstraint +from unimetric.metric import DiscreteMetric + + +def test_solve_alignment(): + a = [1, 2, 2] + b = [1, 1, 1, 2] + + g = DiscreteMetric(int).gram_matrix(a, b) + + assert solve_alignment(g, AlignmentConstraint.OneToOne) == 2 + assert solve_alignment(g, AlignmentConstraint.ManyToOne) == 3 + assert solve_alignment(g, AlignmentConstraint.OneToMany) == 4 + assert solve_alignment(g, AlignmentConstraint.ManyToMany) == 5 diff --git a/tests/test_smatch.py b/tests/unimetric/test_amr.py similarity index 91% rename from tests/test_smatch.py rename to tests/unimetric/test_amr.py index 5978fac..01e207a 100644 --- a/tests/test_smatch.py +++ b/tests/unimetric/test_amr.py @@ -1,6 +1,7 @@ from pytest import approx -from unimetric.amr import AMR, Variable, Prop +from unimetric.amr import AMR, Prop +from unimetric.latent_alignment import Variable def test_smatch(): From 214c716619f96c7070ff9f4c4fe7bc0c042acec8 Mon Sep 17 00:00:00 2001 From: Yunmo Chen Date: Sat, 16 Sep 2023 07:22:19 -0400 Subject: [PATCH 03/15] Fixed all pyright errors. --- src/unimetric/decorator.py | 103 +++++++++++++++++++----------- src/unimetric/latent_alignment.py | 41 ++++++------ src/unimetric/metric.py | 4 +- tests/unimetric/test_amr.py | 7 +- 4 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/unimetric/decorator.py b/src/unimetric/decorator.py index e7cde19..37538ea 100644 --- a/src/unimetric/decorator.py +++ b/src/unimetric/decorator.py @@ -1,12 +1,38 @@ from dataclasses import fields, is_dataclass -from typing import Literal, get_args, get_origin, Collection, Annotated, Union +from typing import ( + Literal, + get_args, + get_origin, + Collection, + Annotated, + Union, + Protocol, + runtime_checkable, + Callable, + TypeVar, +) from unimetric.alignment import AlignmentConstraint, AlignmentMetric from unimetric.latent_alignment import dataclass_has_variable, LatentAlignmentMetric from unimetric.metric import Metric, ProductMetric, DiscreteMetric, FScore, Jaccard, Precision, Recall, UnionMetric +T = TypeVar("T", covariant=True) -def derive_metric(cls: type, constraint: AlignmentConstraint) -> Metric: +NormalizerLiteral = Literal["none", "jaccard", "dice", "f1"] +ConstraintLiteral = Literal["<->", "<-", "->", "~"] + + +@runtime_checkable +class HasMetric(Protocol): + metric: Metric + + +@runtime_checkable +class HasLatentMetric(Protocol): + latent_metric: Metric + + +def derive_metric(cls: object, constraint: AlignmentConstraint) -> Metric: """ Derive a unified metric from any type. :param cls: The type to derive a metric from. @@ -20,34 +46,29 @@ def derive_metric(cls: type, constraint: AlignmentConstraint) -> Metric: return metric # if an explicit metric is defined, use it - if getattr(cls, "metric", None) is not None: + # if getattr(cls, "metric", None) is not None: + if isinstance(cls, HasMetric): return cls.metric - if getattr(cls, "latent_metric", None) is not None: + cls_origin = get_origin(cls) + # if getattr(cls, "latent_metric", None) is not None: + if isinstance(cls, HasLatentMetric): return cls.latent_metric # derive product metric from dataclass elif is_dataclass(cls): return ProductMetric( - cls=cls, - field_metrics={ - fld.name: derive_metric(fld.type, constraint=constraint) - for fld in fields(cls) - } + cls=cls, field_metrics={fld.name: derive_metric(fld.type, constraint=constraint) for fld in fields(cls)} ) # derive union metric from unions - elif get_origin(cls) is Union: + elif cls_origin is Union: return UnionMetric( - cls=cls, - case_metrics={ - case: derive_metric(case, constraint=constraint) - for case in get_args(cls) - } + cls=cls, case_metrics={case: derive_metric(case, constraint=constraint) for case in get_args(cls)} ) # derive alignment metric from collections - elif get_origin(cls) is not None and isinstance(get_origin(cls), type) and issubclass(get_origin(cls), Collection): + elif cls_origin is not None and isinstance(cls_origin, type) and issubclass(cls_origin, Collection): elem_type = get_args(cls)[0] inner_metric = derive_metric(elem_type, constraint=constraint) if dataclass_has_variable(elem_type): @@ -71,38 +92,46 @@ def derive_metric(cls: type, constraint: AlignmentConstraint) -> Metric: def unimetric( - normalizer: Literal['none', 'jaccard', 'dice', 'f1'] = 'none', - constraint: Literal['<->', '<-', '->', '~'] = '<->', -): + normalizer: NormalizerLiteral = "none", + constraint: ConstraintLiteral = "<->", +) -> Callable[[T], T]: """ Derive a unified metric from a class. :param normalizer: :return: """ - def class_decorator(cls): + + def class_decorator(cls: T) -> T: alignment_constraint = { - '<->': AlignmentConstraint.OneToOne, - '<-': AlignmentConstraint.OneToMany, - '->': AlignmentConstraint.ManyToOne, - '~': AlignmentConstraint.ManyToMany, - '1:1': AlignmentConstraint.OneToOne, - '1:*': AlignmentConstraint.OneToMany, - '*:1': AlignmentConstraint.ManyToOne, - '*:*': AlignmentConstraint.ManyToMany, + "<->": AlignmentConstraint.OneToOne, + "<-": AlignmentConstraint.OneToMany, + "->": AlignmentConstraint.ManyToOne, + "~": AlignmentConstraint.ManyToMany, + "1:1": AlignmentConstraint.OneToOne, + "1:*": AlignmentConstraint.OneToMany, + "*:1": AlignmentConstraint.ManyToOne, + "*:*": AlignmentConstraint.ManyToMany, }[constraint] metric = derive_metric(cls, constraint=alignment_constraint) normalized_metric = { - 'none': lambda x: x, - 'jaccard': Jaccard, - 'dice': FScore, - 'f1': FScore, - 'precision': Precision, - 'recall': Recall, + "none": lambda x: x, + "jaccard": Jaccard, + "dice": FScore, + "f1": FScore, + "precision": Precision, + "recall": Recall, }[normalizer](metric) if dataclass_has_variable(cls): - setattr(cls, 'latent_metric', normalized_metric) + class LatentMetricWrapper(cls, HasLatentMetric): # type: ignore + latent_metric = normalized_metric + + return LatentMetricWrapper else: - setattr(cls, 'metric', normalized_metric) - return cls + class MetricWrapper(cls, HasMetric): # type: ignore + metric = normalized_metric + + return MetricWrapper + # return cls + return class_decorator diff --git a/src/unimetric/latent_alignment.py b/src/unimetric/latent_alignment.py index 8149f27..3951e33 100644 --- a/src/unimetric/latent_alignment.py +++ b/src/unimetric/latent_alignment.py @@ -7,7 +7,7 @@ from unimetric.alignment import AlignmentConstraint, solve_alignment from unimetric.metric import Metric -T = TypeVar('T') +T = TypeVar("T") @dataclass @@ -45,28 +45,20 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: # coefficient vector for the objective function coef = np.concatenate( - [ - np.zeros(n_x_vars * n_y_vars), - gram_matrix.reshape([n_pairs]) # score that x_i matches x_j - ], - axis=0 + [np.zeros(n_x_vars * n_y_vars), gram_matrix.reshape([n_pairs])], axis=0 # score that x_i matches x_j ) - + # build the constraint matrix n = n_x_vars * n_y_vars + n_pairs # each variable must be mapped to exactly one variable var_constraint_matrix = _get_one_to_one_constraint_matrix(n_x_vars, n_y_vars) var_constraint_matrix = np.concatenate( - [ - var_constraint_matrix, - np.zeros([var_constraint_matrix.shape[0], n_pairs]) - ], - axis=1 + [var_constraint_matrix, np.zeros([var_constraint_matrix.shape[0], n_pairs])], axis=1 ) var_constraints = spo.LinearConstraint( A=var_constraint_matrix, - ub=np.ones(var_constraint_matrix.shape[0]), + ub=np.ones(var_constraint_matrix.shape[0]), # type: ignore ) # each item may be mapped to some other items given the constraint @@ -83,11 +75,15 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: np.zeros([item_constraint_matrix.shape[0], n_x_vars * n_y_vars]), item_constraint_matrix, ], - axis=1 + axis=1, + ) + item_constraints = ( + None + if item_constraint_matrix is None + else spo.LinearConstraint( + A=item_constraint_matrix, + ub=np.ones(item_constraint_matrix.shape[0]), # type: ignore ) - item_constraints = None if item_constraint_matrix is None else spo.LinearConstraint( - A=item_constraint_matrix, - ub=np.ones(item_constraint_matrix.shape[0]), ) # constrain the item and their variables @@ -108,7 +104,7 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: item_var_constraint_matrix = np.stack(item_var_constraint_vectors, axis=0) # [PC, N] item_var_constraints = spo.LinearConstraint( A=item_var_constraint_matrix, - ub=np.zeros(item_var_constraint_matrix.shape[0]), + ub=np.zeros(item_var_constraint_matrix.shape[0]), # type: ignore ) constraints = [var_constraints, item_var_constraints] @@ -123,7 +119,7 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: ) return -result.fun - def score_self(self, x: T) -> float: + def score_self(self, x: Collection[T]) -> float: return solve_alignment(self.inner.gram_matrix(x, x), self.constraint) @@ -160,6 +156,7 @@ def _all_variables_iterator(obj: object) -> Iterator[Variable]: elif getattr(obj, "__dict__", None) is not None: for fld in vars(x).values(): yield from _all_variables(fld) + return set(_all_variables_iterator(x)) @@ -183,7 +180,7 @@ def _get_one_to_one_constraint_matrix(n_x: int, n_y: int) -> np.ndarray: # [X + return np.concatenate([mask_x, mask_y], axis=0) -def may_be_variable(cls: type) -> bool: +def may_be_variable(cls: object) -> bool: if cls is Variable: return True if get_origin(cls) is not None and get_origin(cls) is Union: @@ -192,10 +189,10 @@ def may_be_variable(cls: type) -> bool: return False -def dataclass_has_variable(cls: type) -> bool: +def dataclass_has_variable(cls: object) -> bool: if cls is Variable: return True - if getattr(cls, "__dataclass_fields__", None) is not None: + if is_dataclass(cls): if any(may_be_variable(t.type) for t in cls.__dataclass_fields__.values()): return True return False diff --git a/src/unimetric/metric.py b/src/unimetric/metric.py index 2e84f82..45033f6 100644 --- a/src/unimetric/metric.py +++ b/src/unimetric/metric.py @@ -126,7 +126,7 @@ def score_self(self, x: T) -> float: class ProductMetric(Metric[T]): - def __init__(self, cls: type, field_metrics: Dict[str, Metric]): + def __init__(self, cls: object, field_metrics: Dict[str, Metric]): if not is_dataclass(cls): raise ValueError(f"{cls} has to be a dataclass.") self.field_metrics = field_metrics @@ -142,7 +142,7 @@ def score(self, x: T, y: T) -> float: class UnionMetric(Metric[T]): - def __init__(self, cls: type, case_metrics: Dict[type, Metric]): + def __init__(self, cls: object, case_metrics: Dict[type, Metric]): if get_origin(cls) is not Union: raise ValueError(f"{cls} has to be a union.") self.case_metrics = case_metrics diff --git a/tests/unimetric/test_amr.py b/tests/unimetric/test_amr.py index 01e207a..ac3268a 100644 --- a/tests/unimetric/test_amr.py +++ b/tests/unimetric/test_amr.py @@ -1,6 +1,7 @@ from pytest import approx from unimetric.amr import AMR, Prop +from unimetric.decorator import HasMetric from unimetric.latent_alignment import Variable @@ -30,4 +31,8 @@ def test_smatch(): ] ) - assert AMR.metric.score(amr1, amr2) == approx(0.73, abs=0.01) + if isinstance(AMR, HasMetric): + assert AMR.metric.score(amr1, amr2) == approx(0.73, abs=0.01) + else: + # In case of failure to derive the metric. + assert False From 2e34bbc5745e378e09a13a956f96cb0b87c23af1 Mon Sep 17 00:00:00 2001 From: wanmok Date: Sat, 16 Sep 2023 07:33:10 -0400 Subject: [PATCH 04/15] Reformatted codebase. --- src/unimetric/alignment.py | 7 +++---- src/unimetric/amr.py | 2 +- src/unimetric/decorator.py | 37 ++++++++++++++++++++++++++++--------- src/unimetric/ie.py | 4 ++-- src/unimetric/metric.py | 16 ++++------------ tests/unimetric/test_amr.py | 22 +++++++++++----------- 6 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/unimetric/alignment.py b/src/unimetric/alignment.py index e94ee2b..47a23f7 100644 --- a/src/unimetric/alignment.py +++ b/src/unimetric/alignment.py @@ -7,13 +7,12 @@ from unimetric.metric import Metric -T = TypeVar('T') +T = TypeVar("T") class AlignmentConstraint(Enum): - """ - Alignment constraints for the alignment metric. - """ + """Alignment constraints for the alignment metric.""" + OneToOne = enum.auto() OneToMany = enum.auto() ManyToOne = enum.auto() diff --git a/src/unimetric/amr.py b/src/unimetric/amr.py index c4b9021..7cd3716 100644 --- a/src/unimetric/amr.py +++ b/src/unimetric/amr.py @@ -16,7 +16,7 @@ def __hash__(self): return hash((self.subj, self.pred, self.obj)) -@unimetric(normalizer='f1') +@unimetric(normalizer="f1") @dataclass class AMR: props: Collection[Prop] diff --git a/src/unimetric/decorator.py b/src/unimetric/decorator.py index 37538ea..34088fd 100644 --- a/src/unimetric/decorator.py +++ b/src/unimetric/decorator.py @@ -33,10 +33,19 @@ class HasLatentMetric(Protocol): def derive_metric(cls: object, constraint: AlignmentConstraint) -> Metric: - """ - Derive a unified metric from any type. - :param cls: The type to derive a metric from. - :return: A metric object for the given type. + """Derive a unified metric from any type. + + Parameters + ---------- + cls : object + The dataclass-like class to derive the metric from. + constraint : AlignmentConstraint + The alignment constraint to use. + + Returns + ------- + Metric + The derived metric. """ # if the type is annotated with a metric instance, use the metric annotation @@ -95,10 +104,19 @@ def unimetric( normalizer: NormalizerLiteral = "none", constraint: ConstraintLiteral = "<->", ) -> Callable[[T], T]: - """ - Derive a unified metric from a class. - :param normalizer: - :return: + """Decorator to derive a metric from a dataclass. + + Parameters + ---------- + normalizer : NormalizerLiteral + The normalizer to use, by default "none" + constraint : ConstraintLiteral + The alignment constraint to use, by default "<->" + + Returns + ------- + Callable[[T], T] + The decorated new class. """ def class_decorator(cls: T) -> T: @@ -123,15 +141,16 @@ def class_decorator(cls: T) -> T: }[normalizer](metric) if dataclass_has_variable(cls): + class LatentMetricWrapper(cls, HasLatentMetric): # type: ignore latent_metric = normalized_metric return LatentMetricWrapper else: + class MetricWrapper(cls, HasMetric): # type: ignore metric = normalized_metric return MetricWrapper - # return cls return class_decorator diff --git a/src/unimetric/ie.py b/src/unimetric/ie.py index 6f8a0bc..78a8202 100644 --- a/src/unimetric/ie.py +++ b/src/unimetric/ie.py @@ -50,13 +50,13 @@ class RelationSet: relations: List[Relation] -@unimetric(normalizer='f1') +@unimetric(normalizer="f1") @dataclass class Entity: mentions: List[Mention] -@unimetric(normalizer='f1') +@unimetric(normalizer="f1") @dataclass class EntitySet: entities: List[Entity] diff --git a/src/unimetric/metric.py b/src/unimetric/metric.py index 45033f6..2de3b4d 100644 --- a/src/unimetric/metric.py +++ b/src/unimetric/metric.py @@ -6,12 +6,11 @@ import numpy as np -T = TypeVar('T', contravariant=True) -U = TypeVar('U') +T = TypeVar("T", contravariant=True) +U = TypeVar("U") class Metric(Generic[T]): - @abstractmethod def score(self, x: T, y: T) -> float: raise NotImplementedError() @@ -25,10 +24,7 @@ def score_self(self, x: T) -> float: return self.score(x, x) def gram_matrix(self, xs: Collection[T], ys: Collection[T]) -> np.ndarray: - return np.array([ - [self.score(x, y) for y in ys] - for x in xs - ]) + return np.array([[self.score(x, y) for y in ys] for x in xs]) @staticmethod def from_function(f: Callable[[T, T], float]) -> "Metric[T]": @@ -133,11 +129,7 @@ def __init__(self, cls: object, field_metrics: Dict[str, Metric]): def score(self, x: T, y: T) -> float: return reduce( - mul, - ( - self.field_metrics[fld].score(getattr(x, fld), getattr(y, fld)) - for fld in self.field_metrics.keys() - ) + mul, (self.field_metrics[fld].score(getattr(x, fld), getattr(y, fld)) for fld in self.field_metrics.keys()) ) diff --git a/tests/unimetric/test_amr.py b/tests/unimetric/test_amr.py index ac3268a..594a729 100644 --- a/tests/unimetric/test_amr.py +++ b/tests/unimetric/test_amr.py @@ -12,22 +12,22 @@ def test_smatch(): amr1 = AMR( props=[ - Prop(Variable('a'), 'instance', 'want-01'), - Prop(Variable('b'), 'instance', 'boy'), - Prop(Variable('c'), 'instance', 'go-01'), - Prop(Variable('a'), 'ARG0', Variable('b')), - Prop(Variable('a'), 'ARG1', Variable('c')), - Prop(Variable('c'), 'ARG0', Variable('b')), + Prop(Variable("a"), "instance", "want-01"), + Prop(Variable("b"), "instance", "boy"), + Prop(Variable("c"), "instance", "go-01"), + Prop(Variable("a"), "ARG0", Variable("b")), + Prop(Variable("a"), "ARG1", Variable("c")), + Prop(Variable("c"), "ARG0", Variable("b")), ] ) amr2 = AMR( props=[ - Prop(Variable('x'), 'instance', 'want-01'), - Prop(Variable('y'), 'instance', 'boy'), - Prop(Variable('z'), 'instance', 'football'), - Prop(Variable('x'), 'ARG0', Variable('y')), - Prop(Variable('x'), 'ARG1', Variable('z')), + Prop(Variable("x"), "instance", "want-01"), + Prop(Variable("y"), "instance", "boy"), + Prop(Variable("z"), "instance", "football"), + Prop(Variable("x"), "ARG0", Variable("y")), + Prop(Variable("x"), "ARG1", Variable("z")), ] ) From 980a425486a3785e0430217f675a07bb0b388f4d Mon Sep 17 00:00:00 2001 From: wanmok Date: Sat, 16 Sep 2023 16:51:05 -0400 Subject: [PATCH 05/15] Configured `ruff` and reformatted codebaes to pass checks. --- pyproject.toml | 17 ++++++++- src/unimetric/__init__.py | 1 + src/unimetric/alignment.py | 30 ++++++++++++++++ src/unimetric/amr.py | 6 ++++ src/unimetric/decorator.py | 8 +++-- src/unimetric/ie.py | 58 ++++++++++++++++++++++++++++++- src/unimetric/latent_alignment.py | 13 +++++-- src/unimetric/metric.py | 56 +++++++++++++++++++++++++++-- tests/__init__.py | 1 + tests/unimetric/__init__.py | 1 + tests/unimetric/test_alignment.py | 2 ++ tests/unimetric/test_amr.py | 6 ++-- 12 files changed, 186 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 770f20c..140f11f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,21 @@ [tool.ruff] line-length = 120 +src = ["src", "tests"] + +select = [ + "E", # pycodestyle + "F", # pyflakes + "UP", # pyupgrade + "D", # pydocstyle +] +ignore = [ + "D105", # Missing docstring in magic method +] + +fixable = ["ALL"] + +[tool.ruff.pydocstyle] +convention = "numpy" [tool.black] line-length = 120 @@ -7,7 +23,6 @@ line-length = 120 [tool.pyright] include = ["src", "tests"] - [tool.pytest.ini_options] minversion = "6.0" testpaths = ["tests"] diff --git a/src/unimetric/__init__.py b/src/unimetric/__init__.py index e69de29..14dfe1d 100644 --- a/src/unimetric/__init__.py +++ b/src/unimetric/__init__.py @@ -0,0 +1 @@ +"""The core functionality of unimetric package.""" diff --git a/src/unimetric/alignment.py b/src/unimetric/alignment.py index 47a23f7..416d6d1 100644 --- a/src/unimetric/alignment.py +++ b/src/unimetric/alignment.py @@ -1,3 +1,4 @@ +"""Metric derivation with alignment constraints.""" import enum from enum import Enum from typing import Collection, TypeVar @@ -20,11 +21,25 @@ class AlignmentConstraint(Enum): class AlignmentMetric(Metric[Collection[T]]): + """A metric derived using some alignment constraints.""" + def __init__(self, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.OneToOne): self.inner = inner self.constraint = constraint def score(self, x: Collection[T], y: Collection[T]) -> float: + """Score two collections of objects. + + Parameters + ---------- + x : Collection[T] + y : Collection[T] + + Returns + ------- + float + The score of the two collections. + """ # TODO: alternative implementation when the inner metric is discrete return solve_alignment( self.inner.gram_matrix(x, y), @@ -32,6 +47,7 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: ) def score_self(self, x: Collection[T]) -> float: + """Score a collection of objects with itself.""" if self.constraint == AlignmentConstraint.ManyToMany: return self.inner.gram_matrix(x, x).sum() else: @@ -39,6 +55,20 @@ def score_self(self, x: Collection[T]) -> float: def solve_alignment(gram_matrix: np.ndarray, constraint: AlignmentConstraint) -> float: + """Solve the alignment problem. + + Parameters + ---------- + gram_matrix : np.ndarray + The gram matrix of the inner metric. + constraint : AlignmentConstraint + The alignment constraint. + + Returns + ------- + float + The score of the alignment. + """ if constraint == AlignmentConstraint.OneToOne: row_idx, col_idx = spo.linear_sum_assignment( cost_matrix=gram_matrix, diff --git a/src/unimetric/amr.py b/src/unimetric/amr.py index 7cd3716..d35d768 100644 --- a/src/unimetric/amr.py +++ b/src/unimetric/amr.py @@ -1,3 +1,4 @@ +"""Abstract Meaning Representation (AMR) structure.""" from dataclasses import dataclass from typing import Collection, Set, Union @@ -8,6 +9,8 @@ @unimetric() @dataclass class Prop: + """A property in an AMR.""" + subj: Variable pred: str obj: Union[Variable, str] @@ -19,9 +22,12 @@ def __hash__(self): @unimetric(normalizer="f1") @dataclass class AMR: + """Abstract Meaning Representation (AMR) structure.""" + props: Collection[Prop] def variables(self) -> Set[Variable]: + """Return the set of variables in the AMR.""" vars = set() for p in self.props: if isinstance(p.subj, Variable): diff --git a/src/unimetric/decorator.py b/src/unimetric/decorator.py index 34088fd..c4cc74e 100644 --- a/src/unimetric/decorator.py +++ b/src/unimetric/decorator.py @@ -1,3 +1,4 @@ +"""Decorator for deriving metrics from dataclasses.""" from dataclasses import fields, is_dataclass from typing import ( Literal, @@ -24,11 +25,15 @@ @runtime_checkable class HasMetric(Protocol): + """Protocol for classes that have a metric.""" + metric: Metric @runtime_checkable class HasLatentMetric(Protocol): + """Protocol for classes that have a latent metric.""" + latent_metric: Metric @@ -47,7 +52,6 @@ def derive_metric(cls: object, constraint: AlignmentConstraint) -> Metric: Metric The derived metric. """ - # if the type is annotated with a metric instance, use the metric annotation if get_origin(cls) is Annotated: metric = get_args(cls)[1] @@ -104,7 +108,7 @@ def unimetric( normalizer: NormalizerLiteral = "none", constraint: ConstraintLiteral = "<->", ) -> Callable[[T], T]: - """Decorator to derive a metric from a dataclass. + """Decorate a dataclass to have corresponding metric derived. Parameters ---------- diff --git a/src/unimetric/ie.py b/src/unimetric/ie.py index 78a8202..a7c13e5 100644 --- a/src/unimetric/ie.py +++ b/src/unimetric/ie.py @@ -1,3 +1,7 @@ +"""Data structures commonly used in information extraction. + +The data structures defined here can automatically derive commonly used metrics in IE. +""" from dataclasses import dataclass from typing import List @@ -7,6 +11,17 @@ @unimetric() @dataclass class Mention: + """A mention span commonly used for . + + The mention span here does not enforce whether it is right exclusive or not. + However, it is a convention that the mention span is right exclusive. + + Attributes + ---------- + left: The left index of the span. + right: The right index of the span. + """ + left: int right: int @@ -14,6 +29,16 @@ class Mention: @unimetric() @dataclass class Relation: + """A relation between two mentions commonly used in relation extraction. + + Attributes + ---------- + type: The type of the relation. + subj: The subject mention. + obj: The object mention. + + """ + type: str subj: Mention obj: Mention @@ -21,12 +46,27 @@ class Relation: @unimetric() class Trigger: + """A trigger mention commonly used in event extraction. + + Attributes + ---------- + type: The type of the trigger, commonly used to indicate event type. + """ + mention: Mention type: str @unimetric() class Argument: + """An argument mention commonly used in event extraction. + + Attributes + ---------- + mention: The mention of the argument. + role: The role of the argument. + """ + mention: Mention role: str @@ -34,29 +74,45 @@ class Argument: @unimetric() @dataclass class Event: - trig: Trigger + """An event commonly used in event extraction. + + Attributes + ---------- + trigger: The trigger of the event. + args: The arguments of the event. + """ + + trigger: Trigger args: List[Argument] @unimetric() @dataclass class EventSet: + """A set of events to present predicted or referenced events.""" + events: List[Event] @unimetric() @dataclass class RelationSet: + """A set of relations to present predicted or referenced relations.""" + relations: List[Relation] @unimetric(normalizer="f1") @dataclass class Entity: + """An entity comprises multiple mentions, commonly used in coreference resolution.""" + mentions: List[Mention] @unimetric(normalizer="f1") @dataclass class EntitySet: + """A set of entities to present predicted or referenced entities.""" + entities: List[Entity] diff --git a/src/unimetric/latent_alignment.py b/src/unimetric/latent_alignment.py index 3951e33..c802527 100644 --- a/src/unimetric/latent_alignment.py +++ b/src/unimetric/latent_alignment.py @@ -1,3 +1,4 @@ +"""Metric derivation with latent alignments.""" from dataclasses import dataclass, fields, is_dataclass from typing import ClassVar, Generic, Collection, Mapping, Tuple, TypeVar, Union, Set, Iterator, get_origin, get_args @@ -12,6 +13,8 @@ @dataclass class Variable: + """A variable in a latent.""" + name: str latent_metric: ClassVar[Metric["Variable"]] = Metric.from_function(lambda x, y: 1.0) @@ -21,6 +24,8 @@ def __hash__(self): class LatentAlignmentMetric(Metric[Collection[T]]): + """A metric derived to support aligning latent variables defined in structures.""" + def __init__(self, cls: type, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.OneToOne): if is_dataclass(cls): self.fields = fields(cls) @@ -30,6 +35,7 @@ def __init__(self, cls: type, inner: Metric[T], constraint: AlignmentConstraint self.constraint = constraint def score(self, x: Collection[T], y: Collection[T]) -> float: + """Score two collections of objects.""" x = list(x) y = list(y) x_vars = _all_variables(x) @@ -120,13 +126,12 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: return -result.fun def score_self(self, x: Collection[T]) -> float: + """Score a collection of objects with itself.""" return solve_alignment(self.inner.gram_matrix(x, x), self.constraint) class _PairIndexer(Generic[T], Mapping[Tuple[T, T], int]): - """ - Creates a mapping of a Cartesian product of objects to a single index. - """ + """Creates a mapping of a Cartesian product of objects to a single index.""" def __init__(self, x: Collection[T], y: Collection[T], offset: int = 0): self.x = x @@ -181,6 +186,7 @@ def _get_one_to_one_constraint_matrix(n_x: int, n_y: int) -> np.ndarray: # [X + def may_be_variable(cls: object) -> bool: + """Check if a type may be a `Variable`.""" if cls is Variable: return True if get_origin(cls) is not None and get_origin(cls) is Union: @@ -190,6 +196,7 @@ def may_be_variable(cls: object) -> bool: def dataclass_has_variable(cls: object) -> bool: + """Check if a dataclass has a field is in `Variable` type.""" if cls is Variable: return True if is_dataclass(cls): diff --git a/src/unimetric/metric.py b/src/unimetric/metric.py index 2de3b4d..396e934 100644 --- a/src/unimetric/metric.py +++ b/src/unimetric/metric.py @@ -1,3 +1,4 @@ +"""Metric interface and implementations for commonly used metrics.""" from abc import abstractmethod from dataclasses import is_dataclass from functools import reduce @@ -11,103 +12,146 @@ class Metric(Generic[T]): + """Metric interface.""" + @abstractmethod def score(self, x: T, y: T) -> float: + """Score two objects.""" raise NotImplementedError() def score_self(self, x: T) -> float: - """ - Scores an object against itself. + """Scores an object against itself. + In many cases there is a faster way to compute this than the general pair case. In such cases, please override this function. """ return self.score(x, x) def gram_matrix(self, xs: Collection[T], ys: Collection[T]) -> np.ndarray: + """Compute the gram matrix of the metric.""" return np.array([[self.score(x, y) for y in ys] for x in xs]) @staticmethod def from_function(f: Callable[[T, T], float]) -> "Metric[T]": + """Create a metric from a function. + + Parameters + ---------- + f : Callable[[T, T], float] + The function to create the metric from. + + + Returns + ------- + Metric[T] + The metric. + """ return MetricFromFunction(f) class MetricFromFunction(Metric[T]): + """A metric wrapped from a function.""" + def __init__(self, f: Callable[[T, T], float]): self.f = f def score(self, x: T, y: T) -> float: + """Score two objects.""" return self.f(x, y) class ContramappedMetric(Metric[T]): + """A metric contramapped by a function.""" + def __init__(self, inner: Metric[U], f: Callable[[T], U]): self.inner = inner self.f = f def score(self, x: T, y: T) -> float: + """Score two objects.""" return self.inner.score(self.f(x), self.f(y)) def score_self(self, x: T) -> float: + """Scores an object against itself.""" return self.inner.score_self(self.f(x)) class DiscreteMetric(Metric[T]): + """A metric for discrete objects.""" + def __init__(self, cls: Type[T]): if getattr(cls, "__eq__", None) is None: raise ValueError("Class must implement __eq__") def score(self, x: T, y: T) -> float: + """Score two objects.""" return float(x == y) def score_self(self, x: T) -> float: + """Scores an object against itself.""" return 1.0 class Jaccard(Metric[T]): + """Jaccard metric.""" + def __init__(self, inner: Metric[T]): self.inner = inner def score(self, x: T, y: T) -> float: + """Score two objects.""" sxy = self.inner.score(x, y) sxx = self.inner.score_self(x) syy = self.inner.score_self(y) return sxy / (sxx + syy - sxy) def score_self(self, x: T) -> float: + """Scores an object against itself.""" return 1.0 class Precision(Metric[T]): + """Precision metric.""" + def __init__(self, inner: Metric[T]): self.inner = inner def score(self, x: T, y: T) -> float: + """Score two objects.""" sxy = self.inner.score(x, y) sxx = self.inner.score_self(x) return sxy / sxx def score_self(self, x: T) -> float: + """Scores an object against itself.""" return 1.0 class Recall(Metric[T]): + """Recall metric.""" + def __init__(self, inner: Metric[T]): self.inner = inner def score(self, x: T, y: T) -> float: + """Score two objects.""" sxy = self.inner.score(x, y) syy = self.inner.score_self(y) return sxy / syy def score_self(self, x: T) -> float: + """Scores an object against itself.""" return 1.0 class FScore(Metric[T]): + """F-score metric.""" + def __init__(self, inner: Metric[T]): self.inner = inner def score(self, x: T, y: T) -> float: + """Score two objects.""" sxy = self.inner.score(x, y) sxx = self.inner.score_self(x) syy = self.inner.score_self(y) @@ -118,28 +162,35 @@ def score(self, x: T, y: T) -> float: return 2 * p * r / (p + r) def score_self(self, x: T) -> float: + """Scores an object against itself.""" return 1.0 class ProductMetric(Metric[T]): + """A metric that is the product of other metrics.""" + def __init__(self, cls: object, field_metrics: Dict[str, Metric]): if not is_dataclass(cls): raise ValueError(f"{cls} has to be a dataclass.") self.field_metrics = field_metrics def score(self, x: T, y: T) -> float: + """Score two objects.""" return reduce( mul, (self.field_metrics[fld].score(getattr(x, fld), getattr(y, fld)) for fld in self.field_metrics.keys()) ) class UnionMetric(Metric[T]): + """A metric that is the union of other metrics.""" + def __init__(self, cls: object, case_metrics: Dict[type, Metric]): if get_origin(cls) is not Union: raise ValueError(f"{cls} has to be a union.") self.case_metrics = case_metrics def score(self, x: T, y: T) -> float: + """Score two objects.""" x_type = type(x) y_type = type(y) if x_type != y_type: @@ -147,4 +198,5 @@ def score(self, x: T, y: T) -> float: return self.case_metrics[x_type].score(x, y) def score_self(self, x: T) -> float: + """Scores an object against itself.""" return 1.0 diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..eb7eff0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for the unimetric project.""" diff --git a/tests/unimetric/__init__.py b/tests/unimetric/__init__.py index e69de29..313c254 100644 --- a/tests/unimetric/__init__.py +++ b/tests/unimetric/__init__.py @@ -0,0 +1 @@ +"""Tests for the unimetric package.""" diff --git a/tests/unimetric/test_alignment.py b/tests/unimetric/test_alignment.py index 24533c4..244bf2a 100644 --- a/tests/unimetric/test_alignment.py +++ b/tests/unimetric/test_alignment.py @@ -1,8 +1,10 @@ +"""Tests for metrics derived with alignments.""" from unimetric.alignment import solve_alignment, AlignmentConstraint from unimetric.metric import DiscreteMetric def test_solve_alignment(): + """Test the alignment solver.""" a = [1, 2, 2] b = [1, 1, 1, 2] diff --git a/tests/unimetric/test_amr.py b/tests/unimetric/test_amr.py index 594a729..b74cf6f 100644 --- a/tests/unimetric/test_amr.py +++ b/tests/unimetric/test_amr.py @@ -1,3 +1,4 @@ +"""Tests for SMatch derived from the AMR structure.""" from pytest import approx from unimetric.amr import AMR, Prop @@ -6,10 +7,7 @@ def test_smatch(): - """ - Example from https://aclanthology.org/P13-2131.pdf. - """ - + """Example from https://aclanthology.org/P13-2131.pdf.""" amr1 = AMR( props=[ Prop(Variable("a"), "instance", "want-01"), From 35adc14f4e4cf80d69ea6910420732f3135e75cd Mon Sep 17 00:00:00 2001 From: wanmok Date: Sat, 16 Sep 2023 16:58:53 -0400 Subject: [PATCH 06/15] Added `ruff` to the dev dependencies. --- poetry.lock | 28 +++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 0a0b84b..f493e18 100644 --- a/poetry.lock +++ b/poetry.lock @@ -150,6 +150,32 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "ruff" +version = "0.0.290" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + { file = "ruff-0.0.290-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:0e2b09ac4213b11a3520221083866a5816616f3ae9da123037b8ab275066fbac" }, + { file = "ruff-0.0.290-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4ca6285aa77b3d966be32c9a3cd531655b3d4a0171e1f9bf26d66d0372186767" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35e3550d1d9f2157b0fcc77670f7bb59154f223bff281766e61bdd1dd854e0c5" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d748c8bd97874f5751aed73e8dde379ce32d16338123d07c18b25c9a2796574a" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:982af5ec67cecd099e2ef5e238650407fb40d56304910102d054c109f390bf3c" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:bbd37352cea4ee007c48a44c9bc45a21f7ba70a57edfe46842e346651e2b995a" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d9be6351b7889462912e0b8185a260c0219c35dfd920fb490c7f256f1d8313e" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75cdc7fe32dcf33b7cec306707552dda54632ac29402775b9e212a3c16aad5e6" }, + { file = "ruff-0.0.290-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb07f37f7aecdbbc91d759c0c09870ce0fb3eed4025eebedf9c4b98c69abd527" }, + { file = "ruff-0.0.290-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2ab41bc0ba359d3f715fc7b705bdeef19c0461351306b70a4e247f836b9350ed" }, + { file = "ruff-0.0.290-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:150bf8050214cea5b990945b66433bf9a5e0cef395c9bc0f50569e7de7540c86" }, + { file = "ruff-0.0.290-py3-none-musllinux_1_2_i686.whl", hash = "sha256:75386ebc15fe5467248c039f5bf6a0cfe7bfc619ffbb8cd62406cd8811815fca" }, + { file = "ruff-0.0.290-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ac93eadf07bc4ab4c48d8bb4e427bf0f58f3a9c578862eb85d99d704669f5da0" }, + { file = "ruff-0.0.290-py3-none-win32.whl", hash = "sha256:461fbd1fb9ca806d4e3d5c745a30e185f7cf3ca77293cdc17abb2f2a990ad3f7" }, + { file = "ruff-0.0.290-py3-none-win_amd64.whl", hash = "sha256:f1f49f5ec967fd5778813780b12a5650ab0ebcb9ddcca28d642c689b36920796" }, + { file = "ruff-0.0.290-py3-none-win_arm64.whl", hash = "sha256:ae5a92dfbdf1f0c689433c223f8dac0782c2b2584bd502dfdbc76475669f1ba1" }, + { file = "ruff-0.0.290.tar.gz", hash = "sha256:949fecbc5467bb11b8db810a7fa53c7e02633856ee6bd1302b2f43adcd71b88d" }, +] + [[package]] name = "scipy" version = "1.11.2" @@ -222,4 +248,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "dac26de6ca105acd6c73153c222d74af1526dd74b7d4e5d79c14b2c047d8da96" +content-hash = "63b22b07ccd3415332ad7433c3e384eb28ec462c428b8d57205dd44b6c35836c" diff --git a/pyproject.toml b/pyproject.toml index 140f11f..6f98e8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ optional = true [tool.poetry.group.dev.dependencies] pytest = "^7.4.2" pyright = "^1.1.327" +ruff = "^0.0.290" [build-system] requires = ["poetry-core"] From 551b08a6c978bd72796ff783f49226b81973a7d4 Mon Sep 17 00:00:00 2001 From: WanMok <16273544+wanmok@users.noreply.github.com> Date: Sat, 16 Sep 2023 17:12:25 -0400 Subject: [PATCH 07/15] Added GitHub action to run linter and tests. --- .github/workflows/python-app.yml | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/python-app.yml diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..01008e2 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,38 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.9 + uses: actions/setup-python@v3 + with: + python-version: "3.9" + - name: Setup Poetry + uses: Gr1N/setup-poetry@v8 + with: + poetry-version: 1.6.1 + - name: Install dependencies + run: | + poetry install --with dev + - uses: chartboost/ruff-action@v1 + with: + version: 0.0.290 + - name: Test with pytest + run: | + pytest From 08fc3cd6266bc56f2f9efe55beeeb7ca08b87c4c Mon Sep 17 00:00:00 2001 From: WanMok <16273544+wanmok@users.noreply.github.com> Date: Sat, 16 Sep 2023 17:16:20 -0400 Subject: [PATCH 08/15] Update python-app.yml --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 01008e2..1b0180c 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -35,4 +35,4 @@ jobs: version: 0.0.290 - name: Test with pytest run: | - pytest + poetry run pytest From 811ef174e044e704bf21b6c903da0a74dbfbe1ee Mon Sep 17 00:00:00 2001 From: wanmok Date: Sat, 16 Sep 2023 18:07:22 -0400 Subject: [PATCH 09/15] Modified packages info. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6f98e8c..7e8b0a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ version = "0.1.0" description = "A Unified View of Evaluation Metrics for Information Extraction" authors = ["Tongfei Chen ", "Yunmo Chen "] readme = "README.md" -packages = [{ include = "src/unimetric" }] +packages = [{ include = "unimetric", from = "src" }] [tool.poetry.dependencies] python = ">=3.9,<3.13" From 284f576e963a28a62fa7b4b419a03b231c9bdfa8 Mon Sep 17 00:00:00 2001 From: WanMok <16273544+wanmok@users.noreply.github.com> Date: Sat, 16 Sep 2023 18:16:16 -0400 Subject: [PATCH 10/15] Added `pyright` actions --- .github/workflows/python-app.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 1b0180c..0364f29 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -19,20 +19,22 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python 3.9 - uses: actions/setup-python@v3 - with: - python-version: "3.9" - name: Setup Poetry uses: Gr1N/setup-poetry@v8 with: poetry-version: 1.6.1 + - name: Set up Python 3.9 + uses: actions/setup-python@v3 + with: + python-version: "3.9" + cache: "poetry" - name: Install dependencies run: | poetry install --with dev - uses: chartboost/ruff-action@v1 with: version: 0.0.290 + - uses: jakebailey/pyright-action@v1 - name: Test with pytest run: | poetry run pytest From 52fba8b1be57d7fafbc47ee960ab1675b34bd6c4 Mon Sep 17 00:00:00 2001 From: WanMok <16273544+wanmok@users.noreply.github.com> Date: Sat, 16 Sep 2023 18:23:09 -0400 Subject: [PATCH 11/15] Update python-app.yml --- .github/workflows/python-app.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 0364f29..ed4d5ec 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -31,10 +31,17 @@ jobs: - name: Install dependencies run: | poetry install --with dev - - uses: chartboost/ruff-action@v1 + - name: Ensure Poetry envs + run: | + echo "$(poetry env info --path)/bin" >> $GITHUB_PATH + - name: Run ruff + uses: chartboost/ruff-action@v1 with: version: 0.0.290 - - uses: jakebailey/pyright-action@v1 + - name: Run pyright + uses: jakebailey/pyright-action@v1 + with: + version: "1.1.327" - name: Test with pytest run: | poetry run pytest From f326364332e3adb2011ddf8c357fae43f740d892 Mon Sep 17 00:00:00 2001 From: wanmok Date: Sat, 16 Sep 2023 18:32:26 -0400 Subject: [PATCH 12/15] Changed back to `setattr` so that the original class name can be kept. --- src/unimetric/decorator.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/unimetric/decorator.py b/src/unimetric/decorator.py index c4cc74e..e41be45 100644 --- a/src/unimetric/decorator.py +++ b/src/unimetric/decorator.py @@ -145,16 +145,9 @@ def class_decorator(cls: T) -> T: }[normalizer](metric) if dataclass_has_variable(cls): - - class LatentMetricWrapper(cls, HasLatentMetric): # type: ignore - latent_metric = normalized_metric - - return LatentMetricWrapper + setattr(cls, "latent_metric", normalized_metric) # type: ignore else: - - class MetricWrapper(cls, HasMetric): # type: ignore - metric = normalized_metric - - return MetricWrapper + setattr(cls, "metric", normalized_metric) # type: ignore + return cls return class_decorator From 8a271f07c5bafe13cda7f44db7d315c371103183 Mon Sep 17 00:00:00 2001 From: wanmok Date: Sat, 16 Sep 2023 20:27:08 -0400 Subject: [PATCH 13/15] Updated dependencies. --- poetry.lock | 63 ++++++++++++++++++++++++++++---------------------- pyproject.toml | 1 - 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/poetry.lock b/poetry.lock index f493e18..32c5f5b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -52,36 +52,43 @@ setuptools = "*" [[package]] name = "numpy" -version = "1.25.2" +version = "1.26.0" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = "<3.13,>=3.9" files = [ - {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, - {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, - {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, - {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, - {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, - {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, - {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, - {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, - {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, - {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, - {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, - {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, - {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, - {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, - {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, - {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, - {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, - {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, + { file = "numpy-1.26.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd" }, + { file = "numpy-1.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292" }, + { file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68" }, + { file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be" }, + { file = "numpy-1.26.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:436c8e9a4bdeeee84e3e59614d38c3dbd3235838a877af8c211cfcac8a80b8d3" }, + { file = "numpy-1.26.0-cp310-cp310-win32.whl", hash = "sha256:c2e698cb0c6dda9372ea98a0344245ee65bdc1c9dd939cceed6bb91256837896" }, + { file = "numpy-1.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91" }, + { file = "numpy-1.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a" }, + { file = "numpy-1.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd" }, + { file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208" }, + { file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c" }, + { file = "numpy-1.26.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:546b7dd7e22f3c6861463bebb000646fa730e55df5ee4a0224408b5694cc6148" }, + { file = "numpy-1.26.0-cp311-cp311-win32.whl", hash = "sha256:c0b45c8b65b79337dee5134d038346d30e109e9e2e9d43464a2970e5c0e93229" }, + { file = "numpy-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99" }, + { file = "numpy-1.26.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388" }, + { file = "numpy-1.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581" }, + { file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb" }, + { file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505" }, + { file = "numpy-1.26.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4acc65dd65da28060e206c8f27a573455ed724e6179941edb19f97e58161bb69" }, + { file = "numpy-1.26.0-cp312-cp312-win32.whl", hash = "sha256:bb0d9a1aaf5f1cb7967320e80690a1d7ff69f1d47ebc5a9bea013e3a21faec95" }, + { file = "numpy-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112" }, + { file = "numpy-1.26.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2" }, + { file = "numpy-1.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8" }, + { file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f" }, + { file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c" }, + { file = "numpy-1.26.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b44e6a09afc12952a7d2a58ca0a2429ee0d49a4f89d83a0a11052da696440e49" }, + { file = "numpy-1.26.0-cp39-cp39-win32.whl", hash = "sha256:5671338034b820c8d58c81ad1dafc0ed5a00771a82fccc71d6438df00302094b" }, + { file = "numpy-1.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2" }, + { file = "numpy-1.26.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0792824ce2f7ea0c82ed2e4fecc29bb86bee0567a080dacaf2e0a01fe7654369" }, + { file = "numpy-1.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d484292eaeb3e84a51432a94f53578689ffdea3f90e10c8b203a99be5af57d8" }, + { file = "numpy-1.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:186ba67fad3c60dbe8a3abff3b67a91351100f2661c8e2a80364ae6279720299" }, + { file = "numpy-1.26.0.tar.gz", hash = "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf" }, ] [[package]] @@ -248,4 +255,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "63b22b07ccd3415332ad7433c3e384eb28ec462c428b8d57205dd44b6c35836c" +content-hash = "74ff927924aa7ab530d1ac05f1c0612ebbf90add0eb02affdd427f334db2e8f1" diff --git a/pyproject.toml b/pyproject.toml index 7e8b0a2..aeeb78d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ packages = [{ include = "unimetric", from = "src" }] python = ">=3.9,<3.13" scipy = "^1.11.2" numpy = "^1.25.2" -pytest = "^7.4.2" [tool.poetry.group.dev] optional = true From 48fc6f2685bc839c42d8b9c892701c8b904dff04 Mon Sep 17 00:00:00 2001 From: wanmok Date: Sun, 17 Sep 2023 03:51:08 -0400 Subject: [PATCH 14/15] Addressed comments. --- poetry.lock | 2 +- pyproject.toml | 1 + src/unimetric/amr.py | 37 ---------------- src/unimetric/core/__init__.py | 0 src/unimetric/{ => core}/alignment.py | 22 +++++----- src/unimetric/{ => core}/decorator.py | 27 ++++++------ src/unimetric/{ => core}/latent_alignment.py | 44 ++++++++++++-------- src/unimetric/{ => core}/metric.py | 0 src/unimetric/metrics/__init__.py | 0 src/unimetric/metrics/amr.py | 24 +++++++++++ src/unimetric/{ => metrics}/ie.py | 2 +- tests/unimetric/test_alignment.py | 12 +++--- tests/unimetric/test_amr.py | 6 +-- 13 files changed, 88 insertions(+), 89 deletions(-) delete mode 100644 src/unimetric/amr.py create mode 100644 src/unimetric/core/__init__.py rename src/unimetric/{ => core}/alignment.py (80%) rename src/unimetric/{ => core}/decorator.py (83%) rename src/unimetric/{ => core}/latent_alignment.py (88%) rename src/unimetric/{ => core}/metric.py (100%) create mode 100644 src/unimetric/metrics/__init__.py create mode 100644 src/unimetric/metrics/amr.py rename src/unimetric/{ => metrics}/ie.py (97%) diff --git a/poetry.lock b/poetry.lock index 32c5f5b..31cdb9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -255,4 +255,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "74ff927924aa7ab530d1ac05f1c0612ebbf90add0eb02affdd427f334db2e8f1" +content-hash = "63b22b07ccd3415332ad7433c3e384eb28ec462c428b8d57205dd44b6c35836c" diff --git a/pyproject.toml b/pyproject.toml index aeeb78d..7e8b0a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ packages = [{ include = "unimetric", from = "src" }] python = ">=3.9,<3.13" scipy = "^1.11.2" numpy = "^1.25.2" +pytest = "^7.4.2" [tool.poetry.group.dev] optional = true diff --git a/src/unimetric/amr.py b/src/unimetric/amr.py deleted file mode 100644 index d35d768..0000000 --- a/src/unimetric/amr.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Abstract Meaning Representation (AMR) structure.""" -from dataclasses import dataclass -from typing import Collection, Set, Union - -from unimetric.decorator import unimetric -from unimetric.latent_alignment import Variable - - -@unimetric() -@dataclass -class Prop: - """A property in an AMR.""" - - subj: Variable - pred: str - obj: Union[Variable, str] - - def __hash__(self): - return hash((self.subj, self.pred, self.obj)) - - -@unimetric(normalizer="f1") -@dataclass -class AMR: - """Abstract Meaning Representation (AMR) structure.""" - - props: Collection[Prop] - - def variables(self) -> Set[Variable]: - """Return the set of variables in the AMR.""" - vars = set() - for p in self.props: - if isinstance(p.subj, Variable): - vars.add(p.subj) - if isinstance(p.obj, Variable): - vars.add(p.obj) - return vars diff --git a/src/unimetric/core/__init__.py b/src/unimetric/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/unimetric/alignment.py b/src/unimetric/core/alignment.py similarity index 80% rename from src/unimetric/alignment.py rename to src/unimetric/core/alignment.py index 416d6d1..c6b2819 100644 --- a/src/unimetric/alignment.py +++ b/src/unimetric/core/alignment.py @@ -6,7 +6,7 @@ import numpy as np import scipy.optimize as spo -from unimetric.metric import Metric +from unimetric.core.metric import Metric T = TypeVar("T") @@ -14,16 +14,16 @@ class AlignmentConstraint(Enum): """Alignment constraints for the alignment metric.""" - OneToOne = enum.auto() - OneToMany = enum.auto() - ManyToOne = enum.auto() - ManyToMany = enum.auto() + ONE_TO_ONE = enum.auto() + ONE_TO_MANY = enum.auto() + MANY_TO_ONE = enum.auto() + MANY_TO_MANY = enum.auto() class AlignmentMetric(Metric[Collection[T]]): """A metric derived using some alignment constraints.""" - def __init__(self, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.OneToOne): + def __init__(self, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.ONE_TO_ONE): self.inner = inner self.constraint = constraint @@ -48,7 +48,7 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: def score_self(self, x: Collection[T]) -> float: """Score a collection of objects with itself.""" - if self.constraint == AlignmentConstraint.ManyToMany: + if self.constraint == AlignmentConstraint.MANY_TO_MANY: return self.inner.gram_matrix(x, x).sum() else: return sum(self.inner.score_self(u) for u in x) @@ -69,15 +69,15 @@ def solve_alignment(gram_matrix: np.ndarray, constraint: AlignmentConstraint) -> float The score of the alignment. """ - if constraint == AlignmentConstraint.OneToOne: + if constraint == AlignmentConstraint.ONE_TO_ONE: row_idx, col_idx = spo.linear_sum_assignment( cost_matrix=gram_matrix, maximize=True, ) return gram_matrix[row_idx, col_idx].sum() - if constraint == AlignmentConstraint.OneToMany: + if constraint == AlignmentConstraint.ONE_TO_MANY: return gram_matrix.max(axis=0).sum() - if constraint == AlignmentConstraint.ManyToOne: + if constraint == AlignmentConstraint.MANY_TO_ONE: return gram_matrix.max(axis=1).sum() - if constraint == AlignmentConstraint.ManyToMany: + if constraint == AlignmentConstraint.MANY_TO_MANY: return gram_matrix.sum() diff --git a/src/unimetric/decorator.py b/src/unimetric/core/decorator.py similarity index 83% rename from src/unimetric/decorator.py rename to src/unimetric/core/decorator.py index e41be45..545a0dc 100644 --- a/src/unimetric/decorator.py +++ b/src/unimetric/core/decorator.py @@ -11,11 +11,12 @@ runtime_checkable, Callable, TypeVar, + Any, ) -from unimetric.alignment import AlignmentConstraint, AlignmentMetric -from unimetric.latent_alignment import dataclass_has_variable, LatentAlignmentMetric -from unimetric.metric import Metric, ProductMetric, DiscreteMetric, FScore, Jaccard, Precision, Recall, UnionMetric +from unimetric.core.alignment import AlignmentConstraint, AlignmentMetric +from unimetric.core.latent_alignment import dataclass_has_variable, LatentAlignmentMetric +from unimetric.core.metric import Metric, ProductMetric, DiscreteMetric, FScore, Jaccard, Precision, Recall, UnionMetric T = TypeVar("T", covariant=True) @@ -37,12 +38,12 @@ class HasLatentMetric(Protocol): latent_metric: Metric -def derive_metric(cls: object, constraint: AlignmentConstraint) -> Metric: +def derive_metric(cls: Any, constraint: AlignmentConstraint) -> Metric: """Derive a unified metric from any type. Parameters ---------- - cls : object + cls : Any The dataclass-like class to derive the metric from. constraint : AlignmentConstraint The alignment constraint to use. @@ -125,14 +126,14 @@ def unimetric( def class_decorator(cls: T) -> T: alignment_constraint = { - "<->": AlignmentConstraint.OneToOne, - "<-": AlignmentConstraint.OneToMany, - "->": AlignmentConstraint.ManyToOne, - "~": AlignmentConstraint.ManyToMany, - "1:1": AlignmentConstraint.OneToOne, - "1:*": AlignmentConstraint.OneToMany, - "*:1": AlignmentConstraint.ManyToOne, - "*:*": AlignmentConstraint.ManyToMany, + "<->": AlignmentConstraint.ONE_TO_ONE, + "<-": AlignmentConstraint.ONE_TO_MANY, + "->": AlignmentConstraint.MANY_TO_ONE, + "~": AlignmentConstraint.MANY_TO_MANY, + "1:1": AlignmentConstraint.ONE_TO_ONE, + "1:*": AlignmentConstraint.ONE_TO_MANY, + "*:1": AlignmentConstraint.MANY_TO_ONE, + "*:*": AlignmentConstraint.MANY_TO_MANY, }[constraint] metric = derive_metric(cls, constraint=alignment_constraint) normalized_metric = { diff --git a/src/unimetric/latent_alignment.py b/src/unimetric/core/latent_alignment.py similarity index 88% rename from src/unimetric/latent_alignment.py rename to src/unimetric/core/latent_alignment.py index c802527..ff095b1 100644 --- a/src/unimetric/latent_alignment.py +++ b/src/unimetric/core/latent_alignment.py @@ -1,32 +1,42 @@ """Metric derivation with latent alignments.""" from dataclasses import dataclass, fields, is_dataclass -from typing import ClassVar, Generic, Collection, Mapping, Tuple, TypeVar, Union, Set, Iterator, get_origin, get_args +from typing import ( + ClassVar, + Generic, + Collection, + Mapping, + Tuple, + TypeVar, + Union, + Set, + Iterator, + get_origin, + get_args, + Any, +) import numpy as np import scipy.optimize as spo -from unimetric.alignment import AlignmentConstraint, solve_alignment -from unimetric.metric import Metric +from unimetric.core.alignment import AlignmentConstraint, solve_alignment +from unimetric.core.metric import Metric T = TypeVar("T") -@dataclass +@dataclass(eq=True, frozen=True) class Variable: - """A variable in a latent.""" + """A variable in latent alignments.""" name: str latent_metric: ClassVar[Metric["Variable"]] = Metric.from_function(lambda x, y: 1.0) - def __hash__(self): - return hash(self.name) - class LatentAlignmentMetric(Metric[Collection[T]]): """A metric derived to support aligning latent variables defined in structures.""" - def __init__(self, cls: type, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.OneToOne): + def __init__(self, cls: type, inner: Metric[T], constraint: AlignmentConstraint = AlignmentConstraint.ONE_TO_ONE): if is_dataclass(cls): self.fields = fields(cls) else: @@ -69,10 +79,10 @@ def score(self, x: Collection[T], y: Collection[T]) -> float: # each item may be mapped to some other items given the constraint item_constraint_matrix_ctor = { - AlignmentConstraint.OneToOne: _get_one_to_one_constraint_matrix, - AlignmentConstraint.OneToMany: _get_one_to_many_constraint_matrix, - AlignmentConstraint.ManyToOne: _get_many_to_one_constraint_matrix, - AlignmentConstraint.ManyToMany: lambda _0, _1: None, + AlignmentConstraint.ONE_TO_ONE: _get_one_to_one_constraint_matrix, + AlignmentConstraint.ONE_TO_MANY: _get_one_to_many_constraint_matrix, + AlignmentConstraint.MANY_TO_ONE: _get_many_to_one_constraint_matrix, + AlignmentConstraint.MANY_TO_MANY: lambda _0, _1: None, }[self.constraint] item_constraint_matrix = item_constraint_matrix_ctor(n_x, n_y) if item_constraint_matrix is not None: @@ -151,8 +161,8 @@ def __iter__(self) -> Iterator[Tuple[T, T]]: return ((x, y) for x in self.x for y in self.y) -def _all_variables(x: object) -> Set[Variable]: - def _all_variables_iterator(obj: object) -> Iterator[Variable]: +def _all_variables(x: Any) -> Set[Variable]: + def _all_variables_iterator(obj: Any) -> Iterator[Variable]: if isinstance(obj, Variable): yield obj elif isinstance(obj, Collection) and not isinstance(obj, str): @@ -185,7 +195,7 @@ def _get_one_to_one_constraint_matrix(n_x: int, n_y: int) -> np.ndarray: # [X + return np.concatenate([mask_x, mask_y], axis=0) -def may_be_variable(cls: object) -> bool: +def may_be_variable(cls: Any) -> bool: """Check if a type may be a `Variable`.""" if cls is Variable: return True @@ -195,7 +205,7 @@ def may_be_variable(cls: object) -> bool: return False -def dataclass_has_variable(cls: object) -> bool: +def dataclass_has_variable(cls: Any) -> bool: """Check if a dataclass has a field is in `Variable` type.""" if cls is Variable: return True diff --git a/src/unimetric/metric.py b/src/unimetric/core/metric.py similarity index 100% rename from src/unimetric/metric.py rename to src/unimetric/core/metric.py diff --git a/src/unimetric/metrics/__init__.py b/src/unimetric/metrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/unimetric/metrics/amr.py b/src/unimetric/metrics/amr.py new file mode 100644 index 0000000..fff302f --- /dev/null +++ b/src/unimetric/metrics/amr.py @@ -0,0 +1,24 @@ +"""Abstract Meaning Representation (AMR) structure.""" +from dataclasses import dataclass +from typing import Collection, Union + +from unimetric.core.decorator import unimetric +from unimetric.core.latent_alignment import Variable + + +@unimetric() +@dataclass(eq=True, frozen=True) +class Prop: + """A Proposition in an AMR.""" + + subj: Variable + pred: str + obj: Union[Variable, str] + + +@unimetric(normalizer="f1") +@dataclass +class AMR: + """Abstract Meaning Representation (AMR) structure.""" + + props: Collection[Prop] diff --git a/src/unimetric/ie.py b/src/unimetric/metrics/ie.py similarity index 97% rename from src/unimetric/ie.py rename to src/unimetric/metrics/ie.py index a7c13e5..407fee4 100644 --- a/src/unimetric/ie.py +++ b/src/unimetric/metrics/ie.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import List -from unimetric.decorator import unimetric +from unimetric.core.decorator import unimetric @unimetric() diff --git a/tests/unimetric/test_alignment.py b/tests/unimetric/test_alignment.py index 244bf2a..b08ccfa 100644 --- a/tests/unimetric/test_alignment.py +++ b/tests/unimetric/test_alignment.py @@ -1,6 +1,6 @@ """Tests for metrics derived with alignments.""" -from unimetric.alignment import solve_alignment, AlignmentConstraint -from unimetric.metric import DiscreteMetric +from unimetric.core.alignment import solve_alignment, AlignmentConstraint +from unimetric.core.metric import DiscreteMetric def test_solve_alignment(): @@ -10,7 +10,7 @@ def test_solve_alignment(): g = DiscreteMetric(int).gram_matrix(a, b) - assert solve_alignment(g, AlignmentConstraint.OneToOne) == 2 - assert solve_alignment(g, AlignmentConstraint.ManyToOne) == 3 - assert solve_alignment(g, AlignmentConstraint.OneToMany) == 4 - assert solve_alignment(g, AlignmentConstraint.ManyToMany) == 5 + assert solve_alignment(g, AlignmentConstraint.ONE_TO_ONE) == 2 + assert solve_alignment(g, AlignmentConstraint.MANY_TO_ONE) == 3 + assert solve_alignment(g, AlignmentConstraint.ONE_TO_MANY) == 4 + assert solve_alignment(g, AlignmentConstraint.MANY_TO_MANY) == 5 diff --git a/tests/unimetric/test_amr.py b/tests/unimetric/test_amr.py index b74cf6f..9e1e021 100644 --- a/tests/unimetric/test_amr.py +++ b/tests/unimetric/test_amr.py @@ -1,9 +1,9 @@ """Tests for SMatch derived from the AMR structure.""" from pytest import approx -from unimetric.amr import AMR, Prop -from unimetric.decorator import HasMetric -from unimetric.latent_alignment import Variable +from unimetric.core.decorator import HasMetric +from unimetric.core.latent_alignment import Variable +from unimetric.metrics.amr import AMR, Prop def test_smatch(): From 0bed9790860bfff19d29107d929a6c9c8f2499e8 Mon Sep 17 00:00:00 2001 From: wanmok Date: Sun, 17 Sep 2023 03:56:15 -0400 Subject: [PATCH 15/15] Fixed module docstring. --- src/unimetric/core/__init__.py | 1 + src/unimetric/metrics/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/unimetric/core/__init__.py b/src/unimetric/core/__init__.py index e69de29..636a954 100644 --- a/src/unimetric/core/__init__.py +++ b/src/unimetric/core/__init__.py @@ -0,0 +1 @@ +"""Core functionality for the unimetric package that provides the automatic metric derivation.""" diff --git a/src/unimetric/metrics/__init__.py b/src/unimetric/metrics/__init__.py index e69de29..41c6818 100644 --- a/src/unimetric/metrics/__init__.py +++ b/src/unimetric/metrics/__init__.py @@ -0,0 +1 @@ +"""Metric implementations for specific tasks."""