From a6a72448b6cc6a16a4f79dd0f48b9896746c2a24 Mon Sep 17 00:00:00 2001 From: Fleur Petit Date: Fri, 6 Feb 2026 11:24:23 +0100 Subject: [PATCH 1/2] chore(uv): add optuna to toml --- pyproject.toml | 1 + uv.lock | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5e22fda10..9ef3469cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ classifiers = [ dependencies = [ "openstef-core", "openstef-models[xgb-cpu]", + "optuna>=4.7.0", ] optional-dependencies.all = [ diff --git a/uv.lock b/uv.lock index bedf5dfb7..1ff5df376 100644 --- a/uv.lock +++ b/uv.lock @@ -171,6 +171,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] +[[package]] +name = "alembic" +version = "1.18.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/41/ab8f624929847b49f84955c594b165855efd829b0c271e1a8cac694138e5/alembic-1.18.3.tar.gz", hash = "sha256:1212aa3778626f2b0f0aa6dd4e99a5f99b94bd25a0c1ac0bba3be65e081e50b0", size = 2052564, upload-time = "2026-01-29T20:24:15.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/8e/d79281f323e7469b060f15bd229e48d7cdd219559e67e71c013720a88340/alembic-1.18.3-py3-none-any.whl", hash = "sha256:12a0359bfc068a4ecbb9b3b02cf77856033abfdb59e4a5aca08b7eacd7b74ddd", size = 262282, upload-time = "2026-01-29T20:24:17.488Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -631,6 +645,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "colorlog" +version = "6.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + [[package]] name = "comm" version = "0.2.3" @@ -1235,6 +1261,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" }, ] +[[package]] +name = "greenlet" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/99/1cd3411c56a410994669062bd73dd58270c00cc074cac15f385a1fd91f8a/greenlet-3.3.1.tar.gz", hash = "sha256:41848f3230b58c08bb43dee542e74a2a2e34d3c59dc3076cec9151aeeedcae98", size = 184690, upload-time = "2026-01-23T15:31:02.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, + { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, + { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, + { url = "https://files.pythonhosted.org/packages/34/2f/5e0e41f33c69655300a5e54aeb637cf8ff57f1786a3aba374eacc0228c1d/greenlet-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc98b9c4e4870fa983436afa999d4eb16b12872fab7071423d5262fa7120d57a", size = 227156, upload-time = "2026-01-23T15:34:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ab/717c58343cf02c5265b531384b248787e04d8160b8afe53d9eec053d7b44/greenlet-3.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:bfb2d1763d777de5ee495c85309460f6fd8146e50ec9d0ae0183dbf6f0a829d1", size = 226403, upload-time = "2026-01-23T15:31:39.372Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, + { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, + { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/c9c23a6478b3bcc91f979ce4ca50879e4d0b2bd7b9a53d8ecded719b92e2/greenlet-3.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:27289986f4e5b0edec7b5a91063c109f0276abb09a7e9bdab08437525977c946", size = 227042, upload-time = "2026-01-23T15:33:58.216Z" }, + { url = "https://files.pythonhosted.org/packages/90/e7/824beda656097edee36ab15809fd063447b200cc03a7f6a24c34d520bc88/greenlet-3.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:2f080e028001c5273e0b42690eaf359aeef9cb1389da0f171ea51a5dc3c7608d", size = 226294, upload-time = "2026-01-23T15:30:52.73Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, + { url = "https://files.pythonhosted.org/packages/52/cb/c21a3fd5d2c9c8b622e7bede6d6d00e00551a5ee474ea6d831b5f567a8b4/greenlet-3.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:96aff77af063b607f2489473484e39a0bbae730f2ea90c9e5606c9b73c44174a", size = 228125, upload-time = "2026-01-23T15:32:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/8a2db6d11491837af1de64b8aff23707c6e85241be13c60ed399a72e2ef8/greenlet-3.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:b066e8b50e28b503f604fa538adc764a638b38cf8e81e025011d26e8a627fa79", size = 227519, upload-time = "2026-01-23T15:31:47.284Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, + { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/98c7f93e6db9977aaee07eb1e51ca63bd5f779b900d362791d3252e60558/greenlet-3.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:301860987846c24cb8964bdec0e31a96ad4a2a801b41b4ef40963c1b44f33451", size = 233181, upload-time = "2026-01-23T15:33:00.29Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -2037,6 +2102,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, ] +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + [[package]] name = "markdown" version = "3.10" @@ -2667,6 +2744,7 @@ source = { editable = "." } dependencies = [ { name = "openstef-core" }, { name = "openstef-models", extra = ["xgb-cpu"] }, + { name = "optuna" }, ] [package.optional-dependencies] @@ -2714,6 +2792,7 @@ requires-dist = [ { name = "openstef-models", extras = ["xgb-cpu"], editable = "packages/openstef-models" }, { name = "openstef-models", extras = ["xgb-cpu"], marker = "extra == 'all'", editable = "packages/openstef-models" }, { name = "openstef-models", extras = ["xgb-cpu"], marker = "extra == 'models'", editable = "packages/openstef-models" }, + { name = "optuna", specifier = ">=4.7.0" }, ] provides-extras = ["all", "beam", "models"] @@ -2954,6 +3033,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/7d/c88d7b15ba8fe5c6b8f93be50fc11795e9fc05386c44afaf6b76fe191f9b/opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed", size = 207954, upload-time = "2025-10-16T08:35:48.054Z" }, ] +[[package]] +name = "optuna" +version = "4.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "colorlog" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/b2/b5e12de7b4486556fe2257611b55dbabf30d0300bdb031831aa943ad20e4/optuna-4.7.0.tar.gz", hash = "sha256:d91817e2079825557bd2e97de2e8c9ae260bfc99b32712502aef8a5095b2d2c0", size = 479740, upload-time = "2026-01-19T05:45:52.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/d1/6c8a4fbb38a9e3565f5c36b871262a85ecab3da48120af036b1e4937a15c/optuna-4.7.0-py3-none-any.whl", hash = "sha256:e41ec84018cecc10eabf28143573b1f0bde0ba56dba8151631a590ecbebc1186", size = 413894, upload-time = "2026-01-19T05:45:50.815Z" }, +] + [[package]] name = "optype" version = "0.14.0" @@ -3687,6 +3784,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/0b/2bdac7f9f7cddcd8096af44338548f1b7d5b797e3bcee27831c3752c9168/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97b6ba9923975667fab130c23bfd8ead66c4cdea4b66ae238de860a06afbb108", size = 1539351, upload-time = "2025-11-05T12:53:37.836Z" }, { url = "https://files.pythonhosted.org/packages/06/fc/48b4932570097a08ed6abc3a7455aacf9a15271ff0099c33d48e7f745eaa/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b16ce0874ef2aee219a2c0dacd7c0ce374562c19937bd9c767093ade91e5e452", size = 1429957, upload-time = "2025-11-05T12:53:39.382Z" }, { url = "https://files.pythonhosted.org/packages/f7/8d/52f52e039e5e1cfb33cf0f79651edd4d8ff7f6a83d1fb5dddf19bca9993a/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2daf29e4958c310c27ce7750741ef60f79b2f4164df26b1f2bdd063f2beddf4c", size = 1375776, upload-time = "2025-11-05T12:53:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/58/7b/253e8c1d6bef9b5d041f8f104ae5ca70afc6a8bb23042b4272d30a67a2f9/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_24_aarch64.whl", hash = "sha256:e5da4b06eea58f89a93d7ae4426e31261d8c571dc5f71d8e13b9c7cdd8e8b253", size = 1317349, upload-time = "2026-01-07T23:30:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/fa/40/8b876c4244fd8cace8e85afc9c30b806f940dde713d81d15318f98179b39/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_24_armv7l.whl", hash = "sha256:a28c3425785fb28cee1316fdf860eab403274626f2cc0b6df14ec7dcce0f66d2", size = 1271918, upload-time = "2026-01-07T23:30:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/26/9c7ac96a390ec246892e427be7937400e9f8fe4e1f1cfa412bb919175f90/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_24_i686.whl", hash = "sha256:584e957a951330f777e632ccd12e87d6e8b2a7d113d4a2abbe1f84b8d85fce06", size = 1430842, upload-time = "2026-01-07T23:30:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cf/a619fbb8b19cafe78b471e69549e306c6f42e1a1296f12899a12403e474a/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_24_ppc64le.whl", hash = "sha256:ad8d5c0825a37ebe3414b0590336700600ee718bf6a461fbd1be01dba68e7a99", size = 1573886, upload-time = "2026-01-07T23:30:25.191Z" }, + { url = "https://files.pythonhosted.org/packages/ab/19/da02ed5b31c71d617273459a34e9401c4303c1c2eb00487faa0b55f0c64c/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_24_s390x.whl", hash = "sha256:a5fd2882b6ae5f0b1651e9e30cb2ba1ad07d99359dc1d056999766ec20706400", size = 1447873, upload-time = "2026-01-07T23:30:27.691Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2a/7eb3f9c1848d72186a9ace63429375e30544f69795b05be0998523e31ff0/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:56abdaa2afafe86436302220ea90bde025dcec066e348e7f9001a7663dc398f8", size = 1416637, upload-time = "2026-01-07T23:30:29.388Z" }, { url = "https://files.pythonhosted.org/packages/3b/24/bab927c42d88befbb063b229b44c9ce9b8a894f650ca14348969858878f5/pyproject_fmt-2.11.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44b1edad216b33817d2651a15fb2793807fd7c9cfff1ce66d565c4885b89640e", size = 1379396, upload-time = "2025-11-05T12:53:41.857Z" }, { url = "https://files.pythonhosted.org/packages/09/fe/b98c2156775067e079ca8f2badbe93a5de431ccc061435534b76f11abc73/pyproject_fmt-2.11.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:08ccf565172179fc7f35a90f4541f68abcdbef7e7a4ea35fcead44f8cabe3e3a", size = 1506485, upload-time = "2025-11-05T12:53:43.108Z" }, { url = "https://files.pythonhosted.org/packages/8e/2f/bf0df9df04a1376d6d1dad6fc49eb41ffafe0c3e63565b2cde8b67a49886/pyproject_fmt-2.11.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:27a9af1fc8d2173deb7a0bbb8c368a585e7817bcbba6acf00922b73c76c8ee23", size = 1546050, upload-time = "2025-11-05T12:53:44.491Z" }, @@ -4712,6 +4815,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] +[[package]] +name = "sqlalchemy" +version = "2.0.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/aa/9ce0f3e7a9829ead5c8ce549392f33a12c4555a6c0609bb27d882e9c7ddf/sqlalchemy-2.0.46.tar.gz", hash = "sha256:cf36851ee7219c170bb0793dbc3da3e80c582e04a5437bc601bfe8c85c9216d7", size = 9865393, upload-time = "2026-01-21T18:03:45.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/35/d16bfa235c8b7caba3730bba43e20b1e376d2224f407c178fbf59559f23e/sqlalchemy-2.0.46-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a9a72b0da8387f15d5810f1facca8f879de9b85af8c645138cba61ea147968c", size = 2153405, upload-time = "2026-01-21T19:05:54.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/6c/3192e24486749862f495ddc6584ed730c0c994a67550ec395d872a2ad650/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2347c3f0efc4de367ba00218e0ae5c4ba2306e47216ef80d6e31761ac97cb0b9", size = 3334702, upload-time = "2026-01-21T18:46:45.384Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/b9f33c8d68a3747d972a0bb758c6b63691f8fb8a49014bc3379ba15d4274/sqlalchemy-2.0.46-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9094c8b3197db12aa6f05c51c05daaad0a92b8c9af5388569847b03b1007fb1b", size = 3347664, upload-time = "2026-01-21T18:40:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/3e59e2a91eaec9db7e8dc6b37b91489b5caeb054f670f32c95bcba98940f/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37fee2164cf21417478b6a906adc1a91d69ae9aba8f9533e67ce882f4bb1de53", size = 3277372, upload-time = "2026-01-21T18:46:47.168Z" }, + { url = "https://files.pythonhosted.org/packages/dd/dd/67bc2e368b524e2192c3927b423798deda72c003e73a1e94c21e74b20a85/sqlalchemy-2.0.46-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b1e14b2f6965a685c7128bd315e27387205429c2e339eeec55cb75ca4ab0ea2e", size = 3312425, upload-time = "2026-01-21T18:40:11.548Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/0ecd68e172bfe62247e96cb47867c2d68752566811a4e8c9d8f6e7c38a65/sqlalchemy-2.0.46-cp312-cp312-win32.whl", hash = "sha256:412f26bb4ba942d52016edc8d12fb15d91d3cd46b0047ba46e424213ad407bcb", size = 2113155, upload-time = "2026-01-21T18:42:49.748Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2a/2821a45742073fc0331dc132552b30de68ba9563230853437cac54b2b53e/sqlalchemy-2.0.46-cp312-cp312-win_amd64.whl", hash = "sha256:ea3cd46b6713a10216323cda3333514944e510aa691c945334713fca6b5279ff", size = 2140078, upload-time = "2026-01-21T18:42:51.197Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4b/fa7838fe20bb752810feed60e45625a9a8b0102c0c09971e2d1d95362992/sqlalchemy-2.0.46-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:93a12da97cca70cea10d4b4fc602589c4511f96c1f8f6c11817620c021d21d00", size = 2150268, upload-time = "2026-01-21T19:05:56.621Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/b34dccd712e8ea846edf396e00973dda82d598cb93762e55e43e6835eba9/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af865c18752d416798dae13f83f38927c52f085c52e2f32b8ab0fef46fdd02c2", size = 3276511, upload-time = "2026-01-21T18:46:49.022Z" }, + { url = "https://files.pythonhosted.org/packages/96/48/a04d9c94753e5d5d096c628c82a98c4793b9c08ca0e7155c3eb7d7db9f24/sqlalchemy-2.0.46-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d679b5f318423eacb61f933a9a0f75535bfca7056daeadbf6bd5bcee6183aee", size = 3292881, upload-time = "2026-01-21T18:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/be/f4/06eda6e91476f90a7d8058f74311cb65a2fb68d988171aced81707189131/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64901e08c33462acc9ec3bad27fc7a5c2b6491665f2aa57564e57a4f5d7c52ad", size = 3224559, upload-time = "2026-01-21T18:46:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/d2af04095412ca6345ac22b33b89fe8d6f32a481e613ffcb2377d931d8d0/sqlalchemy-2.0.46-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8ac45e8f4eaac0f9f8043ea0e224158855c6a4329fd4ee37c45c61e3beb518e", size = 3262728, upload-time = "2026-01-21T18:40:14.883Z" }, + { url = "https://files.pythonhosted.org/packages/31/48/1980c7caa5978a3b8225b4d230e69a2a6538a3562b8b31cea679b6933c83/sqlalchemy-2.0.46-cp313-cp313-win32.whl", hash = "sha256:8d3b44b3d0ab2f1319d71d9863d76eeb46766f8cf9e921ac293511804d39813f", size = 2111295, upload-time = "2026-01-21T18:42:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/54/f8d65bbde3d877617c4720f3c9f60e99bb7266df0d5d78b6e25e7c149f35/sqlalchemy-2.0.46-cp313-cp313-win_amd64.whl", hash = "sha256:77f8071d8fbcbb2dd11b7fd40dedd04e8ebe2eb80497916efedba844298065ef", size = 2137076, upload-time = "2026-01-21T18:42:53.924Z" }, + { url = "https://files.pythonhosted.org/packages/56/ba/9be4f97c7eb2b9d5544f2624adfc2853e796ed51d2bb8aec90bc94b7137e/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1e8cc6cc01da346dc92d9509a63033b9b1bda4fed7a7a7807ed385c7dccdc10", size = 3556533, upload-time = "2026-01-21T18:33:06.636Z" }, + { url = "https://files.pythonhosted.org/packages/20/a6/b1fc6634564dbb4415b7ed6419cdfeaadefd2c39cdab1e3aa07a5f2474c2/sqlalchemy-2.0.46-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96c7cca1a4babaaf3bfff3e4e606e38578856917e52f0384635a95b226c87764", size = 3523208, upload-time = "2026-01-21T18:45:08.436Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d8/41e0bdfc0f930ff236f86fccd12962d8fa03713f17ed57332d38af6a3782/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2a9f9aee38039cf4755891a1e50e1effcc42ea6ba053743f452c372c3152b1b", size = 3464292, upload-time = "2026-01-21T18:33:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8b/9dcbec62d95bea85f5ecad9b8d65b78cc30fb0ffceeb3597961f3712549b/sqlalchemy-2.0.46-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db23b1bf8cfe1f7fda19018e7207b20cdb5168f83c437ff7e95d19e39289c447", size = 3473497, upload-time = "2026-01-21T18:45:10.552Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/5ecdfc73383ec496de038ed1614de9e740a82db9ad67e6e4514ebc0708a3/sqlalchemy-2.0.46-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:56bdd261bfd0895452006d5316cbf35739c53b9bb71a170a331fa0ea560b2ada", size = 2152079, upload-time = "2026-01-21T19:05:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bf/eba3036be7663ce4d9c050bc3d63794dc29fbe01691f2bf5ccb64e048d20/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e462154edb9493f6c3ad2125931e273bbd0be8ae53f3ecd1c161ea9a1dd366", size = 3272216, upload-time = "2026-01-21T18:46:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/05/45/1256fb597bb83b58a01ddb600c59fe6fdf0e5afe333f0456ed75c0f8d7bd/sqlalchemy-2.0.46-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcdce05f056622a632f1d44bb47dbdb677f58cad393612280406ce37530eb6d", size = 3277208, upload-time = "2026-01-21T18:40:16.38Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a0/2053b39e4e63b5d7ceb3372cface0859a067c1ddbd575ea7e9985716f771/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e84b09a9b0f19accedcbeff5c2caf36e0dd537341a33aad8d680336152dc34e", size = 3221994, upload-time = "2026-01-21T18:46:54.622Z" }, + { url = "https://files.pythonhosted.org/packages/1e/87/97713497d9502553c68f105a1cb62786ba1ee91dea3852ae4067ed956a50/sqlalchemy-2.0.46-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4f52f7291a92381e9b4de9050b0a65ce5d6a763333406861e33906b8aa4906bf", size = 3243990, upload-time = "2026-01-21T18:40:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/a8/87/5d1b23548f420ff823c236f8bea36b1a997250fd2f892e44a3838ca424f4/sqlalchemy-2.0.46-cp314-cp314-win32.whl", hash = "sha256:70ed2830b169a9960193f4d4322d22be5c0925357d82cbf485b3369893350908", size = 2114215, upload-time = "2026-01-21T18:42:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/3a/20/555f39cbcf0c10cf452988b6a93c2a12495035f68b3dbd1a408531049d31/sqlalchemy-2.0.46-cp314-cp314-win_amd64.whl", hash = "sha256:3c32e993bc57be6d177f7d5d31edb93f30726d798ad86ff9066d75d9bf2e0b6b", size = 2139867, upload-time = "2026-01-21T18:42:56.474Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f0/f96c8057c982d9d8a7a68f45d69c674bc6f78cad401099692fe16521640a/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4dafb537740eef640c4d6a7c254611dca2df87eaf6d14d6a5fca9d1f4c3fc0fa", size = 3561202, upload-time = "2026-01-21T18:33:10.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/3b37dda0a5b137f21ef608d8dfc77b08477bab0fe2ac9d3e0a66eaeab6fc/sqlalchemy-2.0.46-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42a1643dc5427b69aca967dae540a90b0fbf57eaf248f13a90ea5930e0966863", size = 3526296, upload-time = "2026-01-21T18:45:12.657Z" }, + { url = "https://files.pythonhosted.org/packages/33/75/f28622ba6dde79cd545055ea7bd4062dc934e0621f7b3be2891f8563f8de/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ff33c6e6ad006bbc0f34f5faf941cfc62c45841c64c0a058ac38c799f15b5ede", size = 3470008, upload-time = "2026-01-21T18:33:11.725Z" }, + { url = "https://files.pythonhosted.org/packages/a9/42/4afecbbc38d5e99b18acef446453c76eec6fbd03db0a457a12a056836e22/sqlalchemy-2.0.46-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82ec52100ec1e6ec671563bbd02d7c7c8d0b9e71a0723c72f22ecf52d1755330", size = 3476137, upload-time = "2026-01-21T18:45:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a1/9c4efa03300926601c19c18582531b45aededfb961ab3c3585f1e24f120b/sqlalchemy-2.0.46-py3-none-any.whl", hash = "sha256:f9c11766e7e7c0a2767dda5acb006a118640c9fc0a4104214b96269bfb78399e", size = 1937882, upload-time = "2026-01-21T18:22:10.456Z" }, +] + [[package]] name = "sqlparse" version = "0.5.3" From 82dca3cf4f3b3737573bdbef548bc01bdc05e4b8 Mon Sep 17 00:00:00 2001 From: Fleur Petit Date: Tue, 17 Mar 2026 15:11:25 +0100 Subject: [PATCH 2/2] feat(tuning): hyperparameter tuning with Optuna - Using Annotated fields to set search ranges on XGBoost and GBLinear hyperparameters; - try_optuna.ipynb to explore hyperparameter tuning with optuna. --- examples/tutorials/try_optuna.ipynb | 1199 +++++++++++++++++ .../models/forecasting/gblinear_forecaster.py | 19 +- .../models/forecasting/xgboost_forecaster.py | 31 +- .../src/openstef_models/presets/__init__.py | 11 +- .../presets/forecasting_workflow.py | 192 ++- .../src/openstef_models/utils/tuning.py | 328 +++++ pyproject.toml | 2 +- 7 files changed, 1755 insertions(+), 27 deletions(-) create mode 100644 examples/tutorials/try_optuna.ipynb create mode 100644 packages/openstef-models/src/openstef_models/utils/tuning.py diff --git a/examples/tutorials/try_optuna.ipynb b/examples/tutorials/try_optuna.ipynb new file mode 100644 index 000000000..ed1871ad7 --- /dev/null +++ b/examples/tutorials/try_optuna.ipynb @@ -0,0 +1,1199 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "c25b63fd", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Setup: Logging and Display Configuration ---\n", + "# Configure logging to see training progress and plotly to render as PNG for VS Code compatibility\n", + "import logging\n", + "import pandas as pd\n", + "import plotly.io as pio\n", + "\n", + "pd.options.plotting.backend = \"plotly\"\n", + "pio.renderers.default = \"png\" # Use PNG for VS Code notebook compatibility\n", + "\n", + "logging.basicConfig(level=logging.INFO, format=\"[%(asctime)s][%(levelname)s] %(message)s\")\n", + "logger = logging.getLogger(__name__)\n", + "logging.getLogger(\"choreographer\").setLevel(logging.ERROR)\n", + "logging.getLogger(\"kaleido\").setLevel(logging.ERROR)\n", + "logging.getLogger(\"choreographer\").disabled = True\n", + "logging.getLogger(\"kaleido\").disabled = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "84299333", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloading load_measurements/mv_feeder/OS Gorredijk.parquet...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/fleur.petit/projects/openstef/.venv/lib/python3.12/site-packages/huggingface_hub/utils/_validators.py:202: UserWarning:\n", + "\n", + "The `local_dir_use_symlinks` argument is deprecated and ignored in `hf_hub_download`. Downloading to a local directory does not use symlinks anymore.\n", + "\n", + "[2026-03-05 16:50:42,135][INFO] HTTP Request: HEAD https://huggingface.co/datasets/OpenSTEF/liander2024-energy-forecasting-benchmark/resolve/main/load_measurements/mv_feeder/OS%20Gorredijk.parquet \"HTTP/1.1 302 Found\"\n", + "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n", + "[2026-03-05 16:50:42,137][WARNING] Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n", + "[2026-03-05 16:50:42,275][INFO] HTTP Request: HEAD https://huggingface.co/datasets/OpenSTEF/liander2024-energy-forecasting-benchmark/resolve/main/weather_forecasts_versioned/mv_feeder/OS%20Gorredijk.parquet \"HTTP/1.1 302 Found\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ load_measurements/mv_feeder/OS Gorredijk.parquet downloaded\n", + "Downloading weather_forecasts_versioned/mv_feeder/OS Gorredijk.parquet...\n", + "✓ weather_forecasts_versioned/mv_feeder/OS Gorredijk.parquet downloaded\n", + "Downloading EPEX.parquet...\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[2026-03-05 16:50:42,572][INFO] HTTP Request: HEAD https://huggingface.co/datasets/OpenSTEF/liander2024-energy-forecasting-benchmark/resolve/main/EPEX.parquet \"HTTP/1.1 302 Found\"\n", + "[2026-03-05 16:50:42,701][INFO] HTTP Request: HEAD https://huggingface.co/datasets/OpenSTEF/liander2024-energy-forecasting-benchmark/resolve/main/profiles.parquet \"HTTP/1.1 302 Found\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ EPEX.parquet downloaded\n", + "Downloading profiles.parquet...\n", + "✓ profiles.parquet downloaded\n", + "\n", + "✅ All files downloaded successfully!\n" + ] + } + ], + "source": [ + "# Download dataset from HuggingFace Hub\n", + "# The dataset is stored as parquet files for efficient loading\n", + "from huggingface_hub import hf_hub_download # pyright: ignore[reportUnknownVariableType]\n", + "from openstef_core.base_model import Path\n", + "\n", + "repo_id = \"OpenSTEF/liander2024-energy-forecasting-benchmark\" # Public benchmark dataset\n", + "local_dir = Path(\"./liander_dataset\")\n", + "target = \"mv_feeder/OS Gorredijk\" # Specific installation to focus on\n", + "\n", + "# Download required files: load measurements, weather, prices, and profiles\n", + "files_to_download = [\n", + " f\"load_measurements/{target}.parquet\", # Energy consumption data\n", + " f\"weather_forecasts_versioned/{target}.parquet\", # Weather features\n", + " \"EPEX.parquet\", # Electricity prices (optional feature)\n", + " \"profiles.parquet\" # Standard load profiles (optional feature)\n", + "]\n", + "\n", + "for filename in files_to_download:\n", + " print(f\"Downloading {filename}...\")\n", + " hf_hub_download(repo_id=repo_id, filename=filename, repo_type=\"dataset\",\n", + " local_dir=local_dir, local_dir_use_symlinks=False) # pyright: ignore[reportCallIssue]\n", + " print(f\"✓ {filename} downloaded\")\n", + "\n", + "print(\"\\n✅ All files downloaded successfully!\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "524f65a1", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[2026-03-05 16:50:45,304][WARNING] Parquet file does not contain 'sample_interval' attribute. Using default value of 15 minutes.\n", + "[2026-03-05 16:50:45,336][WARNING] Parquet file does not contain 'sample_interval' attribute. Using default value of 15 minutes.\n", + "[2026-03-05 16:50:45,365][WARNING] Parquet file does not contain 'sample_interval' attribute. Using default value of 15 minutes.\n", + "[2026-03-05 16:50:45,378][WARNING] Parquet file does not contain 'sample_interval' attribute. Using default value of 15 minutes.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset shape: (35136, 28)\n", + "Date range: 2024-01-01 00:00:00+00:00 to 2024-12-31 23:45:00+00:00\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "timestamp", + "rawType": "datetime64[ns, UTC]", + "type": "unknown" + }, + { + "name": "load", + "rawType": "float64", + "type": "float" + }, + { + "name": "temperature_2m", + "rawType": "float32", + "type": "float" + }, + { + "name": "relative_humidity_2m", + "rawType": "float32", + "type": "float" + }, + { + "name": "surface_pressure", + "rawType": "float32", + "type": "float" + }, + { + "name": "cloud_cover", + "rawType": "float32", + "type": "float" + }, + { + "name": "wind_speed_10m", + "rawType": "float32", + "type": "float" + }, + { + "name": "wind_speed_80m", + "rawType": "float32", + "type": "float" + }, + { + "name": "wind_direction_10m", + "rawType": "float32", + "type": "float" + }, + { + "name": "shortwave_radiation", + "rawType": "float32", + "type": "float" + }, + { + "name": "direct_radiation", + "rawType": "float32", + "type": "float" + }, + { + "name": "diffuse_radiation", + "rawType": "float32", + "type": "float" + }, + { + "name": "direct_normal_irradiance", + "rawType": "float32", + "type": "float" + }, + { + "name": "EPEX_NL", + "rawType": "float64", + "type": "float" + }, + { + "name": "E1A_AZI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E1A_AMI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E1B_AZI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E1B_AMI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E1C_AZI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E1C_AMI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E2A_AZI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E2A_AMI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E2B_AZI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E2B_AMI_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E3A_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E3B_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E3C_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E3D_A", + "rawType": "float64", + "type": "float" + }, + { + "name": "E4A_A", + "rawType": "float64", + "type": "float" + } + ], + "ref": "aa9bfad1-6c3e-4ed7-8923-65e22d67bcbf", + "rows": [ + [ + "2024-01-01 00:00:00+00:00", + "423333.3333333333", + "7.2435", + "85.02532", + "994.23645", + "100.0", + "28.185953", + "43.832863", + "204.92845", + "0.0", + "0.0", + "0.0", + "0.0", + "0.01", + "2.97e-05", + "4.031e-05", + "6.206e-05", + "7.815e-05", + "5.683e-05", + "6.431e-05", + "2.42e-05", + "3.404e-05", + "5.292e-05", + "6.425e-05", + "5.839e-05", + "5.839e-05", + "5.839e-05", + "5.839e-05", + "7.931e-05" + ], + [ + "2024-01-01 00:15:00+00:00", + "436666.6666666666", + "7.281", + "84.80853", + "994.1865", + "100.0", + "28.75338", + "44.97622", + "206.93102", + "0.0", + "0.0", + "0.0", + "0.0", + "0.01", + "2.91e-05", + "3.866e-05", + "6.079e-05", + "7.537e-05", + "5.572e-05", + "6.058e-05", + "2.395e-05", + "3.352e-05", + "5.236e-05", + "6.326e-05", + "5.826e-05", + "5.826e-05", + "5.826e-05", + "5.826e-05", + "7.931e-05" + ], + [ + "2024-01-01 00:30:00+00:00", + "410000.0", + "7.3185005", + "84.59174", + "994.1366", + "100.0", + "29.320807", + "46.11957", + "208.93356", + "0.0", + "0.0", + "0.0", + "0.0", + "0.01", + "2.794e-05", + "3.771e-05", + "5.799e-05", + "7.33e-05", + "5.451e-05", + "5.967e-05", + "2.338e-05", + "3.312e-05", + "5.113e-05", + "6.252e-05", + "5.782e-05", + "5.782e-05", + "5.782e-05", + "5.782e-05", + "7.931e-05" + ], + [ + "2024-01-01 00:45:00+00:00", + "403333.3333333333", + "7.3560004", + "84.374954", + "994.08673", + "100.0", + "29.888233", + "47.262924", + "210.93613", + "0.0", + "0.0", + "0.0", + "0.0", + "0.01", + "2.712e-05", + "3.649e-05", + "5.659e-05", + "7.12e-05", + "5.211e-05", + "5.708e-05", + "2.325e-05", + "3.219e-05", + "5.083e-05", + "6.076e-05", + "5.89e-05", + "5.89e-05", + "5.89e-05", + "5.89e-05", + "7.931e-05" + ], + [ + "2024-01-01 01:00:00+00:00", + "420000.0", + "7.3935003", + "84.158165", + "994.0368", + "100.0", + "30.45566", + "48.40628", + "212.93869", + "0.0", + "0.0", + "0.0", + "0.0", + "0.0", + "2.714e-05", + "3.44e-05", + "5.728e-05", + "6.668e-05", + "5.045e-05", + "5.493e-05", + "2.359e-05", + "3.198e-05", + "5.158e-05", + "6.036e-05", + "5.726e-05", + "5.726e-05", + "5.726e-05", + "5.726e-05", + "7.931e-05" + ] + ], + "shape": { + "columns": 28, + "rows": 5 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
loadtemperature_2mrelative_humidity_2msurface_pressurecloud_coverwind_speed_10mwind_speed_80mwind_direction_10mshortwave_radiationdirect_radiation...E1C_AMI_AE2A_AZI_AE2A_AMI_AE2B_AZI_AE2B_AMI_AE3A_AE3B_AE3C_AE3D_AE4A_A
timestamp
2024-01-01 00:00:00+00:00423333.3333337.24350085.025322994.236450100.028.18595343.832863204.9284520.00.0...0.0000640.0000240.0000340.0000530.0000640.0000580.0000580.0000580.0000580.000079
2024-01-01 00:15:00+00:00436666.6666677.28100084.808533994.186523100.028.75338044.976219206.9310150.00.0...0.0000610.0000240.0000340.0000520.0000630.0000580.0000580.0000580.0000580.000079
2024-01-01 00:30:00+00:00410000.0000007.31850184.591743994.136597100.029.32080746.119572208.9335630.00.0...0.0000600.0000230.0000330.0000510.0000630.0000580.0000580.0000580.0000580.000079
2024-01-01 00:45:00+00:00403333.3333337.35600084.374954994.086731100.029.88823347.262924210.9361270.00.0...0.0000570.0000230.0000320.0000510.0000610.0000590.0000590.0000590.0000590.000079
2024-01-01 01:00:00+00:00420000.0000007.39350084.158165994.036804100.030.45566048.406281212.9386900.00.0...0.0000550.0000240.0000320.0000520.0000600.0000570.0000570.0000570.0000570.000079
\n", + "

5 rows × 28 columns

\n", + "
" + ], + "text/plain": [ + " load temperature_2m \\\n", + "timestamp \n", + "2024-01-01 00:00:00+00:00 423333.333333 7.243500 \n", + "2024-01-01 00:15:00+00:00 436666.666667 7.281000 \n", + "2024-01-01 00:30:00+00:00 410000.000000 7.318501 \n", + "2024-01-01 00:45:00+00:00 403333.333333 7.356000 \n", + "2024-01-01 01:00:00+00:00 420000.000000 7.393500 \n", + "\n", + " relative_humidity_2m surface_pressure \\\n", + "timestamp \n", + "2024-01-01 00:00:00+00:00 85.025322 994.236450 \n", + "2024-01-01 00:15:00+00:00 84.808533 994.186523 \n", + "2024-01-01 00:30:00+00:00 84.591743 994.136597 \n", + "2024-01-01 00:45:00+00:00 84.374954 994.086731 \n", + "2024-01-01 01:00:00+00:00 84.158165 994.036804 \n", + "\n", + " cloud_cover wind_speed_10m wind_speed_80m \\\n", + "timestamp \n", + "2024-01-01 00:00:00+00:00 100.0 28.185953 43.832863 \n", + "2024-01-01 00:15:00+00:00 100.0 28.753380 44.976219 \n", + "2024-01-01 00:30:00+00:00 100.0 29.320807 46.119572 \n", + "2024-01-01 00:45:00+00:00 100.0 29.888233 47.262924 \n", + "2024-01-01 01:00:00+00:00 100.0 30.455660 48.406281 \n", + "\n", + " wind_direction_10m shortwave_radiation \\\n", + "timestamp \n", + "2024-01-01 00:00:00+00:00 204.928452 0.0 \n", + "2024-01-01 00:15:00+00:00 206.931015 0.0 \n", + "2024-01-01 00:30:00+00:00 208.933563 0.0 \n", + "2024-01-01 00:45:00+00:00 210.936127 0.0 \n", + "2024-01-01 01:00:00+00:00 212.938690 0.0 \n", + "\n", + " direct_radiation ... E1C_AMI_A E2A_AZI_A \\\n", + "timestamp ... \n", + "2024-01-01 00:00:00+00:00 0.0 ... 0.000064 0.000024 \n", + "2024-01-01 00:15:00+00:00 0.0 ... 0.000061 0.000024 \n", + "2024-01-01 00:30:00+00:00 0.0 ... 0.000060 0.000023 \n", + "2024-01-01 00:45:00+00:00 0.0 ... 0.000057 0.000023 \n", + "2024-01-01 01:00:00+00:00 0.0 ... 0.000055 0.000024 \n", + "\n", + " E2A_AMI_A E2B_AZI_A E2B_AMI_A E3A_A \\\n", + "timestamp \n", + "2024-01-01 00:00:00+00:00 0.000034 0.000053 0.000064 0.000058 \n", + "2024-01-01 00:15:00+00:00 0.000034 0.000052 0.000063 0.000058 \n", + "2024-01-01 00:30:00+00:00 0.000033 0.000051 0.000063 0.000058 \n", + "2024-01-01 00:45:00+00:00 0.000032 0.000051 0.000061 0.000059 \n", + "2024-01-01 01:00:00+00:00 0.000032 0.000052 0.000060 0.000057 \n", + "\n", + " E3B_A E3C_A E3D_A E4A_A \n", + "timestamp \n", + "2024-01-01 00:00:00+00:00 0.000058 0.000058 0.000058 0.000079 \n", + "2024-01-01 00:15:00+00:00 0.000058 0.000058 0.000058 0.000079 \n", + "2024-01-01 00:30:00+00:00 0.000058 0.000058 0.000058 0.000079 \n", + "2024-01-01 00:45:00+00:00 0.000059 0.000059 0.000059 0.000079 \n", + "2024-01-01 01:00:00+00:00 0.000057 0.000057 0.000057 0.000079 \n", + "\n", + "[5 rows x 28 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load datasets using OpenSTEF's VersionedTimeSeriesDataset\n", + "# This class handles versioned data where each value has an \"available_at\" timestamp\n", + "from openstef_core.datasets import VersionedTimeSeriesDataset\n", + "\n", + "# Load each data source from parquet files\n", + "load_dataset = VersionedTimeSeriesDataset.read_parquet(\n", + " local_dir / f\"load_measurements/{target}.parquet\"\n", + ")\n", + "weather_dataset = VersionedTimeSeriesDataset.read_parquet(\n", + " local_dir / f\"weather_forecasts_versioned/{target}.parquet\"\n", + ")\n", + "epex_dataset = VersionedTimeSeriesDataset.read_parquet(local_dir / \"EPEX.parquet\")\n", + "profiles_dataset = VersionedTimeSeriesDataset.read_parquet(local_dir / \"profiles.parquet\")\n", + "\n", + "# Combine all datasets using left join (keep all load timestamps, match features where available)\n", + "# select_version() materializes the lazy dataset into a concrete TimeSeriesDataset\n", + "dataset = VersionedTimeSeriesDataset.concat(\n", + " [load_dataset, weather_dataset, epex_dataset, profiles_dataset], \n", + " mode=\"left\" # Left join keeps all timestamps from the first dataset\n", + ").select_version()\n", + "\n", + "# Preview the combined dataset\n", + "print(f\"Dataset shape: {dataset.data.shape}\")\n", + "print(f\"Date range: {dataset.data.index.min()} to {dataset.data.index.max()}\")\n", + "dataset.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2a64bbb5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "📈 Training period: 2024-03-01 to 2024-05-30 (8640 samples)\n", + "🔮 Forecast period: 2024-05-30 to 2024-06-13 (1344 samples)\n" + ] + } + ], + "source": [ + "# Define training and forecast time periods\n", + "from datetime import datetime, timedelta\n", + "\n", + "# Training period: 90 days of historical data\n", + "train_start = datetime.fromisoformat(\"2024-03-01T00:00:00Z\")\n", + "train_end = train_start + timedelta(days=90)\n", + "\n", + "# Forecast period: 14 days after training (this is where we'll predict)\n", + "forecast_start = train_end\n", + "forecast_end = forecast_start + timedelta(days=14)\n", + "\n", + "# Split the dataset using time-based filtering\n", + "train_dataset = dataset.filter_by_range(start=train_start, end=train_end)\n", + "forecast_dataset = dataset.filter_by_range(start=forecast_start, end=forecast_end)\n", + "\n", + "print(f\"📈 Training period: {train_start.date()} to {train_end.date()} ({len(train_dataset.data)} samples)\")\n", + "print(f\"🔮 Forecast period: {forecast_start.date()} to {forecast_end.date()} ({len(forecast_dataset.data)} samples)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bfe2a41f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize the training data\n", + "# The plot shows the 'load' column (energy consumption in MW) over time\n", + "fig = train_dataset.data[[\"load\"]].plot(title=\"Training Data: Energy Load over Time\")\n", + "fig.update_layout(yaxis_title=\"Load (MW)\", xaxis_title=\"Time\")\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "12d8302c", + "metadata": {}, + "source": [ + "## Define a base config with inline search space\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a001a0f0", + "metadata": {}, + "outputs": [], + "source": [ + "from openstef_core.types import LeadTime, Q\n", + "from openstef_models.models.forecasting.xgboost_forecaster import XGBoostHyperParams\n", + "from openstef_models.presets import ForecastingWorkflowConfig, TuningResult, fit_with_tuning\n", + "from openstef_models.utils.tuning import CategoricalRange, FloatRange, IntRange\n", + "\n", + "config = ForecastingWorkflowConfig(\n", + " model_id=\"xgboost_optuna_demo\",\n", + " model=\"xgboost\",\n", + "\n", + " horizons=[LeadTime.from_string(\"PT36H\")],\n", + " quantiles=[Q(0.5), Q(0.1), Q(0.9)],\n", + "\n", + " target_column=\"load\",\n", + " temperature_column=\"temperature_2m\",\n", + " relative_humidity_column=\"relative_humidity_2m\",\n", + " wind_speed_column=\"wind_speed_10m\",\n", + " radiation_column=\"shortwave_radiation\",\n", + " pressure_column=\"surface_pressure\",\n", + "\n", + " # Pass TuningRange objects directly as field values — tune=True marks them for Optuna.\n", + " # None values for low/high fall back to the class-level defaults in XGBoostHyperParams.\n", + " xgboost_hyperparams=XGBoostHyperParams(\n", + " n_estimators=200,\n", + " learning_rate=FloatRange(None, None, log=True, tune=True), # class default: [0.01, 0.5]\n", + " max_depth=IntRange(5, 15, tune=True), \n", + " min_child_weight=FloatRange(None, None, tune=True), # class default: [1.0, 10.0]\n", + " reg_alpha=FloatRange(None, None, log=True, tune=True), # class default: [1e-8, 10.0]\n", + " reg_lambda=FloatRange(None, None, log=True, tune=True), # class default: [1e-8, 10.0]\n", + " ),\n", + " optuna_n_trials=20,\n", + " optuna_seed=42,\n", + "\n", + " mlflow_storage=None, # Disable MLflow during tuning\n", + " verbosity=0,\n", + ")\n" + ] + }, + { + "cell_type": "markdown", + "id": "9b47824a", + "metadata": {}, + "source": [ + "## Inspect the resolved search space\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "65a76a06", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Resolved search space:\n", + " learning_rate : FloatRange [0.01 — 0.5] [log]\n", + " max_depth : IntRange [5 — 15]\n", + " min_child_weight : FloatRange [1.0 — 10.0]\n", + " reg_alpha : FloatRange [1e-08 — 10.0] [log]\n", + " reg_lambda : FloatRange [1e-08 — 10.0] [log]\n" + ] + } + ], + "source": [ + "from openstef_models.utils.tuning import FloatRange, IntRange, CategoricalRange, get_search_space\n", + "\n", + "# Merge custom annotated hyperparams with class-level annotated defaults (filling in any None bounds).\n", + "resolved_space = get_search_space(config.xgboost_hyperparams)\n", + "\n", + "print(\"Resolved search space:\")\n", + "for name, param in resolved_space.items():\n", + " if isinstance(param, (FloatRange, IntRange)):\n", + " scale = \" [log]\" if param.log else \"\"\n", + " print(f\" {name:25s}: {type(param).__name__} [{param.low} — {param.high}]{scale}\")\n", + " elif isinstance(param, CategoricalRange):\n", + " print(f\" {name:25s}: CategoricalRange {param.choices}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "08768e60", + "metadata": {}, + "source": [ + "## Run the Optuna study with `fit_with_tuning`" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "8601c179", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " 0%| | 0/20 [00:00 CustomFore callbacks=callbacks, experiment_tags=config.experiment_tags, ) + + +class TuningResult: + """Result of a :func:`fit_with_tuning` call. + + Attributes: + workflow: The fitted :class:`CustomForecastingWorkflow`. + fit_result: The :class:`ModelFitResult` from the final training run, or + ``None`` if fitting was skipped (e.g. by an MLflow callback). + study: The completed :class:`optuna.Study`. + best_params: Flat dict of the best hyperparameter values found by Optuna, + or an empty dict when tuning was not performed. + """ + + def __init__( + self, + workflow: CustomForecastingWorkflow, + fit_result: ModelFitResult | None, + study: optuna.Study | None, + best_params: dict[str, Any], + ) -> None: + """Initialize a TuningResult with workflow and tuning outcomes. + + Args: + workflow: The fitted forecasting workflow. + fit_result: The result from the final training run, or None if fitting was skipped. + study: The completed Optuna study, or None if no tuning was performed. + best_params: Dictionary of best hyperparameter values found, or empty if no tuning. + """ + self.workflow = workflow + self.fit_result = fit_result + self.study = study + self.best_params = best_params + + def __repr__(self) -> str: + """Return a string representation of the TuningResult.""" + tuned = f"{len(self.best_params)} params tuned" if self.best_params else "no tuning" + return f"TuningResult({tuned})" + + +def tune( + config: ForecastingWorkflowConfig, + train_dataset: TimeSeriesDataset, +) -> tuple[ForecastingWorkflowConfig, optuna.Study, dict[str, Any]]: + """Run hyperparameter tuning for a forecasting workflow configuration. + + Inspects the ``xgboost_hyperparams`` / ``gblinear_hyperparams`` instance of + *config* to determine which hyperparameters are marked for tuning + (by passing a ``TuningRange(tune=True)`` as the field value). + + The metric maximised during the study is determined by + :attr:`ForecastingWorkflowConfig.model_selection_metric`. + + Args: + config: Workflow configuration. Pass ``TuningRange(tune=True)`` objects as + field values on ``xgboost_hyperparams`` / ``gblinear_hyperparams`` to mark + fields for tuning. ``optuna_n_trials`` and ``optuna_seed`` control the study. + train_dataset: Dataset used for all trial fit calls. + + Returns: + A tuple of: + + - The config updated with the best hyperparameters found. + - The completed :class:`optuna.Study`. + - A flat dict of the best hyperparameter values. + + Raises: + ValueError: If the model type does not support tuning (e.g. ``flatliner``), + or if the model supports tuning but no field has ``tune=True`` + in the hyperparams instance. + """ + if config.model not in {"xgboost", "gblinear"}: + msg = ( + f"Model type '{config.model}' does not support hyperparameter tuning. " + "Use 'xgboost' or 'gblinear' and pass TuningRange(tune=True) as field values." + ) + raise ValueError(msg) + + if config.model == "xgboost": + current_hp = config.xgboost_hyperparams + hp_field = "xgboost_hyperparams" + else: # gblinear + current_hp = config.gblinear_hyperparams + hp_field = "gblinear_hyperparams" + + # Build the effective search space + space = get_search_space(current_hp) + + if not space: + msg = ( + f"No tunable hyperparameters found on `{hp_field}`. " + "Pass TuningRange(tune=True) objects as field values when constructing it, " + "e.g. `n_estimators=IntRange(100, 800, tune=True)`." + ) + raise ValueError(msg) + + # Build the Optuna objective + target_quantile, metric_name, _ = config.model_selection_metric + + def _objective(trial: optuna.Trial) -> float: + tuned_hp = suggest_hyperparams(trial, space, current_hp) + tuned_config = config.model_copy(update={hp_field: tuned_hp}) + trial_workflow = create_forecasting_workflow(tuned_config) + trial_result = trial_workflow.fit(train_dataset) + if trial_result is None: + return float("-inf") + metrics = trial_result.metrics_val if trial_result.metrics_val is not None else trial_result.metrics_train + score = metrics.get_metric(quantile=target_quantile, metric_name=metric_name) + return float(score) if score is not None else float("-inf") + + # Run the study + study = run_optuna_study( + objective=_objective, + n_trials=config.optuna_n_trials, + seed=config.optuna_seed, + study_name=f"tuning_{config.model_id}", + ) + + best_hp = current_hp.model_copy(update=study.best_params) + best_config = config.model_copy(update={hp_field: best_hp}) + return best_config, study, study.best_params + + +def fit_with_tuning( + config: ForecastingWorkflowConfig, + train_dataset: TimeSeriesDataset, +) -> TuningResult: + """Create, optionally tune, and fit a forecasting workflow in one call. + + Inspects the ``xgboost_hyperparams`` / ``gblinear_hyperparams`` instance of + *config* to determine whether any hyperparameter is marked for tuning + (by passing a ``TuningRange(tune=True)`` as the field value). + + * **One or more tunable fields** runs an Optuna Bayesian search for + :attr:`ForecastingWorkflowConfig.optuna_n_trials` trials via :func:`tune`, + then trains the final model with the best hyperparameters found. + + The metric maximised during the study is determined by + :attr:`ForecastingWorkflowConfig.model_selection_metric`. + + Args: + config: Workflow configuration. Pass ``TuningRange(tune=True)`` objects as + field values on ``xgboost_hyperparams`` / ``gblinear_hyperparams`` to mark + fields for tuning. ``optuna_n_trials`` and ``optuna_seed`` control the study. + train_dataset: Dataset used for **all** workflow fit calls (both tuning + trials and the final fit). + + Returns: + :class:`TuningResult` with the fitted workflow, fit result, optional study, + and the best hyperparameter values. + + Example:: + + from openstef_models.presets import ForecastingWorkflowConfig, fit_with_tuning + from openstef_models.utils.tuning import FloatRange, IntRange + from openstef_core.types import LeadTime, Q + + config = ForecastingWorkflowConfig( + model_id="demo", + model="xgboost", + quantiles=[Q(0.5), Q(0.1), Q(0.9)], + horizons=[LeadTime.from_string("PT36H")], + xgboost_hyperparams=XGBoostForecaster.HyperParams( + n_estimators=IntRange(100, 500, tune=True), + learning_rate=FloatRange(None, None, log=True, tune=True), + ), + optuna_n_trials=20, + mlflow_storage=None, + ) + result = fit_with_tuning(config, train_dataset) # doctest: +SKIP + print(result.best_params) # doctest: +SKIP + """ + tuned_config, study, best_params = tune(config, train_dataset) + workflow = create_forecasting_workflow(tuned_config) + result = workflow.fit(train_dataset) + return TuningResult(workflow=workflow, fit_result=result, study=study, best_params=best_params) diff --git a/packages/openstef-models/src/openstef_models/utils/tuning.py b/packages/openstef-models/src/openstef_models/utils/tuning.py new file mode 100644 index 000000000..3d0cbb731 --- /dev/null +++ b/packages/openstef-models/src/openstef_models/utils/tuning.py @@ -0,0 +1,328 @@ +# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +# +# SPDX-License-Identifier: MPL-2.0 +"""Hyperparameter tuning utilities for OpenSTEF models. + +Provides dataclasses for describing hyperparameter search spaces, helper functions to +extract and merge search spaces from annotated HyperParams classes, and a thin wrapper +around Optuna for running Bayesian hyperparameter optimisation studies. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, cast + +import optuna +from pydantic import BaseModel, PrivateAttr, model_validator + +if TYPE_CHECKING: + from collections.abc import Callable + + from pydantic.fields import FieldInfo + +from openstef_core.mixins import HyperParams + + +@dataclass(frozen=True) +class FloatRange: + """Search space metadata for continuous float hyperparameters. + + Attach to a ``HyperParams`` field via ``Annotated`` to declare the + range that a hyperparameter tuner should explore. + + Args: + low: Lower bound of the search interval (inclusive). + high: Upper bound of the search interval (inclusive). + log: When ``True`` the sampler draws on a log scale, which is + recommended for parameters like learning rates and + regularisation coefficients. + + Example: + >>> learning_rate: Annotated[float, FloatRange(0.01, 0.5, log=True)] = 0.3 + """ + + low: float | None + high: float | None + log: bool = False + tune: bool = False + + +@dataclass(frozen=True) +class IntRange: + """Search space metadata for discrete integer hyperparameters. + + Attach to a ``HyperParams`` field via ``Annotated`` to declare the + integer range that a hyperparameter tuner should explore. + + Args: + low: Minimum integer value (inclusive). + high: Maximum integer value (inclusive). + log: When ``True`` the sampler draws on a log scale. + + Example: + >>> n_estimators: Annotated[int, IntRange(50, 500)] = 100 + """ + + low: int | None + high: int | None + log: bool = False + tune: bool = False + + +@dataclass(frozen=True) +class CategoricalRange: + """Search space metadata for categorical hyperparameters. + + Attach to a ``HyperParams`` field via ``Annotated`` to list the + discrete choices that a hyperparameter tuner should explore. + + Args: + choices: Tuple of allowed values for the parameter. + + Example: + >>> tree_method: Annotated[str, CategoricalRange(("hist", "approx"))] = "hist" + """ + + choices: tuple[Any, ...] | None + tune: bool = False + + +#: Union alias for any single-parameter search space descriptor. +TuningRange = FloatRange | IntRange | CategoricalRange + + +class TunableHyperParams(HyperParams): + """HyperParams subclass that accepts ``TuningRange`` objects as field values. + + Pass a :class:`FloatRange`, :class:`IntRange`, or :class:`CategoricalRange` as the + value for any field during construction. The range is stored in the private + ``_instance_ranges`` attribute and the field itself keeps its declared default value. + ``None`` for ``low`` / ``high`` / ``choices`` falls back to the class-level + ``Annotated`` metadata when the search space is resolved. + + This means the tuning search space lives **on the HyperParams instance itself** — no + separate dict is needed. + + Example:: + + hp = XGBoostHyperParams( + n_estimators=IntRange(100, 800, tune=True), + learning_rate=FloatRange(None, None, log=True, tune=True), # → class default [0.01, 0.5] + ) + # hp.n_estimators == 100 (the class default; the IntRange was extracted) + # get_search_space(hp) → {'n_estimators': IntRange(100, 800), 'learning_rate': FloatRange(0.01, 0.5)} + """ + + _instance_ranges: dict[str, TuningRange] = PrivateAttr( # pyright: ignore[reportUnknownVariableType] + default_factory=dict + ) + + @property + def instance_ranges(self) -> dict[str, TuningRange]: + """Public view of the per-instance tuning ranges extracted at construction.""" + return self._instance_ranges + + @model_validator(mode="wrap") + @classmethod + def _extract_tuning_ranges( + cls, + data: dict[str, object] | object, + handler: Callable[[dict[str, object] | object], TunableHyperParams], + ) -> TunableHyperParams: + """Strip TuningRange values from the input dict and store them as instance metadata. + + Returns: + A new :class:`TunableHyperParams` instance with TuningRange values removed + from the fields and stored in the private ``_instance_ranges`` attribute. + """ + instance_ranges: dict[str, TuningRange] = {} + if isinstance(data, dict): + cleaned: dict[str, Any] = {} + for key, value in cast("dict[str, object]", data).items(): + if isinstance(value, (FloatRange, IntRange, CategoricalRange)): + instance_ranges[key] = value + # Keep the key absent: Pydantic uses the declared field default + else: + cleaned[key] = value + data = cleaned + result: TunableHyperParams = handler(data) + if instance_ranges and result.__pydantic_private__ is not None: + result.__pydantic_private__["_instance_ranges"] = instance_ranges + return result + + +def _get_class_range(field_info: FieldInfo) -> TuningRange | None: + """Return the first TuningRange found in a Pydantic FieldInfo's metadata.""" + for meta in field_info.metadata: + if isinstance(meta, (FloatRange, IntRange, CategoricalRange)): + return meta + return None + + +def _merge_range(override: TuningRange, class_range: TuningRange | None) -> TuningRange: + """Merge *override* with *class_range*, filling ``None`` from the class defaults. + + For ``FloatRange`` / ``IntRange``, ``None`` values for ``low`` or ``high`` are filled + in from *class_range*. For ``CategoricalRange``, ``None`` for ``choices`` falls back + to *class_range*. The ``tune`` flag always comes from *override*. + + Returns: + A new :class:`TuningRange` with ``None`` bounds merged from *class_range*. + """ + if isinstance(override, FloatRange): + cr = class_range if isinstance(class_range, FloatRange) else None + return FloatRange( + low=override.low if override.low is not None else (cr.low if cr else None), + high=override.high if override.high is not None else (cr.high if cr else None), + log=override.log, + tune=override.tune, + ) + if isinstance(override, IntRange): + cr = class_range if isinstance(class_range, IntRange) else None + return IntRange( + low=override.low if override.low is not None else (cr.low if cr else None), + high=override.high if override.high is not None else (cr.high if cr else None), + log=override.log, + tune=override.tune, + ) + cr = class_range if isinstance(class_range, CategoricalRange) else None + return CategoricalRange( + choices=override.choices if override.choices is not None else (cr.choices if cr else None), + tune=override.tune, + ) + + +def get_search_space( + hyperparams: BaseModel, + include: set[str] | None = None, +) -> dict[str, TuningRange]: + """Extract the effective tunable search space from a *HyperParams* instance. + + Reads per-instance ``TuningRange`` objects stored in ``_instance_ranges`` + (set by passing ranges directly in the constructor of a + :class:`TunableHyperParams` subclass) and merges them with the class-level + ``Annotated`` metadata. ``None`` bounds fall back to the class-level defaults. + Only fields where the resulting ``tune`` flag is ``True`` are included. + + Args: + hyperparams: A :class:`TunableHyperParams` (or plain ``HyperParams``) instance. + include: If given, restrict the output to exactly these field names. A + ``KeyError`` is raised immediately for any name that is absent or has no + ``tune=True`` annotation (catches typos early). + + Returns: + Mapping of field-name → effective :class:`TuningRange` for all tunable fields. + + Raises: + KeyError: If ``include`` is specified and any requested field name is not + present in the tunable search space. + + Example:: + + hp = XGBoostHyperParams( + n_estimators=IntRange(100, 800, tune=True), + learning_rate=FloatRange(None, None, log=True, tune=True), + ) + space = get_search_space(hp) + # {'n_estimators': IntRange(100, 800), 'learning_rate': FloatRange(0.01, 0.5, log=True)} + """ + # Per-instance ranges take precedence over class-level annotations + instance_ranges: dict[str, TuningRange] = {} + if isinstance(hyperparams, TunableHyperParams): + instance_ranges = hyperparams.instance_ranges + + result: dict[str, TuningRange] = {} + for field_name, field_info in type(hyperparams).model_fields.items(): + class_range = _get_class_range(field_info) + override = instance_ranges.get(field_name) + + if override is not None: + if not override.tune: + continue + result[field_name] = _merge_range(override, class_range) + elif class_range is not None and class_range.tune: + result[field_name] = class_range + + if include is not None: + missing = include - result.keys() + if missing: + msg = ( + f"Fields {sorted(missing)!r} not found in the tunable search space. " + "Check that they exist on the HyperParams class and were passed as " + "TuningRange(tune=True) in the constructor." + ) + raise KeyError(msg) + result = {k: result[k] for k in include} + + return result + + +def suggest_hyperparams[HP: BaseModel]( + trial: optuna.Trial, + space: dict[str, TuningRange], + current: HP, +) -> HP: + """Create an updated *HyperParams* using Optuna trial suggestions. + + Args: + trial: Optuna trial object for suggesting values. + space: Search space returned by :func:`get_search_space`. + current: Current ``HyperParams`` instance to copy-and-update. + + Returns: + A new ``HyperParams`` instance with the suggested values applied. + """ + updates: dict[str, Any] = {} + for field_name, param in space.items(): + if isinstance(param, FloatRange): + if param.low is not None and param.high is not None: + updates[field_name] = trial.suggest_float(field_name, param.low, param.high, log=param.log) + elif isinstance(param, IntRange): + if param.low is not None and param.high is not None: + updates[field_name] = trial.suggest_int(field_name, param.low, param.high, log=param.log) + elif param.choices is not None: + updates[field_name] = trial.suggest_categorical(field_name, list(param.choices)) + return current.model_copy(update=updates) + + +def run_optuna_study( + objective: Callable[[optuna.Trial], float], + n_trials: int, + seed: int | None = 42, + direction: str = "maximize", + study_name: str = "hyperparameter_tuning", +) -> optuna.Study: + """Run a Bayesian hyperparameter optimisation study using Optuna. + + Args: + objective: Callable that receives an :class:`optuna.Trial` and returns a + ``float`` score to optimise. + n_trials: Number of trials to evaluate. + seed: Random seed for the TPE sampler (``None`` disables seeding). + direction: ``"maximize"`` or ``"minimize"``. + study_name: Human-readable label for the study. + + Returns: + Completed :class:`optuna.Study` with all trial results. + """ + study = optuna.create_study( + direction=direction, + sampler=optuna.samplers.TPESampler(seed=seed), + pruner=optuna.pruners.MedianPruner(n_startup_trials=5), + study_name=study_name, + ) + study.optimize(objective, n_trials=n_trials, show_progress_bar=True) + return study + + +__all__ = [ + "CategoricalRange", + "FloatRange", + "IntRange", + "TunableHyperParams", + "TuningRange", + "get_search_space", + "run_optuna_study", + "suggest_hyperparams", +] diff --git a/pyproject.toml b/pyproject.toml index 9ef3469cb..bb16f15cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ classifiers = [ dependencies = [ "openstef-core", "openstef-models[xgb-cpu]", - "optuna>=4.7.0", + "optuna>=4.7", ] optional-dependencies.all = [