diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 86047982fca..c835a92ef77 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -3557,7 +3557,55 @@ "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { - "rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_3548db28": { + "pip_311_qdldl": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "qdldl~=0.1.7", + "repo": "pip_311", + "repo_prefix": "pip_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_311_cvxopt": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "cvxopt~=1.3.2", + "repo": "pip_311", + "repo_prefix": "pip_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_311_pytz": { "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", "ruleClassName": "whl_library", "attributes": { @@ -3791,7 +3839,31 @@ ] } }, - "rules_python_publish_deps_311_cryptography_cp37_abi3_musllinux_1_1_aarch64_5daeb18e": { + "pip_311_osqp": { + "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", + "ruleClassName": "whl_library", + "attributes": { + "requirement": "osqp~=0.6.3", + "repo": "pip_311", + "repo_prefix": "pip_311_", + "whl_patches": {}, + "experimental_target_platforms": [], + "python_interpreter": "", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "quiet": true, + "timeout": 600, + "isolated": true, + "extra_pip_args": [], + "download_only": false, + "pip_data_exclude": [], + "enable_implicit_namespace_pkgs": false, + "environment": {}, + "envsubst": [], + "group_name": "", + "group_deps": [] + } + }, + "pip_311_python_dateutil": { "bzlFile": "@@rules_python~//python/pip_install:pip_repository.bzl", "ruleClassName": "whl_library", "attributes": { @@ -5574,35 +5646,66 @@ "attributes": { "repo_name": "rules_python_publish_deps", "whl_map": { - "six": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"six-1.16.0-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_six_py2_none_any_8abb2f1d\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"six-1.16.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_six_sdist_1e61c374\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "cffi": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"repo\":\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_aarch64_3548db28\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"repo\":\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_ppc64le_91fc98ad\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_cffi_cp311_cp311_manylinux_2_17_x86_64_94411f22\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_cffi_cp311_cp311_musllinux_1_1_x86_64_cc4d65ae\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cffi-1.15.1.tar.gz\",\"repo\":\"rules_python_publish_deps_311_cffi_sdist_d400bfb9\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "idna": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"idna-3.4-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_idna_py3_none_any_90b77e79\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"idna-3.4.tar.gz\",\"repo\":\"rules_python_publish_deps_311_idna_sdist_814f528e\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "rich": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"rich-13.2.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_rich_py3_none_any_7c963f0d\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"rich-13.2.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_rich_sdist_f1a00cdd\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "zipp": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"zipp-3.11.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_zipp_py3_none_any_83a28fcb\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"zipp-3.11.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_zipp_sdist_a7a22e05\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "mdurl": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"mdurl-0.1.2-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_mdurl_py3_none_any_84008a41\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"mdurl-0.1.2.tar.gz\",\"repo\":\"rules_python_publish_deps_311_mdurl_sdist_bb413d29\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "twine": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"twine-4.0.2-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_twine_py3_none_any_929bc3c2\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"twine-4.0.2.tar.gz\",\"repo\":\"rules_python_publish_deps_311_twine_sdist_9e102ef5\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "bleach": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"bleach-6.0.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_bleach_py3_none_any_33c16e33\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"bleach-6.0.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_bleach_sdist_1a1a85c1\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "certifi": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"certifi-2022.12.7-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_certifi_py3_none_any_4ad3232f\",\"target_platforms\":[\"linux_aarch64\",\"linux_arm\",\"linux_ppc\",\"linux_s390x\",\"linux_x86_64\"],\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"certifi-2022.12.7.tar.gz\",\"repo\":\"rules_python_publish_deps_311_certifi_sdist_35824b4c\",\"target_platforms\":[\"linux_aarch64\",\"linux_arm\",\"linux_ppc\",\"linux_s390x\",\"linux_x86_64\"],\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"certifi-2023.7.22-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_certifi_py3_none_any_92d60375\",\"target_platforms\":[\"osx_aarch64\",\"osx_x86_64\",\"windows_x86_64\"],\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"certifi-2023.7.22.tar.gz\",\"repo\":\"rules_python_publish_deps_311_certifi_sdist_539cc1d1\",\"target_platforms\":[\"osx_aarch64\",\"osx_x86_64\",\"windows_x86_64\"],\"version\":\"3.11\"}]", - "jeepney": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"jeepney-0.8.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_jeepney_py3_none_any_c0a454ad\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"jeepney-0.8.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_jeepney_sdist_5efe48d2\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "keyring": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"keyring-23.13.1-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_keyring_py3_none_any_771ed2a9\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"keyring-23.13.1.tar.gz\",\"repo\":\"rules_python_publish_deps_311_keyring_sdist_ba2e15a9\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "pkginfo": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"pkginfo-1.9.6-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_pkginfo_py3_none_any_4b7a555a\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"pkginfo-1.9.6.tar.gz\",\"repo\":\"rules_python_publish_deps_311_pkginfo_sdist_8fd5896e\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "rfc3986": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"rfc3986-2.0.0-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_rfc3986_py2_none_any_50b1502b\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"rfc3986-2.0.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_rfc3986_sdist_97aacf9d\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "urllib3": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"urllib3-1.26.14-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_urllib3_py2_none_any_75edcdc2\",\"target_platforms\":[\"linux_aarch64\",\"linux_arm\",\"linux_ppc\",\"linux_s390x\",\"linux_x86_64\"],\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"urllib3-1.26.14.tar.gz\",\"repo\":\"rules_python_publish_deps_311_urllib3_sdist_076907bf\",\"target_platforms\":[\"linux_aarch64\",\"linux_arm\",\"linux_ppc\",\"linux_s390x\",\"linux_x86_64\"],\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"urllib3-1.26.18-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_urllib3_py2_none_any_34b97092\",\"target_platforms\":[\"osx_aarch64\",\"osx_x86_64\",\"windows_x86_64\"],\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"urllib3-1.26.18.tar.gz\",\"repo\":\"rules_python_publish_deps_311_urllib3_sdist_f8ecc1bb\",\"target_platforms\":[\"osx_aarch64\",\"osx_x86_64\",\"windows_x86_64\"],\"version\":\"3.11\"}]", - "docutils": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"docutils-0.19-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_docutils_py3_none_any_5e1de4d8\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"docutils-0.19.tar.gz\",\"repo\":\"rules_python_publish_deps_311_docutils_sdist_33995a67\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "pygments": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"Pygments-2.14.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_pygments_py3_none_any_fa7bd7bd\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"Pygments-2.14.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_pygments_sdist_b3ed06a9\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "requests": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"requests-2.28.2-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_requests_py3_none_any_64299f49\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"requests-2.28.2.tar.gz\",\"repo\":\"rules_python_publish_deps_311_requests_sdist_98b1b278\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "pycparser": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"pycparser-2.21-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_pycparser_py2_none_any_8ee45429\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"pycparser-2.21.tar.gz\",\"repo\":\"rules_python_publish_deps_311_pycparser_sdist_e644fdec\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "cryptography": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_cryptography_cp37_abi3_musllinux_1_1_x86_64_068bc551\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl\",\"repo\":\"rules_python_publish_deps_311_cryptography_cp37_abi3_musllinux_1_1_aarch64_5daeb18e\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"repo\":\"rules_python_publish_deps_311_cryptography_cp37_abi3_manylinux_2_17_aarch64_afda76d8\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_cryptography_cp37_abi3_manylinux_2_28_x86_64_b648fe2a\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_cryptography_cp37_abi3_manylinux_2_17_x86_64_da46e2b5\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl\",\"repo\":\"rules_python_publish_deps_311_cryptography_cp37_abi3_manylinux_2_28_aarch64_ff369dd1\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"cryptography-41.0.6.tar.gz\",\"repo\":\"rules_python_publish_deps_311_cryptography_sdist_422e3e31\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "webencodings": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"webencodings-0.5.1-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_webencodings_py2_none_any_a0af1213\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"webencodings-0.5.1.tar.gz\",\"repo\":\"rules_python_publish_deps_311_webencodings_sdist_b36a1c24\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "secretstorage": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"SecretStorage-3.3.3-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_secretstorage_py3_none_any_f356e662\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"SecretStorage-3.3.3.tar.gz\",\"repo\":\"rules_python_publish_deps_311_secretstorage_sdist_2403533e\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "jaraco_classes": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"jaraco.classes-3.2.3-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_jaraco_classes_py3_none_any_2353de32\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"jaraco.classes-3.2.3.tar.gz\",\"repo\":\"rules_python_publish_deps_311_jaraco_classes_sdist_89559fa5\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "markdown_it_py": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"markdown_it_py-2.1.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_markdown_it_py_py3_none_any_93de681e\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"markdown-it-py-2.1.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_markdown_it_py_sdist_cf7e59fe\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "more_itertools": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"more_itertools-9.0.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_more_itertools_py3_none_any_250e83d7\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"more-itertools-9.0.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_more_itertools_sdist_5a6257e4\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "readme_renderer": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"readme_renderer-37.3-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_readme_renderer_py3_none_any_f67a16ca\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"readme_renderer-37.3.tar.gz\",\"repo\":\"rules_python_publish_deps_311_readme_renderer_sdist_cd653186\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "requests_toolbelt": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"requests_toolbelt-0.10.1-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_requests_toolbelt_py2_none_any_18565aa5\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"requests-toolbelt-0.10.1.tar.gz\",\"repo\":\"rules_python_publish_deps_311_requests_toolbelt_sdist_62e09f7f\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "charset_normalizer": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_universal2_0298eaff\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_ppc64le_0c0a5902\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_aarch64_14e76c0f\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_1_s390x_4a8fcf28\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_1_ppc64le_5995f016\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_1_aarch64_72966d1b\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_musllinux_1_1_x86_64_761e8904\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_x86_64_79909e27\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_py3_none_any_7e189e2e\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_11_0_arm64_87701167\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_manylinux_2_17_s390x_8c7fe7af\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_win_amd64_9ab77acb\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_cp311_cp311_macosx_10_9_x86_64_a8d0fc94\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"charset-normalizer-3.0.1.tar.gz\",\"repo\":\"rules_python_publish_deps_311_charset_normalizer_sdist_ebea339a\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "importlib_metadata": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"importlib_metadata-6.0.0-py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_importlib_metadata_py3_none_any_7efb448e\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"importlib_metadata-6.0.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_importlib_metadata_sdist_e354bede\",\"target_platforms\":null,\"version\":\"3.11\"}]", - "pywin32_ctypes": "[{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"pywin32_ctypes-0.2.0-py2.py3-none-any.whl\",\"repo\":\"rules_python_publish_deps_311_pywin32_ctypes_py2_none_any_9dc2d991\",\"target_platforms\":null,\"version\":\"3.11\"},{\"config_setting\":\"//_config:is_python_3.11\",\"filename\":\"pywin32-ctypes-0.2.0.tar.gz\",\"repo\":\"rules_python_publish_deps_311_pywin32_ctypes_sdist_24ffc3b3\",\"target_platforms\":null,\"version\":\"3.11\"}]" + "black": [ + "3.11" + ], + "cvxopt": [ + "3.11" + ], + "dateutil": [ + "3.11" + ], + "et_xmlfile": [ + "3.11" + ], + "googleapis_common_protos": [ + "3.11" + ], + "highs": [ + "3.11" + ], + "highspy": [ + "3.11" + ], + "lxml": [ + "3.11" + ], + "numpy": [ + "3.11" + ], + "openpyxl": [ + "3.11" + ], + "osqp": [ + "3.11" + ], + "pandas": [ + "3.11" + ], + "pytest": [ + "3.11" + ], + "python_dateutil": [ + "3.11" + ], + "pytz": [ + "3.11" + ], + "qdldl": [ + "3.11" + ], + "qpsolvers": [ + "3.11" + ], + "scipy": [ + "3.11" + ], + "six": [ + "3.11" + ], + "tzdata": [ + "3.11" + ] }, "default_version": "3.11", "groups": {} diff --git a/requirements.txt b/requirements.txt index 714b1c45ae2..47013a86f6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,10 @@ numpy~=2.1.0 scipy~=1.14.1 +cvxopt~=1.3.2 highs~=1.7.2 highspy~=1.7.2 +qdldl~=0.1.7 +osqp~=0.6.3 qpsolvers~=4.3.3 tzdata~=2024.1 six~=1.16.0 diff --git a/src/main/proto/wfa/measurement/reporting/postprocessing/v2alpha/report_summary.proto b/src/main/proto/wfa/measurement/reporting/postprocessing/v2alpha/report_summary.proto index 2bd5bdee3bb..a0a456e243e 100644 --- a/src/main/proto/wfa/measurement/reporting/postprocessing/v2alpha/report_summary.proto +++ b/src/main/proto/wfa/measurement/reporting/postprocessing/v2alpha/report_summary.proto @@ -30,7 +30,7 @@ message MeasurementDetail { repeated string right_hand_side_targets = 7; message MeasurementResult { - int64 reach = 1; + double reach = 1; double standard_deviation = 2; string metric = 3; } diff --git a/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/BUILD.bazel b/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/BUILD.bazel index 6a5f5d28310..fa16781dd6c 100644 --- a/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/BUILD.bazel +++ b/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/BUILD.bazel @@ -7,8 +7,10 @@ py_library( imports = ["../"], visibility = ["//visibility:public"], deps = [ + requirement("cvxopt"), requirement("highspy"), requirement("numpy"), + requirement("osqp"), requirement("qpsolvers"), ], ) diff --git a/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/solver.py b/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/solver.py index 2a199f46cd4..76d37a1d66e 100644 --- a/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/solver.py +++ b/src/main/python/wfa/measurement/reporting/postprocessing/noiseninja/solver.py @@ -16,10 +16,11 @@ from noiseninja.noised_measurements import SetMeasurementsSpec from qpsolvers import solve_problem, Problem, Solution +from scipy.sparse import csc_matrix from threading import Semaphore -from typing import Any -SOLVER = "highs" +HIGHS_SOLVER = "highs" +OSQP_SOLVER = "osqp" MAX_ATTEMPTS = 10 SEMAPHORE = Semaphore() @@ -76,7 +77,7 @@ def _add_measurement_targets(self, set_measurement_spec: SetMeasurementsSpec, self._add_eq_term(variables, measurement.value) else: self._add_loss_term( - np.multiply(variables, 1 / measurement.sigma), + np.multiply(variables, 1.0 / measurement.sigma), -measurement.value / measurement.sigma) def _map_sets_to_variables(set_measurement_spec: SetMeasurementsSpec) -> dict[ @@ -149,20 +150,16 @@ def _add_gt_term(self, variables: np.array): self.G.append(variables) self.h.append([0]) - def _solve(self): - x0 = np.random.randn(self.num_variables) - return self._solve_with_initial_value(x0) - - def _solve_with_initial_value(self, x0) -> Solution: + def _solve_with_initial_value(self, solver_name, x0) -> Solution: problem = self._problem() - solution = solve_problem(problem, solver=SOLVER, verbose=False) + solution = solve_problem(problem, solver=solver_name, initvals=x0, verbose=False) return solution def _problem(self): problem: Problem if len(self.A) > 0: problem = Problem( - self.P, self.q, np.array(self.G), np.array(self.h), + csc_matrix(self.P), self.q, csc_matrix(np.array(self.G)), np.array(self.h), np.array(self.A), np.array(self.b)) else: problem = Problem( @@ -181,7 +178,7 @@ def solve(self) -> Solution: # TODO: check if qpsolvers is thread safe, # and remove this semaphore. SEMAPHORE.acquire() - solution = self._solve() + solution = self._solve_with_initial_value(HIGHS_SOLVER, self.base_value) SEMAPHORE.release() if solution.found: @@ -189,6 +186,21 @@ def solve(self) -> Solution: else: attempt_count += 1 + # If the highs solver does not converge, switch to the osqp solver which + # is more robust. + if not solution.found: + attempt_count = 0 + while attempt_count < MAX_ATTEMPTS: + SEMAPHORE.acquire() + solution = self._solve_with_initial_value(OSQP_SOLVER, self.base_value) + SEMAPHORE.release() + + if solution.found: + break + else: + attempt_count += 1 + + # Raise the exception when both solvers do not converge. if not solution.found: raise SolutionNotFoundError(solution) diff --git a/src/main/python/wfa/measurement/reporting/postprocessing/report/report.py b/src/main/python/wfa/measurement/reporting/postprocessing/report/report.py index 33a8e2c387c..6942bb361c9 100644 --- a/src/main/python/wfa/measurement/reporting/postprocessing/report/report.py +++ b/src/main/python/wfa/measurement/reporting/postprocessing/report/report.py @@ -118,6 +118,96 @@ def get_cover_relationships(edp_combinations: list[FrozenSet[str]]) -> list[ return cover_relationships +def get_subset_relationships(edp_combinations: list[FrozenSet[str]]): + """Returns a list of tuples where first element in the tuple is the parent + and second element is the subset.""" + subset_relationships = [] + + for comb1, comb2 in combinations(edp_combinations, 2): + if comb1.issubset(comb2): + subset_relationships.append((comb2, comb1)) + elif comb2.issubset(comb1): + subset_relationships.append((comb1, comb2)) + return subset_relationships + + +def is_cover(target_set, possible_cover): + """Checks if a collection of sets covers a target set. + + Args: + target_set: The set that should be covered. + possible_cover: A collection of sets that may cover the target set. + + Returns: + True if the union of the sets in `possible_cover` equals `target_set`, + False otherwise. + """ + union_of_possible_cover = reduce( + lambda x, y: x.union(y), possible_cover + ) + if union_of_possible_cover == target_set: + return True + else: + return False + + +def get_covers(target_set, other_sets): + """Finds all combinations of sets from `other_sets` that cover `target_set`. + + This function identifies all possible combinations of sets within `other_sets` + whose union equals the `target_set`. It only considers sets that are subsets of + the `target_set`. + + Args: + target_set: The set that needs to be covered. + other_sets: A collection of sets that may be used to cover the `target_set`. + + Returns: + A list of tuples, where each tuple represents a covering relationship. + The first element of the tuple is the `target_set`, and the second element + is a tuple containing the sets from `other_sets` that cover it. + """ + def generate_all_length_combinations(data): + """Generates all possible combinations of elements from a list. + + Args: + data: The list of elements. + + Returns: + A list of tuples, where each tuple represents a combination of elements. + """ + return [ + comb for r in range(1, len(data) + 1) for comb in + combinations(data, r) + ] + + cover_relationship = [] + all_subsets_of_possible_covered = [other_set for other_set in other_sets + if + other_set.issubset(target_set)] + possible_covers = generate_all_length_combinations( + all_subsets_of_possible_covered) + for possible_cover in possible_covers: + if is_cover(target_set, possible_cover): + cover_relationship.append((target_set, possible_cover)) + return cover_relationship + + +def get_cover_relationships(edp_combinations: list[FrozenSet[str]]): + """Returns covers as defined here: # https://en.wikipedia.org/wiki/Cover_(topology). + For each set (s_i) in the list, enumerate combinations of all sets excluding this one. + For each of these considered combinations, take their union and check if it is equal to + s_i. If so, this combination is a cover of s_i. + """ + cover_relationships = [] + for i in range(len(edp_combinations)): + possible_covered = edp_combinations[i] + other_sets = edp_combinations[:i] + edp_combinations[i + 1:] + cover_relationship = get_covers(possible_covered, other_sets) + cover_relationships.extend(cover_relationship) + return cover_relationships + + class MetricReport: """Represents a metric sub-report view (e.g., MRC, AMI) within a report. @@ -310,7 +400,6 @@ def __init__( measurement_index += 1 self._num_vars = measurement_index - def get_metric_report(self, metric: str) -> "MetricReport": return self._metric_reports[metric] diff --git a/src/main/python/wfa/measurement/reporting/postprocessing/tools/post_process_origin_report.py b/src/main/python/wfa/measurement/reporting/postprocessing/tools/post_process_origin_report.py index 8c45fe04eb4..b474bba4f54 100644 --- a/src/main/python/wfa/measurement/reporting/postprocessing/tools/post_process_origin_report.py +++ b/src/main/python/wfa/measurement/reporting/postprocessing/tools/post_process_origin_report.py @@ -30,7 +30,6 @@ ami = "ami" mrc = "mrc" - # TODO(@ple13): Extend the class to support custom measurements and composite # set operations such as incremental. class ReportSummaryProcessor: diff --git a/src/test/python/wfa/measurement/reporting/postprocessing/report/test_report.py b/src/test/python/wfa/measurement/reporting/postprocessing/report/test_report.py index 0f3dcc8c1de..611a3d3bbb5 100644 --- a/src/test/python/wfa/measurement/reporting/postprocessing/report/test_report.py +++ b/src/test/python/wfa/measurement/reporting/postprocessing/report/test_report.py @@ -1179,61 +1179,6 @@ def test_correct_report_with_whole_campaign_has_more_edp_combinations(self): self._assertReportsAlmostEqual(expected, corrected, corrected.to_array()) - def test_allows_incorrect_time_series(self): - ami = "ami" - report = Report( - metric_reports={ - ami: MetricReport( - reach_time_series={ - frozenset({EDP_TWO}): [ - Measurement(0.00, 1, "measurement_01"), - Measurement(3.30, 1, "measurement_02"), - Measurement(4.00, 1, "measurement_03"), - ], - frozenset({EDP_ONE}): [ - Measurement(0.00, 1, "measurement_04"), - Measurement(3.30, 1, "measurement_05"), - Measurement(1.00, 1, "measurement_06"), - ], - }, - reach_whole_campaign={}, - ) - }, - metric_subsets_by_parent={}, - cumulative_inconsistency_allowed_edp_combinations=set( - frozenset({EDP_ONE})), - ) - - # The corrected report should be consistent: all the time series reaches are - # monotonic increasing, e.g. reach[edp1][i] <= reach[edp1][i+1], except for - # the one in the exception list, e.g. edp1. - corrected = report.get_corrected_report() - - expected = Report( - metric_reports={ - ami: MetricReport( - reach_time_series={ - frozenset({EDP_TWO}): [ - Measurement(0.00, 1, "measurement_01"), - Measurement(3.30, 1, "measurement_02"), - Measurement(4.00, 1, "measurement_03"), - ], - frozenset({EDP_ONE}): [ - Measurement(0.00, 1, "measurement_04"), - Measurement(3.30, 1, "measurement_05"), - Measurement(1.00, 1, "measurement_06"), - ], - }, - reach_whole_campaign={}, - ) - }, - metric_subsets_by_parent={}, - cumulative_inconsistency_allowed_edp_combinations=set( - frozenset({EDP_ONE})), - ) - - self._assertReportsAlmostEqual(expected, corrected, corrected.to_array()) - def test_can_correct_related_metrics(self): ami = "ami" mrc = "mrc" diff --git a/src/test/python/wfa/measurement/reporting/postprocessing/tools/BUILD.bazel b/src/test/python/wfa/measurement/reporting/postprocessing/tools/BUILD.bazel index f46a06fcaee..b0a43158483 100644 --- a/src/test/python/wfa/measurement/reporting/postprocessing/tools/BUILD.bazel +++ b/src/test/python/wfa/measurement/reporting/postprocessing/tools/BUILD.bazel @@ -1,5 +1,8 @@ +load("@pip//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_library") load("@rules_python//python:defs.bzl", "py_test") + py_test( name = "test_post_process_origin_report", srcs = ["test_post_process_origin_report.py"], @@ -10,6 +13,18 @@ py_test( ], ) +py_test( + name = "test_error_analysis", + srcs = ["test_error_analysis.py"], + data = [":sample_reports"], + deps = [ + "//src/main/proto/wfa/measurement/reporting/postprocessing/v2alpha:report_summary_py_pb2", + "//src/main/python/wfa/measurement/reporting/postprocessing/tools:post_process_origin_report", + requirement("openpyxl"), + ], +) + + filegroup( name = "sample_reports", srcs = glob(["*.json"]), diff --git a/src/test/python/wfa/measurement/reporting/postprocessing/tools/sample_report.json b/src/test/python/wfa/measurement/reporting/postprocessing/tools/sample_report.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_error_analysis.py b/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_error_analysis.py new file mode 100644 index 00000000000..215b6b6c313 --- /dev/null +++ b/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_error_analysis.py @@ -0,0 +1,634 @@ +# Copyright 2024 The Cross-Media Measurement Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import random +import openpyxl +import statistics +import sys +import unittest +import os + +from google.protobuf.json_format import Parse +from src.main.proto.wfa.measurement.reporting.postprocessing.v2alpha import \ + report_summary_pb2 + +from tools.post_process_origin_report import ReportSummaryProcessor + + +class MetricReport: + reach_time_series: dict[str, list[int]] + reach_whole_campaign: dict[str, int] + + def __init__(self, reach_time_series: dict[str, list[int]], + reach_whole_campaign: dict[str, int]): + self.reach_time_series = reach_time_series + self.reach_whole_campaign = reach_whole_campaign + + +CUMULATIVE_EDP = { + "edp1": frozenset({"edp1"}), + "edp2": frozenset({"edp2"}), + "edp3": frozenset({"edp3"}), + "edp4": frozenset({"edp4"}), + "union": frozenset({"edp1", "edp2", "edp3", "edp4"}), +} + +EDP_MAP = { + "edp1": frozenset({"edp1"}), + "edp2": frozenset({"edp2"}), + "edp3": frozenset({"edp3"}), + "edp4": frozenset({"edp4"}), + "edp12": frozenset({"edp1", "edp2"}), + "edp13": frozenset({"edp1", "edp3"}), + "edp14": frozenset({"edp1", "edp4"}), + "edp23": frozenset({"edp2", "edp3"}), + "edp24": frozenset({"edp2", "edp4"}), + "edp34": frozenset({"edp3", "edp4"}), + "edp123": frozenset({"edp1", "edp2", "edp3"}), + "edp124": frozenset({"edp1", "edp2", "edp4"}), + "edp134": frozenset({"edp1", "edp3", "edp4"}), + "edp234": frozenset({"edp2", "edp3", "edp4"}), + "union": frozenset({"edp1", "edp2", "edp3", "edp4"}), +} + +probabilities = {"edp1": 0.015, "edp2": 0.02, "edp3": 0.2, "edp4": 0.25} +scales = {"edp1": 0.9, "edp2": 0.9, "edp3": 0.5, "edp4": 0.5} + +mrc_to_ami_rate = 0.9 + +MEASUREMENT_POLICIES = ["ami", "mrc"] +POPULATION_SIZE = 55000000 +CUMULATIVE_LENGTH = 18 +ITERATIONS = 50 + +# DP params: +# a) Direct measurement: (eps, delta) = (0.00643, 1e-15) --> sigma = 1051 +# b) MPC measurement: (eps, delta) = (0.01265, 1e-15) --> sigma = 542 +SIGMAS = { + "edp1": 1051.0, + "edp2": 1051.0, + "edp3": 1051.0, + "edp4": 1051.0, + "union": 542.0, +} + + +def count_equal_values(noisy_measurement_map, corrected_measurement_map): + count = 0 + for key in noisy_measurement_map: + if abs(noisy_measurement_map[key] - corrected_measurement_map[ + key]) <= DIFF_THRESHOLD: + count += 1 + return count + + +def get_statistics(array, variance_list, bias_list): + variance_list.append(np.var(array)) + bias_list.append(np.mean(array)) + + +TOLERANCE = 1 +DIFF_THRESHOLD = 10 + + +def generate_union_measurements(reach_time_series, reach_whole_campaign): + for i in range(CUMULATIVE_LENGTH): + reach_time_series[EDP_MAP["union"]].append( + int((1.0 - ( + 1 - reach_time_series[EDP_MAP["edp1"]][i] / POPULATION_SIZE) * ( + 1 - + reach_time_series[ + EDP_MAP[ + "edp2"]][ + i] / POPULATION_SIZE) * ( + 1 - + reach_time_series[ + EDP_MAP[ + "edp3"]][ + i] / POPULATION_SIZE) * ( + 1 - + reach_time_series[ + EDP_MAP[ + "edp4"]][ + i] / POPULATION_SIZE)) * POPULATION_SIZE) + ) + + reach_whole_campaign[EDP_MAP["edp12"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - reach_whole_campaign[ + EDP_MAP[ + "edp2"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp13"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - reach_whole_campaign[ + EDP_MAP[ + "edp3"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp14"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp23"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp2"]] / POPULATION_SIZE) * ( + 1 - reach_whole_campaign[ + EDP_MAP[ + "edp3"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp24"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp2"]] / POPULATION_SIZE) * ( + 1 - reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp34"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp3"]] / POPULATION_SIZE) * ( + 1 - reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp123"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp2"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp3"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp124"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp2"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp134"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp3"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["edp234"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp2"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp3"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + reach_whole_campaign[EDP_MAP["union"]] = \ + int((1.0 - ( + 1 - reach_whole_campaign[EDP_MAP["edp1"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp2"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp3"]] / POPULATION_SIZE) * ( + 1 - + reach_whole_campaign[ + EDP_MAP[ + "edp4"]] / POPULATION_SIZE)) * POPULATION_SIZE) + + +def generate_test_report(probabilities: dict[str, float]): + metric_reports = {"ami": {}, "mrc": {}} + + # Generate AMI report. + reach_time_series = {} + reach_whole_campaign = {} + for key in probabilities.keys(): + reach_time_series[EDP_MAP[key]] = [] + reach_whole_campaign[EDP_MAP[key]] = 0 + probability = probabilities[key] + scale = scales[key] + current_sum = 0 + for i in range(CUMULATIVE_LENGTH): + if i == 0: + reach_time_series[EDP_MAP[key]].append(1) + elif i == 1: + reach_time_series[EDP_MAP[key]].append(random.randint(1, 500)) + else: + reach_time_series[EDP_MAP[key]].append( + int(probability * (POPULATION_SIZE - current_sum))) + probability = scale * probability + if i > 0: + reach_time_series[EDP_MAP[key]][i] += reach_time_series[EDP_MAP[key]][ + i - 1] + current_sum = reach_time_series[EDP_MAP[key]][i] + reach_whole_campaign[EDP_MAP[key]] = \ + int(probability * (POPULATION_SIZE - current_sum)) + \ + reach_time_series[EDP_MAP[key]][CUMULATIVE_LENGTH - 1] + + reach_time_series[EDP_MAP["union"]] = [] + reach_whole_campaign[EDP_MAP["union"]] = 0 + + generate_union_measurements(reach_time_series, reach_whole_campaign) + metric_reports["ami"] = MetricReport(reach_time_series, reach_whole_campaign) + + # Generate MRC report + reach_time_series = {} + reach_whole_campaign = {} + for key in probabilities.keys(): + reach_time_series[EDP_MAP[key]] = [] + reach_whole_campaign[EDP_MAP[key]] = 0 + for i in range(CUMULATIVE_LENGTH): + reach_time_series[EDP_MAP[key]].append( + max(int((0.9 + 0.09 * i / CUMULATIVE_LENGTH) * + metric_reports["ami"].reach_time_series[EDP_MAP[key]][i]), 1)) + reach_whole_campaign[EDP_MAP[key]] = int(0.999 * + metric_reports[ + "ami"].reach_whole_campaign[ + EDP_MAP[key]]) + reach_time_series[EDP_MAP["union"]] = [] + reach_whole_campaign[EDP_MAP["union"]] = 0 + generate_union_measurements(reach_time_series, reach_whole_campaign) + metric_reports["mrc"] = MetricReport(reach_time_series, reach_whole_campaign) + + # for key, value in metric_reports["mrc"].reach_time_series.items(): + # print(f"{key}: {value}") + # + # for key, value in metric_reports["mrc"].reach_whole_campaign.items(): + # print(f"{key}: {value}") + + return metric_reports + + +def get_report_summary_from_data(metric_reports): + report_summary = report_summary_pb2.ReportSummary() + # Generates report summary from the measurements. For each edp combination, + # all measurements except the last one are cumulative measurements, and the + # last one is the whole campaign measurement. + noisy_measurement_map = {} + true_measurement_map = {} + + # Processes cumulative measurements. + for policy in MEASUREMENT_POLICIES: + for edp in CUMULATIVE_EDP: + measurement_detail = report_summary.measurement_details.add() + measurement_detail.measurement_policy = policy + measurement_detail.set_operation = "cumulative" + measurement_detail.is_cumulative = True + measurement_detail.data_providers.extend(EDP_MAP[edp]) + + for i in range(CUMULATIVE_LENGTH): + result = measurement_detail.measurement_results.add() + result.standard_deviation = SIGMAS[edp] + result.metric = policy + "_cumulative_" + edp + "_" + str(i).zfill(2) + + result.reach = max( + metric_reports[policy].reach_time_series[EDP_MAP[edp]][i] + int( + np.random.normal(0, SIGMAS[edp], 1)[0]), 0) + true_measurement_map[result.metric] = \ + metric_reports[policy].reach_time_series[EDP_MAP[edp]][i] + noisy_measurement_map[result.metric] = result.reach + + # Processes total campaign measurements. + for policy in MEASUREMENT_POLICIES: + for edp in EDP_MAP: + measurement_detail = report_summary.measurement_details.add() + measurement_detail.measurement_policy = policy + measurement_detail.set_operation = "union" + measurement_detail.is_cumulative = False + measurement_detail.data_providers.extend(EDP_MAP[edp]) + result = measurement_detail.measurement_results.add() + result.standard_deviation = SIGMAS["union"] + result.metric = "total_" + policy + "_" + edp + result.reach = max( + metric_reports[policy].reach_whole_campaign[EDP_MAP[edp]] + int( + np.random.normal(0, SIGMAS["union"], 1)[0]), 0) + true_measurement_map[result.metric] = \ + metric_reports[policy].reach_whole_campaign[EDP_MAP[edp]] + noisy_measurement_map[result.metric] = result.reach + + return report_summary, true_measurement_map, noisy_measurement_map + + +def calculate_means_and_variances(true_measurements, noisy_measurements, + corrected_measurements): + # Number of measurements. + num_measurements = len(true_measurements) + noisy_diff = {} + corrected_diff = {} + + for i in range(num_measurements): + noisy_diff[i] = [] + corrected_diff[i] = [] + + for i in noisy_measurements: + for k in range(num_measurements): + noisy_diff[k].append(noisy_measurements[i][k] - true_measurements[k]) + corrected_diff[k].append( + corrected_measurements[i][k] - true_measurements[k]) + + noisy_means = [] + noisy_variances = [] + corrected_means = [] + corrected_variances = [] + + for k in range(num_measurements): + noisy_means.append(statistics.mean(noisy_diff[k])) + noisy_variances.append(statistics.variance(noisy_diff[k])) + corrected_means.append(statistics.mean(corrected_diff[k])) + corrected_variances.append(statistics.variance(corrected_diff[k])) + + print(f"noisy_means: {noisy_means}") + print(f"noisy_variances: {noisy_variances}") + print(f"corrected_means: {corrected_means}") + print(f"corrected_variances: {corrected_variances}") + for num in [x / y for x, y in zip(noisy_variances, corrected_variances)]: + if num < 0.8: + print(f"{num:.2f}") + + +def get_measurement_name_and_ground_truth(): + metric_reports = generate_test_report(probabilities) + true_report_summary, true_measurement_map, noisy_measurement_map = \ + get_report_summary_from_data(metric_reports) + + print("Measurement name:") + for key, value in sorted(true_measurement_map.items()): + print(f"{key} \t {value}") + + sorted_measurement_names = [ + key for key, value in sorted(true_measurement_map.items()) + ] + sorted_true_measurements = [ + value for key, value in sorted(true_measurement_map.items()) + ] + return sorted_measurement_names, sorted_true_measurements + + +def get_test_results(): + noisy_results = {} + corrected_results = {} + + for i in range(0, ITERATIONS): + print(f"iteration {i}") + metric_reports = generate_test_report(probabilities) + report_summary, true_measurement_map, noisy_measurement_map = \ + get_report_summary_from_data(metric_reports) + + corrected_measurement_map = ReportSummaryProcessor( + report_summary).process() + + sorted_noisy_measurements = [ + value for key, value in sorted(noisy_measurement_map.items()) + ] + + sorted_corrected_measurements = [ + value for key, value in sorted(corrected_measurement_map.items()) + ] + noisy_results[i] = sorted_noisy_measurements + corrected_results[i] = sorted_corrected_measurements + + return noisy_results, corrected_results + + +def compute_statistic(true_values, measurements): + # Convert the measurements to an array of size ITERATIONS x NUM_VARIABLES. + # Each row of the array is the result of a test run. + measurements_array = np.array(list(measurements.values())) + + # Calculate the mean of the measurements. + mean_measurements = np.mean(measurements_array, axis=0) + + # Calculate the relative bias. + relative_bias = (mean_measurements - true_values) / np.array(true_values) + + # Calculate the relative variance for each true value + relative_variances = np.var(measurements_array, axis=0) / np.array( + true_values) + + # Calculate relative error for each measurement + relative_errors = np.abs( + measurements_array - np.array(true_values)) / np.array(true_values) + + # Calculate the % of measurements with relative error > 2% for each measurement. + percent_high_rel_error = (np.sum(relative_errors > 0.02, axis=0) / float( + len(measurements))) * 100 + + avg_rel_error = np.mean(relative_errors) + median_rel_error = np.median(relative_errors) + worst_rel_error = np.max(relative_errors) + quantile_75_rel_error = np.percentile(relative_errors, 75) + + avg_rel_var = np.mean(relative_variances) + median_rel_var = np.median(relative_variances) + worst_rel_var = np.max(relative_variances) + quantile_75_rel_var = np.percentile(relative_variances, 75) + + print("Relative bias:") + print_array(relative_bias) + + print("Relative var:") + print_array(relative_variances) + + print("High error percentage:") + print_array(percent_high_rel_error) + + print(f"Average relative error\t{avg_rel_error}") + print(f"Median relative error\t{median_rel_error}") + print(f"Worst relative error\t{worst_rel_error}") + print(f"Quantile 75 relative error\t{quantile_75_rel_error}") + + print(f"Average relative var\t{avg_rel_var}") + print(f"Median relative var\t{median_rel_var}") + print(f"Worst relative var\t{worst_rel_var}") + print(f"Quantile 75 relative var\t{quantile_75_rel_var}") + + return relative_bias, relative_variances, percent_high_rel_error + + +def get_measurement_name_to_index(measurement_names): + measurement_name_to_index = {} + for i, string in enumerate(measurement_names): + measurement_name_to_index[string] = i + return measurement_name_to_index + + +def compute_unique_reach_statistic(true_values, measurements, parent_index, + child_index): + measurements_array = np.array(list(measurements.values())) + # Calculate the true difference between measurements at indices parent_index and child_index + true_diff = true_values[parent_index] - true_values[child_index] + + # Calculate the difference between measurements at indices i and j + estimated_diff = measurements_array[:, parent_index] - measurements_array[:, + child_index] + + # Calculate the mean of the measurements. + mean_measurements = np.mean(estimated_diff, axis=0) + + # Calculate the relative bias. + relative_bias = (mean_measurements - true_diff) / true_diff + + # Calculate the relative variance for each true value + relative_variances = np.var(estimated_diff, axis=0) / true_diff + + # Calculate relative error for each measurement + relative_errors = np.abs(estimated_diff - true_diff) / true_diff + + # Calculate the % of measurements with relative error > 2% for each measurement. + percent_high_rel_error = np.sum(relative_errors > 0.02, axis=0) / len( + estimated_diff) * 100.0 + + print("Relative bias:") + print(relative_bias) + + print("Relative variance:") + print(relative_variances) + + print("High error percentage:") + print(percent_high_rel_error) + + return relative_bias, relative_variances, percent_high_rel_error + + +def print_array(arr): + for val in arr: + print(val) + + +class TestOriginReport(unittest.TestCase): + def test_variance(self): + sorted_measurement_names, sorted_true_measurements = \ + get_measurement_name_and_ground_truth() + noisy_measurements, corrected_measurements = get_test_results() + + print("Noisy measurement statistics:") + compute_statistic(sorted_true_measurements, noisy_measurements) + + print("Corrected measurement statistics:") + compute_statistic(sorted_true_measurements, corrected_measurements) + + measurement_name_to_index = get_measurement_name_to_index( + sorted_measurement_names) + + print("unique reach edp2 \ edp1 statistics:") + union_edp1_edp2_index = measurement_name_to_index["total_ami_edp12"] + union_edp1_index = measurement_name_to_index["total_ami_edp1"] + + print("Noisy measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + noisy_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("Corrected measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + corrected_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("unique reach edp3 \ edp1 statistics:") + union_edp1_edp2_index = measurement_name_to_index["total_ami_edp13"] + union_edp1_index = measurement_name_to_index["total_ami_edp1"] + + print("Noisy measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + noisy_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("Corrected measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + corrected_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("unique reach edp1 \ edp3 statistics:") + union_edp1_edp2_index = measurement_name_to_index["total_ami_edp13"] + union_edp1_index = measurement_name_to_index["total_ami_edp3"] + + print("Noisy measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + noisy_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("Corrected measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + corrected_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("unique reach edp4 \ edp3 statistics:") + union_edp1_edp2_index = measurement_name_to_index["total_ami_edp34"] + union_edp1_index = measurement_name_to_index["total_ami_edp3"] + + print("Noisy measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + noisy_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + print("Corrected measurement:") + compute_unique_reach_statistic(sorted_true_measurements, + corrected_measurements, + union_edp1_edp2_index, + union_edp1_index + ) + + def _assertFuzzyEqual(self, x: int, y: int, tolerance: int): + self.assertLessEqual(abs(x - y), tolerance) + + def _assertFuzzyLessEqual(self, x: int, y: int, tolerance: int): + self.assertLessEqual(x, y + tolerance) + + +def read_file_to_string(filename: str) -> str: + try: + with open(filename, 'r') as file: + return file.read() + except FileNotFoundError: + sys.exit(1) + + +def get_report_summary(filename: str): + input = read_file_to_string(filename) + report_summary = report_summary_pb2.ReportSummary() + Parse(input, report_summary) + return report_summary + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_post_process_origin_report.py b/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_post_process_origin_report.py index 003c05e6c45..3c77af85b3a 100644 --- a/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_post_process_origin_report.py +++ b/src/test/python/wfa/measurement/reporting/postprocessing/tools/test_post_process_origin_report.py @@ -235,7 +235,7 @@ def test_report_with_custom_policy_is_corrected_successfully(self): 'cumulative/custom/' + edp_combination + '_' + str(i).zfill(2)], corrected_measurements_map[ 'cumulative/custom/' + edp_combination + '_' + str(i + 1).zfill( - 2)], + 2)], ) # Verifies that cumulative measurements are less than or equal to total @@ -532,4 +532,4 @@ def get_report_summary(filename: str): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file