From c08f3206586b0a33be750b38ce717b6d1b3af87c Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Tue, 28 Jan 2025 03:17:41 +0000 Subject: [PATCH] build based on b14ff11 --- previews/PR826/.documenter-siteinfo.json | 1 + previews/PR826/apireference/index.html | 773 ++++++++ previews/PR826/assets/Project.toml | 2 + .../deterministic_linear_policy_graph.png | Bin 0 -> 7325 bytes previews/PR826/assets/documenter.js | 1082 ++++++++++ previews/PR826/assets/enso_markovian.png | Bin 0 -> 65832 bytes previews/PR826/assets/hazard_decision.png | Bin 0 -> 120411 bytes previews/PR826/assets/logo.ico | Bin 0 -> 1150 bytes previews/PR826/assets/logo.png | Bin 0 -> 22236 bytes previews/PR826/assets/logo.svg | 72 + previews/PR826/assets/logo_builder.jl | 58 + previews/PR826/assets/logo_text.png | Bin 0 -> 48547 bytes previews/PR826/assets/logo_without_text.svg | 40 + previews/PR826/assets/powder_policy_graph.png | Bin 0 -> 174932 bytes previews/PR826/assets/publication_plot.png | Bin 0 -> 26535 bytes previews/PR826/assets/spaghetti_plot.html | 237 +++ .../assets/stochastic_linear_policy_graph.png | Bin 0 -> 10220 bytes .../stochastic_markovian_policy_graph.png | Bin 0 -> 30279 bytes .../PR826/assets/themes/catppuccin-frappe.css | 1 + .../PR826/assets/themes/catppuccin-latte.css | 1 + .../assets/themes/catppuccin-macchiato.css | 1 + .../PR826/assets/themes/catppuccin-mocha.css | 1 + .../PR826/assets/themes/documenter-dark.css | 7 + .../PR826/assets/themes/documenter-light.css | 9 + previews/PR826/assets/themeswap.js | 84 + previews/PR826/assets/value_function.html | 29 + previews/PR826/assets/warner.js | 52 + previews/PR826/changelog/index.html | 6 + .../PR826/examples/FAST_hydro_thermal.ipynb | 78 + previews/PR826/examples/FAST_hydro_thermal.jl | 46 + .../examples/FAST_hydro_thermal/index.html | 78 + .../examples/FAST_production_management.ipynb | 74 + .../examples/FAST_production_management.jl | 42 + .../FAST_production_management/index.html | 38 + previews/PR826/examples/FAST_quickstart.ipynb | 72 + previews/PR826/examples/FAST_quickstart.jl | 40 + .../PR826/examples/FAST_quickstart/index.html | 36 + previews/PR826/examples/Hydro_thermal.ipynb | 268 +++ previews/PR826/examples/Hydro_thermal.jl | 111 ++ .../PR826/examples/Hydro_thermal/index.html | 77 + previews/PR826/examples/SDDP.log | 1608 +++++++++++++++ previews/PR826/examples/SDDP_0.0.log | 32 + previews/PR826/examples/SDDP_0.0625.log | 33 + previews/PR826/examples/SDDP_0.125.log | 33 + previews/PR826/examples/SDDP_0.25.log | 33 + previews/PR826/examples/SDDP_0.375.log | 33 + previews/PR826/examples/SDDP_0.5.log | 33 + previews/PR826/examples/SDDP_0.625.log | 33 + previews/PR826/examples/SDDP_0.75.log | 33 + previews/PR826/examples/SDDP_0.875.log | 33 + previews/PR826/examples/SDDP_1.0.log | 33 + ...tochDynamicProgramming.jl_multistock.ipynb | 93 + .../StochDynamicProgramming.jl_multistock.jl | 61 + .../index.html | 101 + .../StochDynamicProgramming.jl_stock.ipynb | 71 + .../StochDynamicProgramming.jl_stock.jl | 39 + .../index.html | 87 + ...StructDualDynProg.jl_prob5.2_2stages.ipynb | 97 + .../StructDualDynProg.jl_prob5.2_2stages.jl | 65 + .../index.html | 101 + ...StructDualDynProg.jl_prob5.2_3stages.ipynb | 92 + .../StructDualDynProg.jl_prob5.2_3stages.jl | 60 + .../index.html | 96 + .../examples/agriculture_mccardle_farm.ipynb | 182 ++ .../examples/agriculture_mccardle_farm.jl | 140 ++ .../agriculture_mccardle_farm/index.html | 127 ++ .../PR826/examples/air_conditioning.ipynb | 116 ++ previews/PR826/examples/air_conditioning.jl | 74 + .../examples/air_conditioning/index.html | 132 ++ .../examples/air_conditioning_forward.ipynb | 69 + .../examples/air_conditioning_forward.jl | 42 + .../air_conditioning_forward/index.html | 40 + previews/PR826/examples/all_blacks.ipynb | 67 + previews/PR826/examples/all_blacks.jl | 40 + previews/PR826/examples/all_blacks/index.html | 73 + .../examples/asset_management_simple.ipynb | 91 + .../PR826/examples/asset_management_simple.jl | 59 + .../asset_management_simple/index.html | 92 + .../examples/asset_management_stagewise.ipynb | 108 + .../examples/asset_management_stagewise.jl | 76 + .../asset_management_stagewise/index.html | 145 ++ previews/PR826/examples/belief.ipynb | 99 + previews/PR826/examples/belief.jl | 72 + previews/PR826/examples/belief/index.html | 114 ++ .../PR826/examples/biobjective_hydro.ipynb | 91 + previews/PR826/examples/biobjective_hydro.jl | 64 + .../examples/biobjective_hydro/index.html | 389 ++++ .../PR826/examples/booking_management.ipynb | 152 ++ previews/PR826/examples/booking_management.jl | 110 ++ .../examples/booking_management/index.html | 99 + .../PR826/examples/generation_expansion.ipynb | 120 ++ .../PR826/examples/generation_expansion.jl | 93 + .../examples/generation_expansion/index.html | 175 ++ previews/PR826/examples/hydro_valley.ipynb | 358 ++++ previews/PR826/examples/hydro_valley.jl | 306 +++ .../PR826/examples/hydro_valley/index.html | 283 +++ .../infinite_horizon_hydro_thermal.ipynb | 100 + .../infinite_horizon_hydro_thermal.jl | 73 + .../infinite_horizon_hydro_thermal/index.html | 149 ++ .../examples/infinite_horizon_trivial.ipynb | 57 + .../examples/infinite_horizon_trivial.jl | 30 + .../infinite_horizon_trivial/index.html | 63 + .../PR826/examples/no_strong_duality.ipynb | 64 + previews/PR826/examples/no_strong_duality.jl | 32 + .../examples/no_strong_duality/index.html | 60 + .../examples/objective_state_newsvendor.ipynb | 114 ++ .../examples/objective_state_newsvendor.jl | 81 + .../objective_state_newsvendor/index.html | 289 +++ .../PR826/examples/sldp_example_one.ipynb | 79 + previews/PR826/examples/sldp_example_one.jl | 47 + .../examples/sldp_example_one/index.html | 84 + .../PR826/examples/sldp_example_two.ipynb | 105 + previews/PR826/examples/sldp_example_two.jl | 73 + .../examples/sldp_example_two/index.html | 191 ++ .../examples/stochastic_all_blacks.ipynb | 82 + .../PR826/examples/stochastic_all_blacks.jl | 55 + .../examples/stochastic_all_blacks/index.html | 90 + .../PR826/examples/the_farmers_problem.ipynb | 614 ++++++ .../PR826/examples/the_farmers_problem.jl | 258 +++ .../examples/the_farmers_problem/index.html | 137 ++ .../PR826/examples/vehicle_location.ipynb | 171 ++ previews/PR826/examples/vehicle_location.jl | 129 ++ .../examples/vehicle_location/index.html | 111 ++ previews/PR826/explanation/risk.ipynb | 1752 +++++++++++++++++ previews/PR826/explanation/risk.jl | 940 +++++++++ previews/PR826/explanation/risk/index.html | 539 +++++ previews/PR826/explanation/theory_intro.ipynb | 1509 ++++++++++++++ previews/PR826/explanation/theory_intro.jl | 818 ++++++++ .../PR826/explanation/theory_intro/index.html | 921 +++++++++ .../access_previous_variables/index.html | 101 + .../index.html | 22 + .../guides/add_a_risk_measure/index.html | 27 + .../PR826/guides/add_integrality/index.html | 28 + .../add_multidimensional_noise/index.html | 87 + .../index.html | 18 + .../guides/choose_a_stopping_rule/index.html | 24 + .../guides/create_a_belief_state/index.html | 37 + .../create_a_general_policy_graph/index.html | 113 ++ .../PR826/guides/debug_a_model/index.html | 71 + .../index.html | 15 + .../index.html | 168 ++ previews/PR826/index.html | 50 + previews/PR826/objects.inv | Bin 0 -> 10118 bytes previews/PR826/release_notes/index.html | 6 + previews/PR826/search_index.js | 3 + previews/PR826/siteinfo.js | 1 + previews/PR826/tutorial/SDDP.log | 819 ++++++++ previews/PR826/tutorial/arma.ipynb | 404 ++++ previews/PR826/tutorial/arma.jl | 217 ++ previews/PR826/tutorial/arma/index.html | 134 ++ previews/PR826/tutorial/convex.cuts.json | 1 + previews/PR826/tutorial/decision_hazard.ipynb | 357 ++++ previews/PR826/tutorial/decision_hazard.jl | 176 ++ .../PR826/tutorial/decision_hazard/index.html | 77 + .../tutorial/example_milk_producer.ipynb | 513 +++++ .../PR826/tutorial/example_milk_producer.jl | 255 +++ .../example_milk_producer/058d9f82.svg | 625 ++++++ .../example_milk_producer/09925323.svg | 544 +++++ .../example_milk_producer/2ddc7ff5.svg | 142 ++ .../tutorial/example_milk_producer/index.html | 184 ++ .../PR826/tutorial/example_newsvendor.ipynb | 948 +++++++++ previews/PR826/tutorial/example_newsvendor.jl | 462 +++++ .../tutorial/example_newsvendor/01120dfc.svg | 37 + .../tutorial/example_newsvendor/b847ac9f.svg | 97 + .../tutorial/example_newsvendor/index.html | 384 ++++ .../PR826/tutorial/example_reservoir.ipynb | 1020 ++++++++++ previews/PR826/tutorial/example_reservoir.jl | 429 ++++ .../tutorial/example_reservoir/0ecd7287.svg | 109 + .../tutorial/example_reservoir/1b42dc01.svg | 46 + .../tutorial/example_reservoir/452ae0d0.svg | 148 ++ .../tutorial/example_reservoir/51620718.svg | 86 + .../tutorial/example_reservoir/570c16ca.svg | 52 + .../tutorial/example_reservoir/8ee90425.svg | 52 + .../tutorial/example_reservoir/92ce3a4c.svg | 46 + .../tutorial/example_reservoir/index.html | 504 +++++ previews/PR826/tutorial/first_steps.ipynb | 1751 ++++++++++++++++ previews/PR826/tutorial/first_steps.jl | 912 +++++++++ .../PR826/tutorial/first_steps/index.html | 295 +++ previews/PR826/tutorial/inventory.ipynb | 375 ++++ previews/PR826/tutorial/inventory.jl | 192 ++ .../PR826/tutorial/inventory/084e6905.svg | 57 + .../PR826/tutorial/inventory/2a021f08.svg | 51 + previews/PR826/tutorial/inventory/index.html | 203 ++ .../PR826/tutorial/markov_uncertainty.ipynb | 250 +++ previews/PR826/tutorial/markov_uncertainty.jl | 135 ++ .../tutorial/markov_uncertainty/index.html | 110 ++ previews/PR826/tutorial/mdps.ipynb | 427 ++++ previews/PR826/tutorial/mdps.jl | 211 ++ previews/PR826/tutorial/mdps/index.html | 182 ++ .../model_infeasible_node_2.cuts.json | 1 + .../PR826/tutorial/objective_states.ipynb | 441 +++++ previews/PR826/tutorial/objective_states.jl | 253 +++ .../tutorial/objective_states/index.html | 190 ++ .../tutorial/objective_uncertainty.ipynb | 166 ++ .../PR826/tutorial/objective_uncertainty.jl | 92 + .../tutorial/objective_uncertainty/index.html | 98 + previews/PR826/tutorial/pglib_opf.ipynb | 311 +++ previews/PR826/tutorial/pglib_opf.jl | 130 ++ previews/PR826/tutorial/pglib_opf/index.html | 128 ++ previews/PR826/tutorial/pglib_opf_case5_pjm.m | 116 ++ previews/PR826/tutorial/plotting.ipynb | 407 ++++ previews/PR826/tutorial/plotting.jl | 201 ++ previews/PR826/tutorial/plotting/e5228a78.svg | 84 + previews/PR826/tutorial/plotting/index.html | 110 ++ previews/PR826/tutorial/spaghetti_plot.html | 238 +++ previews/PR826/tutorial/subproblem_2.mof.json | 1 + previews/PR826/tutorial/warnings.ipynb | 370 ++++ previews/PR826/tutorial/warnings.jl | 190 ++ previews/PR826/tutorial/warnings/index.html | 146 ++ 209 files changed, 38794 insertions(+) create mode 100644 previews/PR826/.documenter-siteinfo.json create mode 100644 previews/PR826/apireference/index.html create mode 100644 previews/PR826/assets/Project.toml create mode 100644 previews/PR826/assets/deterministic_linear_policy_graph.png create mode 100644 previews/PR826/assets/documenter.js create mode 100644 previews/PR826/assets/enso_markovian.png create mode 100644 previews/PR826/assets/hazard_decision.png create mode 100644 previews/PR826/assets/logo.ico create mode 100644 previews/PR826/assets/logo.png create mode 100644 previews/PR826/assets/logo.svg create mode 100644 previews/PR826/assets/logo_builder.jl create mode 100644 previews/PR826/assets/logo_text.png create mode 100644 previews/PR826/assets/logo_without_text.svg create mode 100644 previews/PR826/assets/powder_policy_graph.png create mode 100644 previews/PR826/assets/publication_plot.png create mode 100644 previews/PR826/assets/spaghetti_plot.html create mode 100644 previews/PR826/assets/stochastic_linear_policy_graph.png create mode 100644 previews/PR826/assets/stochastic_markovian_policy_graph.png create mode 100644 previews/PR826/assets/themes/catppuccin-frappe.css create mode 100644 previews/PR826/assets/themes/catppuccin-latte.css create mode 100644 previews/PR826/assets/themes/catppuccin-macchiato.css create mode 100644 previews/PR826/assets/themes/catppuccin-mocha.css create mode 100644 previews/PR826/assets/themes/documenter-dark.css create mode 100644 previews/PR826/assets/themes/documenter-light.css create mode 100644 previews/PR826/assets/themeswap.js create mode 100644 previews/PR826/assets/value_function.html create mode 100644 previews/PR826/assets/warner.js create mode 100644 previews/PR826/changelog/index.html create mode 100644 previews/PR826/examples/FAST_hydro_thermal.ipynb create mode 100644 previews/PR826/examples/FAST_hydro_thermal.jl create mode 100644 previews/PR826/examples/FAST_hydro_thermal/index.html create mode 100644 previews/PR826/examples/FAST_production_management.ipynb create mode 100644 previews/PR826/examples/FAST_production_management.jl create mode 100644 previews/PR826/examples/FAST_production_management/index.html create mode 100644 previews/PR826/examples/FAST_quickstart.ipynb create mode 100644 previews/PR826/examples/FAST_quickstart.jl create mode 100644 previews/PR826/examples/FAST_quickstart/index.html create mode 100644 previews/PR826/examples/Hydro_thermal.ipynb create mode 100644 previews/PR826/examples/Hydro_thermal.jl create mode 100644 previews/PR826/examples/Hydro_thermal/index.html create mode 100644 previews/PR826/examples/SDDP.log create mode 100644 previews/PR826/examples/SDDP_0.0.log create mode 100644 previews/PR826/examples/SDDP_0.0625.log create mode 100644 previews/PR826/examples/SDDP_0.125.log create mode 100644 previews/PR826/examples/SDDP_0.25.log create mode 100644 previews/PR826/examples/SDDP_0.375.log create mode 100644 previews/PR826/examples/SDDP_0.5.log create mode 100644 previews/PR826/examples/SDDP_0.625.log create mode 100644 previews/PR826/examples/SDDP_0.75.log create mode 100644 previews/PR826/examples/SDDP_0.875.log create mode 100644 previews/PR826/examples/SDDP_1.0.log create mode 100644 previews/PR826/examples/StochDynamicProgramming.jl_multistock.ipynb create mode 100644 previews/PR826/examples/StochDynamicProgramming.jl_multistock.jl create mode 100644 previews/PR826/examples/StochDynamicProgramming.jl_multistock/index.html create mode 100644 previews/PR826/examples/StochDynamicProgramming.jl_stock.ipynb create mode 100644 previews/PR826/examples/StochDynamicProgramming.jl_stock.jl create mode 100644 previews/PR826/examples/StochDynamicProgramming.jl_stock/index.html create mode 100644 previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.ipynb create mode 100644 previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.jl create mode 100644 previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages/index.html create mode 100644 previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.ipynb create mode 100644 previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.jl create mode 100644 previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages/index.html create mode 100644 previews/PR826/examples/agriculture_mccardle_farm.ipynb create mode 100644 previews/PR826/examples/agriculture_mccardle_farm.jl create mode 100644 previews/PR826/examples/agriculture_mccardle_farm/index.html create mode 100644 previews/PR826/examples/air_conditioning.ipynb create mode 100644 previews/PR826/examples/air_conditioning.jl create mode 100644 previews/PR826/examples/air_conditioning/index.html create mode 100644 previews/PR826/examples/air_conditioning_forward.ipynb create mode 100644 previews/PR826/examples/air_conditioning_forward.jl create mode 100644 previews/PR826/examples/air_conditioning_forward/index.html create mode 100644 previews/PR826/examples/all_blacks.ipynb create mode 100644 previews/PR826/examples/all_blacks.jl create mode 100644 previews/PR826/examples/all_blacks/index.html create mode 100644 previews/PR826/examples/asset_management_simple.ipynb create mode 100644 previews/PR826/examples/asset_management_simple.jl create mode 100644 previews/PR826/examples/asset_management_simple/index.html create mode 100644 previews/PR826/examples/asset_management_stagewise.ipynb create mode 100644 previews/PR826/examples/asset_management_stagewise.jl create mode 100644 previews/PR826/examples/asset_management_stagewise/index.html create mode 100644 previews/PR826/examples/belief.ipynb create mode 100644 previews/PR826/examples/belief.jl create mode 100644 previews/PR826/examples/belief/index.html create mode 100644 previews/PR826/examples/biobjective_hydro.ipynb create mode 100644 previews/PR826/examples/biobjective_hydro.jl create mode 100644 previews/PR826/examples/biobjective_hydro/index.html create mode 100644 previews/PR826/examples/booking_management.ipynb create mode 100644 previews/PR826/examples/booking_management.jl create mode 100644 previews/PR826/examples/booking_management/index.html create mode 100644 previews/PR826/examples/generation_expansion.ipynb create mode 100644 previews/PR826/examples/generation_expansion.jl create mode 100644 previews/PR826/examples/generation_expansion/index.html create mode 100644 previews/PR826/examples/hydro_valley.ipynb create mode 100644 previews/PR826/examples/hydro_valley.jl create mode 100644 previews/PR826/examples/hydro_valley/index.html create mode 100644 previews/PR826/examples/infinite_horizon_hydro_thermal.ipynb create mode 100644 previews/PR826/examples/infinite_horizon_hydro_thermal.jl create mode 100644 previews/PR826/examples/infinite_horizon_hydro_thermal/index.html create mode 100644 previews/PR826/examples/infinite_horizon_trivial.ipynb create mode 100644 previews/PR826/examples/infinite_horizon_trivial.jl create mode 100644 previews/PR826/examples/infinite_horizon_trivial/index.html create mode 100644 previews/PR826/examples/no_strong_duality.ipynb create mode 100644 previews/PR826/examples/no_strong_duality.jl create mode 100644 previews/PR826/examples/no_strong_duality/index.html create mode 100644 previews/PR826/examples/objective_state_newsvendor.ipynb create mode 100644 previews/PR826/examples/objective_state_newsvendor.jl create mode 100644 previews/PR826/examples/objective_state_newsvendor/index.html create mode 100644 previews/PR826/examples/sldp_example_one.ipynb create mode 100644 previews/PR826/examples/sldp_example_one.jl create mode 100644 previews/PR826/examples/sldp_example_one/index.html create mode 100644 previews/PR826/examples/sldp_example_two.ipynb create mode 100644 previews/PR826/examples/sldp_example_two.jl create mode 100644 previews/PR826/examples/sldp_example_two/index.html create mode 100644 previews/PR826/examples/stochastic_all_blacks.ipynb create mode 100644 previews/PR826/examples/stochastic_all_blacks.jl create mode 100644 previews/PR826/examples/stochastic_all_blacks/index.html create mode 100644 previews/PR826/examples/the_farmers_problem.ipynb create mode 100644 previews/PR826/examples/the_farmers_problem.jl create mode 100644 previews/PR826/examples/the_farmers_problem/index.html create mode 100644 previews/PR826/examples/vehicle_location.ipynb create mode 100644 previews/PR826/examples/vehicle_location.jl create mode 100644 previews/PR826/examples/vehicle_location/index.html create mode 100644 previews/PR826/explanation/risk.ipynb create mode 100644 previews/PR826/explanation/risk.jl create mode 100644 previews/PR826/explanation/risk/index.html create mode 100644 previews/PR826/explanation/theory_intro.ipynb create mode 100644 previews/PR826/explanation/theory_intro.jl create mode 100644 previews/PR826/explanation/theory_intro/index.html create mode 100644 previews/PR826/guides/access_previous_variables/index.html create mode 100644 previews/PR826/guides/add_a_multidimensional_state_variable/index.html create mode 100644 previews/PR826/guides/add_a_risk_measure/index.html create mode 100644 previews/PR826/guides/add_integrality/index.html create mode 100644 previews/PR826/guides/add_multidimensional_noise/index.html create mode 100644 previews/PR826/guides/add_noise_in_the_constraint_matrix/index.html create mode 100644 previews/PR826/guides/choose_a_stopping_rule/index.html create mode 100644 previews/PR826/guides/create_a_belief_state/index.html create mode 100644 previews/PR826/guides/create_a_general_policy_graph/index.html create mode 100644 previews/PR826/guides/debug_a_model/index.html create mode 100644 previews/PR826/guides/improve_computational_performance/index.html create mode 100644 previews/PR826/guides/simulate_using_a_different_sampling_scheme/index.html create mode 100644 previews/PR826/index.html create mode 100644 previews/PR826/objects.inv create mode 100644 previews/PR826/release_notes/index.html create mode 100644 previews/PR826/search_index.js create mode 100644 previews/PR826/siteinfo.js create mode 100644 previews/PR826/tutorial/SDDP.log create mode 100644 previews/PR826/tutorial/arma.ipynb create mode 100644 previews/PR826/tutorial/arma.jl create mode 100644 previews/PR826/tutorial/arma/index.html create mode 100644 previews/PR826/tutorial/convex.cuts.json create mode 100644 previews/PR826/tutorial/decision_hazard.ipynb create mode 100644 previews/PR826/tutorial/decision_hazard.jl create mode 100644 previews/PR826/tutorial/decision_hazard/index.html create mode 100644 previews/PR826/tutorial/example_milk_producer.ipynb create mode 100644 previews/PR826/tutorial/example_milk_producer.jl create mode 100644 previews/PR826/tutorial/example_milk_producer/058d9f82.svg create mode 100644 previews/PR826/tutorial/example_milk_producer/09925323.svg create mode 100644 previews/PR826/tutorial/example_milk_producer/2ddc7ff5.svg create mode 100644 previews/PR826/tutorial/example_milk_producer/index.html create mode 100644 previews/PR826/tutorial/example_newsvendor.ipynb create mode 100644 previews/PR826/tutorial/example_newsvendor.jl create mode 100644 previews/PR826/tutorial/example_newsvendor/01120dfc.svg create mode 100644 previews/PR826/tutorial/example_newsvendor/b847ac9f.svg create mode 100644 previews/PR826/tutorial/example_newsvendor/index.html create mode 100644 previews/PR826/tutorial/example_reservoir.ipynb create mode 100644 previews/PR826/tutorial/example_reservoir.jl create mode 100644 previews/PR826/tutorial/example_reservoir/0ecd7287.svg create mode 100644 previews/PR826/tutorial/example_reservoir/1b42dc01.svg create mode 100644 previews/PR826/tutorial/example_reservoir/452ae0d0.svg create mode 100644 previews/PR826/tutorial/example_reservoir/51620718.svg create mode 100644 previews/PR826/tutorial/example_reservoir/570c16ca.svg create mode 100644 previews/PR826/tutorial/example_reservoir/8ee90425.svg create mode 100644 previews/PR826/tutorial/example_reservoir/92ce3a4c.svg create mode 100644 previews/PR826/tutorial/example_reservoir/index.html create mode 100644 previews/PR826/tutorial/first_steps.ipynb create mode 100644 previews/PR826/tutorial/first_steps.jl create mode 100644 previews/PR826/tutorial/first_steps/index.html create mode 100644 previews/PR826/tutorial/inventory.ipynb create mode 100644 previews/PR826/tutorial/inventory.jl create mode 100644 previews/PR826/tutorial/inventory/084e6905.svg create mode 100644 previews/PR826/tutorial/inventory/2a021f08.svg create mode 100644 previews/PR826/tutorial/inventory/index.html create mode 100644 previews/PR826/tutorial/markov_uncertainty.ipynb create mode 100644 previews/PR826/tutorial/markov_uncertainty.jl create mode 100644 previews/PR826/tutorial/markov_uncertainty/index.html create mode 100644 previews/PR826/tutorial/mdps.ipynb create mode 100644 previews/PR826/tutorial/mdps.jl create mode 100644 previews/PR826/tutorial/mdps/index.html create mode 100644 previews/PR826/tutorial/model_infeasible_node_2.cuts.json create mode 100644 previews/PR826/tutorial/objective_states.ipynb create mode 100644 previews/PR826/tutorial/objective_states.jl create mode 100644 previews/PR826/tutorial/objective_states/index.html create mode 100644 previews/PR826/tutorial/objective_uncertainty.ipynb create mode 100644 previews/PR826/tutorial/objective_uncertainty.jl create mode 100644 previews/PR826/tutorial/objective_uncertainty/index.html create mode 100644 previews/PR826/tutorial/pglib_opf.ipynb create mode 100644 previews/PR826/tutorial/pglib_opf.jl create mode 100644 previews/PR826/tutorial/pglib_opf/index.html create mode 100644 previews/PR826/tutorial/pglib_opf_case5_pjm.m create mode 100644 previews/PR826/tutorial/plotting.ipynb create mode 100644 previews/PR826/tutorial/plotting.jl create mode 100644 previews/PR826/tutorial/plotting/e5228a78.svg create mode 100644 previews/PR826/tutorial/plotting/index.html create mode 100644 previews/PR826/tutorial/spaghetti_plot.html create mode 100644 previews/PR826/tutorial/subproblem_2.mof.json create mode 100644 previews/PR826/tutorial/warnings.ipynb create mode 100644 previews/PR826/tutorial/warnings.jl create mode 100644 previews/PR826/tutorial/warnings/index.html diff --git a/previews/PR826/.documenter-siteinfo.json b/previews/PR826/.documenter-siteinfo.json new file mode 100644 index 0000000000..6aa5c77ed9 --- /dev/null +++ b/previews/PR826/.documenter-siteinfo.json @@ -0,0 +1 @@ +{"documenter":{"julia_version":"1.11.3","generation_timestamp":"2025-01-28T03:17:29","documenter_version":"1.8.0"}} \ No newline at end of file diff --git a/previews/PR826/apireference/index.html b/previews/PR826/apireference/index.html new file mode 100644 index 0000000000..8b9cc929e8 --- /dev/null +++ b/previews/PR826/apireference/index.html @@ -0,0 +1,773 @@ + +API Reference · SDDP.jl

API Reference

This page lists the public API of SDDP.jl. Any functions in SDDP that are not listed here are considered part of the private API and may change in any future release.

Info

This page is a semi-structured list of the SDDP.jl API. For a more structured overview, read the How-to guides or Tutorial parts of this documentation.

Load SDDP using:

using SDDP

SDDP exports only @stageobjective. Therefore, all other calls must be prefixed with SDDP..

Graph

SDDP.GraphType
Graph(root_node::T) where T

Create an empty graph struture with the root node root_node.

Example

julia> graph = SDDP.Graph(0)
+Root
+ 0
+Nodes
+ {}
+Arcs
+ {}
+
+julia> graph = SDDP.Graph(:root)
+Root
+ root
+Nodes
+ {}
+Arcs
+ {}
+
+julia> graph = SDDP.Graph((0, 0))
+Root
+ (0, 0)
+Nodes
+ {}
+Arcs
+ {}
source

add_node

SDDP.add_nodeFunction
add_node(graph::Graph{T}, node::T) where {T}

Add a node to the graph graph.

Examples

julia> graph = SDDP.Graph(:root);
+
+julia> SDDP.add_node(graph, :A)
+
+julia> graph
+Root
+ root
+Nodes
+ A
+Arcs
+ {}
julia> graph = SDDP.Graph(0);
+
+julia> SDDP.add_node(graph, 2)
+
+julia> graph
+Root
+ 0
+Nodes
+ 2
+Arcs
+ {}
source

add_edge

SDDP.add_edgeFunction
add_edge(graph::Graph{T}, edge::Pair{T, T}, probability::Float64) where {T}

Add an edge to the graph graph.

Examples

julia> graph = SDDP.Graph(0);
+
+julia> SDDP.add_node(graph, 1)
+
+julia> SDDP.add_edge(graph, 0 => 1, 0.9)
+
+julia> graph
+Root
+ 0
+Nodes
+ 1
+Arcs
+ 0 => 1 w.p. 0.9
julia> graph = SDDP.Graph(:root);
+
+julia> SDDP.add_node(graph, :A)
+
+julia> SDDP.add_edge(graph, :root => :A, 1.0)
+
+julia> graph
+Root
+ root
+Nodes
+ A
+Arcs
+ root => A w.p. 1.0
source

add_ambiguity_set

SDDP.add_ambiguity_setFunction
add_ambiguity_set(
+    graph::Graph{T},
+    set::Vector{T},
+    lipschitz::Vector{Float64},
+) where {T}

Add set to the belief partition of graph.

lipschitz is a vector of Lipschitz constants, with one element for each node in set. The Lipschitz constant is the maximum slope of the cost-to-go function with respect to the belief state associated with each node at any point in the state-space.

Examples

julia> graph = SDDP.LinearGraph(3)
+Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0
+
+julia> SDDP.add_ambiguity_set(graph, [1, 2], [1e3, 1e2])
+
+julia> SDDP.add_ambiguity_set(graph, [3], [1e5])
+
+julia> graph
+Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0
+Partitions
+ {1, 2}
+ {3}
source
add_ambiguity_set(graph::Graph{T}, set::Vector{T}, lipschitz::Float64)

Add set to the belief partition of graph.

lipschitz is a Lipschitz constant for each node in set. The Lipschitz constant is the maximum slope of the cost-to-go function with respect to the belief state associated with each node at any point in the state-space.

Examples

julia> graph = SDDP.LinearGraph(3);
+
+julia> SDDP.add_ambiguity_set(graph, [1, 2], 1e3)
+
+julia> SDDP.add_ambiguity_set(graph, [3], 1e5)
+
+julia> graph
+Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0
+Partitions
+ {1, 2}
+ {3}
source

LinearGraph

SDDP.LinearGraphFunction
LinearGraph(stages::Int)

Create a linear graph with stages number of nodes.

Examples

julia> graph = SDDP.LinearGraph(3)
+Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0
source

MarkovianGraph

SDDP.MarkovianGraphFunction
MarkovianGraph(transition_matrices::Vector{Matrix{Float64}})

Construct a Markovian graph from the vector of transition matrices.

transition_matrices[t][i, j] gives the probability of transitioning from Markov state i in stage t - 1 to Markov state j in stage t.

The dimension of the first transition matrix should be (1, N), and transition_matrics[1][1, i] is the probability of transitioning from the root node to the Markov state i.

Examples

julia> graph = SDDP.MarkovianGraph([ones(1, 1), [0.5 0.5], [0.8 0.2; 0.2 0.8]])
+Root
+ (0, 1)
+Nodes
+ (1, 1)
+ (2, 1)
+ (2, 2)
+ (3, 1)
+ (3, 2)
+Arcs
+ (0, 1) => (1, 1) w.p. 1.0
+ (1, 1) => (2, 1) w.p. 0.5
+ (1, 1) => (2, 2) w.p. 0.5
+ (2, 1) => (3, 1) w.p. 0.8
+ (2, 1) => (3, 2) w.p. 0.2
+ (2, 2) => (3, 1) w.p. 0.2
+ (2, 2) => (3, 2) w.p. 0.8
source
MarkovianGraph(;
+    stages::Int,
+    transition_matrix::Matrix{Float64},
+    root_node_transition::Vector{Float64},
+)

Construct a Markovian graph object with stages number of stages and time-independent Markov transition probabilities.

transition_matrix must be a square matrix, and the probability of transitioning from Markov state i in stage t to Markov state j in stage t + 1 is given by transition_matrix[i, j].

root_node_transition[i] is the probability of transitioning from the root node to Markov state i in the first stage.

Examples

julia> graph = SDDP.MarkovianGraph(;
+           stages = 3,
+           transition_matrix = [0.8 0.2; 0.2 0.8],
+           root_node_transition = [0.5, 0.5],
+       )
+Root
+ (0, 1)
+Nodes
+ (1, 1)
+ (1, 2)
+ (2, 1)
+ (2, 2)
+ (3, 1)
+ (3, 2)
+Arcs
+ (0, 1) => (1, 1) w.p. 0.5
+ (0, 1) => (1, 2) w.p. 0.5
+ (1, 1) => (2, 1) w.p. 0.8
+ (1, 1) => (2, 2) w.p. 0.2
+ (1, 2) => (2, 1) w.p. 0.2
+ (1, 2) => (2, 2) w.p. 0.8
+ (2, 1) => (3, 1) w.p. 0.8
+ (2, 1) => (3, 2) w.p. 0.2
+ (2, 2) => (3, 1) w.p. 0.2
+ (2, 2) => (3, 2) w.p. 0.8
source
MarkovianGraph(
+    simulator::Function;
+    budget::Union{Int,Vector{Int}},
+    scenarios::Int = 1000,
+)

Construct a Markovian graph by fitting Markov chain to scenarios generated by simulator().

budget is the total number of nodes in the resulting Markov chain. This can either be specified as a single Int, in which case we will attempt to intelligently distributed the nodes between stages. Alternatively, budget can be a Vector{Int}, which details the number of Markov state to have in each stage.

source

UnicyclicGraph

SDDP.UnicyclicGraphFunction
UnicyclicGraph(discount_factor::Float64; num_nodes::Int = 1)

Construct a graph composed of num_nodes nodes that form a single cycle, with a probability of discount_factor of continuing the cycle.

Examples

julia> graph = SDDP.UnicyclicGraph(0.9; num_nodes = 2)
+Root
+ 0
+Nodes
+ 1
+ 2
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 1 w.p. 0.9
source

LinearPolicyGraph

SDDP.LinearPolicyGraphFunction
LinearPolicyGraph(builder::Function; stages::Int, kwargs...)

Create a linear policy graph with stages number of stages.

Keyword arguments

  • stages: the number of stages in the graph

  • kwargs: other keyword arguments are passed to SDDP.PolicyGraph.

Examples

julia> SDDP.LinearPolicyGraph(; stages = 2, lower_bound = 0.0) do sp, t
+    # ... build model ...
+end
+A policy graph with 2 nodes.
+Node indices: 1, 2

is equivalent to

julia> graph = SDDP.LinearGraph(2);
+
+julia> SDDP.PolicyGraph(graph; lower_bound = 0.0) do sp, t
+    # ... build model ...
+end
+A policy graph with 2 nodes.
+Node indices: 1, 2
source

MarkovianPolicyGraph

SDDP.MarkovianPolicyGraphFunction
MarkovianPolicyGraph(
+    builder::Function;
+    transition_matrices::Vector{Array{Float64,2}},
+    kwargs...
+)

Create a Markovian policy graph based on the transition matrices given in transition_matrices.

Keyword arguments

  • transition_matrices[t][i, j] gives the probability of transitioning from Markov state i in stage t - 1 to Markov state j in stage t. The dimension of the first transition matrix should be (1, N), and transition_matrics[1][1, i] is the probability of transitioning from the root node to the Markov state i.

  • kwargs: other keyword arguments are passed to SDDP.PolicyGraph.

See also

See SDDP.MarkovianGraph for other ways of specifying a Markovian policy graph.

See SDDP.PolicyGraph for the other keyword arguments.

Examples

julia> SDDP.MarkovianPolicyGraph(;
+           transition_matrices = [ones(1, 1), [0.5 0.5], [0.8 0.2; 0.2 0.8]],
+           lower_bound = 0.0,
+       ) do sp, node
+           # ... build model ...
+       end
+A policy graph with 5 nodes.
+ Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)

is equivalent to

julia> graph = SDDP.MarkovianGraph([ones(1, 1), [0.5 0.5], [0.8 0.2; 0.2 0.8]]);
+
+julia> SDDP.PolicyGraph(graph; lower_bound = 0.0) do sp, t
+    # ... build model ...
+end
+A policy graph with 5 nodes.
+ Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)
source

PolicyGraph

SDDP.PolicyGraphType
PolicyGraph(
+    builder::Function,
+    graph::Graph{T};
+    sense::Symbol = :Min,
+    lower_bound = -Inf,
+    upper_bound = Inf,
+    optimizer = nothing,
+) where {T}

Construct a policy graph based on the graph structure of graph. (See SDDP.Graph for details.)

Keyword arguments

  • sense: whether we are minimizing (:Min) or maximizing (:Max).

  • lower_bound: if mimimizing, a valid lower bound for the cost to go in all subproblems.

  • upper_bound: if maximizing, a valid upper bound for the value to go in all subproblems.

  • optimizer: the optimizer to use for each of the subproblems

Examples

function builder(subproblem::JuMP.Model, index)
+    # ... subproblem definition ...
+end
+
+model = PolicyGraph(
+    builder,
+    graph;
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+)

Or, using the Julia do ... end syntax:

model = PolicyGraph(
+    graph;
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, index
+    # ... subproblem definitions ...
+end
source

@stageobjective

SDDP.@stageobjectiveMacro
@stageobjective(subproblem, expr)

Set the stage-objective of subproblem to expr.

Examples

@stageobjective(subproblem, 2x + y)
source

parameterize

SDDP.parameterizeFunction
parameterize(
+    modify::Function,
+    subproblem::JuMP.Model,
+    realizations::Vector{T},
+    probability::Vector{Float64} = fill(1.0 / length(realizations))
+) where {T}

Add a parameterization function modify to subproblem. The modify function takes one argument and modifies subproblem based on the realization of the noise sampled from realizations with corresponding probabilities probability.

In order to conduct an out-of-sample simulation, modify should accept arguments that are not in realizations (but still of type T).

Examples

SDDP.parameterize(subproblem, [1, 2, 3], [0.4, 0.3, 0.3]) do ω
+    JuMP.set_upper_bound(x, ω)
+end
source
parameterize(node::Node, noise)

Parameterize node node with the noise noise.

source

add_objective_state

SDDP.add_objective_stateFunction
add_objective_state(update::Function, subproblem::JuMP.Model; kwargs...)

Add an objective state variable to subproblem.

Required kwargs are:

  • initial_value: The initial value of the objective state variable at the root node.
  • lipschitz: The lipschitz constant of the objective state variable.

Setting a tight value for the lipschitz constant can significantly improve the speed of convergence.

Optional kwargs are:

  • lower_bound: A valid lower bound for the objective state variable. Can be -Inf.
  • upper_bound: A valid upper bound for the objective state variable. Can be +Inf.

Setting tight values for these optional variables can significantly improve the speed of convergence.

If the objective state is N-dimensional, each keyword argument must be an NTuple{N,Float64}. For example, initial_value = (0.0, 1.0).

source

objective_state

Noise

SDDP.NoiseType
Noise(support, probability)

An atom of a discrete random variable at the point of support support and associated probability probability.

source

numerical_stability_report

SDDP.numerical_stability_reportFunction
numerical_stability_report(
+    [io::IO = stdout,]
+    model::PolicyGraph;
+    by_node::Bool = false,
+    print::Bool = true,
+    warn::Bool = true,
+)

Print a report identifying possible numeric stability issues.

Keyword arguments

  • If by_node, print a report for each node in the graph.

  • If print, print to io.

  • If warn, warn if the coefficients may cause numerical issues.

source

train

SDDP.trainFunction
SDDP.train(model::PolicyGraph; kwargs...)

Train the policy for model.

Keyword arguments

  • iteration_limit::Int: number of iterations to conduct before termination.

  • time_limit::Float64: number of seconds to train before termination.

  • stoping_rules: a vector of SDDP.AbstractStoppingRules. Defaults to SimulationStoppingRule.

  • print_level::Int: control the level of printing to the screen. Defaults to 1. Set to 0 to disable all printing.

  • log_file::String: filepath at which to write a log of the training progress. Defaults to SDDP.log.

  • log_frequency::Int: control the frequency with which the logging is outputted (iterations/log). It must be at least 1. Defaults to 1.

  • log_every_seconds::Float64: control the frequency with which the logging is outputted (seconds/log). Defaults to 0.0.

  • log_every_iteration::Bool; over-rides log_frequency and log_every_seconds to force every iteration to be printed. Defaults to false.

  • run_numerical_stability_report::Bool: generate (and print) a numerical stability report prior to solve. Defaults to true.

  • refine_at_similar_nodes::Bool: if SDDP can detect that two nodes have the same children, it can cheaply add a cut discovered at one to the other. In almost all cases this should be set to true.

  • cut_deletion_minimum::Int: the minimum number of cuts to cache before deleting cuts from the subproblem. The impact on performance is solver specific; however, smaller values result in smaller subproblems (and therefore quicker solves), at the expense of more time spent performing cut selection.

  • risk_measure: the risk measure to use at each node. Defaults to Expectation.

  • root_node_risk_measure::AbstractRiskMeasure: the risk measure to use at the root node when computing the Bound column. Note that the choice of this option does not change the primal policy, and it applies only if the transition from the root node to the first stage is stochastic. Defaults to Expectation.

  • sampling_scheme: a sampling scheme to use on the forward pass of the algorithm. Defaults to InSampleMonteCarlo.

  • backward_sampling_scheme: a backward pass sampling scheme to use on the backward pass of the algorithm. Defaults to CompleteSampler.

  • cut_type: choose between SDDP.SINGLE_CUT and SDDP.MULTI_CUT versions of SDDP.

  • dashboard::Bool: open a visualization of the training over time. Defaults to false.

  • parallel_scheme::AbstractParallelScheme: specify a scheme for solving in parallel. Defaults to Threaded().

  • forward_pass::AbstractForwardPass: specify a scheme to use for the forward passes.

  • forward_pass_resampling_probability::Union{Nothing,Float64}: set to a value in (0, 1) to enable RiskAdjustedForwardPass. Defaults to nothing (disabled).

  • add_to_existing_cuts::Bool: set to true to allow training a model that was previously trained. Defaults to false.

  • duality_handler::AbstractDualityHandler: specify a duality handler to use when creating cuts.

  • post_iteration_callback::Function: a callback with the signature post_iteration_callback(::IterationResult) that is evaluated after each iteration of the algorithm.

There is also a special option for infinite horizon problems

  • cycle_discretization_delta: the maximum distance between states allowed on the forward pass. This is for advanced users only and needs to be used in conjunction with a different sampling_scheme.
source

termination_status

write_cuts_to_file

SDDP.write_cuts_to_fileFunction
write_cuts_to_file(
+    model::PolicyGraph{T},
+    filename::String;
+    kwargs...,
+) where {T}

Write the cuts that form the policy in model to filename in JSON format.

Keyword arguments

  • node_name_parser is a function which converts the name of each node into a string representation. It has the signature: node_name_parser(::T)::String.

  • write_only_selected_cuts write only the selected cuts to the json file. Defaults to false.

See also SDDP.read_cuts_from_file.

source

read_cuts_from_file

SDDP.read_cuts_from_fileFunction
read_cuts_from_file(
+    model::PolicyGraph{T},
+    filename::String;
+    kwargs...,
+) where {T}

Read cuts (saved using SDDP.write_cuts_to_file) from filename into model.

Since T can be an arbitrary Julia type, the conversion to JSON is lossy. When reading, read_cuts_from_file only supports T=Int, T=NTuple{N, Int}, and T=Symbol. If you have manually created a policy graph with a different node type T, provide a function node_name_parser with the signature

Keyword arguments

  • node_name_parser(T, name::String)::T where {T} that returns the name of each node given the string name name. If node_name_parser returns nothing, those cuts are skipped.

  • cut_selection::Bool run or not the cut selection algorithm when adding the cuts to the model.

See also SDDP.write_cuts_to_file.

source

write_log_to_csv

SDDP.write_log_to_csvFunction
write_log_to_csv(model::PolicyGraph, filename::String)

Write the log of the most recent training to a csv for post-analysis.

Assumes that the model has been trained via SDDP.train.

source

set_numerical_difficulty_callback

SDDP.set_numerical_difficulty_callbackFunction
set_numerical_difficulty_callback(
+    model::PolicyGraph,
+    callback::Function,
+)

Set a callback function callback(::PolicyGraph, ::Node; require_dual::Bool) that is run when the optimizer terminates without finding a primal solution (and dual solution if require_dual is true).

Default callback

The default callback is a small variation of:

function callback(::PolicyGraph, node::Node; require_dual::Bool)
+    MOI.Utilities.reset_optimizer(node.subproblem)
+    optimize!(node.subproblem)
+    return
+end

This callback is the default because a common issue is solvers declaring the infeasible because of numerical issues related to the large number of cutting planes. Resetting the subproblem–-and therefore starting from a fresh problem instead of warm-starting from the previous solution–-is often enough to fix the problem and allow more iterations.

Other callbacks

In cases where the problem is truely infeasible (not because of numerical issues ), it may be helpful to write out the irreducible infeasible subsystem (IIS) for debugging. For this use-case, use a callback as follows:

function callback(::PolicyGraph, node::Node; require_dual::Bool)
+    JuMP.compute_conflict!(node.suprobblem)
+    status = JuMP.get_attribute(node.subproblem, MOI.ConflictStatus())
+    if status == MOI.CONFLICT_FOUND
+        iis_model, _ = JuMP.copy_conflict(node.subproblem)
+        print(iis_model)
+    end
+    return
+end
+SDDP.set_numerical_difficulty_callback(model, callback)
source

AbstractStoppingRule

stopping_rule_status

convergence_test

SDDP.convergence_testFunction
convergence_test(
+    model::PolicyGraph,
+    log::Vector{Log},
+    ::AbstractStoppingRule,
+)::Bool

Return a Bool indicating if the algorithm should terminate the training.

source

IterationLimit

TimeLimit

SDDP.TimeLimitType
TimeLimit(limit::Float64)

Teriminate the algorithm after limit seconds of computation.

source

Statistical

SDDP.StatisticalType
Statistical(;
+    num_replications::Int,
+    iteration_period::Int = 1,
+    z_score::Float64 = 1.96,
+    verbose::Bool = true,
+    disable_warning::Bool = false,
+)

Perform an in-sample Monte Carlo simulation of the policy with num_replications replications every iteration_periods and terminate if the deterministic bound (lower if minimizing) falls into the confidence interval for the mean of the simulated cost.

If verbose = true, print the confidence interval.

If disable_warning = true, disable the warning telling you not to use this stopping rule (see below).

Why this stopping rule is not good

This stopping rule is one of the most common stopping rules seen in the literature. Don't follow the crowd. It is a poor choice for your model, and should be rarely used. Instead, you should use the default stopping rule, or use a fixed limit like a time or iteration limit.

To understand why this stopping rule is a bad idea, assume we have conducted num_replications simulations and the objectives are in a vector objectives::Vector{Float64}.

Our mean is μ = mean(objectives) and the half-width of the confidence interval is w = z_score * std(objectives) / sqrt(num_replications).

Many papers suggest terminating the algorithm once the deterministic bound (lower if minimizing, upper if maximizing) is contained within the confidence interval. That is, if μ - w <= bound <= μ + w. Even worse, some papers define an optimization gap of (μ + w) / bound (if minimizing) or (μ - w) / bound (if maximizing), and they terminate once the gap is less than a value like 1%.

Both of these approaches are misleading, and more often than not, they will result in terminating with a sub-optimal policy that performs worse than expected. There are two main reasons for this:

  1. The half-width depends on the number of replications. To reduce the computational cost, users are often tempted to choose a small number of replications. This increases the half-width and makes it more likely that the algorithm will stop early. But if we choose a large number of replications, then the computational cost is high, and we would have been better off to run a fixed number of iterations and use that computational time to run extra training iterations.
  2. The confidence interval assumes that the simulated values are normally distributed. In infinite horizon models, this is almost never the case. The distribution is usually closer to exponential or log-normal.

There is a third, more technical reason which relates to the conditional dependence of constructing multiple confidence intervals.

The default value of z_score = 1.96 corresponds to a 95% confidence interval. You should interpret the interval as "if we re-run this simulation 100 times, then the true mean will lie in the confidence interval 95 times out of 100." But if the bound is within the confidence interval, then we know the true mean cannot be better than the bound. Therfore, there is a more than 95% chance that the mean is within the interval.

A separate problem arises if we simulate, find that the bound is outside the confidence interval, keep training, and then re-simulate to compute a new confidence interval. Because we will terminate when the bound enters the confidence interval, the repeated construction of a confidence interval means that the unconditional probability that we terminate with a false positive is larger than 5% (there are now more chances that the sample mean is optimistic and that the confidence interval includes the bound but not the true mean). One fix is to simulate with a sequentially increasing number of replicates, so that the unconditional probability stays at 95%, but this runs into the problem of computational cost. For more information on sequential sampling, see, for example, Güzin Bayraksan, David P. Morton, (2011) A Sequential Sampling Procedure for Stochastic Programming. Operations Research 59(4):898-913.

source

BoundStalling

SDDP.BoundStallingType
BoundStalling(num_previous_iterations::Int, tolerance::Float64)

Teriminate the algorithm once the deterministic bound (lower if minimizing, upper if maximizing) fails to improve by more than tolerance in absolute terms for more than num_previous_iterations consecutve iterations, provided it has improved relative to the bound after the first iteration.

Checking for an improvement relative to the first iteration avoids early termination in a situation where the bound fails to improve for the first N iterations. This frequently happens in models with a large number of stages, where it takes time for the cuts to propogate backward enough to modify the bound of the root node.

source

StoppingChain

SDDP.StoppingChainType
StoppingChain(rules::AbstractStoppingRule...)

Terminate once all of the rules are statified.

This stopping rule short-circuits, so subsequent rules are only tested if the previous pass.

Examples

A stopping rule that runs 100 iterations, then checks for the bound stalling:

StoppingChain(IterationLimit(100), BoundStalling(5, 0.1))
source

SimulationStoppingRule

SDDP.SimulationStoppingRuleType
SimulationStoppingRule(;
+    sampling_scheme::AbstractSamplingScheme = SDDP.InSampleMonteCarlo(),
+    replications::Int = -1,
+    period::Int = -1,
+    distance_tol::Float64 = 1e-2,
+    bound_tol::Float64 = 1e-4,
+)

Terminate the algorithm using a mix of heuristics. Unless you know otherwise, this is typically a good default.

Termination criteria

First, we check that the deterministic bound has stabilized. That is, over the last five iterations, the deterministic bound has changed by less than an absolute or relative tolerance of bound_tol.

Then, if we have not done one in the last period iterations, we perform a primal simulation of the policy using replications out-of-sample realizations from sampling_scheme. The realizations are stored and re-used in each simulation. From each simulation, we record the value of the stage objective. We terminate the policy if each of the trajectories in two consecutive simulations differ by less than distance_tol.

By default, replications and period are -1, and SDDP.jl will guess good values for these. Over-ride the default behavior by setting an appropriate value.

Example

SDDP.train(model; stopping_rules = [SimulationStoppingRule()])
source

FirstStageStoppingRule

SDDP.FirstStageStoppingRuleType
FirstStageStoppingRule(; atol::Float64 = 1e-3, iterations::Int = 50)

Terminate the algorithm when the outgoing values of the first-stage state variables have not changed by more than atol for iterations number of consecutive iterations.

Example

SDDP.train(model; stopping_rules = [FirstStageStoppingRule()])
source

AbstractSamplingScheme

sample_scenario

SDDP.sample_scenarioFunction
sample_scenario(graph::PolicyGraph{T}, ::AbstractSamplingScheme) where {T}

Sample a scenario from the policy graph graph based on the sampling scheme.

Returns ::Tuple{Vector{Tuple{T, <:Any}}, Bool}, where the first element is the scenario, and the second element is a Boolean flag indicating if the scenario was terminated due to the detection of a cycle.

The scenario is a list of tuples (type Vector{Tuple{T, <:Any}}) where the first component of each tuple is the index of the node, and the second component is the stagewise-independent noise term observed in that node.

source

InSampleMonteCarlo

SDDP.InSampleMonteCarloType
InSampleMonteCarlo(;
+    max_depth::Int = 0,
+    terminate_on_cycle::Function = false,
+    terminate_on_dummy_leaf::Function = true,
+    rollout_limit::Function = (i::Int) -> typemax(Int),
+    initial_node::Any = nothing,
+)

A Monte Carlo sampling scheme using the in-sample data from the policy graph definition.

If terminate_on_cycle, terminate the forward pass once a cycle is detected. If max_depth > 0, return once max_depth nodes have been sampled. If terminate_on_dummy_leaf, terminate the forward pass with 1 - probability of sampling a child node.

Note that if terminate_on_cycle = false and terminate_on_dummy_leaf = false then max_depth must be set > 0.

Control which node the trajectories start from using initial_node. If it is left as nothing, the root node is used as the starting node.

You can use rollout_limit to set iteration specific depth limits. For example:

InSampleMonteCarlo(rollout_limit = i -> 2 * i)
source

OutOfSampleMonteCarlo

SDDP.OutOfSampleMonteCarloType
OutOfSampleMonteCarlo(
+    f::Function,
+    graph::PolicyGraph;
+    use_insample_transition::Bool = false,
+    max_depth::Int = 0,
+    terminate_on_cycle::Bool = false,
+    terminate_on_dummy_leaf::Bool = true,
+    rollout_limit::Function = i -> typemax(Int),
+    initial_node = nothing,
+)

Create a Monte Carlo sampler using out-of-sample probabilities and/or supports for the stagewise-independent noise terms, and out-of-sample probabilities for the node-transition matrix.

f is a function that takes the name of a node and returns a tuple containing a vector of new SDDP.Noise terms for the children of that node, and a vector of new SDDP.Noise terms for the stagewise-independent noise.

If f is called with the name of the root node (e.g., 0 in a linear policy graph, (0, 1) in a Markovian Policy Graph), then return a vector of SDDP.Noise for the children of the root node.

If use_insample_transition, the in-sample transition probabilities will be used. Therefore, f should only return a vector of the stagewise-independent noise terms, and f will not be called for the root node.

If terminate_on_cycle, terminate the forward pass once a cycle is detected. If max_depth > 0, return once max_depth nodes have been sampled. If terminate_on_dummy_leaf, terminate the forward pass with 1 - probability of sampling a child node.

Note that if terminate_on_cycle = false and terminate_on_dummy_leaf = false then max_depth must be set > 0.

Control which node the trajectories start from using initial_node. If it is left as nothing, the root node is used as the starting node.

If a node is deterministic, pass [SDDP.Noise(nothing, 1.0)] as the vector of noise terms.

You can use rollout_limit to set iteration specific depth limits. For example:

OutOfSampleMonteCarlo(rollout_limit = i -> 2 * i)

Examples

Given linear policy graph graph with T stages:

sampler = OutOfSampleMonteCarlo(graph) do node
+    if node == 0
+        return [SDDP.Noise(1, 1.0)]
+    else
+        noise_terms = [SDDP.Noise(node, 0.3), SDDP.Noise(node + 1, 0.7)]
+        children = node < T ? [SDDP.Noise(node + 1, 0.9)] : SDDP.Noise{Int}[]
+        return children, noise_terms
+    end
+end

Given linear policy graph graph with T stages:

sampler = OutOfSampleMonteCarlo(graph, use_insample_transition=true) do node
+    return [SDDP.Noise(node, 0.3), SDDP.Noise(node + 1, 0.7)]
+end
source

Historical

SDDP.HistoricalType
Historical(
+    scenarios::Vector{Vector{Tuple{T,S}}},
+    probability::Vector{Float64};
+    terminate_on_cycle::Bool = false,
+) where {T,S}

A sampling scheme that samples a scenario from the vector of scenarios scenarios according to probability.

Examples

Historical(
+    [
+        [(1, 0.5), (2, 1.0), (3, 0.5)],
+        [(1, 0.5), (2, 0.0), (3, 1.0)],
+        [(1, 1.0), (2, 0.0), (3, 0.0)]
+    ],
+    [0.2, 0.5, 0.3],
+)
source
Historical(
+    scenarios::Vector{Vector{Tuple{T,S}}};
+    terminate_on_cycle::Bool = false,
+) where {T,S}

A deterministic sampling scheme that iterates through the vector of provided scenarios.

Examples

Historical([
+    [(1, 0.5), (2, 1.0), (3, 0.5)],
+    [(1, 0.5), (2, 0.0), (3, 1.0)],
+    [(1, 1.0), (2, 0.0), (3, 0.0)],
+])
source
Historical(
+    scenario::Vector{Tuple{T,S}};
+    terminate_on_cycle::Bool = false,
+) where {T,S}

A deterministic sampling scheme that always samples scenario.

Examples

Historical([(1, 0.5), (2, 1.5), (3, 0.75)])
source

PSRSamplingScheme

SDDP.PSRSamplingSchemeType
PSRSamplingScheme(N::Int; sampling_scheme = InSampleMonteCarlo())

A sampling scheme with N scenarios, similar to how PSR does it.

source

SimulatorSamplingScheme

SDDP.SimulatorSamplingSchemeType
SimulatorSamplingScheme(simulator::Function)

Create a sampling scheme based on a univariate scenario generator simulator, which returns a Vector{Float64} when called with no arguments like simulator().

This sampling scheme must be used with a Markovian graph constructed from the same simulator.

The sample space for SDDP.parameterize must be a tuple with 1 or 2 values, value is the Markov state and the second value is the random variable for the current node. If the node is deterministic, use Ω = [(markov_state,)].

This sampling scheme generates a new scenario by calling simulator(), and then picking the sequence of nodes in the Markovian graph that is closest to the new trajectory.

Example

julia> using SDDP
+
+julia> import HiGHS
+
+julia> simulator() = cumsum(rand(10))
+simulator (generic function with 1 method)
+
+julia> model = SDDP.PolicyGraph(
+           SDDP.MarkovianGraph(simulator; budget = 20, scenarios = 100);
+           sense = :Max,
+           upper_bound = 12,
+           optimizer = HiGHS.Optimizer,
+       ) do sp, node
+           t, markov_state = node
+           @variable(sp, x >= 0, SDDP.State, initial_value = 1)
+           @variable(sp, u >= 0)
+           @constraint(sp, x.out == x.in - u)
+           # Elements of Ω MUST be a tuple in which `markov_state` is the first
+           # element.
+           Ω = [(markov_state, (u = u_max,)) for u_max in (0.0, 0.5)]
+           SDDP.parameterize(sp, Ω) do (markov_state, ω)
+               set_upper_bound(u, ω.u)
+               @stageobjective(sp, markov_state * u)
+           end
+       end;
+
+julia> SDDP.train(
+           model;
+           print_level = 0,
+           iteration_limit = 10,
+           sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),
+       )
+
source

AbstractParallelScheme

Serial

Threaded

SDDP.ThreadedType
Threaded()

Run SDDP in multi-threaded mode.

Use julia --threads N to start Julia with N threads. In most cases, you should pick N to be the number of physical cores on your machine.

Danger

This plug-in is experimental, and parts of SDDP.jl may not be threadsafe. If you encounter any problems or crashes, please open a GitHub issue.

Example

SDDP.train(model; parallel_scheme = SDDP.Threaded())
+SDDP.simulate(model; parallel_scheme = SDDP.Threaded())
source

Asynchronous

SDDP.AsynchronousType
Asynchronous(
+    [init_callback::Function,]
+    slave_pids::Vector{Int} = workers();
+    use_master::Bool = true,
+)

Run SDDP in asynchronous mode workers with pid's slave_pids.

After initializing the models on each worker, call init_callback(model). Note that init_callback is run locally on the worker and not on the master thread.

If use_master is true, iterations are also conducted on the master process.

source
Asynchronous(
+    solver::Any,
+    slave_pids::Vector{Int} = workers();
+    use_master::Bool = true,
+)

Run SDDP in asynchronous mode workers with pid's slave_pids.

Set the optimizer on each worker by calling JuMP.set_optimizer(model, solver).

source

AbstractForwardPass

DefaultForwardPass

SDDP.DefaultForwardPassType
DefaultForwardPass(; include_last_node::Bool = true)

The default forward pass.

If include_last_node = false and the sample terminated due to a cycle, then the last node (which forms the cycle) is omitted. This can be useful option to set when training, but it comes at the cost of not knowing which node formed the cycle (if there are multiple possibilities).

source

RevisitingForwardPass

SDDP.RevisitingForwardPassType
RevisitingForwardPass(
+    period::Int = 500;
+    sub_pass::AbstractForwardPass = DefaultForwardPass(),
+)

A forward pass scheme that generate period new forward passes (using sub_pass), then revisits all previously explored forward passes. This can be useful to encourage convergence at a diversity of points in the state-space.

Set period = typemax(Int) to disable.

For example, if period = 2, then the forward passes will be revisited as follows: 1, 2, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 1, 2, ....

source

RiskAdjustedForwardPass

SDDP.RiskAdjustedForwardPassType
RiskAdjustedForwardPass(;
+    forward_pass::AbstractForwardPass,
+    risk_measure::AbstractRiskMeasure,
+    resampling_probability::Float64,
+    rejection_count::Int = 5,
+)

A forward pass that resamples a previous forward pass with resampling_probability probability, and otherwise samples a new forward pass using forward_pass.

The forward pass to revisit is chosen based on the risk-adjusted (using risk_measure) probability of the cumulative stage objectives.

Note that this objective corresponds to the first time we visited the trajectory. Subsequent visits may have improved things, but we don't have the mechanisms in-place to update it. Therefore, remove the forward pass from resampling consideration after rejection_count revisits.

source

AlternativeForwardPass

SDDP.AlternativeForwardPassType
AlternativeForwardPass(
+    forward_model::SDDP.PolicyGraph{T};
+    forward_pass::AbstractForwardPass = DefaultForwardPass(),
+)

A forward pass that simulates using forward_model, which may be different to the model used in the backwards pass.

When using this forward pass, you should almost always pass SDDP.AlternativePostIterationCallback to the post_iteration_callback argument of SDDP.train.

This forward pass is most useful when the forward_model is non-convex and we use a convex approximation of the model in the backward pass.

For example, in optimal power flow models, we can use an AC-OPF formulation as the forward_model and a DC-OPF formulation as the backward model.

For more details see the paper:

Rosemberg, A., and Street, A., and Garcia, J.D., and Valladão, D.M., and Silva, T., and Dowson, O. (2021). Assessing the cost of network simplifications in long-term hydrothermal dispatch planning models. IEEE Transactions on Sustainable Energy. 13(1), 196-206.

source

AlternativePostIterationCallback

RegularizedForwardPass

SDDP.RegularizedForwardPassType
RegularizedForwardPass(;
+    rho::Float64 = 0.05,
+    forward_pass::AbstractForwardPass = DefaultForwardPass(),
+)

A forward pass that regularizes the outgoing first-stage state variables with an L-infty trust-region constraint about the previous iteration's solution. Specifically, the bounds of the outgoing state variable x are updated from (l, u) to max(l, x^k - rho * (u - l)) <= x <= min(u, x^k + rho * (u - l)), where x^k is the optimal solution of x in the previous iteration. On the first iteration, the value of the state at the root node is used.

By default, rho is set to 5%, which seems to work well empirically.

Pass a different forward_pass to control the forward pass within the regularized forward pass.

This forward pass is largely intended to be used for investment problems in which the first stage makes a series of capacity decisions that then influence the rest of the graph. An error is thrown if the first stage problem is not deterministic, and states are silently skipped if they do not have finite bounds.

source

AbstractRiskMeasure

adjust_probability

SDDP.adjust_probabilityFunction
adjust_probability(
+    measure::Expectation
+    risk_adjusted_probability::Vector{Float64},
+    original_probability::Vector{Float64},
+    noise_support::Vector{Noise{T}},
+    objective_realizations::Vector{Float64},
+    is_minimization::Bool,
+) where {T}
source

Expectation

SDDP.ExpectationType
Expectation()

The Expectation risk measure.

This risk measure is identical to taking the expectation with respect to the nominal distribution.

Example

julia> risk_adjusted_probability = zeros(4);
+
+julia> SDDP.adjust_probability(
+           SDDP.Expectation(),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.1
+ 0.2
+ 0.3
+ 0.4
source

WorstCase

SDDP.WorstCaseType
WorstCase()

The worst-case risk measure.

This risk measure places all of the probability weight on the worst outcome.

Example

julia> risk_adjusted_probability = zeros(4);
+
+julia> SDDP.adjust_probability(
+           SDDP.WorstCase(),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.0
+ 0.0
+ 1.0
+ 0.0
source

AVaR

SDDP.AVaRType
AVaR(β)

The average value at risk (AV@R) risk measure.

This risk measure computes the expectation of the β fraction of worst outcomes. β must be in [0, 1].

When β=1, this is equivalent to the Expectation risk measure. When β=0, this is equivalent to the WorstCase risk measure.

AV@R is also known as the conditional value at risk (CV@R) or expected shortfall.

Example

julia> risk_adjusted_probability = zeros(4);
+
+julia> SDDP.adjust_probability(
+           SDDP.AVaR(0.5),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.2
+ 0.19999999999999996
+ 0.6
+ 0.0
+
+julia> SDDP.adjust_probability(
+           SDDP.AVaR(1.0),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.1
+ 0.2
+ 0.3
+ 0.4
+
+julia> SDDP.adjust_probability(
+           SDDP.AVaR(0.0),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.0
+ 0.0
+ 1.0
+ 0.0
source

CVaR

SDDP.CVaRType
CVaR(γ)

The conditional value at risk (CV@R) risk measure.

This risk measure computes the expectation of the γ fraction of worst outcomes. γ must be in [0, 1].

When γ=1, this is equivalent to the Expectation risk measure. When γ=0, this is equivalent to the WorstCase risk measure.

CV@R is also known as the average value at risk (AV@R) or expected shortfall.

Example

julia> risk_adjusted_probability = zeros(4);
+
+julia> SDDP.adjust_probability(
+           SDDP.CVaR(0.5),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.2
+ 0.19999999999999996
+ 0.6
+ 0.0
+
+julia> SDDP.adjust_probability(
+           SDDP.CVaR(1.0),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.1
+ 0.2
+ 0.3
+ 0.4
+
+julia> SDDP.adjust_probability(
+           SDDP.CVaR(0.0),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.0
+ 0.0
+ 1.0
+ 0.0
source

ConvexCombination

SDDP.ConvexCombinationType
ConvexCombination((weight::Float64, measure::AbstractRiskMeasure)...)

Create a weighted combination of risk measures.

Examples

julia> SDDP.ConvexCombination(
+           (0.5, SDDP.Expectation()),
+           (0.5, SDDP.AVaR(0.25))
+       )
+A convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25)

Convex combinations can also be constructed by adding weighted risk measures together as follows:

julia> 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.5)
+A convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.5)
source

EAVaR

SDDP.EAVaRFunction
EAVaR(;lambda=1.0, beta=1.0)

A risk measure that is a convex combination of Expectation and Average Value @ Risk (also called Conditional Value @ Risk).

    λ * E[x] + (1 - λ) * AV@R(β)[x]

Keyword Arguments

  • lambda: Convex weight on the expectation ((1-lambda) weight is put on the AV@R component. Inreasing values of lambda are less risk averse (more weight on expectation).

  • beta: The quantile at which to calculate the Average Value @ Risk. Increasing values of beta are less risk averse. If beta=0, then the AV@R component is the worst case risk measure.

Example

julia> SDDP.EAVaR(; lambda = 1.0, beta = 1.0)
+A convex combination of 1.0 * SDDP.Expectation() + 0.0 * SDDP.AVaR(1.0)
+
+julia> SDDP.EAVaR(; lambda = 0.0, beta = 1.0)
+A convex combination of 0.0 * SDDP.Expectation() + 1.0 * SDDP.AVaR(1.0)
+
+julia> SDDP.EAVaR(; lambda = 0.5, beta = 0.5)
+A convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.5)
source

ModifiedChiSquared

SDDP.ModifiedChiSquaredType
ModifiedChiSquared(radius::Float64; minimum_std=1e-5)

The distributionally robust SDDP risk measure of Philpott, A., de Matos, V., Kapelevich, L. Distributionally robust SDDP. Computational Management Science (2018) 165:431-454.

Explanation

In a Distributionally Robust Optimization (DRO) approach, we modify the probabilities we associate with all future scenarios so that the resulting probability distribution is the "worst case" probability distribution, in some sense.

In each backward pass we will compute a worst case probability distribution vector p. We compute p so that:

p ∈ argmax p'z
+      s.t. [r; p - a] in SecondOrderCone()
+           sum(p) == 1
+           p >= 0

where

  1. z is a vector of future costs. We assume that our aim is to minimize future cost p'z. If we maximize reward, we would have p ∈ argmin{p'z}.
  2. a is the uniform distribution
  3. r is a user specified radius - the larger the radius, the more conservative the policy.

Notes

The largest radius that will work with S scenarios is sqrt((S-1)/S).

If the uncorrected standard deviation of the objecive realizations is less than minimum_std, then the risk-measure will default to Expectation().

This code was contributed by Lea Kapelevich.

Example

julia> risk_adjusted_probability = zeros(4);
+
+julia> SDDP.adjust_probability(
+           SDDP.ModifiedChiSquared(0.5),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.2267731382092775
+ 0.1577422872635742
+ 0.5958039891549808
+ 0.019680585372167547
+
+julia> SDDP.adjust_probability(
+           SDDP.ModifiedChiSquared(0.5),
+           risk_adjusted_probability,
+           [0.25, 0.25, 0.25, 0.25],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.25, 0.25, 0.25, 0.25]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.3333333333333333
+ 0.044658198738520394
+ 0.6220084679281462
+ 0.0
source

Entropic

SDDP.EntropicType
Entropic(γ::Float64)

The entropic risk measure as described by:

Dowson, O., Morton, D.P. & Pagnoncelli, B.K. Incorporating convex risk
+measures into multistage stochastic programming algorithms. Annals of
+Operations Research (2022). [doi](https://doi.org/10.1007/s10479-022-04977-w).

As γ increases, the measure becomes more risk-averse.

Example

julia> risk_adjusted_probability = zeros(4);
+
+julia> SDDP.adjust_probability(
+           SDDP.Entropic(0.1),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+-0.14333892665462006
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.1100296362588547
+ 0.19911786395979578
+ 0.3648046623591841
+ 0.3260478374221655
+
+julia> SDDP.adjust_probability(
+           SDDP.Entropic(1.0),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+-0.12038063114659443
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 0.09911045746726178
+ 0.07292139941460454
+ 0.8082304666305623
+ 0.019737676487571337
+
+julia> SDDP.adjust_probability(
+           SDDP.Entropic(10.0),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+-0.12038063114659443
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+ 1.5133080886430772e-5
+ 1.374081618667918e-9
+ 0.999984865545032
+ 5.664386611687232e-18
source

Wasserstein

SDDP.WassersteinType
Wasserstein(norm::Function, solver_factory; alpha::Float64)

A distributionally-robust risk measure based on the Wasserstein distance.

As alpha increases, the measure becomes more risk-averse. When alpha=0, the measure is equivalent to the expectation operator. As alpha increases, the measure approaches the Worst-case risk measure.

norm

The norm argument is a fuction that computes the distance between two supports of your distribution. It must have the signature:

wasserstein_norm(x::SDDP.Noise, y::SDDP.Noise)::Float64

The input arguments are of type Noise. The .term values will depend on what supports you passed to parameterize.

Example

julia> import HiGHS
+
+julia> risk_adjusted_probability = zeros(4);
+
+julia> wasserstein_norm(x::SDDP.Noise, y::SDDP.Noise) = abs(x.term - y.term);
+
+julia> SDDP.adjust_probability(
+           SDDP.Wasserstein(wasserstein_norm, HiGHS.Optimizer; alpha = 0.5),
+           risk_adjusted_probability,
+           [0.1, 0.2, 0.3, 0.4],  # nominal_probability,
+           SDDP.Noise.([1.0, 2.0, 3.0, 4.0], [0.1, 0.2, 0.3, 0.4]),  # noise_supports,
+           [5.0, 4.0, 6.0, 2.0],  # cost_realizations,
+           true,                  # is_minimization
+       )
+0.0
+
+julia> risk_adjusted_probability
+4-element Vector{Float64}:
+  0.1
+  0.10000000000000003
+  0.7999999999999999
+ -0.0
source

AbstractDualityHandler

ContinuousConicDuality

SDDP.ContinuousConicDualityType
ContinuousConicDuality()

Compute dual variables in the backward pass using conic duality, relaxing any binary or integer restrictions as necessary.

Theory

Given the problem

min Cᵢ(x̄, u, w) + θᵢ
+ st (x̄, x′, u) in Xᵢ(w) ∩ S
+    x̄ - x == 0          [λ]

where S ⊆ ℝ×ℤ, we relax integrality and using conic duality to solve for λ in the problem:

min Cᵢ(x̄, u, w) + θᵢ
+ st (x̄, x′, u) in Xᵢ(w)
+    x̄ - x == 0          [λ]
source

LagrangianDuality

SDDP.LagrangianDualityType
LagrangianDuality(;
+    method::LocalImprovementSearch.AbstractSearchMethod =
+        LocalImprovementSearch.BFGS(100),
+)

Obtain dual variables in the backward pass using Lagrangian duality.

Arguments

  • method: the LocalImprovementSearch method for maximizing the Lagrangian dual problem.

Theory

Given the problem

min Cᵢ(x̄, u, w) + θᵢ
+ st (x̄, x′, u) in Xᵢ(w) ∩ S
+    x̄ - x == 0          [λ]

where S ⊆ ℝ×ℤ, we solve the problem max L(λ), where:

L(λ) = min Cᵢ(x̄, u, w) + θᵢ - λ' h(x̄)
+        st (x̄, x′, u) in Xᵢ(w) ∩ S

and where h(x̄) = x̄ - x.

source

StrengthenedConicDuality

SDDP.StrengthenedConicDualityType
StrengthenedConicDuality()

Obtain dual variables in the backward pass using strengthened conic duality.

Theory

Given the problem

min Cᵢ(x̄, u, w) + θᵢ
+ st (x̄, x′, u) in Xᵢ(w) ∩ S
+    x̄ - x == 0          [λ]

we first obtain an estimate for λ using ContinuousConicDuality.

Then, we evaluate the Lagrangian function:

L(λ) = min Cᵢ(x̄, u, w) + θᵢ - λ' (x̄ - x`)
+        st (x̄, x′, u) in Xᵢ(w) ∩ S

to obtain a better estimate of the intercept.

source

BanditDuality

SDDP.BanditDualityType
BanditDuality()

Formulates the problem of choosing a duality handler as a multi-armed bandit problem. The arms to choose between are:

Our problem isn't a typical multi-armed bandit for a two reasons:

  1. The reward distribution is non-stationary (each arm converges to 0 as it keeps getting pulled.
  2. The distribution of rewards is dependent on the history of the arms that were chosen.

We choose a very simple heuristic: pick the arm with the best mean + 1 standard deviation. That should ensure we consistently pick the arm with the best likelihood of improving the value function.

In future, we should consider discounting the rewards of earlier iterations, and focus more on the more-recent rewards.

source

simulate

SDDP.simulateFunction
simulate(
+    model::PolicyGraph,
+    number_replications::Int = 1,
+    variables::Vector{Symbol} = Symbol[];
+    sampling_scheme::AbstractSamplingScheme =
+        InSampleMonteCarlo(),
+    custom_recorders = Dict{Symbol, Function}(),
+    duality_handler::Union{Nothing,AbstractDualityHandler} = nothing,
+    skip_undefined_variables::Bool = false,
+    parallel_scheme::AbstractParallelScheme = Serial(),
+    incoming_state::Dict{String,Float64} = _initial_state(model),
+ )::Vector{Vector{Dict{Symbol,Any}}}

Perform a simulation of the policy model with number_replications replications.

Return data structure

Returns a vector with one element for each replication. Each element is a vector with one-element for each node in the scenario that was sampled. Each element in that vector is a dictionary containing information about the subproblem that was solved.

In that dictionary there are four special keys:

  • :node_index, which records the index of the sampled node in the policy model

  • :noise_term, which records the noise observed at the node

  • :stage_objective, which records the stage-objective of the subproblem

  • :bellman_term, which records the cost/value-to-go of the node.

The sum of :stage_objective + :bellman_term will equal the objective value of the solved subproblem.

In addition to the special keys, the dictionary will contain the result of key => JuMP.value(subproblem[key]) for each key in variables. This is useful to obtain the primal value of the state and control variables.

Positonal arguments

  • model: the model to simulate

  • number_replications::Int = 1: the number of simulation replications to conduct, that is, the length of the simulation vector that is returned by this function. If omitted, this defaults to 1.`

  • variables::Vector{Symbol} = Symbol[]: a list of the variable names to record the value of in each stage.

Keyword arguments

  • sampling_scheme: the sampling scheme used when simulating.

  • custom_recorders: see Custom recorders section below.

  • duality_handler: the SDDP.AbstractDualityHandler used to compute dual variables. If you do not require dual variables (or if they are not available), pass duality_handler = nothing.

  • skip_undefined_variables: If you attempt to simulate the value of a variable that is only defined in some of the stage problems, an error will be thrown. To over-ride this (and return a NaN instead), pass skip_undefined_variables = true.

  • parallel_scheme: Use parallel_scheme::[AbstractParallelScheme](@ref) to specify a scheme for simulating in parallel. Defaults to Serial.

  • initial_state: Use incoming_state to pass an initial value of the state variable, if it differs from that at the root node. Each key should be the string name of the state variable.

Custom recorders

For more complicated data, the custom_recorders keyword argument can be used.

For example, to record the dual of a constraint named my_constraint, pass the following:

simulation_results = SDDP.simulate(model, 2;
+    custom_recorders = Dict{Symbol, Function}(
+        :constraint_dual => sp -> JuMP.dual(sp[:my_constraint])
+    )
+)

The value of the dual in the first stage of the second replication can be accessed as:

simulation_results[2][1][:constraint_dual]
source

calculate_bound

SDDP.calculate_boundFunction
SDDP.calculate_bound(
+    model::PolicyGraph,
+    state::Dict{Symbol,Float64} = model.initial_root_state;
+    risk_measure::AbstractRiskMeasure = Expectation(),
+)

Calculate the lower bound (if minimizing, otherwise upper bound) of the problem model at the point state, assuming the risk measure at the root node is risk_measure.

source

add_all_cuts

SDDP.add_all_cutsFunction
add_all_cuts(model::PolicyGraph)

Add all cuts that may have been deleted back into the model.

Explanation

During the solve, SDDP.jl may decide to remove cuts for a variety of reasons.

These can include cuts that define the optimal value function, particularly around the extremes of the state-space (e.g., reservoirs empty).

This function ensures that all cuts discovered are added back into the model.

You should call this after train and before simulate.

source

Decision rules

DecisionRule

SDDP.DecisionRuleType
DecisionRule(model::PolicyGraph{T}; node::T)

Create a decision rule for node node in model.

Example

rule = SDDP.DecisionRule(model; node = 1)
source

evaluate

SDDP.evaluateFunction
evaluate(
+    rule::DecisionRule;
+    incoming_state::Dict{Symbol,Float64},
+    noise = nothing,
+    controls_to_record = Symbol[],
+)

Evalute the decision rule rule at the point described by the incoming_state and noise.

If the node is deterministic, omit the noise argument.

Pass a list of symbols to controls_to_record to save the optimal primal solution corresponding to the names registered in the model.

source
evaluate(
+    V::ValueFunction,
+    point::Dict{Union{Symbol,String},<:Real}
+    objective_state = nothing,
+    belief_state = nothing
+)

Evaluate the value function V at point in the state-space.

Returns a tuple containing the height of the function, and the subgradient w.r.t. the convex state-variables.

Examples

evaluate(V, Dict(:volume => 1.0))

If the state variable is constructed like @variable(sp, volume[1:4] >= 0, SDDP.State, initial_value = 0.0), use [i] to index the state variable:

evaluate(V, Dict(Symbol("volume[1]") => 1.0))

You can also use strings or symbols for the keys.

evaluate(V, Dict("volume[1]" => 1))
source
evalute(V::ValueFunction{Nothing, Nothing}; kwargs...)

Evalute the value function V at the point in the state-space specified by kwargs.

Examples

evaluate(V; volume = 1)
source
evaluate(
+    model::PolicyGraph{T},
+    validation_scenarios::ValidationScenarios{T,S},
+) where {T,S}

Evaluate the performance of the policy contained in model after a call to train on the scenarios specified by validation_scenarios.

Examples

model, validation_scenarios = read_from_file("my_model.sof.json")
+train(model; iteration_limit = 100)
+simulations = evaluate(model, validation_scenarios)
source

SpaghettiPlot

SDDP.SpaghettiPlotType
SDDP.SpaghettiPlot(; stages, scenarios)

Initialize a new SpaghettiPlot with stages stages and scenarios number of replications.

source

add_spaghetti

SDDP.add_spaghettiFunction
SDDP.add_spaghetti(data_function::Function, plt::SpaghettiPlot; kwargs...)

Description

Add a new figure to the SpaghettiPlot plt, where the y-value of the scenarioth line when x = stage is given by data_function(plt.simulations[scenario][stage]).

Keyword arguments

  • xlabel: set the xaxis label
  • ylabel: set the yaxis label
  • title: set the title of the plot
  • ymin: set the minimum y value
  • ymax: set the maximum y value
  • cumulative: plot the additive accumulation of the value across the stages
  • interpolate: interpolation method for lines between stages.

Defaults to "linear" see the d3 docs for all options.

Examples

simulations = simulate(model, 10)
+plt = SDDP.spaghetti_plot(simulations)
+SDDP.add_spaghetti(plt; title = "Stage objective") do data
+    return data[:stage_objective]
+end
source

publication_plot

SDDP.publication_plotFunction
SDDP.publication_plot(
+    data_function, simulations;
+    quantile = [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0],
+    kwargs...)

Create a Plots.jl recipe plot of the simulations.

See Plots.jl for the list of keyword arguments.

Examples

SDDP.publication_plot(simulations; title = "My title") do data
+    return data[:stage_objective]
+end
source

ValueFunction

SDDP.ValueFunctionType
ValueFunction

A representation of the value function. SDDP.jl uses the following unique representation of the value function that is undocumented in the literature.

It supports three types of state variables:

  1. x - convex "resource" states
  2. b - concave "belief" states
  3. y - concave "objective" states

In addition, we have three types of cuts:

  1. Single-cuts (also called "average" cuts in the literature), which involve the risk-adjusted expectation of the cost-to-go.
  2. Multi-cuts, which use a different cost-to-go term for each realization w.
  3. Risk-cuts, which correspond to the facets of the dual interpretation of a coherent risk measure.

Therefore, ValueFunction returns a JuMP model of the following form:

V(x, b, y) = min: μᵀb + νᵀy + θ
+             s.t. # "Single" / "Average" cuts
+                  μᵀb(j) + νᵀy(j) + θ >= α(j) + xᵀβ(j), ∀ j ∈ J
+                  # "Multi" cuts
+                  μᵀb(k) + νᵀy(k) + φ(w) >= α(k, w) + xᵀβ(k, w), ∀w ∈ Ω, k ∈ K
+                  # "Risk-set" cuts
+                  θ ≥ Σ{p(k, w) * φ(w)}_w - μᵀb(k) - νᵀy(k), ∀ k ∈ K
source

evaluate

SDDP.evaluateMethod
evaluate(
+    V::ValueFunction,
+    point::Dict{Union{Symbol,String},<:Real}
+    objective_state = nothing,
+    belief_state = nothing
+)

Evaluate the value function V at point in the state-space.

Returns a tuple containing the height of the function, and the subgradient w.r.t. the convex state-variables.

Examples

evaluate(V, Dict(:volume => 1.0))

If the state variable is constructed like @variable(sp, volume[1:4] >= 0, SDDP.State, initial_value = 0.0), use [i] to index the state variable:

evaluate(V, Dict(Symbol("volume[1]") => 1.0))

You can also use strings or symbols for the keys.

evaluate(V, Dict("volume[1]" => 1))
source

plot

SDDP.plotFunction
plot(plt::SpaghettiPlot[, filename::String]; open::Bool = true)

The SpaghettiPlot plot plt to filename. If filename is not given, it will be saved to a temporary directory. If open = true, then a browser window will be opened to display the resulting HTML file.

source

write_subproblem_to_file

SDDP.write_subproblem_to_fileFunction
write_subproblem_to_file(
+    node::Node,
+    filename::String;
+    throw_error::Bool = false,
+)

Write the subproblem contained in node to the file filename.

The throw_error is an argument used internally by SDDP.jl. If set, an error will be thrown.

Example

SDDP.write_subproblem_to_file(model[1], "subproblem_1.lp")
source

deterministic_equivalent

SDDP.deterministic_equivalentFunction
deterministic_equivalent(
+    pg::PolicyGraph{T},
+    optimizer = nothing;
+    time_limit::Union{Real,Nothing} = 60.0,
+)

Form a JuMP model that represents the deterministic equivalent of the problem.

Examples

deterministic_equivalent(model)
deterministic_equivalent(model, HiGHS.Optimizer)
source

write_to_file

SDDP.write_to_fileFunction
write_to_file(
+    model::PolicyGraph,
+    filename::String;
+    compression::MOI.FileFormats.AbstractCompressionScheme =
+        MOI.FileFormats.AutomaticCompression(),
+    kwargs...
+)

Write model to filename in the StochOptFormat file format.

Pass an argument to compression to override the default of automatically detecting the file compression to use based on the extension of filename.

See Base.write(::IO, ::PolicyGraph) for information on the keyword arguments that can be provided.

Warning

This function is experimental. See the full warning in Base.write(::IO, ::PolicyGraph).

Examples

write_to_file(model, "my_model.sof.json"; validation_scenarios = 10)
source

read_from_file

SDDP.read_from_fileFunction
read_from_file(
+    filename::String;
+    compression::MOI.FileFormats.AbstractCompressionScheme =
+        MOI.FileFormats.AutomaticCompression(),
+    kwargs...
+)::Tuple{PolicyGraph, ValidationScenarios}

Return a tuple containing a PolicyGraph object and a ValidationScenarios read from filename in the StochOptFormat file format.

Pass an argument to compression to override the default of automatically detecting the file compression to use based on the extension of filename.

See Base.read(::IO, ::Type{PolicyGraph}) for information on the keyword arguments that can be provided.

Warning

This function is experimental. See the full warning in Base.read(::IO, ::Type{PolicyGraph}).

Examples

model, validation_scenarios = read_from_file("my_model.sof.json")
source

write

Base.writeMethod
Base.write(
+    io::IO,
+    model::PolicyGraph;
+    validation_scenarios::Union{Nothing,Int,ValidationScenarios} = nothing,
+    sampling_scheme::AbstractSamplingScheme = InSampleMonteCarlo(),
+    kwargs...
+)

Write model to io in the StochOptFormat file format.

Pass an Int to validation_scenarios (default nothing) to specify the number of test scenarios to generate using the sampling_scheme sampling scheme. Alternatively, pass a ValidationScenarios object to manually specify the test scenarios to use.

Any additional kwargs passed to write will be stored in the top-level of the resulting StochOptFormat file. Valid arguments include name, author, date, and description.

Compatibility

Warning

THIS FUNCTION IS EXPERIMENTAL. THINGS MAY CHANGE BETWEEN COMMITS. YOU SHOULD NOT RELY ON THIS FUNCTIONALITY AS A LONG-TERM FILE FORMAT (YET).

In addition to potential changes to the underlying format, only a subset of possible modifications are supported. These include:

  • JuMP.fix
  • JuMP.set_lower_bound
  • JuMP.set_upper_bound
  • JuMP.set_normalized_rhs
  • Changes to the constant or affine terms in a stage objective.

If your model uses something other than this, this function will silently write an incorrect formulation of the problem.

Examples

open("my_model.sof.json", "w") do io
+    write(
+        io,
+        model;
+        validation_scenarios = 10,
+        name = "MyModel",
+        author = "@odow",
+        date = "2020-07-20",
+        description = "Example problem for the SDDP.jl documentation",
+    )
+end
source

read

Base.readMethod
Base.read(
+    io::IO,
+    ::Type{PolicyGraph};
+    bound::Float64 = 1e6,
+)::Tuple{PolicyGraph,ValidationScenarios}

Return a tuple containing a PolicyGraph object and a ValidationScenarios read from io in the StochOptFormat file format.

See also: evaluate.

Compatibility

Warning

This function is experimental. Things may change between commits. You should not rely on this functionality as a long-term file format (yet).

In addition to potential changes to the underlying format, only a subset of possible modifications are supported. These include:

  • Additive random variables in the constraints or in the objective
  • Multiplicative random variables in the objective

If your model uses something other than this, this function may throw an error or silently build a non-convex model.

Examples

open("my_model.sof.json", "r") do io
+    model, validation_scenarios = read(io, PolicyGraph)
+end
source

evaluate

SDDP.evaluateMethod
evaluate(
+    model::PolicyGraph{T},
+    validation_scenarios::ValidationScenarios{T,S},
+) where {T,S}

Evaluate the performance of the policy contained in model after a call to train on the scenarios specified by validation_scenarios.

Examples

model, validation_scenarios = read_from_file("my_model.sof.json")
+train(model; iteration_limit = 100)
+simulations = evaluate(model, validation_scenarios)
source

ValidationScenarios

SDDP.ValidationScenariosType
ValidationScenario{T,S}(scenarios::Vector{ValidationScenario{T,S}})

An AbstractSamplingScheme based on a vector of scenarios.

Each scenario is a vector of Tuple{T, S} where the first element is the node to visit and the second element is the realization of the stagewise-independent noise term. Pass nothing if the node is deterministic.

source

ValidationScenario

diff --git a/previews/PR826/assets/Project.toml b/previews/PR826/assets/Project.toml new file mode 100644 index 0000000000..49e68fe0f0 --- /dev/null +++ b/previews/PR826/assets/Project.toml @@ -0,0 +1,2 @@ +[deps] +Luxor = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc" diff --git a/previews/PR826/assets/deterministic_linear_policy_graph.png b/previews/PR826/assets/deterministic_linear_policy_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..33781e566a8dc1205c38c4d9263355bd127e3250 GIT binary patch literal 7325 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 Nnv(XzG6nOH{{Z7Tkg)&& literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/documenter.js b/previews/PR826/assets/documenter.js new file mode 100644 index 0000000000..7d68cd808b --- /dev/null +++ b/previews/PR826/assets/documenter.js @@ -0,0 +1,1082 @@ +// Generated by Documenter.jl +requirejs.config({ + paths: { + 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia.min', + 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/headroom.min', + 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/jquery-ui.min', + 'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min', + 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min', + 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.12.0/jQuery.headroom.min', + 'katex': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min', + 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min', + 'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/julia-repl.min', + }, + shim: { + "highlight-julia": { + "deps": [ + "highlight" + ] + }, + "katex-auto-render": { + "deps": [ + "katex" + ] + }, + "headroom-jquery": { + "deps": [ + "jquery", + "headroom" + ] + }, + "highlight-julia-repl": { + "deps": [ + "highlight" + ] + } +} +}); +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'katex', 'katex-auto-render'], function($, katex, renderMathInElement) { +$(document).ready(function() { + renderMathInElement( + document.body, + { + "delimiters": [ + { + "left": "$", + "right": "$", + "display": false + }, + { + "left": "$$", + "right": "$$", + "display": true + }, + { + "left": "\\[", + "right": "\\]", + "display": true + } + ] +} + + ); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($) { +$(document).ready(function() { + hljs.highlightAll(); +}) + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +let timer = 0; +var isExpanded = true; + +$(document).on( + "click", + ".docstring .docstring-article-toggle-button", + function () { + let articleToggleTitle = "Expand docstring"; + const parent = $(this).parent(); + + debounce(() => { + if (parent.siblings("section").is(":visible")) { + parent + .find("a.docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + } else { + parent + .find("a.docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + articleToggleTitle = "Collapse docstring"; + } + + parent + .children(".docstring-article-toggle-button") + .prop("title", articleToggleTitle); + parent.siblings("section").slideToggle(); + }); + } +); + +$(document).on("click", ".docs-article-toggle-button", function (event) { + let articleToggleTitle = "Expand docstring"; + let navArticleToggleTitle = "Expand all docstrings"; + let animationSpeed = event.noToggleAnimation ? 0 : 400; + + debounce(() => { + if (isExpanded) { + $(this).removeClass("fa-chevron-up").addClass("fa-chevron-down"); + $("a.docstring-article-toggle-button") + .removeClass("fa-chevron-down") + .addClass("fa-chevron-right"); + + isExpanded = false; + + $(".docstring section").slideUp(animationSpeed); + } else { + $(this).removeClass("fa-chevron-down").addClass("fa-chevron-up"); + $("a.docstring-article-toggle-button") + .removeClass("fa-chevron-right") + .addClass("fa-chevron-down"); + + isExpanded = true; + articleToggleTitle = "Collapse docstring"; + navArticleToggleTitle = "Collapse all docstrings"; + + $(".docstring section").slideDown(animationSpeed); + } + + $(this).prop("title", navArticleToggleTitle); + $(".docstring-article-toggle-button").prop("title", articleToggleTitle); + }); +}); + +function debounce(callback, timeout = 300) { + if (Date.now() - timer > timeout) { + callback(); + } + + clearTimeout(timer); + + timer = Date.now(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require([], function() { +function addCopyButtonCallbacks() { + for (const el of document.getElementsByTagName("pre")) { + const button = document.createElement("button"); + button.classList.add("copy-button", "fa-solid", "fa-copy"); + button.setAttribute("aria-label", "Copy this code block"); + button.setAttribute("title", "Copy"); + + el.appendChild(button); + + const success = function () { + button.classList.add("success", "fa-check"); + button.classList.remove("fa-copy"); + }; + + const failure = function () { + button.classList.add("error", "fa-xmark"); + button.classList.remove("fa-copy"); + }; + + button.addEventListener("click", function () { + copyToClipboard(el.innerText).then(success, failure); + + setTimeout(function () { + button.classList.add("fa-copy"); + button.classList.remove("success", "fa-check", "fa-xmark"); + }, 5000); + }); + } +} + +function copyToClipboard(text) { + // clipboard API is only available in secure contexts + if (window.navigator && window.navigator.clipboard) { + return window.navigator.clipboard.writeText(text); + } else { + return new Promise(function (resolve, reject) { + try { + const el = document.createElement("textarea"); + el.textContent = text; + el.style.position = "fixed"; + el.style.opacity = 0; + document.body.appendChild(el); + el.select(); + document.execCommand("copy"); + + resolve(); + } catch (err) { + reject(err); + } finally { + document.body.removeChild(el); + } + }); + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", addCopyButtonCallbacks); +} else { + addCopyButtonCallbacks(); +} + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) { + +// Manages the top navigation bar (hides it when the user starts scrolling down on the +// mobile). +window.Headroom = Headroom; // work around buggy module loading? +$(document).ready(function () { + $("#documenter .docs-navbar").headroom({ + tolerance: { up: 10, down: 10 }, + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +$(document).ready(function () { + let meta = $("div[data-docstringscollapsed]").data(); + + if (meta?.docstringscollapsed) { + $("#documenter-article-toggle-button").trigger({ + type: "click", + noToggleAnimation: true, + }); + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +/* +To get an in-depth about the thought process you can refer: https://hetarth02.hashnode.dev/series/gsoc + +PSEUDOCODE: + +Searching happens automatically as the user types or adjusts the selected filters. +To preserve responsiveness, as much as possible of the slow parts of the search are done +in a web worker. Searching and result generation are done in the worker, and filtering and +DOM updates are done in the main thread. The filters are in the main thread as they should +be very quick to apply. This lets filters be changed without re-searching with minisearch +(which is possible even if filtering is on the worker thread) and also lets filters be +changed _while_ the worker is searching and without message passing (neither of which are +possible if filtering is on the worker thread) + +SEARCH WORKER: + +Import minisearch + +Build index + +On message from main thread + run search + find the first 200 unique results from each category, and compute their divs for display + note that this is necessary and sufficient information for the main thread to find the + first 200 unique results from any given filter set + post results to main thread + +MAIN: + +Launch worker + +Declare nonconstant globals (worker_is_running, last_search_text, unfiltered_results) + +On text update + if worker is not running, launch_search() + +launch_search + set worker_is_running to true, set last_search_text to the search text + post the search query to worker + +on message from worker + if last_search_text is not the same as the text in the search field, + the latest search result is not reflective of the latest search query, so update again + launch_search() + otherwise + set worker_is_running to false + + regardless, display the new search results to the user + save the unfiltered_results as a global + update_search() + +on filter click + adjust the filter selection + update_search() + +update_search + apply search filters by looping through the unfiltered_results and finding the first 200 + unique results that match the filters + + Update the DOM +*/ + +/////// SEARCH WORKER /////// + +function worker_function(documenterSearchIndex, documenterBaseURL, filters) { + importScripts( + "https://cdn.jsdelivr.net/npm/minisearch@6.1.0/dist/umd/index.min.js" + ); + + let data = documenterSearchIndex.map((x, key) => { + x["id"] = key; // minisearch requires a unique for each object + return x; + }); + + // list below is the lunr 2.1.3 list minus the intersect with names(Base) + // (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) + // ideally we'd just filter the original list but it's not available as a variable + const stopWords = new Set([ + "a", + "able", + "about", + "across", + "after", + "almost", + "also", + "am", + "among", + "an", + "and", + "are", + "as", + "at", + "be", + "because", + "been", + "but", + "by", + "can", + "cannot", + "could", + "dear", + "did", + "does", + "either", + "ever", + "every", + "from", + "got", + "had", + "has", + "have", + "he", + "her", + "hers", + "him", + "his", + "how", + "however", + "i", + "if", + "into", + "it", + "its", + "just", + "least", + "like", + "likely", + "may", + "me", + "might", + "most", + "must", + "my", + "neither", + "no", + "nor", + "not", + "of", + "off", + "often", + "on", + "or", + "other", + "our", + "own", + "rather", + "said", + "say", + "says", + "she", + "should", + "since", + "so", + "some", + "than", + "that", + "the", + "their", + "them", + "then", + "there", + "these", + "they", + "this", + "tis", + "to", + "too", + "twas", + "us", + "wants", + "was", + "we", + "were", + "what", + "when", + "who", + "whom", + "why", + "will", + "would", + "yet", + "you", + "your", + ]); + + let index = new MiniSearch({ + fields: ["title", "text"], // fields to index for full-text search + storeFields: ["location", "title", "text", "category", "page"], // fields to return with results + processTerm: (term) => { + let word = stopWords.has(term) ? null : term; + if (word) { + // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names + word = word + .replace(/^[^a-zA-Z0-9@!]+/, "") + .replace(/[^a-zA-Z0-9@!]+$/, ""); + + word = word.toLowerCase(); + } + + return word ?? null; + }, + // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not + // find anything if searching for "add!", only for the entire qualification + tokenize: (string) => string.split(/[\s\-\.]+/), + // options which will be applied during the search + searchOptions: { + prefix: true, + boost: { title: 100 }, + fuzzy: 2, + }, + }); + + index.addAll(data); + + /** + * Used to map characters to HTML entities. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const htmlEscapes = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }; + + /** + * Used to match HTML entities and HTML characters. + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + const reUnescapedHtml = /[&<>"']/g; + const reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + + /** + * Escape function from lodash + * Refer: https://github.com/lodash/lodash/blob/main/src/escape.ts + */ + function escape(string) { + return string && reHasUnescapedHtml.test(string) + ? string.replace(reUnescapedHtml, (chr) => htmlEscapes[chr]) + : string || ""; + } + + /** + * RegX escape function from MDN + * Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + } + + /** + * Make the result component given a minisearch result data object and the value + * of the search input as queryString. To view the result object structure, refer: + * https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchresult + * + * @param {object} result + * @param {string} querystring + * @returns string + */ + function make_search_result(result, querystring) { + let search_divider = `
`; + let display_link = + result.location.slice(Math.max(0), Math.min(50, result.location.length)) + + (result.location.length > 30 ? "..." : ""); // To cut-off the link because it messes with the overflow of the whole div + + if (result.page !== "") { + display_link += ` (${result.page})`; + } + searchstring = escapeRegExp(querystring); + let textindex = new RegExp(`${searchstring}`, "i").exec(result.text); + let text = + textindex !== null + ? result.text.slice( + Math.max(textindex.index - 100, 0), + Math.min( + textindex.index + querystring.length + 100, + result.text.length + ) + ) + : ""; // cut-off text before and after from the match + + text = text.length ? escape(text) : ""; + + let display_result = text.length + ? "..." + + text.replace( + new RegExp(`${escape(searchstring)}`, "i"), // For first occurrence + '$&' + ) + + "..." + : ""; // highlights the match + + let in_code = false; + if (!["page", "section"].includes(result.category.toLowerCase())) { + in_code = true; + } + + // We encode the full url to escape some special characters which can lead to broken links + let result_div = ` + +
+
${escape(result.title)}
+
${result.category}
+
+

+ ${display_result} +

+
+ ${display_link} +
+
+ ${search_divider} + `; + + return result_div; + } + + self.onmessage = function (e) { + let query = e.data; + let results = index.search(query, { + filter: (result) => { + // Only return relevant results + return result.score >= 1; + }, + combineWith: "AND", + }); + + // Pre-filter to deduplicate and limit to 200 per category to the extent + // possible without knowing what the filters are. + let filtered_results = []; + let counts = {}; + for (let filter of filters) { + counts[filter] = 0; + } + let present = {}; + + for (let result of results) { + cat = result.category; + cnt = counts[cat]; + if (cnt < 200) { + id = cat + "---" + result.location; + if (present[id]) { + continue; + } + present[id] = true; + filtered_results.push({ + location: result.location, + category: cat, + div: make_search_result(result, query), + }); + } + } + + postMessage(filtered_results); + }; +} + +/////// SEARCH MAIN /////// + +function runSearchMainCode() { + // `worker = Threads.@spawn worker_function(documenterSearchIndex)`, but in JavaScript! + const filters = [ + ...new Set(documenterSearchIndex["docs"].map((x) => x.category)), + ]; + const worker_str = + "(" + + worker_function.toString() + + ")(" + + JSON.stringify(documenterSearchIndex["docs"]) + + "," + + JSON.stringify(documenterBaseURL) + + "," + + JSON.stringify(filters) + + ")"; + const worker_blob = new Blob([worker_str], { type: "text/javascript" }); + const worker = new Worker(URL.createObjectURL(worker_blob)); + + // Whether the worker is currently handling a search. This is a boolean + // as the worker only ever handles 1 or 0 searches at a time. + var worker_is_running = false; + + // The last search text that was sent to the worker. This is used to determine + // if the worker should be launched again when it reports back results. + var last_search_text = ""; + + // The results of the last search. This, in combination with the state of the filters + // in the DOM, is used compute the results to display on calls to update_search. + var unfiltered_results = []; + + // Which filter is currently selected + var selected_filter = ""; + + $(document).on("input", ".documenter-search-input", function (event) { + if (!worker_is_running) { + launch_search(); + } + }); + + function launch_search() { + worker_is_running = true; + last_search_text = $(".documenter-search-input").val(); + worker.postMessage(last_search_text); + } + + worker.onmessage = function (e) { + if (last_search_text !== $(".documenter-search-input").val()) { + launch_search(); + } else { + worker_is_running = false; + } + + unfiltered_results = e.data; + update_search(); + }; + + $(document).on("click", ".search-filter", function () { + if ($(this).hasClass("search-filter-selected")) { + selected_filter = ""; + } else { + selected_filter = $(this).text().toLowerCase(); + } + + // This updates search results and toggles classes for UI: + update_search(); + }); + + /** + * Make/Update the search component + */ + function update_search() { + let querystring = $(".documenter-search-input").val(); + + if (querystring.trim()) { + if (selected_filter == "") { + results = unfiltered_results; + } else { + results = unfiltered_results.filter((result) => { + return selected_filter == result.category.toLowerCase(); + }); + } + + let search_result_container = ``; + let modal_filters = make_modal_body_filters(); + let search_divider = `
`; + + if (results.length) { + let links = []; + let count = 0; + let search_results = ""; + + for (var i = 0, n = results.length; i < n && count < 200; ++i) { + let result = results[i]; + if (result.location && !links.includes(result.location)) { + search_results += result.div; + count++; + links.push(result.location); + } + } + + if (count == 1) { + count_str = "1 result"; + } else if (count == 200) { + count_str = "200+ results"; + } else { + count_str = count + " results"; + } + let result_count = `
${count_str}
`; + + search_result_container = ` +
+ ${modal_filters} + ${search_divider} + ${result_count} +
+ ${search_results} +
+
+ `; + } else { + search_result_container = ` +
+ ${modal_filters} + ${search_divider} +
0 result(s)
+
+
No result found!
+ `; + } + + if ($(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").removeClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(search_result_container); + } else { + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".search-modal-card-body").html(` +
Type something to get started!
+ `); + } + } + + /** + * Make the modal filter html + * + * @returns string + */ + function make_modal_body_filters() { + let str = filters + .map((val) => { + if (selected_filter == val.toLowerCase()) { + return `${val}`; + } else { + return `${val}`; + } + }) + .join(""); + + return ` +
+ Filters: + ${str} +
`; + } +} + +function waitUntilSearchIndexAvailable() { + // It is possible that the documenter.js script runs before the page + // has finished loading and documenterSearchIndex gets defined. + // So we need to wait until the search index actually loads before setting + // up all the search-related stuff. + if (typeof documenterSearchIndex !== "undefined") { + runSearchMainCode(); + } else { + console.warn("Search Index not available, waiting"); + setTimeout(waitUntilSearchIndexAvailable, 1000); + } +} + +// The actual entry point to the search code +waitUntilSearchIndexAvailable(); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Modal settings dialog +$(document).ready(function () { + var settings = $("#documenter-settings"); + $("#documenter-settings-button").click(function () { + settings.toggleClass("is-active"); + }); + // Close the dialog if X is clicked + $("#documenter-settings button.delete").click(function () { + settings.removeClass("is-active"); + }); + // Close dialog if ESC is pressed + $(document).keyup(function (e) { + if (e.keyCode == 27) settings.removeClass("is-active"); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +$(document).ready(function () { + let search_modal_header = ` + + `; + + let initial_search_body = ` +
Type something to get started!
+ `; + + let search_modal_footer = ` +
+ + Ctrl + + / to search + + esc to close +
+ `; + + $(document.body).append( + ` + + ` + ); + + document.querySelector(".docs-search-query").addEventListener("click", () => { + openModal(); + }); + + document + .querySelector(".close-search-modal") + .addEventListener("click", () => { + closeModal(); + }); + + $(document).on("click", ".search-result-link", function () { + closeModal(); + }); + + document.addEventListener("keydown", (event) => { + if ((event.ctrlKey || event.metaKey) && event.key === "/") { + openModal(); + } else if (event.key === "Escape") { + closeModal(); + } + + return false; + }); + + // Functions to open and close a modal + function openModal() { + let searchModal = document.querySelector("#search-modal"); + + searchModal.classList.add("is-active"); + document.querySelector(".documenter-search-input").focus(); + } + + function closeModal() { + let searchModal = document.querySelector("#search-modal"); + let initial_search_body = ` +
Type something to get started!
+ `; + + searchModal.classList.remove("is-active"); + document.querySelector(".documenter-search-input").blur(); + + if (!$(".search-modal-card-body").hasClass("is-justify-content-center")) { + $(".search-modal-card-body").addClass("is-justify-content-center"); + } + + $(".documenter-search-input").val(""); + $(".search-modal-card-body").html(initial_search_body); + } + + document + .querySelector("#search-modal .modal-background") + .addEventListener("click", () => { + closeModal(); + }); +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Manages the showing and hiding of the sidebar. +$(document).ready(function () { + var sidebar = $("#documenter > .docs-sidebar"); + var sidebar_button = $("#documenter-sidebar-button"); + sidebar_button.click(function (ev) { + ev.preventDefault(); + sidebar.toggleClass("visible"); + if (sidebar.hasClass("visible")) { + // Makes sure that the current menu item is visible in the sidebar. + $("#documenter .docs-menu a.is-active").focus(); + } + }); + $("#documenter > .docs-main").bind("click", function (ev) { + if ($(ev.target).is(sidebar_button)) { + return; + } + if (sidebar.hasClass("visible")) { + sidebar.removeClass("visible"); + } + }); +}); + +// Resizes the package name / sitename in the sidebar if it is too wide. +// Inspired by: https://github.com/davatron5000/FitText.js +$(document).ready(function () { + e = $("#documenter .docs-autofit"); + function resize() { + var L = parseInt(e.css("max-width"), 10); + var L0 = e.width(); + if (L0 > L) { + var h0 = parseInt(e.css("font-size"), 10); + e.css("font-size", (L * h0) / L0); + // TODO: make sure it survives resizes? + } + } + // call once and then register events + resize(); + $(window).resize(resize); + $(window).on("orientationchange", resize); +}); + +// Scroll the navigation bar to the currently selected menu item +$(document).ready(function () { + var sidebar = $("#documenter .docs-menu").get(0); + var active = $("#documenter .docs-menu .is-active").get(0); + if (typeof active !== "undefined") { + sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15; + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// Theme picker setup +$(document).ready(function () { + // onchange callback + $("#documenter-themepicker").change(function themepick_callback(ev) { + var themename = $("#documenter-themepicker option:selected").attr("value"); + if (themename === "auto") { + // set_theme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + window.localStorage.removeItem("documenter-theme"); + } else { + // set_theme(themename); + window.localStorage.setItem("documenter-theme", themename); + } + // We re-use the global function from themeswap.js to actually do the swapping. + set_theme_from_local_storage(); + }); + + // Make sure that the themepicker displays the correct theme when the theme is retrieved + // from localStorage + if (typeof window.localStorage !== "undefined") { + var theme = window.localStorage.getItem("documenter-theme"); + if (theme !== null) { + $("#documenter-themepicker option").each(function (i, e) { + e.selected = e.value === theme; + }); + } + } +}); + +}) +//////////////////////////////////////////////////////////////////////////////// +require(['jquery'], function($) { + +// update the version selector with info from the siteinfo.js and ../versions.js files +$(document).ready(function () { + // If the version selector is disabled with DOCUMENTER_VERSION_SELECTOR_DISABLED in the + // siteinfo.js file, we just return immediately and not display the version selector. + if ( + typeof DOCUMENTER_VERSION_SELECTOR_DISABLED === "boolean" && + DOCUMENTER_VERSION_SELECTOR_DISABLED + ) { + return; + } + + var version_selector = $("#documenter .docs-version-selector"); + var version_selector_select = $("#documenter .docs-version-selector select"); + + version_selector_select.change(function (x) { + target_href = version_selector_select + .children("option:selected") + .get(0).value; + window.location.href = target_href; + }); + + // add the current version to the selector based on siteinfo.js, but only if the selector is empty + if ( + typeof DOCUMENTER_CURRENT_VERSION !== "undefined" && + $("#version-selector > option").length == 0 + ) { + var option = $( + "" + ); + version_selector_select.append(option); + } + + if (typeof DOC_VERSIONS !== "undefined") { + var existing_versions = version_selector_select.children("option"); + var existing_versions_texts = existing_versions.map(function (i, x) { + return x.text; + }); + DOC_VERSIONS.forEach(function (each) { + var version_url = documenterBaseURL + "/../" + each + "/"; + var existing_id = $.inArray(each, existing_versions_texts); + // if not already in the version selector, add it as a new option, + // otherwise update the old option with the URL and enable it + if (existing_id == -1) { + var option = $( + "" + ); + version_selector_select.append(option); + } else { + var option = existing_versions[existing_id]; + option.value = version_url; + option.disabled = false; + } + }); + } + + // only show the version selector if the selector has been populated + if (version_selector_select.children("option").length > 0) { + version_selector.toggleClass("visible"); + } +}); + +}) diff --git a/previews/PR826/assets/enso_markovian.png b/previews/PR826/assets/enso_markovian.png new file mode 100644 index 0000000000000000000000000000000000000000..ea9c4aa5f1debe04015dbb0d3094ec75b6fce91b GIT binary patch literal 65832 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 CvdxSD literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/hazard_decision.png b/previews/PR826/assets/hazard_decision.png new file mode 100644 index 0000000000000000000000000000000000000000..04610e301caaf2112f74725826cd719849bc43a1 GIT binary patch literal 120411 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 Fe*kameZc?# literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/logo.ico b/previews/PR826/assets/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3bb49ce641fb4a7f5fba44edcd04d6af917a6c8 GIT binary patch literal 1150 zcmb`F-%C?r7{{M&i3CP!SqOneH)WPk7yIRsQkpKEx@Fs%ZdO#s$ygRyfD;3YU=U=(6M+uU#VGJex=whDTvhi} zo;%iCQ~f4KXvFx?)2s-G4f5&vq|Y+ufEvyf;7a+)ne}3m_f?h$j04KBez?6)HSTYa zN8WOL){6)A?HrfoFs_2bd&ly55v@<~Qa_acT<2|WIUkzj^uT&~jk-?2dS{*;$iXhK z{%u40VT#m0qNt%iv$r~6L=Q9YQt$B&bq>JXCU6w+!KQ!2qzF^HGMJ_=ox7nwH}fW$ zF^gK(YZb_19%IX6XM!ZMB@!0JbI%rkaMqSaU*KIHqka!a`>V$Z6UmlogLsMb^iogR znFS?hds~-cWPog-5z68@+mbj%NuP%~1MoOo|8$v15UuSdT1_X435dG3NEF0~(pQP{ N)`*IK6D3l>{R3VZG?V}U literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/logo.png b/previews/PR826/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9f07cffb8c2783f30fd4b538d05218d2c8d82fb1 GIT binary patch literal 22236 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 literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/logo.svg b/previews/PR826/assets/logo.svg new file mode 100644 index 0000000000..d1e27d6c85 --- /dev/null +++ b/previews/PR826/assets/logo.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/assets/logo_builder.jl b/previews/PR826/assets/logo_builder.jl new file mode 100644 index 0000000000..0eed1f6e6d --- /dev/null +++ b/previews/PR826/assets/logo_builder.jl @@ -0,0 +1,58 @@ +using Luxor + +function logo_with_text() + P = 20 + R = 100 + H = 10 + Drawing(2R + 2P, 2R + H + 2P, joinpath(@__DIR__, "logo.svg")) + function draw_box(color, angle, w) + sethue(color) + rotate(angle) + box(Point(w * R, 0), R, 3R, :fill) + rotate(-angle) + return + end + origin() + setopacity(3 / 4) + circle(Point(0, -H / 2 - 50), R, :clip) + draw_box(Luxor.julia_green, 2π / 3, 1 / 3) + draw_box(Luxor.julia_purple, π / 3, 2 / 5) + draw_box(Luxor.julia_red, π / 6, 3 / 4) + clipreset() + setopacity(1) + setcolor("black") + setfont("Arial", 60) + settext( + "SDDP.jl", + Point(0, R + H / 2 + P); + halign = "center", + markup = true, + ) + finish() + return +end + +function logo_without_text() + P = 10 + R = 100 + H = 10 + Drawing(2R + 2P, 2R + 2P - 50, joinpath(@__DIR__, "logo_without_text.svg")) + p = origin(Point(110, R + P - 25 + 55 / 2)) + function draw_box(color, angle, w) + sethue(color) + rotate(angle) + box(Point(w * R, 0), R, 3R, :fill) + rotate(-angle) + return + end + setopacity(3 / 4) + circle(Point(0, -H / 2 - 50), R, :clip) + draw_box(Luxor.julia_green, 2π / 3, 1 / 3) + draw_box(Luxor.julia_purple, π / 3, 2 / 5) + draw_box(Luxor.julia_red, π / 6, 3 / 4) + finish() + return +end + +logo_with_text() +logo_without_text() diff --git a/previews/PR826/assets/logo_text.png b/previews/PR826/assets/logo_text.png new file mode 100644 index 0000000000000000000000000000000000000000..5c69ee7449cbddbaa1934f3bd5f9222e1f5da4d3 GIT binary patch literal 48547 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 literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/logo_without_text.svg b/previews/PR826/assets/logo_without_text.svg new file mode 100644 index 0000000000..1ff7c0e8ef --- /dev/null +++ b/previews/PR826/assets/logo_without_text.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/assets/powder_policy_graph.png b/previews/PR826/assets/powder_policy_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..8b653e781f556fded0a4dd90145782e6244b15ba GIT binary patch literal 174932 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 C2e9&5tSi6+SlXxP+#G+}hiAkpVcSCLXPQ(pG; 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 RZfkB|v6n7H`DcSoP7umeJ}keS1*Qh2 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%0DJ`>I#=c1^hMmtCl{BpZY)k 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%0D?VFoFo+M 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 literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/publication_plot.png b/previews/PR826/assets/publication_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..7175a0a01a57660668941a31a8ae0a37f03d878e GIT binary patch literal 26535 zcmd43by$^O*EPBYL_kCY0YOp`q`SKtq(i#9yQM?IO@oA_v~-73($Wpm&6aNX7XIS# zdEf7x>->4tOS!h(weC6R7<0_A2z?_f_6P+J1p~>!W zxDZXMWVd#PQYvLt-91Jww|948&SYLJY>_D^taWxN>0FHXgUpeqgg@U!+=DlM{1BZO z0napTfk^Vp`?A(&o2H)gK1a%gL<8q(m`5K=2i(%urCv%v!QDV0zCT6tIDdZyNhHK@ z`TY^PZ##jpxuM|-Tg!Pr^p(_8l>6Zw3WZ}-Dn(~)L^rxs8a6JftSMHWp0h*(#)gJJ zOqqRlricWoG$2S=`}_Nqo9wBwFAP0Cp+EA#wr=aB_0;32dRo=k`YD_G02~jkQ*-?PA|)e0uuA=xD$>76fu*JIHg2iiRe8 z<$85y7v52BzbXl{ioOa&n5EUjAl;*Hnrm<=)oO?*4uN^WJowJL!V|CUxO+HWLw!Dd&Raz2dDQ02Jyg+)X<`fnw(hHj2^ZmTjfejcp$ zdYtT#STkG=G<*=vVqsx%yBW-;;k)|Pc5q-HDva^``Q@}l>69faxaNls3}yEupg!ll zI`K4bu?YwWh=^Lu#|oM}PUpAyG5PA}_7-kVYP!3-@zKb6c&-y#u5#wA)1NL@2A`bl zR16NAR<_(;!{#HqtmQP-9CKj@>NbwAj{Z zIypHNsuYJsMn*di=k03Um5hdaiI5{fP?g?7mrV9 zd(e=OylY#`&dt5qG2v@)!-K-m3^VgC&If&HKZt94?6NGOT@+qiTpVvttgo&jBfup| ztGs#h>teffVb(^@f{fr&dccB=?Sr~zy4j#(&&+DSOBn7vu8BQ1I-EEpy{D%K$?tQ1 z{(f1+deu%ZU5hl-(B1u_-$!*mB8AmTfiez3z;e3MY$R6(bEffPs}MmTL^yM-P&FYT z!J$rb@a*H!(NU;Xn)?=oA91_>KKx^D+Xb&vyE;ujV(}a%bQi5n+^xJbk73`*mdJU4;6wydq)Qkc-I!=MIR({ z5ClATEp)&=DyF@J1Vm6=V#9ni>g;pK-+8YknVfm@3ZR!Z9)z75k2n*jT{oW2dmspi zlR_tSd{RGJ`5c2=OqI>UMEDRBa~NUcnN?n{@cJnR!hXMvw*#38vmRJj)Ip2sl+- z>+0&7nwrw$gMxx4C)HrG$d4$p18(_^`s~m9rlZmW$gZ=#k z`1nVat``o0f(&K-(wyjn-q{M9H`kYQ)-^86yCiH#AIHRtvvK>c(~J z%Qi?qcROzm?Yp?>+mj}*>bufjzN98rA_hr&m#SS|m-EJ!mVF}>(G@bYL~bjfVxbXm zuFRcv@!bqH#Ar3Svk0JzMdI(zucf-olOQdDO7kfSes{90?NSv31EaAfsr!@7Nh-`U zm^kDy-_?)9tSL)uBm@cy3e0O*QC6yB21QMNadE0CLrOn_z?UgY7@aC!MCWAgn*7MvR zMwF7AJZb4{W|r@=9EL@OhAu4X6m<4~XBB zDHF1oO|Uxq?LXSo#kerl)zvX?7aTZ|k&)+b-*J};kTWwgv!@QEBwT+QG6nU;#+wGQp8_jU4+=vZ!<$PIalI}Hr=p!m7w!OVgr&;?2fCDH)xap4s`ilN|ZQV%& zf4nq9V`D--@5ZvSp)T{N1Ueluq$L$a4gdprZyO4-vR;yrbuvxc_^ns11jqACTthi z%)dZd`uc#Vaf~%BA&3{0isR#B8I6_fmKL9BD@RU(ePwbds^;zYeJei{_Z+S-jBFI=-oqH0jSyOfgP9H-^`d!F54$%U-4d>nH z?@(jGeY<#PXGf0j+R@N3ySmyL#Fc&BRkbr?Or+P^`fA?xatl-Hfwn*N?c%*qF6rfz zg{xfQ7vO#MmS{EP&i-_1xztitR+g9laQappmeXFM-Q<2a91vq@XgFzEz1$V$!A8jK z`s1?e=*Tgp-+8GW8P<7yd5XzPbOMTgaN6*46rU`5`*`u&8mk%OnwfNuy*iH26KA?P zY4BRN961L=a*=m;cj=mrhA&|)KI@kceCe@&q8VMQI^|X_Mc$oG8ldvaq(6oi>51>?U;1`nFrn zdubDy0IjRiWQ75|l#&69@yn$1B=-ec@?CT6(_~z2Atc!|8$_mZgD$i(wVlm3paktT zU+#rYxFsbenGAePnzRS7h#&xpo7Xi!s5Q104x9PL9H|6%#1k1yMI|LrT9a8UnlI;I zvdr#jYT+GA069xF>!4lBZYqmfUv%G@z}L)Nuf)hg38LuBnaC1ZqEl-Rt(8v@)hzMee6I6yn6MDn^3NBtoeK` z#oyl_L4f;LD|}3K-FljbA!Cw8v38yPYO~j6V-#;gyB7#z1s9hZlhL1GL9K5lW+I%) z60FBVxd3MatDQjemMo?l)Y_$vaK5vj{-ME~5cJMHZaeb4ZT>ZSyPBC@z_o3Lhlgu5 zt-ZZ(qP&k@#&jP|nWX0{QGO6V>cG)CJ6YBVlOPx`poU(@?`?wc{FBH+oR zL}hDcAMis^A3uIejh&;=tzSllx4pGx@8AGv%;w$G>$#aiAio|s?x2^@b71*>%aut1 zAyREUw>7tsU$ze0WO+)62Z3DOBj5_4;&sE9-n5D0p} zj~aN$n+}~&4^`C(Owbj~d%!jw>%kFsm^X3*7SEze15mjk(|OnBN57A;{C!M_C^Vbz z#AD_AhnDMI8-V(R0_g5dyi80Ewzkp4At1^DLjcVfGb<~o$<_J!n<^1CAfATD##ZN` ziHV6HKkh_TfOWIkn7Idey;9ol<=4A9{Y4KxRn})c)dk599SOm`DXn)^qgW+Ql7*Ag zy|9xasei3uuU_iY8&@{olSzZS_un3++VG(HxC`EmqQ}R>uuS3G=}b}}DU?)9R5V+G zV(c9PMd6qvIzmEk4reqm3nSwT>~J$?Jn;F;l>RmQI#=$rD62$ivXp)_HF0mOv*tPd zGWuulzts6UJoIV+cjqOvuqX88cvs$g?(!Y{18+dbi`B|NQ*suV7f32GsDmg0rA_D+ zTaNuGYhq(#BP8S>Wd$|R^6{=((qSVSSon3*bS|+C>z0s<&Fd0zP%oGHY zdVtX=4sAcK!;0x9MMwHkCl@N4$Auq>biVv4+s36%Q<`k#beBp0lTAAIg)`BQnoD#` zuZ9*37bq!c0dKEYx?=#-*VE9L7WS~Tv~+QCF*i2{ccH2IF{+d6lYEHF?Sjn?r`IYu z5&lhrOgc@!Oe57xS=trTIB2$-dV$7(?(De$1o8r{%8LuaA8+c_4%E-vfFYj{uZ~9z}q?hQl@RE>x z`trrLH=ZWVC%N2C_I3NL4zGufUb#CgFy3qZtsJXk0uCv8Va}YpLCw7DfjxNw78VYT zY-CJsr${ftvui}iPpRmmT?phuH!MbRJ$}e^)Rb8?tCW0IG;4p&E?QZJ=K*b{mi0E5 zZCUD|9u1xNctLxNw|w7vr`W@o<(xzP%=@#y@X_U#7M-C=%)`O!>f22DemY#IYvO`W>*hHxjU z6&g9H(iZpcPCzOAW=R%3K)Tf{*Yn0?J$Y;9LDvXSv+q6RuXOEF@iX>U!|W?iH;wtsVDaJ+bLtR~lI ze*JNXk%5?3ox+GD^sXrTa$wYjatWZr(Qa?9x?K*`q&2wrkG#M+9FH?PMvEY<8+9ux zsh}2n8;dRU(zu>~_!0b45^5C!{L)<|*R>XQ1dEB_|5dGuScU>~k85Vhv4K0nZYcA( z*S-Pq{Uu#xy4g-0n_Noj=f*i6E`o^%?G=gAaRI@kgm;f%Of!t>5|!y@o)b`WEu1xE znQA)vx?^yGuSMBp(xc>fG;V(NzCh7RfWYRqy$>{L(x9VSxUA<~< zf`+{)%r|?nL>>`b5agv~!wrOeGjuv4h|_K>Qn@=K@jmI^1afT=IjlkNd&7f>3?p(% zCgy|W_?s5O<|28?o^^83@U@Jclh_Ik#W8y&w>$mMS0cK0p_OSxt2laxV}zMu<+|4= zb0i$ZN_S5|=lt#Ul724P#e5?viA`9tOocC_+})*qKF2tv=#x_@=xB_R8;yFePJWF^ z6(*mYMIjr(BfoGwuvY<&RbL~}rk+S6JK?F|bH$0qNRh%q2VDbq!Zy1O>y)BVuP zQ6UKVZAbF*}pNHqpx?4mmshqHCKGj!8^i#iz>#d^@O+ASp zF*p1YW^869dyX1Co7~0SOSC z&eYk7jSbJXe;a+qX_d>8V%%k+LIq!;ad+hx_b^U3jm(r$LN{WT6N2BzzqQJW{WTG1 zw7bRn29I%?q|fjyimsPOe!{7!WMtL;e3DUw#%uFk zW{~XlUV$-kx}tU>zKaRCK|OtyLYJgGH-;gH1_;{S@d&6Gr!y6#Vp-5%&S}<_)2Y0y zSwp5%p*_+OnbihAv$Bqd1oy5x4wV$VTbGVl8Hm$}k?KOojh_S#d^XI94b&*UJIg2d z&`T?+hy=cx0@LlXAn&$B7|o{R^p&Bqb!K^P4$S{Lp^^cLb-!DIgq+?^*35QvSdXy^ zP2(9`pLrZ3eK8PDA;h&LcIl6jd@rTXuLcKL#%dl*H!%3iQpqZ=zoIBicE7D$_ly^9 zw)BSj3hm=X{=H@zV9hYXJ6vl?sc1DEzvOnThn$vHn=nc`FpC_Hl0UEaX*sl%FQ{i0 zYty^maeSKgU)2bSRp0}AP)XqCJ_?D?Ws+$47;DU~8~)nCx0U;wWny$L*0b`;(6}$J zUq<3$3AZUxtGor@Vy0r@{t+5}Z~yMmV+EJI^PmPfbVaW(R@#l`elQEolZTvt>=D^~ zm4y6&&dG9C-b+qff3fiy=Nv4&d4aZ+QH}-^eu<&*?x}q-#Anxi)mq)1&#wTI4Qe&9 z9xgz`-L`wm-GfCRYnt+@OsL0a(MX1WdLod}j2VO2&V*}@jMxqj4o1MkIIa7tORqvB zuGkZBIZ(|$$Mo#*Ca%_WXr%X$>oVej`Sc+3$!tnqzpT;)-2m6RqcrVdY{pXygLd)X zRqP5S^iq>DIZs8>^=;HxO3}&8g-EgF)z$UkTc@C=bqzVKO)c%{*k>>I6{8(YGy5>@ zLK73mlAq)~efV0N>8^?rUlH4-8z9^7hZly`5SGHmpWB?eD2@&%P=(=f9&yRCN!c6wI#;={n zw!D%uQ23~OzgI}KH+4shVzHE}cWeClU4(h%n5Z|iN)=`V%3sL-1vITP(X|g^|ENU` zFkwHztDT+UBF(!85TfI2teftE;E_ zUTA%Zfm15&gjMNN4v8?0i(zIJVJRw zCrc(B7MD}-H%0Qbeb=5U$$0NH>EJv))uO9*q&64-;QDN~w_f0v8OVD=j9xdH3&qWxG8OB|qA*a1} z)jOZOV8wMEC6oZFV315jzWzhV{O>mFTU6*}XT)D#?#rj%gR$6g7iX2GrY!TB{h;t&&O#o0I;7U3Pc{!A8DT`2r;Uw`w`WnezpOW5 zFrWxnOq3`Wj)5kzePiQuo);KCJZH7kt(XFTPgYx{+zO%?cb5(0s65TjZmgRtP8`hF zjJ=udIiCoUv4L@Wl&G(hb=UC-f8UojoAWBzY^h1TNFOXXRbpd1L7m@+-Kdjg~~Y!GL9E) zOn4ELag-b!Qw{t1d1>y8fjDH|_yhz-j7gf-;Bnd>FLDWa%LP|7QOgl^gTjkdP>3O=zOYhDW*Gk0RgfAQga7po?1 z!rGOtlKbdN7iib3>{qI)lBP#>W@3w9yai z80C>}N&4%Wn|b3T0|&hRX;zwZ1x7ZHzNl}${^iwBqT^E_UvpK${DkhscyB!osl>_Y zX;*vu8v^lY#w2M{36#J^&Gi-8jNcv2iEjy|!{WCFKA|u+ z`BppnZNMP=>>e$gu;*;(t;5VuA#zG*)0@fZC_Uzfn`GHR_G$4|>{%pgA6My1L7?;J&xT;MB`6X|8c`<9=2yN};YD zO^O+`G7I6etD9>x7?%&1dGQm4T{>pWxO{s00u@ABpFxNCeTE^yDkb5^8>c$H8JK6y z*OI3{LprpRV_L()j!tzU=<-**onF?s5uy>I2F!R~Hkexmv*dM?ykFL(V@7z`wv%fj zy+D!w?FC3nl+H~d-4Ua=;KqFGY)Jnf~4JN`@eHpupqSryft&0mv z{b2%my&c?f(&$u}J#Ak6+?@g!^f7kPwp2`;GfvkRJUE<~(4(lPWS0xgxAsN;S3 zh7lHeed7vC#(TlbPDYy6E-hb2ZRp3UPs)-|bJQ~89~xu@K2iRH?TcZ6J#&+Mav!%$ zf|6pa4A3e#jME<(f51GS3eie=Ry+7{6dwThZCTb)N*Z|DW-I+4T@}*OXW$7 zBnen7ZYXvAi(7=u&X4gxJ8C;6$sk9AQ;;Kw_HY8dl@4Eay}Q0dFDpkkEjd!&0)oC5 zEf;|LXM1w(PW&XQ>7ry5+7|#|^LdRb=uLD!;u3YV66|vp_77P&{Ff@HI?EZA4xnEi~w#zi@1j^VVh z(Ie&$#?ai9RZp$@HW5##3&Ku^oie9iamYcP6pgv+aBqSk+^Wz1ut?<|Av#=NoVWkx z;nrGtWBZqMb2!Kc)KHI&?%{%`7(?V;bdPaqZg}GrzUipOj}@~K*8cFmkUTBFYNw_N zE1YZPwyv4nfkS&s`IA1^gKp8e;Zd%gzuA@KsjhIp`F$ivcz;<-Zk4OHrMV{a-TI&1 z$2g77Mt9IrQ$E@p3c{Xy13^p3K<8_)|F;uL0}S18)bcILpEq05stlJ4&i~E{TMfSRyEyYU&-zH2+{W!AX=3) z-9w)Z8&k=j7oYHt9%K)3v!txB&|KfLYv#ox_dJ9u4R;rQ6DyYh?HEImX%C|uWf?Jw z1b6HEM|d>p=@zuT5=xRk1n%*l@h=e`C>X2`7E?~U7kyfw$4-37xN!ui$k)b1Q2HOA zOqd&xQlZ3T!^LVg4Q$#!#_P{A4oVz+Vrz2V2Mf9f@>WOgy!!NYZBW%e()#LL7Ei6b=0s+=A@HQ9~-^Q8>BM?Oy?lRI5j)yRl`UQTXF<; zO2A-q-O84g9Y+g3y3T?GeO9KSAR0Kkbnh-%!E+Bp(Z2BE<^2H^$TtYaDg9S{2143y z(BcymnIV4%jag)`VeFb|*jheN>~6g$K*BniMIuU3cvNtGgnucE)=RePz`n!rT6Ei% z6ZA*AYn&ITqA^Zp-Wa?uA?RDtx`ACUf<50Qy7M2+mskOs)<`s7;(eCNI znt22mcrdL~bJO9@u%^{r=SC3Ttg}v0*R%e`P;l=fPFAYDN}&ejH&<7PAF(Cc#G+Ix zheS`KFxFl8_F;fWC&m7frlVd7+V;A_2{<3ez{Hjk>OvFS9e$aa{Gh#ZZY%F{s3@^l z%fMZB;KQLwQT}9n=HO%WW(`?a(z>Rhy2{3|PXk&U`i%`Zl?23R+c5u?ni_sAIU?Bh zU8*jvelv+*cR2wx-jWNn|2Ao?T=KYQ^nTb9P;Hi!+8Ym=MIf{E8@}#<10~|+YPc)D z4*>>ot<$4t5xHwQ=b>j$A^hJ#S1|xyN*u=gxWO@5VJZ|-Nx6SW^-#kph;E?jWAiW& z5TKti6fq=h4@NEVs-{IW$NQv829ob;-IYE6ReRC+9VGjELtcar>CbqSl00c+nCyN)+rxmLCsBMZB6zb|@lb7>p z$Po`geoe{)|o2>!mV{}ck-5cG5R}4p&yYLOl>DlJg>C zP*`CcLSGG)fdC+C7MaV=aw6wVEl>i~(}E&u z#L>ELi)=pryuDz>hJx#}F4#8vk%ifkbjeaMG4Nm3+HCC?ej^&74y-^dA40 zZ(eLmw(3R4y$6rSs?!^1C7h@OLb^&cW3}V6cvL3p>^zQ_`p!W7J7_1p&IbL{K7bqF zHhl%1_v6ggOs7*b7pTC@b05Dhu{8DoBWJ^nq@#q0zCQyztO7LR6P~_Y z+buCK=bBaO5)?Ty=)Ty(n2GAy#ove6z}5bQFN^sb711- zw}MQ$DAvl2pwE%3QphO#4peJ3Td0?`m8q5+6m-(Ekssa=D4~-`d@EcjSO5#|cUm+a z$|zShBq-2|(|s?I<-eeY6oN!3KKqj3FQ@^^Bhw=uTAZrwe$9l;_~y#`FP{`!E~P61 z2DO1Q_nao1^E2@hMcsvRKbiB7=7hx8hr;Lqs!8Mwi0DG%rrS*KHLQ#{GzgoW;rMO& zm(UOd(18YkvGVOV$mpuWqC^izEuj;YN4M9usTyRj>BJQz zeafQ>4H9-->1B_Cl!O)2QN||#2KY$@m(UcYBIBCBWNu^`;9NcuDC@Z-)#q)nm%jih zCX~RrFz@DUR#I)Njhae0TEPV0r)VEHsMj4Z8%-<9wOXW7r4+FLiy!%}Wl%7Yg)-8~ zi#Sn3(Cxs=(uCeGT34k#Tc7HbD)v(9Vr2Or0CTyPWjtcIX4lZMhHk5-a4+F2(k_)h zqZ~TvthI}k#lrW*Do~;WZK5IQ{Nl4;aS;7KYsP+xM?upxcj5H2{1;n55`f7VLEWsA zTnQPMyoA#mfdEp8(qx(0U#|ZfTMC5y#fMmQnUj+f*2tGi(I9Y$z<~oRIXX);UHl7M zw$!XCL}+B(GCnJtoGoegZc_2A22zAlGK>k_e6$_T3WtKwa?_q*E54`pM2fPIE&GzZ${#UmJP8|4u2N~K*$-h9RRdbUN zzwAATxP7cAkSi1|)LBGC|1xudWCgNWm+Fp9v|$n$Au4AvNU(&WBJ&+D8#$S~Im(qV zNrH@%rhwsZ%t8M2H|9XXTu7 z8uM$o*4b}7$a~-a)7UKLf|LI#h5p;vV9Hv&oaYGdw=SDH$B&o(9;z>_a!bw2+z?jlU^Ce$wa00EhC=)1cWUsA8qA?^mY4SabkPZ)FE3B6@|{jcNO@ttl;~on ztW&M7{qg#W%aH!w9PU1M3yt;)teIytNt*0V0vugV(eUwUQOFlw;C%(`VUC>sc{ZoZ z&rUsnA7!m`eqOs+MG8G&o#A~{=Ucmmxjfm_1M4TD!W!tcKn+L z_E($cIU!B!mNT_&n|VTK67CMpt(tz?JA{s>@X%4D}1dRfkZ{YN_Vny zeRd4m=gRlU?bKy~aOV*16l2E~`lYkh7~`9d*y-48r&%cQKWc)R5&Z@J@kJO_X>{l_ zV7S;=gFs+7#Vu07(RzuR+064JoV2~O)7ixZ6Jcm(<~a_|de#%cgx|&l?_9F>phl0s zsKSDmt3Z+TFVKwcaE=(3i!QMn6Ya<*%$x2J;4z7&Y%R zT16xMX3m4Ki$}Rc%?&_nshDHvU%tv19yghEsHR{vVTDIvf|3S{(l`-Zr(VAt(AAQs z`4M_42OKWb)6)R~0brj&`qc_KFCGahwRDtE5p{5vks`r&>c8v}bfpy4(*)05ln6?< zQKsb>l{HRoQdmQ9pasfp;$Vqd>`7nP6_wTnQFG1hj}B+m4e4;U-+k|P zkXSw%0;JiHUVWSE0q2F7-<9)Pm$K2-S5szlX1uJS4UgyV?OL#)q?ebM2KxGNZf~-> zZ-&akuY=IluBY#mJciX-+omDU4F!`ZZ^wUlOQ^v;8M2rtBiysQ;uE8dT$2-?+G4}9A|1;Om?5tNBD@Gp1BZ#)li~TZjZDhs+x3vAZEj|XYJ|9&7(pY6_3bX@HX_vTWSO-n3{U8 z`d!8TXwG`we&uAOz8!Bo=5%yR$dZ!lmn_2{=UVqG{{Re)6mQhMj=Nw)MbF z*{h1Zx@cY4{cZ4R_(|!bW>P$YN>0!Me^@5*mr}xi2>nV-iDUFR#}7VXI+cV4&k5>U zw~>T(Hxz}(nU2)lt0z~DN&S4s+XbO3|52cxe<*$B%Sd5v7N#*>z+}lY^krSMw?1bI z7umkRKUsUi*RIdLidmIvDRA-y2jo+)xx+?~3}Q^&7Qgj#3>T!24a3U@nSo#u8+^e@ z15VQeF9POmIO`g+Y$x%+;Z)#f^y#ziPqOYOhi9M1$d>HdJz04abXwqEDS(OV_)`|g zjobC~btR0iZ1Jn+abIutnZesnv!Li6Zx^|wJb;6^4D30AnN?6{5y{0gzK{`d2FA(~ zjw)7{NN^kfzv}iN!!mr-zAC%U5ZqHzQv-G{r2qEHrW{6z@YS)*S0R9*Avo-a#EnauufQ652cRpXF)V zygm!(;|{8&px;P6Sb2bpCn6)Y5bvMBLb-q7N1BArl2YREr-I!Vl6r;kt}A0COku1} zqGv02KHZ8#;Uo5?xLGb~hy`oi+}_2&JH413X0hI6b2M5=U*yQ@9$O;fdt;Z4A|59y zaOJ1E^v?>hvx~J82}K@vE6u@?BF(8?xw{4i|} zN|r&lEqdJvwd|*|z^R>Eu>h6jm$+B-?USt_X^(;G_lcb#rHJusCCUeN3${04Oo`u! zEy$y5;R69Sr)#SduLXA>nyEYm=BUHes)x1IYg`QjU}D<8BMS=o`Sh>JS{}nSn2_tw z@~obRG1}US%=G#fU(-*#9J6^7Z;`sF0oi@ab=-{Ig#cLqSc+dY>V$fP78&&BL$Dc% z-2=*w9t2>p6IXfAd+^yMvdfs3PWncrBY`CaO=s&1$A{*HR%4f~Aeqlo2=B<3%BZk& znvVE~e20#<8ZKQ_Sva%=F2pu$Iin_=KiZ&0IMLkmi}-3-3>$ShiDLh@C+go$@VfK& zsJewqRmzFjL89G2l~&T{=EiTaY(HYA5cHVFDk)hmwvM{(_})#F2ppll6~=ngimPs_ z^+DQRr>%=p1be!vxz0HrzkYT&H()kBXqAr8uzZvXZ^h}~|0GSWKe|@v4d7iz-=Wgj zOhq@ut1sqXN-?IXr}2u@&5jheSwI!<<5w%S>d}AO>V1w-hY)y{w0bxA zu2xj#Eo{D+9NwWW_S?!JV^X*B_(M(Lr)P|sacU#W;&_qk_!3x%Gyl-A(Z|aA7$O9Z zX2X7u&>vdml^n(5kiO~&82o<2B?b*p?N&xJXB8mM`JB_u5C~@Er_Hq)PY;P2$hjtY z(`fS=3d+Rz^tEGge>o^nze_924@jUmj%W#cSpm5 zw=rUu4$dWpGU?SZ9b_|%G2a9VLs ziNBvL10_a4Cl>cMHFyt-GI_gy8`;=WL-x6IK*KT;o~5{dZjm(&T~|X#SrHE@#ayxi z>>Km@mtR<}8!+G=4deHp5~)9p>(-UL5}rR45i}$Qf>5%8@a-IU-5H%g?xyM2lIM>4s{4i@yyZ()Ca@uu zD$REoG5EA>Q%jc2_wCuDy-##%GpJ=m*ZmJ`5R>NfbXnLdSo@jJ4MqxpGIR&PD$&P5 z&f|fcKMFQt$x#Q&4~g#Y**yn0ZORt^Rmg8Ao41=x+1Q?|2e~<`j2_UuA4Ppyp+rdg zlMSdY%=v8SdqgX}`I8K^(xH-B2qiRU`5?^QAdv2%7n*z;vW{0sOs0sVwZ7 zI9jii0biWr7_dhzn&h_{9-EFK;E0Jp8YOp;4DUV7}%1w1&cZK^ZbFo(VQ z)kld;sfLCji0m(*MBDR5>iz@TDlP)On^{?NS^V?KS(WEB6yeG zbj1|r8$SSz9?U28v!U)CqwILvX+R$VPX-LT2OqE-;3+MT=+1?;f| zcv+m-Ex@0*iF{BQun~lIU5zQjNMd4SC;9&Aswz-8x}U^2_32|CPQ4&}FvW$?r}}E_ zDbCx=`Li=acRx=p^=e5;^?ov-C{=_HOq}~ZW%Gb6PY}uA4=|qje+A>$btp=DA`J0{ z7OX{0V9KQ9ar-k^Y>VRRvVf(SA<&yv9Ha4(T;vin?~<(Lr+NyC)+p%{4y2 z+NE^;BvRedW3a|~DJCj$V7aD748pf5iiUWnomEUM$>a=TYRmKW~FCkWJ%#_DBHreg~g zxM$Mi7qUDk8S^9{56S*gObFT%k?8s%WaFblx+2LSam40T(9An%VevnzXq${!&gUi`@+L&oF_$1nRcSrE5h%Bkr# zKfFq!Hg3Px>lJAfi#O!1;J%&yvS}sPt&ED__yI(0Ye*6lAZ_>GNUIIC`wM9m6zrAR zm@nL%mVbSI)!G$^WU_YTob-6)2*g&D?SEf_jKWVZxU23_G<9PW&0Q4)7!x4TjA?r5A( zT2sBXnRC48lkX5|m9F{Z@g=vie7m7{+_b`EhqYR4mW;^^JnoU;$>_7e$o`*^F@W1Q z{8{KcHRTz1cIswer%pTY%g#>xj&}`vE`f13Xu6|Cv6=B?g%6)|9>Ki41t=EzoaLN} z`ThsmhEtt&)XuqCnWEl*bBz$n6+n*dzkgOfD^vw^Vt36a9Lz6(-_}h=9!#{>H;was zD>7e;(10-8=67xIGvAPC+G^}0klE>Hf z#PQ{j95xEgCE>jgG0RUN0HGG;$bRzHWWH9!92#LxLUlKbygU4jr0ebB;Nkx_kPS(e zv1XH>n{_B??u`h1v3T-mV54r;v*V|26~npP+Q$TC_3PTlp!9t%#c z&!kj6@$Z)VZ;*vhwmttn32WX)dV*q}va#eh-^p#3_k85s!iz4V2Jy zEe4Q!W?-`5(9ek~_Yo+-;1UA96zx}~amHT7)Q4(X>!9&K0+i>kIe?W|i%t|AiPfln z!*Gtx2<|UIs353GOCfgy0qoVhJEPVcuy+I)h{tLAOSU&|ulFjB3Eq27@P2M~oXLd`hcDbDE+CukxY+wEhvAV`OzK;RK zzIgH{#4>zOAzoU|G*RiZ8N)r%F$Lxr2e~vd9s${Z=LDLZ*bxB`NqH&Y@$csmdMn4g z!?93aLe15dPBd6@Z#i+*+^j^UefbRb8lCcu48Cy*~qNTK!d$^po zWy`K!rw89iixmXH1q?HdW1_pT52pY|+!M+g8pd2PLmmPfA#TstIpTzWMHtm-;Bmlf1mc3;l~8KweI|y?(Utxf6cpZ)5u+Jm$sAu zZy{%@XqMY@1RESKhOck0yZiao(x@76vb{*V`pNZ}_WL+x4Swy_P+8C-LnC?9ySx?i z!3?`T^R9?TULM2@8B;@O!a+6 zQs8}fhT2(h->eWPS8Rwu>yi^ORpn#u&7S{5eIq}YQ_V>yL@FM+AZQ^WSehuT4mD`8 zTh0M(O^WC4wLcL-e$Dh1RQnVya+hPq&%Rd=RtWd`x_*&CNfE`klAo8V-gSTJvG*^3 zwgpectRc_a%LU*wy#+tg;D2ua8`@mxGi9cX>-qfodC;2A<;;Tj@t7+4KNe5g2_JHd zdU5FJ_OYaq@C5qtryQ4zg0ex~DuZuZq$&*+_4zRUU|v4?GIp(gV-ZJ!LW&`z34>Q= z3@G_gR$vR@n`c4GpHikH+*R%f&#c+Y} zNBdlgY+moc03jhE8Is@NI@p45$BrM~k@@CzpYdU_cki7_m%O)U4HE@B%=F9tvVjr~ z`g{$h{adp8g0VCzO@=7}75wzY2X;?+wlEpK&E${~BNRz0QVB zOzocF^c-R?J>{+5N4@u0O+5CO%ff(hMJcW{%|uYcg&5d=Ai>yIy58q>vPoBoR<(gz z7^b*KCmVVo6u0r-)lFVQJd*O`#th&n8-Tvde=W1{x&}MQ0b`^VhgNcEt=R<|&01Ga zSuLL^2X>psm2P8xZ|2D;?9l|fXgxg}v$F$;k-*a!`>3}vva(h;G;p!7RMph5(b8H^ zUxx@cuhosTFp@I_<3dU8>^w6@Hq)fCl_jerDFo2Lj~z(<>UcbDtGe*LN-u`0JjV4& zf>zz@CQ_=!pB<5GHK`Qcl4`$wbLp4@=m=nws>oI_RkRmZO!vn#*J-U1*3?he)3$)4 z&YLEMDw(^Q8fMuW`jW*f-2$zalOv z*#mw6&EW$g&mSY&^73VFhif|5TdFB+|EIIF42$wv8~B1Kf=cJo(uj0-Bi$e^-QBtV zB~(&bLb@atq?;8GRFGC0mlWv+>3U~9=Unf3zrOQvqlN!&tMGTYS8VvF>$g7QPwjJ6uE;bt#MM2QScTui|yb12o+fUw|n?; z0?&Y@ye+_yWRsp43m5>&-qv3G05N$RTbRttzBf!~Xfg)aSuFsK2dC=i>Yd=LXMNwk zeFHocZu3_CzdKUNYEDyKMuRO%pz%s{%AMMjbUgaqP3aw)f*BvS&)+uE%9n&QdACZ# zdj*`{IQ|X$3}Xj_(0i143syA&TVu8VP*Cb+Uu5~SP1KQ+p7Q*YlTVJw@G0x~WKtdX z>%d^uGOwpPAgk-(v~s`=CsgFlAN6y|#o0UuKv6Gka(Z zGe2AV#Gy)d>9&s7U-n@U+1boEV86bgk^8lS+7b(B`!O~lJTl?31{Ij10gH2C{_aEf zCBRlEkZS`0OOULtZd}!Lx)o;<@rn3qNBivNlBjb)q))>!yL1kdtdQ>-zQvZd>S^LE zT(XI7_&3FR!NBPPrQE_TqJa6^+MlJo0^sk*S9lV|2bPZ0nKtKMJoZvv)_*QuL~xJ9 zy0hy~;4a2~D zdB!TI^_$rehml`)mfV90>4k|7rny#Q&X4+k9Ef~aZ@Lqs;EJK2#N>vT2C`SQ0Z(0z zt3^Y=>2#kJuno}P2qlB{;MK_x{ z4%1k8uf2!c5)&6sW;_fuL+hz_woAt-@Uva2zLBzgS-9Cq=69~cFFlHYMo7NdITW$) z9{ZIh+^n#Kw?L@Af^Xx)5?d6aO?B7&MV+(=I0^eBzt~rHE8_Mq3eD!K$=jZpI3*@m z;TFuvgk}t+skS&n7Ra-npTx|@FK)`dy|y3zb0CmWp`0leGYaQ-(6i&lm{Rffocey# zCM8ploW);!r*2$qV^vy19yI7hs9s#Bb~h3+p{?X;S6M9rhZQScCtj^GKb(uWjo8}L zwye7M3^VJly)14AeUw(*Gy%7fllnUDC#`fMBXXk4W9Vi6{QLhNA`ZP>gvhWrg~$=C=?n1sft-qAn|-&`+_}x^I*SCa@#DoSMvY$ABNW&1vD@Z^460%Gc{j}`2TUv1 z1ezf;HnqpSM4>({Axk^aL886iTro{fhob{KG_7LoUrGjBu7pkZvU2&+h{9})jVCTc zc^L!Go5*xFmGOic4SwUE)l}Bjf7%`k9@R)0vg`~F@JQw0bQ3={W{INTo2lZNa;J31tm3XS_8F^8#c*X)Yn=eSu~u&1lo z!>66a!)wHanHemz*5>@WBD?S8ox|zu)dvkTAJhA9Xb}R*Iqxs=d^>fVh~ZGl)eZU6 z6@Q*OhrRlWxgb!w-G}Eiw%5U){aYv(b=8$k}j2 zA?&FG2OL+vdUe+3J|kI+ZV7*SSQ|l;mjdJMZ*lAO`Ciz0mEt`s!PpJ60{9W?D~ea4 zI{z?^?*ATdzc`FCt_y0nAo(PjX=@QR$L{JB>b~*eU8B_aO?Go@Q~vhrvyl(Koh))B zBcR#)!FD-pF;N$)E$N#A%)vNQl%q~y;K|@O34I}sv{Y{>-fux9e#=0<#LM12J$Bxe zGmc?n=$?$le8&P)wYrCk+m|uepG7k{BUm3a-(qYw)Q5c#iSuJL@KHnf@4c2|J_HXt zp6T4|XX%mTOF?L_`1j^p-xFc#4R>zeR{4rU92382MD4)9$wO&XLLG`OLF9Vsy3d z=;8)NFCvPMuW6LI?-3W0lNEnadgx8+IE=45MbRm%NO>vYC1;a)sU^Ol%7>L`u<~Wb z#vX3dk5si`OT$fPSQFbxAJoJHy8ro(m=H3-e%7%Os>E4X_{UWG_97AH9&tThge?{} zsl&mc9uc&%S6U)a_Tg`XVM1l4$g^N`$d2qLiI6>8p&UnY(cxy*aRn-P>pFBlX+(WO zQmL#rr8U9FXr=!dVc8i)Ad+VHGHv!+8&s0@ROZV~?vHV7$yu|))o5aV(t+v*JoU8YS`6jyT5&>+#6`OaK3GGCn! zDIo3?ot^+0Q$Pl#uo*tpa9WX2IcQ1?!ONDs)K>ZV`Rh7E9Ks@J;>Qo5*X)8H3kH}H zE8!Dk!EiH#pk9N5xK;NUE9d|g!D@8i#!FeHy3c9J_9@E)QvNkAOFP}(>sv|c7!Qp) zEq}OJM3H|z_X;|GZgMl2GFM36nwnxaI81(9OB9}8WF+cZufG!Wj-k20Lq1l3dEnhS z88qOzj6xdMKjecZI?R;Xn_6Z&qO&tbDXd9$0~x_Eh>WtYij~@L?^L6eKlhyy1!qNk zE3aw}vRbC~u4v8kM`#^gEBZ)#lxieepR|6@wor6hiX%By%AHE=kc1C|~h> zQ{Zw?OnL91?Nh(roj%dN5&Bj?YhhCnBk029aqy5sK5?n?-obmAP#ZCbI{;^#pcv>;a#E%&AqrRR2(#y580^l z5?zdq89A-_=KA6}|IUFcyu7mhh{81zzsJDcj&CpHC$>n zjVlD47e-o;kmYdZ4 zQMD_!y?+_;AJ41Fn3ZBdjZq4`?(oGDFMo0B?)cAA$A_;zof16A^TK`};<-9yHvD|0 zSa6W5fDv009Tvu{DTe&$ksdq0V@^Fa0W zX8)*TBdyy8o2J%K7R_pdU+`#!0COAu?iV0NY2bPw#ZFsOSw3YAqevT{{J4caGb^v? z1$CGCm9SN&f4SO_0bi^=Y5gZwvCpFCq?PrPzDmz>-s~nO7_P>qCEF>g(<1Dfh~wSD zR4eiYl-IPx=Twrk*W=n%_2dMSa%I7*a_8yQFHh@UdDtaaw)$$-9%m!-LTK9wd|0F9 z*IjaNr?F%R+BL0gUhyAqo=80*F>IEuXLD{-sT@nM zu-Ja*k*HLwYsD(r94l=`3Xx%N$BnQB&-dx7)nd(?p|#t}Li6BbmaVN2M%;2aNh5{- zY2k_8T$-DN7h-cQYRJxR+92qm(%zTMvJ~jjb@xP!8H2P?Ut#$HTYM*{k&}jnx*oJ9 z4p>2@!_wY3N2ZP_ATEioth`#hTYJn!#)sZy;O1eQ43-)zA z=ftqdpuGL_16383RC>pP{!!NknvSXL%%nHmntx`3CB9^0lu8f0%cu~d%J92+32Oe> z*>Ck`|HlVXgQPiz?H|QMqmRSz2MARZ_KAN+NA*#O*<}*7b-#i zR$@0pX<&A#MsxL9OfOxZLJlsu8%-E%h8RsBpuGb7y^&T}{KXi!@#2uc%*pN*`yS;A z3xN`#h~A6ch?Tk9Q3%B4^C3s9=0{aU)kVPP)ma0d=TD8S|F^@moy+Flq#Rp|n|ySr zp!31=gzHnAM0Rl`rJkn@c(?5W{;osvdKGA2%UHXzlOOiLZOe0|t4m+Ux*j=4{V7pH zr0Q2UVj?e(9w8$8Fu`L7`t#%N3@4Yv@~xn*$o;eu6KU^LvygQCcO=SRAMfU<>9vM+ zkG&w&qX%Xc;uq*%=I08lKGb({wLO*py8ha%QQYvs!i_tMz@uiC!gn?L)by?M{=DCx zj;ks?5CLcnq{XRO^O?H&rLi?F&%P}d;Q0{Xp(e=!#o;gJp#_0T9yNaazy0O5GB*zJ zm!!qsjO_*kQ_J}<9N9aN4`^cBh3 zze+@sXUOdd`>_2)Vv+Xw~oU7|T!w`$D9Lfj2UY=MjB@#D`m zT2!n_HB!AZcJ5L6ht_4oOpH4uy!{&?Vq&D6TF8XMM{$4ve{MPKr<1CUz4~MRd81nk zXe)QwGgZ?PvP~3-)-R$K)sSD-C+n&0WoCpAjWA{$S=e1)n++ZGW#z~YqQl;77uW|0 z2X}uWSpwXYV;LJ4gGv#hnC0W!n;K-`VXY;Bzdn@Y$CCvd;1@ngYj~6ivU53u5iJ>! z!ALGPTz_n^8vFhimd9gCdTn*?D2;anZNWOSFXgV55vf-WRnlHC_aWDuIa^8K5cQL? zlGYjn1>w_Wrxwy*1(=bu?zM5&D6v3nFk1IuC(t|W>aQ#=w88UXr)J7wv7!HPGBfjb zBh~KQB{|9XUl$tId=n~n|Dt!p%*%QJ@kkQ4s8WDcR3uOdWe=jcE9W-VkhI(%)4+{d z3&x>+Td%~%@f!24Qis394N2M^NhZ!nW^gq zcoonxYci5oOA^(YWRUKRzUAJ#cw9rjhgH;E9k(xXY5dOEU-y?YbrZTMR(hK4mAQbB zOg7%C%YvG}ScVZDqE9#o+SZ~k|km(;4^xf#UK*Y;4nJZZRT}} zo`{;JCkP?k#>57Bj9^hecQi1(gEUjSlnB0D`6w_OUycqQq)ADbWY1r3+MP%~eYY~T zAMW7Bsv=j!Kc~N*6&6IV%xJ8QWO1lNeIUc7$))G0X9wuY(aj@(2HuI~Zl|E61e8G! z&12PgB}NilL1=tzfN7n*hOBI773|6d|3mWepWrY#xfee~#;MlsWB}AMI0}puxpm77 zFg7hMEq&{aMsX1~z{{IBfLW|K%v=W;hd$z{C_X5Zs-WHD!neoJk{d3kjcQ~0j(GV^m4V~Mo#A#zLy|2toYzN+95W%$Uv$S0KIZ6kYz;p=Y<+~D zefF#1;e3bfo6EXk?^2|7O_YpraSe)%&Rm?mE~j)6t_bW~aQ z^#mzUb(X`07^i;gn$W#YY&26bw2dG}Ea-qqB`GB(1$08O4fJPN>caKu(|1+matuN* znt7>x?O!4Il74kA^^>I{1HFum(QMO?jY7HdZYR*OV;o~w1*BAcO>RDa!k0J>8)B?N zTabAPoj=i5cW@|q!j!R?EpU~hRQQ@s;&83t!u(@*WGfBI5NlXBo+UUEkYsn439W#&y z8px+M^!wS{+v`ht#BK4B4c_a&HAPr`@&QmFSxdJekR8R>z-FS0S>YE$%D+E(J^&#E zx}alH^!GdrnB$C_kSX9PL~0?%Hvkm_fC#|(`qFV{Ztgf-GwQ5^gM&QNoRA$+^zUFW zz%w(ch>7(K4E$E(H#`rTrKB*zuc^g+pFDgE_}BniUDbZ>JX0qUG=teNdwa~ETpMLB z?-uaSc|-PbJQ}{y-`59*mC$0HN5@s;6DB~imdoD*jOG1}z7uDoZi=UL9K0Pubmt?D^re<$n9FVpW z!iAS0m%rdC0_+Gio&y-_fO9=(nd3nt9e$bakmGe3?CHq^674hhFfV_5g@s~XUIMc7 z#R(eaVN&70JJ&|^+V)@ny!WqO%eVzR03t9M^ZzNlg9j%K08o7as7ew@5x;%=cFvr{ z;2So*Jp_U+g1#d_J))Ka{_~hCF1f*m%F_+vT&}wE$B#4?t zLP`V%7H8Idv1GAs-_92EbDq%y^1l67-jjdjoqPYQ3o=)Dhm)+|mhUYSSfP8$nHoT@ zNE&d}(l?kX++tQ4KqfD!oIuUa$$5FT3-f3&>5+_rzw`|X&B6ry_{}3Uj6Nndwns7n z-eV3b1>M1Dm=zrJpDZ;)Id$~R3jQPW9fblX0?fFyR85Oh_^Ou1b+lek+U4bW z|IX(`*yU1cDp(*n=%(`EG;JK1+&pLm8>7vU{@b%iBYFZrtzm)byA?bIT zWw}k=#F84>Zq|w}FQ&S{3lq2rlxLfx#m751eS%+SsY6wbP4YoQCG(=DH*oK7z@l7(!YYSj{*W&yZfKd01&(A zwUG8OWUsBa1Q8)2nun^WtW1nWNJ64OprEOlutaWDWntdvYOJfPtDvCZ?!E&^sVkv$ zNld~XbDSxvHa0eNAzL+YRxPpXS4#-+g6Y6<6Hc=7>gwvoMl$IROvoPFnEzd0sg@9l z=OwevUWH|4G4!v1VFciDx2bC2KSXj3*hm1u{BTeh7!O-pCMhY4hi^kq7hi%48iD}V zobrA&|5GafF>w{T0BmkC&xKFrRqYu7sru0wtm1;Z4V+zE@M$HQ-Dkz_MC=1__vu0* zCLqCqhCLl;I&=L30z9T`m-l<<5?{T#d+#2q#i!&D+(AH{@95lc4y=we?E2%E3)t5~CE+K?c>{g@z;j|i&H@tVykGKy?xD(veO**! zqzD})6dDXP$$w`?l!u1^3RRtv`lOI#8y0p2xb0wx$@lVfzp+uMM~@>Vre}#gr4LYK zdM>ggf@*7PN4&-X4>_Wf=Xh5`U43hJ_hfDr3v!nU_=%|{yznRa+L6)G94V@+i~vgm zVwOsi3R((^=EEbv4F3ae0RSf)XvAr<6B82>h?;~n#6)&X7Cu?maG5oK=j#TeT4LV| z@T6qiu)$<9oShe4ya0{bhE5{v+}GE)UWT6HTHDlg0(^`W4@G=@ zJOY6LaYO@%vH;iU{Nm#9;9z5O6LeK?Rm%yz7=HbyMB2O~yzMFH99Yyp-bS5lqgIjl z4%K6f_3KOMjr(K5}!1WM{LYafh-ZWL}YHCZKBID!Z zt?A&vs*(gS?dYKIudN8#K$IlVN{YEmaIR*$&bA7Qi)VhdCM6{eJ6le( zxOoNcm%#?wL>aAzVRi<{+Yj{9wEU(twapae0lmG(h& + + + + + + + + + + +
+ + + + + diff --git a/previews/PR826/assets/stochastic_linear_policy_graph.png b/previews/PR826/assets/stochastic_linear_policy_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..febec02f5fcd5a66367f86cfe74cf933499de2f3 GIT binary patch literal 10220 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 ZNmx9C72i#Px7)BDKYF5&CvV{MzX0!A@D2a~ literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/stochastic_markovian_policy_graph.png b/previews/PR826/assets/stochastic_markovian_policy_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..073e9ed69b3d1a7a8d65dbc27045d83a3e47dbf3 GIT binary patch literal 30279 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 literal 0 HcmV?d00001 diff --git a/previews/PR826/assets/themes/catppuccin-frappe.css b/previews/PR826/assets/themes/catppuccin-frappe.css new file mode 100644 index 0000000000..32e3f00823 --- /dev/null +++ b/previews/PR826/assets/themes/catppuccin-frappe.css @@ -0,0 +1 @@ +html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe .file-name,html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-frappe .pagination-previous:focus,html.theme--catppuccin-frappe .pagination-next:focus,html.theme--catppuccin-frappe .pagination-link:focus,html.theme--catppuccin-frappe .pagination-ellipsis:focus,html.theme--catppuccin-frappe .file-cta:focus,html.theme--catppuccin-frappe .file-name:focus,html.theme--catppuccin-frappe .select select:focus,html.theme--catppuccin-frappe .textarea:focus,html.theme--catppuccin-frappe .input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-frappe .button:focus,html.theme--catppuccin-frappe .is-focused.pagination-previous,html.theme--catppuccin-frappe .is-focused.pagination-next,html.theme--catppuccin-frappe .is-focused.pagination-link,html.theme--catppuccin-frappe .is-focused.pagination-ellipsis,html.theme--catppuccin-frappe .is-focused.file-cta,html.theme--catppuccin-frappe .is-focused.file-name,html.theme--catppuccin-frappe .select select.is-focused,html.theme--catppuccin-frappe .is-focused.textarea,html.theme--catppuccin-frappe .is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-focused.button,html.theme--catppuccin-frappe .pagination-previous:active,html.theme--catppuccin-frappe .pagination-next:active,html.theme--catppuccin-frappe .pagination-link:active,html.theme--catppuccin-frappe .pagination-ellipsis:active,html.theme--catppuccin-frappe .file-cta:active,html.theme--catppuccin-frappe .file-name:active,html.theme--catppuccin-frappe .select select:active,html.theme--catppuccin-frappe .textarea:active,html.theme--catppuccin-frappe .input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-frappe .button:active,html.theme--catppuccin-frappe .is-active.pagination-previous,html.theme--catppuccin-frappe .is-active.pagination-next,html.theme--catppuccin-frappe .is-active.pagination-link,html.theme--catppuccin-frappe .is-active.pagination-ellipsis,html.theme--catppuccin-frappe .is-active.file-cta,html.theme--catppuccin-frappe .is-active.file-name,html.theme--catppuccin-frappe .select select.is-active,html.theme--catppuccin-frappe .is-active.textarea,html.theme--catppuccin-frappe .is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .is-active.button{outline:none}html.theme--catppuccin-frappe .pagination-previous[disabled],html.theme--catppuccin-frappe .pagination-next[disabled],html.theme--catppuccin-frappe .pagination-link[disabled],html.theme--catppuccin-frappe .pagination-ellipsis[disabled],html.theme--catppuccin-frappe .file-cta[disabled],html.theme--catppuccin-frappe .file-name[disabled],html.theme--catppuccin-frappe .select select[disabled],html.theme--catppuccin-frappe .textarea[disabled],html.theme--catppuccin-frappe .input[disabled],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-frappe .button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-frappe .file-name,html.theme--catppuccin-frappe fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-frappe .select select,fieldset[disabled] html.theme--catppuccin-frappe .textarea,fieldset[disabled] html.theme--catppuccin-frappe .input,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe fieldset[disabled] .select select,html.theme--catppuccin-frappe .select fieldset[disabled] select,html.theme--catppuccin-frappe fieldset[disabled] .textarea,html.theme--catppuccin-frappe fieldset[disabled] .input,html.theme--catppuccin-frappe fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-frappe .button,html.theme--catppuccin-frappe fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-frappe .tabs,html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .breadcrumb,html.theme--catppuccin-frappe .file,html.theme--catppuccin-frappe .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-frappe .admonition:not(:last-child),html.theme--catppuccin-frappe .tabs:not(:last-child),html.theme--catppuccin-frappe .pagination:not(:last-child),html.theme--catppuccin-frappe .message:not(:last-child),html.theme--catppuccin-frappe .level:not(:last-child),html.theme--catppuccin-frappe .breadcrumb:not(:last-child),html.theme--catppuccin-frappe .block:not(:last-child),html.theme--catppuccin-frappe .title:not(:last-child),html.theme--catppuccin-frappe .subtitle:not(:last-child),html.theme--catppuccin-frappe .table-container:not(:last-child),html.theme--catppuccin-frappe .table:not(:last-child),html.theme--catppuccin-frappe .progress:not(:last-child),html.theme--catppuccin-frappe .notification:not(:last-child),html.theme--catppuccin-frappe .content:not(:last-child),html.theme--catppuccin-frappe .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .modal-close,html.theme--catppuccin-frappe .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-frappe .modal-close::before,html.theme--catppuccin-frappe .delete::before,html.theme--catppuccin-frappe .modal-close::after,html.theme--catppuccin-frappe .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-frappe .modal-close::before,html.theme--catppuccin-frappe .delete::before{height:2px;width:50%}html.theme--catppuccin-frappe .modal-close::after,html.theme--catppuccin-frappe .delete::after{height:50%;width:2px}html.theme--catppuccin-frappe .modal-close:hover,html.theme--catppuccin-frappe .delete:hover,html.theme--catppuccin-frappe .modal-close:focus,html.theme--catppuccin-frappe .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-frappe .modal-close:active,html.theme--catppuccin-frappe .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-frappe .is-small.modal-close,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-frappe .is-small.delete,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-frappe .is-medium.modal-close,html.theme--catppuccin-frappe .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-frappe .is-large.modal-close,html.theme--catppuccin-frappe .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-frappe .control.is-loading::after,html.theme--catppuccin-frappe .select.is-loading::after,html.theme--catppuccin-frappe .loader,html.theme--catppuccin-frappe .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #838ba7;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-frappe .hero-video,html.theme--catppuccin-frappe .modal-background,html.theme--catppuccin-frappe .modal,html.theme--catppuccin-frappe .image.is-square img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-frappe .image.is-square .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-frappe .image.is-1by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-frappe .image.is-1by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-frappe .image.is-5by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-frappe .image.is-5by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-frappe .image.is-4by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-frappe .image.is-4by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-frappe .image.is-3by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-frappe .image.is-5by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-frappe .image.is-5by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-frappe .image.is-16by9 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-frappe .image.is-16by9 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-frappe .image.is-2by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-frappe .image.is-2by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-frappe .image.is-3by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-frappe .image.is-3by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-frappe .image.is-4by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-frappe .image.is-4by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-frappe .image.is-3by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-frappe .image.is-3by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-frappe .image.is-2by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-frappe .image.is-2by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-frappe .image.is-3by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-frappe .image.is-9by16 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-frappe .image.is-9by16 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-frappe .image.is-1by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-frappe .image.is-1by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-frappe .image.is-1by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-frappe .image.is-1by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-frappe .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#414559 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#2b2e3c !important}.has-background-dark{background-color:#414559 !important}.has-text-primary{color:#8caaee !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#6089e7 !important}.has-background-primary{background-color:#8caaee !important}.has-text-primary-light{color:#edf2fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c1d1f6 !important}.has-background-primary-light{background-color:#edf2fc !important}.has-text-primary-dark{color:#153a8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#1c4cbb !important}.has-background-primary-dark{background-color:#153a8e !important}.has-text-link{color:#8caaee !important}a.has-text-link:hover,a.has-text-link:focus{color:#6089e7 !important}.has-background-link{background-color:#8caaee !important}.has-text-link-light{color:#edf2fc !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c1d1f6 !important}.has-background-link-light{background-color:#edf2fc !important}.has-text-link-dark{color:#153a8e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1c4cbb !important}.has-background-link-dark{background-color:#153a8e !important}.has-text-info{color:#81c8be !important}a.has-text-info:hover,a.has-text-info:focus{color:#5db9ac !important}.has-background-info{background-color:#81c8be !important}.has-text-info-light{color:#f1f9f8 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#cde9e5 !important}.has-background-info-light{background-color:#f1f9f8 !important}.has-text-info-dark{color:#2d675f !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#3c8a7f !important}.has-background-info-dark{background-color:#2d675f !important}.has-text-success{color:#a6d189 !important}a.has-text-success:hover,a.has-text-success:focus{color:#8ac364 !important}.has-background-success{background-color:#a6d189 !important}.has-text-success-light{color:#f4f9f0 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#d8ebcc !important}.has-background-success-light{background-color:#f4f9f0 !important}.has-text-success-dark{color:#446a29 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#5b8f38 !important}.has-background-success-dark{background-color:#446a29 !important}.has-text-warning{color:#e5c890 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#dbb467 !important}.has-background-warning{background-color:#e5c890 !important}.has-text-warning-light{color:#fbf7ee !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f1e2c5 !important}.has-background-warning-light{background-color:#fbf7ee !important}.has-text-warning-dark{color:#78591c !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#a17726 !important}.has-background-warning-dark{background-color:#78591c !important}.has-text-danger{color:#e78284 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#df575a !important}.has-background-danger{background-color:#e78284 !important}.has-text-danger-light{color:#fceeee !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f3c3c4 !important}.has-background-danger-light{background-color:#fceeee !important}.has-text-danger-dark{color:#9a1e20 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c52629 !important}.has-background-danger-dark{background-color:#9a1e20 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#414559 !important}.has-background-grey-darker{background-color:#414559 !important}.has-text-grey-dark{color:#51576d !important}.has-background-grey-dark{background-color:#51576d !important}.has-text-grey{color:#626880 !important}.has-background-grey{background-color:#626880 !important}.has-text-grey-light{color:#737994 !important}.has-background-grey-light{background-color:#737994 !important}.has-text-grey-lighter{color:#838ba7 !important}.has-background-grey-lighter{background-color:#838ba7 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-frappe html{background-color:#303446;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-frappe article,html.theme--catppuccin-frappe aside,html.theme--catppuccin-frappe figure,html.theme--catppuccin-frappe footer,html.theme--catppuccin-frappe header,html.theme--catppuccin-frappe hgroup,html.theme--catppuccin-frappe section{display:block}html.theme--catppuccin-frappe body,html.theme--catppuccin-frappe button,html.theme--catppuccin-frappe input,html.theme--catppuccin-frappe optgroup,html.theme--catppuccin-frappe select,html.theme--catppuccin-frappe textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-frappe code,html.theme--catppuccin-frappe pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-frappe body{color:#c6d0f5;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-frappe a{color:#8caaee;cursor:pointer;text-decoration:none}html.theme--catppuccin-frappe a strong{color:currentColor}html.theme--catppuccin-frappe a:hover{color:#99d1db}html.theme--catppuccin-frappe code{background-color:#292c3c;color:#c6d0f5;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-frappe hr{background-color:#292c3c;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-frappe img{height:auto;max-width:100%}html.theme--catppuccin-frappe input[type="checkbox"],html.theme--catppuccin-frappe input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-frappe small{font-size:.875em}html.theme--catppuccin-frappe span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-frappe strong{color:#b0bef1;font-weight:700}html.theme--catppuccin-frappe fieldset{border:none}html.theme--catppuccin-frappe pre{-webkit-overflow-scrolling:touch;background-color:#292c3c;color:#c6d0f5;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-frappe pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-frappe table td,html.theme--catppuccin-frappe table th{vertical-align:top}html.theme--catppuccin-frappe table td:not([align]),html.theme--catppuccin-frappe table th:not([align]){text-align:inherit}html.theme--catppuccin-frappe table th{color:#b0bef1}html.theme--catppuccin-frappe .box{background-color:#51576d;border-radius:8px;box-shadow:none;color:#c6d0f5;display:block;padding:1.25rem}html.theme--catppuccin-frappe a.box:hover,html.theme--catppuccin-frappe a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #8caaee}html.theme--catppuccin-frappe a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #8caaee}html.theme--catppuccin-frappe .button{background-color:#292c3c;border-color:#484d69;border-width:1px;color:#8caaee;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-frappe .button strong{color:inherit}html.theme--catppuccin-frappe .button .icon,html.theme--catppuccin-frappe .button .icon.is-small,html.theme--catppuccin-frappe .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-frappe .button .icon.is-medium,html.theme--catppuccin-frappe .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-frappe .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-frappe .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-frappe .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-frappe .button:hover,html.theme--catppuccin-frappe .button.is-hovered{border-color:#737994;color:#b0bef1}html.theme--catppuccin-frappe .button:focus,html.theme--catppuccin-frappe .button.is-focused{border-color:#737994;color:#769aeb}html.theme--catppuccin-frappe .button:focus:not(:active),html.theme--catppuccin-frappe .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button:active,html.theme--catppuccin-frappe .button.is-active{border-color:#51576d;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text{background-color:transparent;border-color:transparent;color:#c6d0f5;text-decoration:underline}html.theme--catppuccin-frappe .button.is-text:hover,html.theme--catppuccin-frappe .button.is-text.is-hovered,html.theme--catppuccin-frappe .button.is-text:focus,html.theme--catppuccin-frappe .button.is-text.is-focused{background-color:#292c3c;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text:active,html.theme--catppuccin-frappe .button.is-text.is-active{background-color:#1f212d;color:#b0bef1}html.theme--catppuccin-frappe .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-frappe .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#8caaee;text-decoration:none}html.theme--catppuccin-frappe .button.is-ghost:hover,html.theme--catppuccin-frappe .button.is-ghost.is-hovered{color:#8caaee;text-decoration:underline}html.theme--catppuccin-frappe .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:hover,html.theme--catppuccin-frappe .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:focus,html.theme--catppuccin-frappe .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white:focus:not(:active),html.theme--catppuccin-frappe .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .button.is-white:active,html.theme--catppuccin-frappe .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-frappe .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted:hover,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-frappe .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-outlined:hover,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-white.is-outlined:focus,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:hover,html.theme--catppuccin-frappe .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:focus,html.theme--catppuccin-frappe .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black:focus:not(:active),html.theme--catppuccin-frappe .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .button.is-black:active,html.theme--catppuccin-frappe .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-frappe .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted:hover,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-outlined:hover,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-black.is-outlined:focus,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:hover,html.theme--catppuccin-frappe .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:focus,html.theme--catppuccin-frappe .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light:focus:not(:active),html.theme--catppuccin-frappe .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .button.is-light:active,html.theme--catppuccin-frappe .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-frappe .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted:hover,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-outlined:hover,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-light.is-outlined:focus,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-dark,html.theme--catppuccin-frappe .content kbd.button{background-color:#414559;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:hover,html.theme--catppuccin-frappe .content kbd.button:hover,html.theme--catppuccin-frappe .button.is-dark.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-hovered{background-color:#3c3f52;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:focus,html.theme--catppuccin-frappe .content kbd.button:focus,html.theme--catppuccin-frappe .button.is-dark.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark:focus:not(:active),html.theme--catppuccin-frappe .content kbd.button:focus:not(:active),html.theme--catppuccin-frappe .button.is-dark.is-focused:not(:active),html.theme--catppuccin-frappe .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .button.is-dark:active,html.theme--catppuccin-frappe .content kbd.button:active,html.theme--catppuccin-frappe .button.is-dark.is-active,html.theme--catppuccin-frappe .content kbd.button.is-active{background-color:#363a4a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-dark[disabled],html.theme--catppuccin-frappe .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button{background-color:#414559;border-color:#414559;box-shadow:none}html.theme--catppuccin-frappe .button.is-dark.is-inverted,html.theme--catppuccin-frappe .content kbd.button.is-inverted{background-color:#fff;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted:hover,html.theme--catppuccin-frappe .content kbd.button.is-inverted:hover,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-dark.is-inverted[disabled],html.theme--catppuccin-frappe .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-loading::after,html.theme--catppuccin-frappe .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined,html.theme--catppuccin-frappe .content kbd.button.is-outlined{background-color:transparent;border-color:#414559;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-outlined:hover,html.theme--catppuccin-frappe .content kbd.button.is-outlined:hover,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-dark.is-outlined:focus,html.theme--catppuccin-frappe .content kbd.button.is-outlined:focus,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-focused{background-color:#414559;border-color:#414559;color:#fff}html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #414559 #414559 !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-dark.is-outlined[disabled],html.theme--catppuccin-frappe .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-outlined{background-color:transparent;border-color:#414559;box-shadow:none;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#414559}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #414559 #414559 !important}html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-primary,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:hover,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:focus,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-focused,html.theme--catppuccin-frappe .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary:focus:not(:active),html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-frappe .button.is-primary.is-focused:not(:active),html.theme--catppuccin-frappe .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button.is-primary:active,html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-frappe .button.is-primary.is-active,html.theme--catppuccin-frappe .docstring>section>a.button.is-active.docs-sourcelink{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-primary[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.docs-sourcelink{background-color:#8caaee;border-color:#8caaee;box-shadow:none}html.theme--catppuccin-frappe .button.is-primary.is-inverted,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-primary.is-inverted[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-loading::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-outlined:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-frappe .button.is-primary.is-outlined:focus,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-frappe .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-primary.is-outlined[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8caaee;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-frappe .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-primary.is-light,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.docs-sourcelink{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .button.is-primary.is-light:hover,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-frappe .button.is-primary.is-light.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e2eafb;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-primary.is-light:active,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-frappe .button.is-primary.is-light.is-active,html.theme--catppuccin-frappe .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d7e1f9;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-link{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:hover,html.theme--catppuccin-frappe .button.is-link.is-hovered{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:focus,html.theme--catppuccin-frappe .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link:focus:not(:active),html.theme--catppuccin-frappe .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .button.is-link:active,html.theme--catppuccin-frappe .button.is-link.is-active{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link{background-color:#8caaee;border-color:#8caaee;box-shadow:none}html.theme--catppuccin-frappe .button.is-link.is-inverted{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted:hover,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-link.is-outlined{background-color:transparent;border-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-outlined:hover,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-link.is-outlined:focus,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-focused{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-outlined{background-color:transparent;border-color:#8caaee;box-shadow:none;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8caaee #8caaee !important}html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-link.is-light{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .button.is-link.is-light:hover,html.theme--catppuccin-frappe .button.is-link.is-light.is-hovered{background-color:#e2eafb;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-link.is-light:active,html.theme--catppuccin-frappe .button.is-link.is-light.is-active{background-color:#d7e1f9;border-color:transparent;color:#153a8e}html.theme--catppuccin-frappe .button.is-info{background-color:#81c8be;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:hover,html.theme--catppuccin-frappe .button.is-info.is-hovered{background-color:#78c4b9;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:focus,html.theme--catppuccin-frappe .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info:focus:not(:active),html.theme--catppuccin-frappe .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .button.is-info:active,html.theme--catppuccin-frappe .button.is-info.is-active{background-color:#6fc0b5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info{background-color:#81c8be;border-color:#81c8be;box-shadow:none}html.theme--catppuccin-frappe .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted:hover,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-info.is-outlined{background-color:transparent;border-color:#81c8be;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-outlined:hover,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-info.is-outlined:focus,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-focused{background-color:#81c8be;border-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #81c8be #81c8be !important}html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-outlined{background-color:transparent;border-color:#81c8be;box-shadow:none;color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #81c8be #81c8be !important}html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-info.is-light{background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .button.is-info.is-light:hover,html.theme--catppuccin-frappe .button.is-info.is-light.is-hovered{background-color:#e8f5f3;border-color:transparent;color:#2d675f}html.theme--catppuccin-frappe .button.is-info.is-light:active,html.theme--catppuccin-frappe .button.is-info.is-light.is-active{background-color:#dff1ef;border-color:transparent;color:#2d675f}html.theme--catppuccin-frappe .button.is-success{background-color:#a6d189;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:hover,html.theme--catppuccin-frappe .button.is-success.is-hovered{background-color:#9fcd80;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:focus,html.theme--catppuccin-frappe .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success:focus:not(:active),html.theme--catppuccin-frappe .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .button.is-success:active,html.theme--catppuccin-frappe .button.is-success.is-active{background-color:#98ca77;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success{background-color:#a6d189;border-color:#a6d189;box-shadow:none}html.theme--catppuccin-frappe .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted:hover,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-success.is-outlined{background-color:transparent;border-color:#a6d189;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-outlined:hover,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-success.is-outlined:focus,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-focused{background-color:#a6d189;border-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6d189 #a6d189 !important}html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-outlined{background-color:transparent;border-color:#a6d189;box-shadow:none;color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6d189 #a6d189 !important}html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-success.is-light{background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .button.is-success.is-light:hover,html.theme--catppuccin-frappe .button.is-success.is-light.is-hovered{background-color:#edf6e7;border-color:transparent;color:#446a29}html.theme--catppuccin-frappe .button.is-success.is-light:active,html.theme--catppuccin-frappe .button.is-success.is-light.is-active{background-color:#e6f2de;border-color:transparent;color:#446a29}html.theme--catppuccin-frappe .button.is-warning{background-color:#e5c890;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:hover,html.theme--catppuccin-frappe .button.is-warning.is-hovered{background-color:#e3c386;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:focus,html.theme--catppuccin-frappe .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning:focus:not(:active),html.theme--catppuccin-frappe .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .button.is-warning:active,html.theme--catppuccin-frappe .button.is-warning.is-active{background-color:#e0be7b;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning{background-color:#e5c890;border-color:#e5c890;box-shadow:none}html.theme--catppuccin-frappe .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted:hover,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined{background-color:transparent;border-color:#e5c890;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-outlined:hover,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-warning.is-outlined:focus,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-focused{background-color:#e5c890;border-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #e5c890 #e5c890 !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-frappe .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-outlined{background-color:transparent;border-color:#e5c890;box-shadow:none;color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #e5c890 #e5c890 !important}html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .button.is-warning.is-light{background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .button.is-warning.is-light:hover,html.theme--catppuccin-frappe .button.is-warning.is-light.is-hovered{background-color:#f9f2e4;border-color:transparent;color:#78591c}html.theme--catppuccin-frappe .button.is-warning.is-light:active,html.theme--catppuccin-frappe .button.is-warning.is-light.is-active{background-color:#f6edda;border-color:transparent;color:#78591c}html.theme--catppuccin-frappe .button.is-danger{background-color:#e78284;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:hover,html.theme--catppuccin-frappe .button.is-danger.is-hovered{background-color:#e57779;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:focus,html.theme--catppuccin-frappe .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger:focus:not(:active),html.theme--catppuccin-frappe .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .button.is-danger:active,html.theme--catppuccin-frappe .button.is-danger.is-active{background-color:#e36d6f;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger{background-color:#e78284;border-color:#e78284;box-shadow:none}html.theme--catppuccin-frappe .button.is-danger.is-inverted{background-color:#fff;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted:hover,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-frappe .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined{background-color:transparent;border-color:#e78284;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-outlined:hover,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-danger.is-outlined:focus,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-focused{background-color:#e78284;border-color:#e78284;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #e78284 #e78284 !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-frappe .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-outlined{background-color:transparent;border-color:#e78284;box-shadow:none;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#e78284}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #e78284 #e78284 !important}html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-frappe .button.is-danger.is-light{background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .button.is-danger.is-light:hover,html.theme--catppuccin-frappe .button.is-danger.is-light.is-hovered{background-color:#fae3e4;border-color:transparent;color:#9a1e20}html.theme--catppuccin-frappe .button.is-danger.is-light:active,html.theme--catppuccin-frappe .button.is-danger.is-light.is-active{background-color:#f8d8d9;border-color:transparent;color:#9a1e20}html.theme--catppuccin-frappe .button.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-frappe .button.is-small:not(.is-rounded),html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-frappe .button.is-normal{font-size:1rem}html.theme--catppuccin-frappe .button.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .button.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .button[disabled],fieldset[disabled] html.theme--catppuccin-frappe .button{background-color:#737994;border-color:#626880;box-shadow:none;opacity:.5}html.theme--catppuccin-frappe .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-frappe .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-frappe .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-frappe .button.is-static{background-color:#292c3c;border-color:#626880;color:#838ba7;box-shadow:none;pointer-events:none}html.theme--catppuccin-frappe .button.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-frappe .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-frappe .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-frappe .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-frappe .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-frappe .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-frappe .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-frappe .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-frappe .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-frappe .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-frappe .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-frappe .buttons.has-addons .button:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-frappe .buttons.has-addons .button:focus,html.theme--catppuccin-frappe .buttons.has-addons .button.is-focused,html.theme--catppuccin-frappe .buttons.has-addons .button:active,html.theme--catppuccin-frappe .buttons.has-addons .button.is-active,html.theme--catppuccin-frappe .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-frappe .buttons.has-addons .button:focus:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-frappe .buttons.has-addons .button:active:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-frappe .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-frappe .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .buttons.is-centered{justify-content:center}html.theme--catppuccin-frappe .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-frappe .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .button.is-responsive.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-frappe .button.is-responsive,html.theme--catppuccin-frappe .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-frappe .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-frappe .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .button.is-responsive.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-frappe .button.is-responsive,html.theme--catppuccin-frappe .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-frappe .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-frappe .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-frappe .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-frappe .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-frappe .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-frappe .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-frappe .content li+li{margin-top:0.25em}html.theme--catppuccin-frappe .content p:not(:last-child),html.theme--catppuccin-frappe .content dl:not(:last-child),html.theme--catppuccin-frappe .content ol:not(:last-child),html.theme--catppuccin-frappe .content ul:not(:last-child),html.theme--catppuccin-frappe .content blockquote:not(:last-child),html.theme--catppuccin-frappe .content pre:not(:last-child),html.theme--catppuccin-frappe .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-frappe .content h1,html.theme--catppuccin-frappe .content h2,html.theme--catppuccin-frappe .content h3,html.theme--catppuccin-frappe .content h4,html.theme--catppuccin-frappe .content h5,html.theme--catppuccin-frappe .content h6{color:#c6d0f5;font-weight:600;line-height:1.125}html.theme--catppuccin-frappe .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-frappe .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-frappe .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-frappe .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-frappe .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-frappe .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-frappe .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-frappe .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-frappe .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-frappe .content blockquote{background-color:#292c3c;border-left:5px solid #626880;padding:1.25em 1.5em}html.theme--catppuccin-frappe .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-frappe .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-frappe .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-frappe .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-frappe .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-frappe .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-frappe .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-frappe .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-frappe .content ul ul ul{list-style-type:square}html.theme--catppuccin-frappe .content dd{margin-left:2em}html.theme--catppuccin-frappe .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-frappe .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-frappe .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-frappe .content figure img{display:inline-block}html.theme--catppuccin-frappe .content figure figcaption{font-style:italic}html.theme--catppuccin-frappe .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-frappe .content sup,html.theme--catppuccin-frappe .content sub{font-size:75%}html.theme--catppuccin-frappe .content table{width:100%}html.theme--catppuccin-frappe .content table td,html.theme--catppuccin-frappe .content table th{border:1px solid #626880;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-frappe .content table th{color:#b0bef1}html.theme--catppuccin-frappe .content table th:not([align]){text-align:inherit}html.theme--catppuccin-frappe .content table thead td,html.theme--catppuccin-frappe .content table thead th{border-width:0 0 2px;color:#b0bef1}html.theme--catppuccin-frappe .content table tfoot td,html.theme--catppuccin-frappe .content table tfoot th{border-width:2px 0 0;color:#b0bef1}html.theme--catppuccin-frappe .content table tbody tr:last-child td,html.theme--catppuccin-frappe .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-frappe .content .tabs li+li{margin-top:0}html.theme--catppuccin-frappe .content.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-frappe .content.is-normal{font-size:1rem}html.theme--catppuccin-frappe .content.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .content.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-frappe .icon.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-frappe .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-frappe .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-frappe .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-frappe .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-frappe .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-frappe div.icon-text{display:flex}html.theme--catppuccin-frappe .image,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-frappe .image img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-frappe .image img.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-frappe .image.is-fullwidth,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-frappe .image.is-square img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-frappe .image.is-square .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-frappe .image.is-1by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-frappe .image.is-1by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-frappe .image.is-5by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-frappe .image.is-5by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-frappe .image.is-4by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-frappe .image.is-4by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-frappe .image.is-3by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-frappe .image.is-5by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-frappe .image.is-5by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-frappe .image.is-16by9 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-frappe .image.is-16by9 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-frappe .image.is-2by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-frappe .image.is-2by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-frappe .image.is-3by1 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-frappe .image.is-3by1 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-frappe .image.is-4by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-frappe .image.is-4by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-frappe .image.is-3by4 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-frappe .image.is-3by4 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-frappe .image.is-2by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-frappe .image.is-2by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-frappe .image.is-3by5 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-frappe .image.is-3by5 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-frappe .image.is-9by16 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-frappe .image.is-9by16 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-frappe .image.is-1by2 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-frappe .image.is-1by2 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-frappe .image.is-1by3 img,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-frappe .image.is-1by3 .has-ratio,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-frappe .image.is-square,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-frappe .image.is-1by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-frappe .image.is-5by4,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-frappe .image.is-4by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-frappe .image.is-3by2,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-frappe .image.is-5by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-frappe .image.is-16by9,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-frappe .image.is-2by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-frappe .image.is-3by1,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-frappe .image.is-4by5,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-frappe .image.is-3by4,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-frappe .image.is-2by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-frappe .image.is-3by5,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-frappe .image.is-9by16,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-frappe .image.is-1by2,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-frappe .image.is-1by3,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-frappe .image.is-16x16,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-frappe .image.is-24x24,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-frappe .image.is-32x32,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-frappe .image.is-48x48,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-frappe .image.is-64x64,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-frappe .image.is-96x96,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-frappe .image.is-128x128,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-frappe .notification{background-color:#292c3c;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-frappe .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-frappe .notification strong{color:currentColor}html.theme--catppuccin-frappe .notification code,html.theme--catppuccin-frappe .notification pre{background:#fff}html.theme--catppuccin-frappe .notification pre code{background:transparent}html.theme--catppuccin-frappe .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-frappe .notification .title,html.theme--catppuccin-frappe .notification .subtitle,html.theme--catppuccin-frappe .notification .content{color:currentColor}html.theme--catppuccin-frappe .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-dark,html.theme--catppuccin-frappe .content kbd.notification{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .notification.is-primary,html.theme--catppuccin-frappe .docstring>section>a.notification.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .notification.is-primary.is-light,html.theme--catppuccin-frappe .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .notification.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .notification.is-link.is-light{background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .notification.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-info.is-light{background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .notification.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-success.is-light{background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .notification.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .notification.is-warning.is-light{background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .notification.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .notification.is-danger.is-light{background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-frappe .progress::-webkit-progress-bar{background-color:#51576d}html.theme--catppuccin-frappe .progress::-webkit-progress-value{background-color:#838ba7}html.theme--catppuccin-frappe .progress::-moz-progress-bar{background-color:#838ba7}html.theme--catppuccin-frappe .progress::-ms-fill{background-color:#838ba7;border:none}html.theme--catppuccin-frappe .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-frappe .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-frappe .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-frappe .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-frappe .content kbd.progress::-webkit-progress-value{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-frappe .content kbd.progress::-moz-progress-bar{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark::-ms-fill,html.theme--catppuccin-frappe .content kbd.progress::-ms-fill{background-color:#414559}html.theme--catppuccin-frappe .progress.is-dark:indeterminate,html.theme--catppuccin-frappe .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #414559 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary::-ms-fill,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-primary:indeterminate,html.theme--catppuccin-frappe .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #8caaee 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-link::-webkit-progress-value{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link::-moz-progress-bar{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link::-ms-fill{background-color:#8caaee}html.theme--catppuccin-frappe .progress.is-link:indeterminate{background-image:linear-gradient(to right, #8caaee 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-info::-webkit-progress-value{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info::-moz-progress-bar{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info::-ms-fill{background-color:#81c8be}html.theme--catppuccin-frappe .progress.is-info:indeterminate{background-image:linear-gradient(to right, #81c8be 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-success::-webkit-progress-value{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success::-moz-progress-bar{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success::-ms-fill{background-color:#a6d189}html.theme--catppuccin-frappe .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6d189 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-warning::-webkit-progress-value{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning::-moz-progress-bar{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning::-ms-fill{background-color:#e5c890}html.theme--catppuccin-frappe .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #e5c890 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress.is-danger::-webkit-progress-value{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger::-moz-progress-bar{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger::-ms-fill{background-color:#e78284}html.theme--catppuccin-frappe .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #e78284 30%, #51576d 30%)}html.theme--catppuccin-frappe .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#51576d;background-image:linear-gradient(to right, #c6d0f5 30%, #51576d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-frappe .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-frappe .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-frappe .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-frappe .progress.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-frappe .progress.is-medium{height:1.25rem}html.theme--catppuccin-frappe .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-frappe .table{background-color:#51576d;color:#c6d0f5}html.theme--catppuccin-frappe .table td,html.theme--catppuccin-frappe .table th{border:1px solid #626880;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-frappe .table td.is-white,html.theme--catppuccin-frappe .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .table td.is-black,html.theme--catppuccin-frappe .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .table td.is-light,html.theme--catppuccin-frappe .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-dark,html.theme--catppuccin-frappe .table th.is-dark{background-color:#414559;border-color:#414559;color:#fff}html.theme--catppuccin-frappe .table td.is-primary,html.theme--catppuccin-frappe .table th.is-primary{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-link,html.theme--catppuccin-frappe .table th.is-link{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-info,html.theme--catppuccin-frappe .table th.is-info{background-color:#81c8be;border-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-success,html.theme--catppuccin-frappe .table th.is-success{background-color:#a6d189;border-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-warning,html.theme--catppuccin-frappe .table th.is-warning{background-color:#e5c890;border-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .table td.is-danger,html.theme--catppuccin-frappe .table th.is-danger{background-color:#e78284;border-color:#e78284;color:#fff}html.theme--catppuccin-frappe .table td.is-narrow,html.theme--catppuccin-frappe .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-frappe .table td.is-selected,html.theme--catppuccin-frappe .table th.is-selected{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table td.is-selected a,html.theme--catppuccin-frappe .table td.is-selected strong,html.theme--catppuccin-frappe .table th.is-selected a,html.theme--catppuccin-frappe .table th.is-selected strong{color:currentColor}html.theme--catppuccin-frappe .table td.is-vcentered,html.theme--catppuccin-frappe .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-frappe .table th{color:#b0bef1}html.theme--catppuccin-frappe .table th:not([align]){text-align:left}html.theme--catppuccin-frappe .table tr.is-selected{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .table tr.is-selected a,html.theme--catppuccin-frappe .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-frappe .table tr.is-selected td,html.theme--catppuccin-frappe .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-frappe .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table thead td,html.theme--catppuccin-frappe .table thead th{border-width:0 0 2px;color:#b0bef1}html.theme--catppuccin-frappe .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table tfoot td,html.theme--catppuccin-frappe .table tfoot th{border-width:2px 0 0;color:#b0bef1}html.theme--catppuccin-frappe .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .table tbody tr:last-child td,html.theme--catppuccin-frappe .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-frappe .table.is-bordered td,html.theme--catppuccin-frappe .table.is-bordered th{border-width:1px}html.theme--catppuccin-frappe .table.is-bordered tr:last-child td,html.theme--catppuccin-frappe .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-frappe .table.is-fullwidth{width:100%}html.theme--catppuccin-frappe .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#414559}html.theme--catppuccin-frappe .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#414559}html.theme--catppuccin-frappe .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#454a5f}html.theme--catppuccin-frappe .table.is-narrow td,html.theme--catppuccin-frappe .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-frappe .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#414559}html.theme--catppuccin-frappe .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-frappe .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .tags .tag,html.theme--catppuccin-frappe .tags .content kbd,html.theme--catppuccin-frappe .content .tags kbd,html.theme--catppuccin-frappe .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-frappe .tags .tag:not(:last-child),html.theme--catppuccin-frappe .tags .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags kbd:not(:last-child),html.theme--catppuccin-frappe .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-frappe .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-frappe .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-frappe .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-frappe .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-frappe .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-frappe .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-frappe .tags.is-centered{justify-content:center}html.theme--catppuccin-frappe .tags.is-centered .tag,html.theme--catppuccin-frappe .tags.is-centered .content kbd,html.theme--catppuccin-frappe .content .tags.is-centered kbd,html.theme--catppuccin-frappe .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-frappe .tags.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .tags.is-right .tag:not(:first-child),html.theme--catppuccin-frappe .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-frappe .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-frappe .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-frappe .tags.is-right .tag:not(:last-child),html.theme--catppuccin-frappe .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-frappe .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-frappe .tags.has-addons .tag,html.theme--catppuccin-frappe .tags.has-addons .content kbd,html.theme--catppuccin-frappe .content .tags.has-addons kbd,html.theme--catppuccin-frappe .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-frappe .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-frappe .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-frappe .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-frappe .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-frappe .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-frappe .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-frappe .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-frappe .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-frappe .tag:not(body),html.theme--catppuccin-frappe .content kbd:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#292c3c;border-radius:.4em;color:#c6d0f5;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-frappe .tag:not(body) .delete,html.theme--catppuccin-frappe .content kbd:not(body) .delete,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-frappe .tag.is-white:not(body),html.theme--catppuccin-frappe .content kbd.is-white:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .tag.is-black:not(body),html.theme--catppuccin-frappe .content kbd.is-black:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .tag.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-dark:not(body),html.theme--catppuccin-frappe .content kbd:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-frappe .content .docstring>section>kbd:not(body){background-color:#414559;color:#fff}html.theme--catppuccin-frappe .tag.is-primary:not(body),html.theme--catppuccin-frappe .content kbd.is-primary:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .tag.is-primary.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .tag.is-link:not(body),html.theme--catppuccin-frappe .content kbd.is-link:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .tag.is-link.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-link.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edf2fc;color:#153a8e}html.theme--catppuccin-frappe .tag.is-info:not(body),html.theme--catppuccin-frappe .content kbd.is-info:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-info.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-info.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#f1f9f8;color:#2d675f}html.theme--catppuccin-frappe .tag.is-success:not(body),html.theme--catppuccin-frappe .content kbd.is-success:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-success.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-success.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f4f9f0;color:#446a29}html.theme--catppuccin-frappe .tag.is-warning:not(body),html.theme--catppuccin-frappe .content kbd.is-warning:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .tag.is-warning.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fbf7ee;color:#78591c}html.theme--catppuccin-frappe .tag.is-danger:not(body),html.theme--catppuccin-frappe .content kbd.is-danger:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .tag.is-danger.is-light:not(body),html.theme--catppuccin-frappe .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fceeee;color:#9a1e20}html.theme--catppuccin-frappe .tag.is-normal:not(body),html.theme--catppuccin-frappe .content kbd.is-normal:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-frappe .tag.is-medium:not(body),html.theme--catppuccin-frappe .content kbd.is-medium:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-frappe .tag.is-large:not(body),html.theme--catppuccin-frappe .content kbd.is-large:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-frappe .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-frappe .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-frappe .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-frappe .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-frappe .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-frappe .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-frappe .tag.is-delete:not(body),html.theme--catppuccin-frappe .content kbd.is-delete:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-frappe .tag.is-delete:not(body)::before,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::before,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-frappe .tag.is-delete:not(body)::after,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::after,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-frappe .tag.is-delete:not(body)::before,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::before,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-frappe .tag.is-delete:not(body)::after,html.theme--catppuccin-frappe .content kbd.is-delete:not(body)::after,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-frappe .tag.is-delete:not(body):hover,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):hover,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-frappe .tag.is-delete:not(body):focus,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):focus,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1f212d}html.theme--catppuccin-frappe .tag.is-delete:not(body):active,html.theme--catppuccin-frappe .content kbd.is-delete:not(body):active,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#14161e}html.theme--catppuccin-frappe .tag.is-rounded:not(body),html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-frappe .content kbd.is-rounded:not(body),html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-frappe a.tag:hover,html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-frappe .title,html.theme--catppuccin-frappe .subtitle{word-break:break-word}html.theme--catppuccin-frappe .title em,html.theme--catppuccin-frappe .title span,html.theme--catppuccin-frappe .subtitle em,html.theme--catppuccin-frappe .subtitle span{font-weight:inherit}html.theme--catppuccin-frappe .title sub,html.theme--catppuccin-frappe .subtitle sub{font-size:.75em}html.theme--catppuccin-frappe .title sup,html.theme--catppuccin-frappe .subtitle sup{font-size:.75em}html.theme--catppuccin-frappe .title .tag,html.theme--catppuccin-frappe .title .content kbd,html.theme--catppuccin-frappe .content .title kbd,html.theme--catppuccin-frappe .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-frappe .subtitle .tag,html.theme--catppuccin-frappe .subtitle .content kbd,html.theme--catppuccin-frappe .content .subtitle kbd,html.theme--catppuccin-frappe .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-frappe .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-frappe .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-frappe .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-frappe .title.is-1{font-size:3rem}html.theme--catppuccin-frappe .title.is-2{font-size:2.5rem}html.theme--catppuccin-frappe .title.is-3{font-size:2rem}html.theme--catppuccin-frappe .title.is-4{font-size:1.5rem}html.theme--catppuccin-frappe .title.is-5{font-size:1.25rem}html.theme--catppuccin-frappe .title.is-6{font-size:1rem}html.theme--catppuccin-frappe .title.is-7{font-size:.75rem}html.theme--catppuccin-frappe .subtitle{color:#737994;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-frappe .subtitle strong{color:#737994;font-weight:600}html.theme--catppuccin-frappe .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-frappe .subtitle.is-1{font-size:3rem}html.theme--catppuccin-frappe .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-frappe .subtitle.is-3{font-size:2rem}html.theme--catppuccin-frappe .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-frappe .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-frappe .subtitle.is-6{font-size:1rem}html.theme--catppuccin-frappe .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-frappe .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-frappe .number{align-items:center;background-color:#292c3c;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{background-color:#303446;border-color:#626880;border-radius:.4em;color:#838ba7}html.theme--catppuccin-frappe .select select::-moz-placeholder,html.theme--catppuccin-frappe .textarea::-moz-placeholder,html.theme--catppuccin-frappe .input::-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select::-webkit-input-placeholder,html.theme--catppuccin-frappe .textarea::-webkit-input-placeholder,html.theme--catppuccin-frappe .input::-webkit-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:-moz-placeholder,html.theme--catppuccin-frappe .textarea:-moz-placeholder,html.theme--catppuccin-frappe .input:-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:-ms-input-placeholder,html.theme--catppuccin-frappe .textarea:-ms-input-placeholder,html.theme--catppuccin-frappe .input:-ms-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-frappe .select select:hover,html.theme--catppuccin-frappe .textarea:hover,html.theme--catppuccin-frappe .input:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-frappe .select select.is-hovered,html.theme--catppuccin-frappe .is-hovered.textarea,html.theme--catppuccin-frappe .is-hovered.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#737994}html.theme--catppuccin-frappe .select select:focus,html.theme--catppuccin-frappe .textarea:focus,html.theme--catppuccin-frappe .input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-frappe .select select.is-focused,html.theme--catppuccin-frappe .is-focused.textarea,html.theme--catppuccin-frappe .is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .select select:active,html.theme--catppuccin-frappe .textarea:active,html.theme--catppuccin-frappe .input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-frappe .select select.is-active,html.theme--catppuccin-frappe .is-active.textarea,html.theme--catppuccin-frappe .is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#8caaee;box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select select[disabled],html.theme--catppuccin-frappe .textarea[disabled],html.theme--catppuccin-frappe .input[disabled],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-frappe .select select,fieldset[disabled] html.theme--catppuccin-frappe .textarea,fieldset[disabled] html.theme--catppuccin-frappe .input,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{background-color:#737994;border-color:#292c3c;box-shadow:none;color:#f1f4fd}html.theme--catppuccin-frappe .select select[disabled]::-moz-placeholder,html.theme--catppuccin-frappe .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-frappe .input[disabled]::-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]:-moz-placeholder,html.theme--catppuccin-frappe .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-frappe .input[disabled]:-moz-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(241,244,253,0.3)}html.theme--catppuccin-frappe .textarea,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-frappe .textarea[readonly],html.theme--catppuccin-frappe .input[readonly],html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-frappe .is-white.textarea,html.theme--catppuccin-frappe .is-white.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-frappe .is-white.textarea:focus,html.theme--catppuccin-frappe .is-white.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-frappe .is-white.is-focused.textarea,html.theme--catppuccin-frappe .is-white.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-white.textarea:active,html.theme--catppuccin-frappe .is-white.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-frappe .is-white.is-active.textarea,html.theme--catppuccin-frappe .is-white.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .is-black.textarea,html.theme--catppuccin-frappe .is-black.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-frappe .is-black.textarea:focus,html.theme--catppuccin-frappe .is-black.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-frappe .is-black.is-focused.textarea,html.theme--catppuccin-frappe .is-black.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-black.textarea:active,html.theme--catppuccin-frappe .is-black.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-frappe .is-black.is-active.textarea,html.theme--catppuccin-frappe .is-black.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .is-light.textarea,html.theme--catppuccin-frappe .is-light.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-frappe .is-light.textarea:focus,html.theme--catppuccin-frappe .is-light.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-frappe .is-light.is-focused.textarea,html.theme--catppuccin-frappe .is-light.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-light.textarea:active,html.theme--catppuccin-frappe .is-light.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-frappe .is-light.is-active.textarea,html.theme--catppuccin-frappe .is-light.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .is-dark.textarea,html.theme--catppuccin-frappe .content kbd.textarea,html.theme--catppuccin-frappe .is-dark.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-frappe .content kbd.input{border-color:#414559}html.theme--catppuccin-frappe .is-dark.textarea:focus,html.theme--catppuccin-frappe .content kbd.textarea:focus,html.theme--catppuccin-frappe .is-dark.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-frappe .content kbd.input:focus,html.theme--catppuccin-frappe .is-dark.is-focused.textarea,html.theme--catppuccin-frappe .content kbd.is-focused.textarea,html.theme--catppuccin-frappe .is-dark.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .content kbd.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-dark.textarea:active,html.theme--catppuccin-frappe .content kbd.textarea:active,html.theme--catppuccin-frappe .is-dark.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-frappe .content kbd.input:active,html.theme--catppuccin-frappe .is-dark.is-active.textarea,html.theme--catppuccin-frappe .content kbd.is-active.textarea,html.theme--catppuccin-frappe .is-dark.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .content kbd.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .is-primary.textarea,html.theme--catppuccin-frappe .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-frappe .docstring>section>a.input.docs-sourcelink{border-color:#8caaee}html.theme--catppuccin-frappe .is-primary.textarea:focus,html.theme--catppuccin-frappe .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-frappe .is-primary.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-frappe .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-frappe .is-primary.is-focused.textarea,html.theme--catppuccin-frappe .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.textarea:active,html.theme--catppuccin-frappe .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-frappe .is-primary.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-frappe .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-frappe .is-primary.is-active.textarea,html.theme--catppuccin-frappe .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-frappe .is-primary.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-frappe .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .is-link.textarea,html.theme--catppuccin-frappe .is-link.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#8caaee}html.theme--catppuccin-frappe .is-link.textarea:focus,html.theme--catppuccin-frappe .is-link.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-frappe .is-link.is-focused.textarea,html.theme--catppuccin-frappe .is-link.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-link.textarea:active,html.theme--catppuccin-frappe .is-link.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-frappe .is-link.is-active.textarea,html.theme--catppuccin-frappe .is-link.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .is-info.textarea,html.theme--catppuccin-frappe .is-info.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#81c8be}html.theme--catppuccin-frappe .is-info.textarea:focus,html.theme--catppuccin-frappe .is-info.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-frappe .is-info.is-focused.textarea,html.theme--catppuccin-frappe .is-info.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-info.textarea:active,html.theme--catppuccin-frappe .is-info.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-frappe .is-info.is-active.textarea,html.theme--catppuccin-frappe .is-info.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .is-success.textarea,html.theme--catppuccin-frappe .is-success.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6d189}html.theme--catppuccin-frappe .is-success.textarea:focus,html.theme--catppuccin-frappe .is-success.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-frappe .is-success.is-focused.textarea,html.theme--catppuccin-frappe .is-success.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-success.textarea:active,html.theme--catppuccin-frappe .is-success.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-frappe .is-success.is-active.textarea,html.theme--catppuccin-frappe .is-success.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .is-warning.textarea,html.theme--catppuccin-frappe .is-warning.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#e5c890}html.theme--catppuccin-frappe .is-warning.textarea:focus,html.theme--catppuccin-frappe .is-warning.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-frappe .is-warning.is-focused.textarea,html.theme--catppuccin-frappe .is-warning.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-warning.textarea:active,html.theme--catppuccin-frappe .is-warning.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-frappe .is-warning.is-active.textarea,html.theme--catppuccin-frappe .is-warning.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .is-danger.textarea,html.theme--catppuccin-frappe .is-danger.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#e78284}html.theme--catppuccin-frappe .is-danger.textarea:focus,html.theme--catppuccin-frappe .is-danger.input:focus,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-frappe .is-danger.is-focused.textarea,html.theme--catppuccin-frappe .is-danger.is-focused.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-frappe .is-danger.textarea:active,html.theme--catppuccin-frappe .is-danger.input:active,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-frappe .is-danger.is-active.textarea,html.theme--catppuccin-frappe .is-danger.is-active.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .is-small.textarea,html.theme--catppuccin-frappe .is-small.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-frappe .is-medium.textarea,html.theme--catppuccin-frappe .is-medium.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .is-large.textarea,html.theme--catppuccin-frappe .is-large.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .is-fullwidth.textarea,html.theme--catppuccin-frappe .is-fullwidth.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-frappe .is-inline.textarea,html.theme--catppuccin-frappe .is-inline.input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-frappe .input.is-rounded,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-frappe .input.is-static,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-frappe .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-frappe .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-frappe .textarea[rows]{height:initial}html.theme--catppuccin-frappe .textarea.has-fixed-size{resize:none}html.theme--catppuccin-frappe .radio,html.theme--catppuccin-frappe .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-frappe .radio input,html.theme--catppuccin-frappe .checkbox input{cursor:pointer}html.theme--catppuccin-frappe .radio:hover,html.theme--catppuccin-frappe .checkbox:hover{color:#99d1db}html.theme--catppuccin-frappe .radio[disabled],html.theme--catppuccin-frappe .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-frappe .radio,fieldset[disabled] html.theme--catppuccin-frappe .checkbox,html.theme--catppuccin-frappe .radio input[disabled],html.theme--catppuccin-frappe .checkbox input[disabled]{color:#f1f4fd;cursor:not-allowed}html.theme--catppuccin-frappe .radio+.radio{margin-left:.5em}html.theme--catppuccin-frappe .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-frappe .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading)::after{border-color:#8caaee;right:1.125em;z-index:4}html.theme--catppuccin-frappe .select.is-rounded select,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-frappe .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-frappe .select select::-ms-expand{display:none}html.theme--catppuccin-frappe .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-frappe .select select:hover{border-color:#292c3c}html.theme--catppuccin-frappe .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-frappe .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-frappe .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-frappe .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#99d1db}html.theme--catppuccin-frappe .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-frappe .select.is-white select{border-color:#fff}html.theme--catppuccin-frappe .select.is-white select:hover,html.theme--catppuccin-frappe .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-frappe .select.is-white select:focus,html.theme--catppuccin-frappe .select.is-white select.is-focused,html.theme--catppuccin-frappe .select.is-white select:active,html.theme--catppuccin-frappe .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-frappe .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-frappe .select.is-black select:hover,html.theme--catppuccin-frappe .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-frappe .select.is-black select:focus,html.theme--catppuccin-frappe .select.is-black select.is-focused,html.theme--catppuccin-frappe .select.is-black select:active,html.theme--catppuccin-frappe .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-frappe .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-frappe .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-frappe .select.is-light select:hover,html.theme--catppuccin-frappe .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-frappe .select.is-light select:focus,html.theme--catppuccin-frappe .select.is-light select.is-focused,html.theme--catppuccin-frappe .select.is-light select:active,html.theme--catppuccin-frappe .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-frappe .select.is-dark:not(:hover)::after,html.theme--catppuccin-frappe .content kbd.select:not(:hover)::after{border-color:#414559}html.theme--catppuccin-frappe .select.is-dark select,html.theme--catppuccin-frappe .content kbd.select select{border-color:#414559}html.theme--catppuccin-frappe .select.is-dark select:hover,html.theme--catppuccin-frappe .content kbd.select select:hover,html.theme--catppuccin-frappe .select.is-dark select.is-hovered,html.theme--catppuccin-frappe .content kbd.select select.is-hovered{border-color:#363a4a}html.theme--catppuccin-frappe .select.is-dark select:focus,html.theme--catppuccin-frappe .content kbd.select select:focus,html.theme--catppuccin-frappe .select.is-dark select.is-focused,html.theme--catppuccin-frappe .content kbd.select select.is-focused,html.theme--catppuccin-frappe .select.is-dark select:active,html.theme--catppuccin-frappe .content kbd.select select:active,html.theme--catppuccin-frappe .select.is-dark select.is-active,html.theme--catppuccin-frappe .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(65,69,89,0.25)}html.theme--catppuccin-frappe .select.is-primary:not(:hover)::after,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-primary select,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-primary select:hover,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-frappe .select.is-primary select.is-hovered,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#769aeb}html.theme--catppuccin-frappe .select.is-primary select:focus,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-frappe .select.is-primary select.is-focused,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-frappe .select.is-primary select:active,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-frappe .select.is-primary select.is-active,html.theme--catppuccin-frappe .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select.is-link:not(:hover)::after{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-link select{border-color:#8caaee}html.theme--catppuccin-frappe .select.is-link select:hover,html.theme--catppuccin-frappe .select.is-link select.is-hovered{border-color:#769aeb}html.theme--catppuccin-frappe .select.is-link select:focus,html.theme--catppuccin-frappe .select.is-link select.is-focused,html.theme--catppuccin-frappe .select.is-link select:active,html.theme--catppuccin-frappe .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(140,170,238,0.25)}html.theme--catppuccin-frappe .select.is-info:not(:hover)::after{border-color:#81c8be}html.theme--catppuccin-frappe .select.is-info select{border-color:#81c8be}html.theme--catppuccin-frappe .select.is-info select:hover,html.theme--catppuccin-frappe .select.is-info select.is-hovered{border-color:#6fc0b5}html.theme--catppuccin-frappe .select.is-info select:focus,html.theme--catppuccin-frappe .select.is-info select.is-focused,html.theme--catppuccin-frappe .select.is-info select:active,html.theme--catppuccin-frappe .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(129,200,190,0.25)}html.theme--catppuccin-frappe .select.is-success:not(:hover)::after{border-color:#a6d189}html.theme--catppuccin-frappe .select.is-success select{border-color:#a6d189}html.theme--catppuccin-frappe .select.is-success select:hover,html.theme--catppuccin-frappe .select.is-success select.is-hovered{border-color:#98ca77}html.theme--catppuccin-frappe .select.is-success select:focus,html.theme--catppuccin-frappe .select.is-success select.is-focused,html.theme--catppuccin-frappe .select.is-success select:active,html.theme--catppuccin-frappe .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,209,137,0.25)}html.theme--catppuccin-frappe .select.is-warning:not(:hover)::after{border-color:#e5c890}html.theme--catppuccin-frappe .select.is-warning select{border-color:#e5c890}html.theme--catppuccin-frappe .select.is-warning select:hover,html.theme--catppuccin-frappe .select.is-warning select.is-hovered{border-color:#e0be7b}html.theme--catppuccin-frappe .select.is-warning select:focus,html.theme--catppuccin-frappe .select.is-warning select.is-focused,html.theme--catppuccin-frappe .select.is-warning select:active,html.theme--catppuccin-frappe .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(229,200,144,0.25)}html.theme--catppuccin-frappe .select.is-danger:not(:hover)::after{border-color:#e78284}html.theme--catppuccin-frappe .select.is-danger select{border-color:#e78284}html.theme--catppuccin-frappe .select.is-danger select:hover,html.theme--catppuccin-frappe .select.is-danger select.is-hovered{border-color:#e36d6f}html.theme--catppuccin-frappe .select.is-danger select:focus,html.theme--catppuccin-frappe .select.is-danger select.is-focused,html.theme--catppuccin-frappe .select.is-danger select:active,html.theme--catppuccin-frappe .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(231,130,132,0.25)}html.theme--catppuccin-frappe .select.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-frappe .select.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .select.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .select.is-disabled::after{border-color:#f1f4fd !important;opacity:0.5}html.theme--catppuccin-frappe .select.is-fullwidth{width:100%}html.theme--catppuccin-frappe .select.is-fullwidth select{width:100%}html.theme--catppuccin-frappe .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-frappe .select.is-loading.is-small:after,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-frappe .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-frappe .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-frappe .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-frappe .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:hover .file-cta,html.theme--catppuccin-frappe .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:focus .file-cta,html.theme--catppuccin-frappe .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-frappe .file.is-white:active .file-cta,html.theme--catppuccin-frappe .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-frappe .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-black:hover .file-cta,html.theme--catppuccin-frappe .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-black:focus .file-cta,html.theme--catppuccin-frappe .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-black:active .file-cta,html.theme--catppuccin-frappe .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:hover .file-cta,html.theme--catppuccin-frappe .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:focus .file-cta,html.theme--catppuccin-frappe .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-light:active .file-cta,html.theme--catppuccin-frappe .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-dark .file-cta,html.theme--catppuccin-frappe .content kbd.file .file-cta{background-color:#414559;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-dark:hover .file-cta,html.theme--catppuccin-frappe .content kbd.file:hover .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-hovered .file-cta{background-color:#3c3f52;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-dark:focus .file-cta,html.theme--catppuccin-frappe .content kbd.file:focus .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-focused .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(65,69,89,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-dark:active .file-cta,html.theme--catppuccin-frappe .content kbd.file:active .file-cta,html.theme--catppuccin-frappe .file.is-dark.is-active .file-cta,html.theme--catppuccin-frappe .content kbd.file.is-active .file-cta{background-color:#363a4a;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary:hover .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-primary:focus .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-focused .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(140,170,238,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-primary:active .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-frappe .file.is-primary.is-active .file-cta,html.theme--catppuccin-frappe .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link .file-cta{background-color:#8caaee;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link:hover .file-cta,html.theme--catppuccin-frappe .file.is-link.is-hovered .file-cta{background-color:#81a2ec;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-link:focus .file-cta,html.theme--catppuccin-frappe .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(140,170,238,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-link:active .file-cta,html.theme--catppuccin-frappe .file.is-link.is-active .file-cta{background-color:#769aeb;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-info .file-cta{background-color:#81c8be;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:hover .file-cta,html.theme--catppuccin-frappe .file.is-info.is-hovered .file-cta{background-color:#78c4b9;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:focus .file-cta,html.theme--catppuccin-frappe .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(129,200,190,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-info:active .file-cta,html.theme--catppuccin-frappe .file.is-info.is-active .file-cta{background-color:#6fc0b5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success .file-cta{background-color:#a6d189;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:hover .file-cta,html.theme--catppuccin-frappe .file.is-success.is-hovered .file-cta{background-color:#9fcd80;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:focus .file-cta,html.theme--catppuccin-frappe .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,209,137,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-success:active .file-cta,html.theme--catppuccin-frappe .file.is-success.is-active .file-cta{background-color:#98ca77;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning .file-cta{background-color:#e5c890;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:hover .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-hovered .file-cta{background-color:#e3c386;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:focus .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(229,200,144,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-warning:active .file-cta,html.theme--catppuccin-frappe .file.is-warning.is-active .file-cta{background-color:#e0be7b;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .file.is-danger .file-cta{background-color:#e78284;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-danger:hover .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-hovered .file-cta{background-color:#e57779;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-danger:focus .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(231,130,132,0.25);color:#fff}html.theme--catppuccin-frappe .file.is-danger:active .file-cta,html.theme--catppuccin-frappe .file.is-danger.is-active .file-cta{background-color:#e36d6f;border-color:transparent;color:#fff}html.theme--catppuccin-frappe .file.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-frappe .file.is-normal{font-size:1rem}html.theme--catppuccin-frappe .file.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-frappe .file.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-frappe .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-frappe .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-frappe .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-frappe .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-frappe .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-frappe .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-frappe .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-frappe .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-frappe .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-frappe .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-frappe .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-frappe .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-frappe .file.is-centered{justify-content:center}html.theme--catppuccin-frappe .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-frappe .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-frappe .file.is-right{justify-content:flex-end}html.theme--catppuccin-frappe .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-frappe .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-frappe .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-frappe .file-label:hover .file-cta{background-color:#3c3f52;color:#b0bef1}html.theme--catppuccin-frappe .file-label:hover .file-name{border-color:#5c6279}html.theme--catppuccin-frappe .file-label:active .file-cta{background-color:#363a4a;color:#b0bef1}html.theme--catppuccin-frappe .file-label:active .file-name{border-color:#575c72}html.theme--catppuccin-frappe .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-frappe .file-cta,html.theme--catppuccin-frappe .file-name{border-color:#626880;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-frappe .file-cta{background-color:#414559;color:#c6d0f5}html.theme--catppuccin-frappe .file-name{border-color:#626880;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-frappe .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-frappe .file-icon .fa{font-size:14px}html.theme--catppuccin-frappe .label{color:#b0bef1;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-frappe .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-frappe .label.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-frappe .label.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .label.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-frappe .help.is-white{color:#fff}html.theme--catppuccin-frappe .help.is-black{color:#0a0a0a}html.theme--catppuccin-frappe .help.is-light{color:#f5f5f5}html.theme--catppuccin-frappe .help.is-dark,html.theme--catppuccin-frappe .content kbd.help{color:#414559}html.theme--catppuccin-frappe .help.is-primary,html.theme--catppuccin-frappe .docstring>section>a.help.docs-sourcelink{color:#8caaee}html.theme--catppuccin-frappe .help.is-link{color:#8caaee}html.theme--catppuccin-frappe .help.is-info{color:#81c8be}html.theme--catppuccin-frappe .help.is-success{color:#a6d189}html.theme--catppuccin-frappe .help.is-warning{color:#e5c890}html.theme--catppuccin-frappe .help.is-danger{color:#e78284}html.theme--catppuccin-frappe .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-frappe .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-frappe .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-frappe .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-frappe .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-frappe .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-frappe .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-frappe .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-frappe .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-frappe .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-frappe .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-frappe .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-frappe .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field.is-horizontal{display:flex}}html.theme--catppuccin-frappe .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-frappe .field-label.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-frappe .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-frappe .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-frappe .field-body .field{margin-bottom:0}html.theme--catppuccin-frappe .field-body>.field{flex-shrink:1}html.theme--catppuccin-frappe .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-frappe .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-frappe .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-frappe .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select:focus~.icon{color:#414559}html.theme--catppuccin-frappe .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-frappe .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-frappe .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-frappe .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-frappe .control.has-icons-left .icon,html.theme--catppuccin-frappe .control.has-icons-right .icon{color:#626880;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-frappe .control.has-icons-left .input,html.theme--catppuccin-frappe .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-frappe .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-frappe .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-frappe .control.has-icons-right .input,html.theme--catppuccin-frappe .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-frappe .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-frappe .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-frappe .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-frappe .control.is-loading.is-small:after,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-frappe .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-frappe .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-frappe .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-frappe .breadcrumb a{align-items:center;color:#8caaee;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-frappe .breadcrumb a:hover{color:#99d1db}html.theme--catppuccin-frappe .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-frappe .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-frappe .breadcrumb li.is-active a{color:#b0bef1;cursor:default;pointer-events:none}html.theme--catppuccin-frappe .breadcrumb li+li::before{color:#737994;content:"\0002f"}html.theme--catppuccin-frappe .breadcrumb ul,html.theme--catppuccin-frappe .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-frappe .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-frappe .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-frappe .breadcrumb.is-centered ol,html.theme--catppuccin-frappe .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-frappe .breadcrumb.is-right ol,html.theme--catppuccin-frappe .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-frappe .breadcrumb.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-frappe .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-frappe .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-frappe .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-frappe .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-frappe .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#c6d0f5;max-width:100%;position:relative}html.theme--catppuccin-frappe .card-footer:first-child,html.theme--catppuccin-frappe .card-content:first-child,html.theme--catppuccin-frappe .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-frappe .card-footer:last-child,html.theme--catppuccin-frappe .card-content:last-child,html.theme--catppuccin-frappe .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-frappe .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-frappe .card-header-title{align-items:center;color:#b0bef1;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-frappe .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-frappe .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-frappe .card-image{display:block;position:relative}html.theme--catppuccin-frappe .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-frappe .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-frappe .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-frappe .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-frappe .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-frappe .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-frappe .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-frappe .dropdown.is-active .dropdown-menu,html.theme--catppuccin-frappe .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-frappe .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-frappe .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-frappe .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-frappe .dropdown-content{background-color:#292c3c;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-frappe .dropdown-item{color:#c6d0f5;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-frappe a.dropdown-item,html.theme--catppuccin-frappe button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-frappe a.dropdown-item:hover,html.theme--catppuccin-frappe button.dropdown-item:hover{background-color:#292c3c;color:#0a0a0a}html.theme--catppuccin-frappe a.dropdown-item.is-active,html.theme--catppuccin-frappe button.dropdown-item.is-active{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-frappe .level{align-items:center;justify-content:space-between}html.theme--catppuccin-frappe .level code{border-radius:.4em}html.theme--catppuccin-frappe .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-frappe .level.is-mobile{display:flex}html.theme--catppuccin-frappe .level.is-mobile .level-left,html.theme--catppuccin-frappe .level.is-mobile .level-right{display:flex}html.theme--catppuccin-frappe .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-frappe .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-frappe .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level{display:flex}html.theme--catppuccin-frappe .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-frappe .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-frappe .level-item .title,html.theme--catppuccin-frappe .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-frappe .level-left,html.theme--catppuccin-frappe .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .level-left .level-item.is-flexible,html.theme--catppuccin-frappe .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-left .level-item:not(:last-child),html.theme--catppuccin-frappe .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-frappe .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-left{display:flex}}html.theme--catppuccin-frappe .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .level-right{display:flex}}html.theme--catppuccin-frappe .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-frappe .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-frappe .media .media{border-top:1px solid rgba(98,104,128,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-frappe .media .media .content:not(:last-child),html.theme--catppuccin-frappe .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-frappe .media .media .media{padding-top:.5rem}html.theme--catppuccin-frappe .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-frappe .media+.media{border-top:1px solid rgba(98,104,128,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-frappe .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-frappe .media-left,html.theme--catppuccin-frappe .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .media-left{margin-right:1rem}html.theme--catppuccin-frappe .media-right{margin-left:1rem}html.theme--catppuccin-frappe .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .media-content{overflow-x:auto}}html.theme--catppuccin-frappe .menu{font-size:1rem}html.theme--catppuccin-frappe .menu.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-frappe .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .menu.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .menu-list{line-height:1.25}html.theme--catppuccin-frappe .menu-list a{border-radius:3px;color:#c6d0f5;display:block;padding:0.5em 0.75em}html.theme--catppuccin-frappe .menu-list a:hover{background-color:#292c3c;color:#b0bef1}html.theme--catppuccin-frappe .menu-list a.is-active{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .menu-list li ul{border-left:1px solid #626880;margin:.75em;padding-left:.75em}html.theme--catppuccin-frappe .menu-label{color:#f1f4fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-frappe .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-frappe .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-frappe .message{background-color:#292c3c;border-radius:.4em;font-size:1rem}html.theme--catppuccin-frappe .message strong{color:currentColor}html.theme--catppuccin-frappe .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-frappe .message.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-frappe .message.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .message.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .message.is-white{background-color:#fff}html.theme--catppuccin-frappe .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-frappe .message.is-black{background-color:#fafafa}html.theme--catppuccin-frappe .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-frappe .message.is-light{background-color:#fafafa}html.theme--catppuccin-frappe .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-frappe .message.is-dark,html.theme--catppuccin-frappe .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-frappe .message.is-dark .message-header,html.theme--catppuccin-frappe .content kbd.message .message-header{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .message.is-dark .message-body,html.theme--catppuccin-frappe .content kbd.message .message-body{border-color:#414559}html.theme--catppuccin-frappe .message.is-primary,html.theme--catppuccin-frappe .docstring>section>a.message.docs-sourcelink{background-color:#edf2fc}html.theme--catppuccin-frappe .message.is-primary .message-header,html.theme--catppuccin-frappe .docstring>section>a.message.docs-sourcelink .message-header{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .message.is-primary .message-body,html.theme--catppuccin-frappe .docstring>section>a.message.docs-sourcelink .message-body{border-color:#8caaee;color:#153a8e}html.theme--catppuccin-frappe .message.is-link{background-color:#edf2fc}html.theme--catppuccin-frappe .message.is-link .message-header{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .message.is-link .message-body{border-color:#8caaee;color:#153a8e}html.theme--catppuccin-frappe .message.is-info{background-color:#f1f9f8}html.theme--catppuccin-frappe .message.is-info .message-header{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-info .message-body{border-color:#81c8be;color:#2d675f}html.theme--catppuccin-frappe .message.is-success{background-color:#f4f9f0}html.theme--catppuccin-frappe .message.is-success .message-header{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-success .message-body{border-color:#a6d189;color:#446a29}html.theme--catppuccin-frappe .message.is-warning{background-color:#fbf7ee}html.theme--catppuccin-frappe .message.is-warning .message-header{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .message.is-warning .message-body{border-color:#e5c890;color:#78591c}html.theme--catppuccin-frappe .message.is-danger{background-color:#fceeee}html.theme--catppuccin-frappe .message.is-danger .message-header{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .message.is-danger .message-body{border-color:#e78284;color:#9a1e20}html.theme--catppuccin-frappe .message-header{align-items:center;background-color:#c6d0f5;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-frappe .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-frappe .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-frappe .message-body{border-color:#626880;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#c6d0f5;padding:1.25em 1.5em}html.theme--catppuccin-frappe .message-body code,html.theme--catppuccin-frappe .message-body pre{background-color:#fff}html.theme--catppuccin-frappe .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-frappe .modal.is-active{display:flex}html.theme--catppuccin-frappe .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-frappe .modal-content,html.theme--catppuccin-frappe .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-frappe .modal-content,html.theme--catppuccin-frappe .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-frappe .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-frappe .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-frappe .modal-card-head,html.theme--catppuccin-frappe .modal-card-foot{align-items:center;background-color:#292c3c;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-frappe .modal-card-head{border-bottom:1px solid #626880;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-frappe .modal-card-title{color:#c6d0f5;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-frappe .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #626880}html.theme--catppuccin-frappe .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-frappe .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#303446;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-frappe .navbar{background-color:#8caaee;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-frappe .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-frappe .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-frappe .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-frappe .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-dark,html.theme--catppuccin-frappe .content kbd.navbar{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-burger,html.theme--catppuccin-frappe .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-frappe .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#414559;color:#fff}}html.theme--catppuccin-frappe .navbar.is-primary,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-burger,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee;color:#fff}}html.theme--catppuccin-frappe .navbar.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee;color:#fff}}html.theme--catppuccin-frappe .navbar.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#81c8be;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6d189;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#e5c890;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-frappe .navbar.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-frappe .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#e78284;color:#fff}}html.theme--catppuccin-frappe .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-frappe .navbar.has-shadow{box-shadow:0 2px 0 0 #292c3c}html.theme--catppuccin-frappe .navbar.is-fixed-bottom,html.theme--catppuccin-frappe .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #292c3c}html.theme--catppuccin-frappe .navbar.is-fixed-top{top:0}html.theme--catppuccin-frappe html.has-navbar-fixed-top,html.theme--catppuccin-frappe body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-frappe .navbar-brand,html.theme--catppuccin-frappe .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-frappe .navbar-brand a.navbar-item:focus,html.theme--catppuccin-frappe .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-frappe .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-frappe .navbar-burger{color:#c6d0f5;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-frappe .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-frappe .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-frappe .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-frappe .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-frappe .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-frappe .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-frappe .navbar-menu{display:none}html.theme--catppuccin-frappe .navbar-item,html.theme--catppuccin-frappe .navbar-link{color:#c6d0f5;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-frappe .navbar-item .icon:only-child,html.theme--catppuccin-frappe .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-frappe a.navbar-item,html.theme--catppuccin-frappe .navbar-link{cursor:pointer}html.theme--catppuccin-frappe a.navbar-item:focus,html.theme--catppuccin-frappe a.navbar-item:focus-within,html.theme--catppuccin-frappe a.navbar-item:hover,html.theme--catppuccin-frappe a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar-link:focus,html.theme--catppuccin-frappe .navbar-link:focus-within,html.theme--catppuccin-frappe .navbar-link:hover,html.theme--catppuccin-frappe .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#8caaee}html.theme--catppuccin-frappe .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .navbar-item img{max-height:1.75rem}html.theme--catppuccin-frappe .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-frappe .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-frappe .navbar-item.is-tab:focus,html.theme--catppuccin-frappe .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#8caaee}html.theme--catppuccin-frappe .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#8caaee;border-bottom-style:solid;border-bottom-width:3px;color:#8caaee;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-frappe .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-frappe .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-frappe .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-frappe .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-frappe .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .navbar>.container{display:block}html.theme--catppuccin-frappe .navbar-brand .navbar-item,html.theme--catppuccin-frappe .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-frappe .navbar-link::after{display:none}html.theme--catppuccin-frappe .navbar-menu{background-color:#8caaee;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-frappe .navbar-menu.is-active{display:block}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch,html.theme--catppuccin-frappe .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-frappe .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-frappe .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-frappe html.has-navbar-fixed-top-touch,html.theme--catppuccin-frappe body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .navbar,html.theme--catppuccin-frappe .navbar-menu,html.theme--catppuccin-frappe .navbar-start,html.theme--catppuccin-frappe .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-frappe .navbar{min-height:4rem}html.theme--catppuccin-frappe .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-frappe .navbar.is-spaced .navbar-start,html.theme--catppuccin-frappe .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-frappe .navbar.is-spaced a.navbar-item,html.theme--catppuccin-frappe .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-frappe .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#838ba7}html.theme--catppuccin-frappe .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8caaee}html.theme--catppuccin-frappe .navbar-burger{display:none}html.theme--catppuccin-frappe .navbar-item,html.theme--catppuccin-frappe .navbar-link{align-items:center;display:flex}html.theme--catppuccin-frappe .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-frappe .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-frappe .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-frappe .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-frappe .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-frappe .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-frappe .navbar-dropdown{background-color:#8caaee;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-frappe .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#838ba7}html.theme--catppuccin-frappe .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8caaee}.navbar.is-spaced html.theme--catppuccin-frappe .navbar-dropdown,html.theme--catppuccin-frappe .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-frappe .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-frappe .navbar-divider{display:block}html.theme--catppuccin-frappe .navbar>.container .navbar-brand,html.theme--catppuccin-frappe .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-frappe .navbar>.container .navbar-menu,html.theme--catppuccin-frappe .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-frappe .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-frappe .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-frappe html.has-navbar-fixed-top-desktop,html.theme--catppuccin-frappe body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-frappe html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-frappe body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-frappe html.has-spaced-navbar-fixed-top,html.theme--catppuccin-frappe body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-frappe html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-frappe body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-frappe a.navbar-item.is-active,html.theme--catppuccin-frappe .navbar-link.is-active{color:#8caaee}html.theme--catppuccin-frappe a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-frappe .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-frappe .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-frappe .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-frappe .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-frappe .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-frappe .pagination.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-frappe .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .pagination.is-rounded .pagination-previous,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-frappe .pagination.is-rounded .pagination-next,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-frappe .pagination.is-rounded .pagination-link,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-frappe .pagination,html.theme--catppuccin-frappe .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link{border-color:#626880;color:#8caaee;min-width:2.5em}html.theme--catppuccin-frappe .pagination-previous:hover,html.theme--catppuccin-frappe .pagination-next:hover,html.theme--catppuccin-frappe .pagination-link:hover{border-color:#737994;color:#99d1db}html.theme--catppuccin-frappe .pagination-previous:focus,html.theme--catppuccin-frappe .pagination-next:focus,html.theme--catppuccin-frappe .pagination-link:focus{border-color:#737994}html.theme--catppuccin-frappe .pagination-previous:active,html.theme--catppuccin-frappe .pagination-next:active,html.theme--catppuccin-frappe .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-frappe .pagination-previous[disabled],html.theme--catppuccin-frappe .pagination-previous.is-disabled,html.theme--catppuccin-frappe .pagination-next[disabled],html.theme--catppuccin-frappe .pagination-next.is-disabled,html.theme--catppuccin-frappe .pagination-link[disabled],html.theme--catppuccin-frappe .pagination-link.is-disabled{background-color:#626880;border-color:#626880;box-shadow:none;color:#f1f4fd;opacity:0.5}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-frappe .pagination-link.is-current{background-color:#8caaee;border-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .pagination-ellipsis{color:#737994;pointer-events:none}html.theme--catppuccin-frappe .pagination-list{flex-wrap:wrap}html.theme--catppuccin-frappe .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .pagination{flex-wrap:wrap}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-frappe .pagination-previous{order:2}html.theme--catppuccin-frappe .pagination-next{order:3}html.theme--catppuccin-frappe .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-frappe .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-frappe .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-frappe .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-frappe .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-frappe .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-frappe .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-frappe .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-frappe .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-frappe .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-frappe .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-frappe .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-frappe .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-frappe .panel.is-dark .panel-heading,html.theme--catppuccin-frappe .content kbd.panel .panel-heading{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-frappe .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#414559}html.theme--catppuccin-frappe .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-frappe .content kbd.panel .panel-block.is-active .panel-icon{color:#414559}html.theme--catppuccin-frappe .panel.is-primary .panel-heading,html.theme--catppuccin-frappe .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-frappe .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#8caaee}html.theme--catppuccin-frappe .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-frappe .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel.is-link .panel-heading{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .panel.is-link .panel-tabs a.is-active{border-bottom-color:#8caaee}html.theme--catppuccin-frappe .panel.is-link .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel.is-info .panel-heading{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-info .panel-tabs a.is-active{border-bottom-color:#81c8be}html.theme--catppuccin-frappe .panel.is-info .panel-block.is-active .panel-icon{color:#81c8be}html.theme--catppuccin-frappe .panel.is-success .panel-heading{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6d189}html.theme--catppuccin-frappe .panel.is-success .panel-block.is-active .panel-icon{color:#a6d189}html.theme--catppuccin-frappe .panel.is-warning .panel-heading{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#e5c890}html.theme--catppuccin-frappe .panel.is-warning .panel-block.is-active .panel-icon{color:#e5c890}html.theme--catppuccin-frappe .panel.is-danger .panel-heading{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#e78284}html.theme--catppuccin-frappe .panel.is-danger .panel-block.is-active .panel-icon{color:#e78284}html.theme--catppuccin-frappe .panel-tabs:not(:last-child),html.theme--catppuccin-frappe .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-frappe .panel-heading{background-color:#51576d;border-radius:8px 8px 0 0;color:#b0bef1;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-frappe .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-frappe .panel-tabs a{border-bottom:1px solid #626880;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-frappe .panel-tabs a.is-active{border-bottom-color:#51576d;color:#769aeb}html.theme--catppuccin-frappe .panel-list a{color:#c6d0f5}html.theme--catppuccin-frappe .panel-list a:hover{color:#8caaee}html.theme--catppuccin-frappe .panel-block{align-items:center;color:#b0bef1;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-frappe .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-frappe .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-frappe .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-frappe .panel-block.is-active{border-left-color:#8caaee;color:#769aeb}html.theme--catppuccin-frappe .panel-block.is-active .panel-icon{color:#8caaee}html.theme--catppuccin-frappe .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-frappe a.panel-block,html.theme--catppuccin-frappe label.panel-block{cursor:pointer}html.theme--catppuccin-frappe a.panel-block:hover,html.theme--catppuccin-frappe label.panel-block:hover{background-color:#292c3c}html.theme--catppuccin-frappe .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f1f4fd;margin-right:.75em}html.theme--catppuccin-frappe .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-frappe .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-frappe .tabs a{align-items:center;border-bottom-color:#626880;border-bottom-style:solid;border-bottom-width:1px;color:#c6d0f5;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-frappe .tabs a:hover{border-bottom-color:#b0bef1;color:#b0bef1}html.theme--catppuccin-frappe .tabs li{display:block}html.theme--catppuccin-frappe .tabs li.is-active a{border-bottom-color:#8caaee;color:#8caaee}html.theme--catppuccin-frappe .tabs ul{align-items:center;border-bottom-color:#626880;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-frappe .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-frappe .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-frappe .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-frappe .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-frappe .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-frappe .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-frappe .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-frappe .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-frappe .tabs.is-boxed a:hover{background-color:#292c3c;border-bottom-color:#626880}html.theme--catppuccin-frappe .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#626880;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-frappe .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-frappe .tabs.is-toggle a{border-color:#626880;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-frappe .tabs.is-toggle a:hover{background-color:#292c3c;border-color:#737994;z-index:2}html.theme--catppuccin-frappe .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-frappe .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-frappe .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-frappe .tabs.is-toggle li.is-active a{background-color:#8caaee;border-color:#8caaee;color:#fff;z-index:1}html.theme--catppuccin-frappe .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-frappe .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-frappe .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-frappe .tabs.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-frappe .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-frappe .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .column.is-narrow,html.theme--catppuccin-frappe .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full,html.theme--catppuccin-frappe .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters,html.theme--catppuccin-frappe .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds,html.theme--catppuccin-frappe .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half,html.theme--catppuccin-frappe .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third,html.theme--catppuccin-frappe .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter,html.theme--catppuccin-frappe .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth,html.theme--catppuccin-frappe .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths,html.theme--catppuccin-frappe .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths,html.theme--catppuccin-frappe .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths,html.theme--catppuccin-frappe .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters,html.theme--catppuccin-frappe .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds,html.theme--catppuccin-frappe .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half,html.theme--catppuccin-frappe .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third,html.theme--catppuccin-frappe .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter,html.theme--catppuccin-frappe .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth,html.theme--catppuccin-frappe .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths,html.theme--catppuccin-frappe .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths,html.theme--catppuccin-frappe .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths,html.theme--catppuccin-frappe .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-frappe .column.is-0,html.theme--catppuccin-frappe .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0,html.theme--catppuccin-frappe .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-frappe .column.is-1,html.theme--catppuccin-frappe .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1,html.theme--catppuccin-frappe .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2,html.theme--catppuccin-frappe .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2,html.theme--catppuccin-frappe .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3,html.theme--catppuccin-frappe .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3,html.theme--catppuccin-frappe .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-frappe .column.is-4,html.theme--catppuccin-frappe .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4,html.theme--catppuccin-frappe .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5,html.theme--catppuccin-frappe .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5,html.theme--catppuccin-frappe .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6,html.theme--catppuccin-frappe .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6,html.theme--catppuccin-frappe .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-frappe .column.is-7,html.theme--catppuccin-frappe .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7,html.theme--catppuccin-frappe .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8,html.theme--catppuccin-frappe .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8,html.theme--catppuccin-frappe .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9,html.theme--catppuccin-frappe .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9,html.theme--catppuccin-frappe .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-frappe .column.is-10,html.theme--catppuccin-frappe .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10,html.theme--catppuccin-frappe .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11,html.theme--catppuccin-frappe .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11,html.theme--catppuccin-frappe .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12,html.theme--catppuccin-frappe .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12,html.theme--catppuccin-frappe .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-frappe .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-frappe .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-frappe .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-frappe .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-frappe .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-frappe .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-frappe .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-frappe .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-frappe .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-frappe .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-frappe .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-frappe .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-frappe .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-frappe .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-frappe .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-frappe .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-frappe .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-frappe .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-frappe .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-frappe .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-frappe .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-frappe .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-frappe .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-frappe .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-frappe .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-frappe .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-frappe .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-frappe .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-frappe .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-frappe .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-frappe .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-frappe .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-frappe .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-frappe .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-frappe .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-frappe .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-frappe .columns.is-centered{justify-content:center}html.theme--catppuccin-frappe .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-frappe .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-frappe .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-frappe .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-frappe .columns.is-mobile{display:flex}html.theme--catppuccin-frappe .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-frappe .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-desktop{display:flex}}html.theme--catppuccin-frappe .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-frappe .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-frappe .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-frappe .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-frappe .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-frappe .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-frappe .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-frappe .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-frappe .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-frappe .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-frappe .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-frappe .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-frappe .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-frappe .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-frappe .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-frappe .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-frappe .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-frappe .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-frappe .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-frappe .tile.is-child{margin:0 !important}html.theme--catppuccin-frappe .tile.is-parent{padding:.75rem}html.theme--catppuccin-frappe .tile.is-vertical{flex-direction:column}html.theme--catppuccin-frappe .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .tile:not(.is-child){display:flex}html.theme--catppuccin-frappe .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-frappe .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-frappe .tile.is-3{flex:none;width:25%}html.theme--catppuccin-frappe .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-frappe .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-frappe .tile.is-6{flex:none;width:50%}html.theme--catppuccin-frappe .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-frappe .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-frappe .tile.is-9{flex:none;width:75%}html.theme--catppuccin-frappe .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-frappe .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-frappe .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-frappe .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-frappe .hero .navbar{background:none}html.theme--catppuccin-frappe .hero .tabs ul{border-bottom:none}html.theme--catppuccin-frappe .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-white strong{color:inherit}html.theme--catppuccin-frappe .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-frappe .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-frappe .hero.is-white .navbar-item,html.theme--catppuccin-frappe .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-frappe .hero.is-white a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-white .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-frappe .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-frappe .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-frappe .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-black strong{color:inherit}html.theme--catppuccin-frappe .hero.is-black .title{color:#fff}html.theme--catppuccin-frappe .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-frappe .hero.is-black .navbar-item,html.theme--catppuccin-frappe .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-black a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-black .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-frappe .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-frappe .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-frappe .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-light strong{color:inherit}html.theme--catppuccin-frappe .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-frappe .hero.is-light .navbar-item,html.theme--catppuccin-frappe .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-light .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-frappe .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-frappe .hero.is-dark,html.theme--catppuccin-frappe .content kbd.hero{background-color:#414559;color:#fff}html.theme--catppuccin-frappe .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-dark strong,html.theme--catppuccin-frappe .content kbd.hero strong{color:inherit}html.theme--catppuccin-frappe .hero.is-dark .title,html.theme--catppuccin-frappe .content kbd.hero .title{color:#fff}html.theme--catppuccin-frappe .hero.is-dark .subtitle,html.theme--catppuccin-frappe .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-frappe .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-dark .subtitle strong,html.theme--catppuccin-frappe .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-dark .navbar-menu,html.theme--catppuccin-frappe .content kbd.hero .navbar-menu{background-color:#414559}}html.theme--catppuccin-frappe .hero.is-dark .navbar-item,html.theme--catppuccin-frappe .content kbd.hero .navbar-item,html.theme--catppuccin-frappe .hero.is-dark .navbar-link,html.theme--catppuccin-frappe .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-frappe .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-frappe .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-dark .navbar-link:hover,html.theme--catppuccin-frappe .content kbd.hero .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-frappe .content kbd.hero .navbar-link.is-active{background-color:#363a4a;color:#fff}html.theme--catppuccin-frappe .hero.is-dark .tabs a,html.theme--catppuccin-frappe .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-dark .tabs a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs li.is-active a{color:#414559 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#414559}html.theme--catppuccin-frappe .hero.is-dark.is-bold,html.theme--catppuccin-frappe .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #262f41 0%, #414559 71%, #47476c 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-frappe .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #262f41 0%, #414559 71%, #47476c 100%)}}html.theme--catppuccin-frappe .hero.is-primary,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-primary strong,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-frappe .hero.is-primary .title,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-frappe .hero.is-primary .subtitle,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-primary .subtitle strong,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-primary .navbar-menu,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#8caaee}}html.theme--catppuccin-frappe .hero.is-primary .navbar-item,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-frappe .hero.is-primary .navbar-link,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-primary .navbar-link:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .hero.is-primary .tabs a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-primary .tabs a:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#8caaee !important;opacity:1}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .hero.is-primary.is-bold,html.theme--catppuccin-frappe .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-frappe .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}}html.theme--catppuccin-frappe .hero.is-link{background-color:#8caaee;color:#fff}html.theme--catppuccin-frappe .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-link strong{color:inherit}html.theme--catppuccin-frappe .hero.is-link .title{color:#fff}html.theme--catppuccin-frappe .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-link .navbar-menu{background-color:#8caaee}}html.theme--catppuccin-frappe .hero.is-link .navbar-item,html.theme--catppuccin-frappe .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-link a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-link .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-link .navbar-link.is-active{background-color:#769aeb;color:#fff}html.theme--catppuccin-frappe .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-link .tabs li.is-active a{color:#8caaee !important;opacity:1}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8caaee}html.theme--catppuccin-frappe .hero.is-link.is-bold{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #569ff1 0%, #8caaee 71%, #a0abf4 100%)}}html.theme--catppuccin-frappe .hero.is-info{background-color:#81c8be;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-info strong{color:inherit}html.theme--catppuccin-frappe .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-info .navbar-menu{background-color:#81c8be}}html.theme--catppuccin-frappe .hero.is-info .navbar-item,html.theme--catppuccin-frappe .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-info .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-info .navbar-link.is-active{background-color:#6fc0b5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-info .tabs li.is-active a{color:#81c8be !important;opacity:1}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#81c8be}html.theme--catppuccin-frappe .hero.is-info.is-bold{background-image:linear-gradient(141deg, #52c4a1 0%, #81c8be 71%, #8fd2d4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #52c4a1 0%, #81c8be 71%, #8fd2d4 100%)}}html.theme--catppuccin-frappe .hero.is-success{background-color:#a6d189;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-success strong{color:inherit}html.theme--catppuccin-frappe .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-success .navbar-menu{background-color:#a6d189}}html.theme--catppuccin-frappe .hero.is-success .navbar-item,html.theme--catppuccin-frappe .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-success .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-success .navbar-link.is-active{background-color:#98ca77;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-success .tabs li.is-active a{color:#a6d189 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6d189}html.theme--catppuccin-frappe .hero.is-success.is-bold{background-image:linear-gradient(141deg, #9ccd5a 0%, #a6d189 71%, #a8dc98 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #9ccd5a 0%, #a6d189 71%, #a8dc98 100%)}}html.theme--catppuccin-frappe .hero.is-warning{background-color:#e5c890;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-warning strong{color:inherit}html.theme--catppuccin-frappe .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-frappe .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-warning .navbar-menu{background-color:#e5c890}}html.theme--catppuccin-frappe .hero.is-warning .navbar-item,html.theme--catppuccin-frappe .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-warning .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-warning .navbar-link.is-active{background-color:#e0be7b;color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-frappe .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-warning .tabs li.is-active a{color:#e5c890 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#e5c890}html.theme--catppuccin-frappe .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #e5a05d 0%, #e5c890 71%, #ede0a2 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e5a05d 0%, #e5c890 71%, #ede0a2 100%)}}html.theme--catppuccin-frappe .hero.is-danger{background-color:#e78284;color:#fff}html.theme--catppuccin-frappe .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-frappe .hero.is-danger strong{color:inherit}html.theme--catppuccin-frappe .hero.is-danger .title{color:#fff}html.theme--catppuccin-frappe .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-frappe .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-frappe .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .hero.is-danger .navbar-menu{background-color:#e78284}}html.theme--catppuccin-frappe .hero.is-danger .navbar-item,html.theme--catppuccin-frappe .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-frappe .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-frappe .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-frappe .hero.is-danger .navbar-link:hover,html.theme--catppuccin-frappe .hero.is-danger .navbar-link.is-active{background-color:#e36d6f;color:#fff}html.theme--catppuccin-frappe .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-frappe .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-frappe .hero.is-danger .tabs li.is-active a{color:#e78284 !important;opacity:1}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-frappe .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#e78284}html.theme--catppuccin-frappe .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #e94d6a 0%, #e78284 71%, #eea294 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e94d6a 0%, #e78284 71%, #eea294 100%)}}html.theme--catppuccin-frappe .hero.is-small .hero-body,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-frappe .hero.is-halfheight .hero-body,html.theme--catppuccin-frappe .hero.is-fullheight .hero-body,html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-frappe .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-frappe .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-frappe .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-frappe .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-frappe .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-frappe .hero-video{overflow:hidden}html.theme--catppuccin-frappe .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-frappe .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero-video{display:none}}html.theme--catppuccin-frappe .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-frappe .hero-buttons .button{display:flex}html.theme--catppuccin-frappe .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-frappe .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-frappe .hero-head,html.theme--catppuccin-frappe .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-frappe .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-frappe .hero-body{padding:3rem 3rem}}html.theme--catppuccin-frappe .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe .section{padding:3rem 3rem}html.theme--catppuccin-frappe .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-frappe .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-frappe .footer{background-color:#292c3c;padding:3rem 1.5rem 6rem}html.theme--catppuccin-frappe h1 .docs-heading-anchor,html.theme--catppuccin-frappe h1 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h1 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h2 .docs-heading-anchor,html.theme--catppuccin-frappe h2 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h2 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h3 .docs-heading-anchor,html.theme--catppuccin-frappe h3 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h3 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h4 .docs-heading-anchor,html.theme--catppuccin-frappe h4 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h4 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h5 .docs-heading-anchor,html.theme--catppuccin-frappe h5 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h5 .docs-heading-anchor:visited,html.theme--catppuccin-frappe h6 .docs-heading-anchor,html.theme--catppuccin-frappe h6 .docs-heading-anchor:hover,html.theme--catppuccin-frappe h6 .docs-heading-anchor:visited{color:#c6d0f5}html.theme--catppuccin-frappe h1 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h2 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h3 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h4 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h5 .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-frappe h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-frappe h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-frappe h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-frappe h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-frappe .docs-light-only{display:none !important}html.theme--catppuccin-frappe pre{position:relative;overflow:hidden}html.theme--catppuccin-frappe pre code,html.theme--catppuccin-frappe pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-frappe pre code:first-of-type,html.theme--catppuccin-frappe pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-frappe pre code:last-of-type,html.theme--catppuccin-frappe pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-frappe pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#c6d0f5;cursor:pointer;text-align:center}html.theme--catppuccin-frappe pre .copy-button:focus,html.theme--catppuccin-frappe pre .copy-button:hover{opacity:1;background:rgba(198,208,245,0.1);color:#8caaee}html.theme--catppuccin-frappe pre .copy-button.success{color:#a6d189;opacity:1}html.theme--catppuccin-frappe pre .copy-button.error{color:#e78284;opacity:1}html.theme--catppuccin-frappe pre:hover .copy-button{opacity:1}html.theme--catppuccin-frappe .admonition{background-color:#292c3c;border-style:solid;border-width:2px;border-color:#b5bfe2;border-radius:4px;font-size:1rem}html.theme--catppuccin-frappe .admonition strong{color:currentColor}html.theme--catppuccin-frappe .admonition.is-small,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-frappe .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-frappe .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-frappe .admonition.is-default{background-color:#292c3c;border-color:#b5bfe2}html.theme--catppuccin-frappe .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#b5bfe2}html.theme--catppuccin-frappe .admonition.is-default>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-info{background-color:#292c3c;border-color:#81c8be}html.theme--catppuccin-frappe .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#81c8be}html.theme--catppuccin-frappe .admonition.is-info>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-success{background-color:#292c3c;border-color:#a6d189}html.theme--catppuccin-frappe .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6d189}html.theme--catppuccin-frappe .admonition.is-success>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-warning{background-color:#292c3c;border-color:#e5c890}html.theme--catppuccin-frappe .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#e5c890}html.theme--catppuccin-frappe .admonition.is-warning>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-danger{background-color:#292c3c;border-color:#e78284}html.theme--catppuccin-frappe .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#e78284}html.theme--catppuccin-frappe .admonition.is-danger>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-compat{background-color:#292c3c;border-color:#99d1db}html.theme--catppuccin-frappe .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#99d1db}html.theme--catppuccin-frappe .admonition.is-compat>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition.is-todo{background-color:#292c3c;border-color:#ca9ee6}html.theme--catppuccin-frappe .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#ca9ee6}html.theme--catppuccin-frappe .admonition.is-todo>.admonition-body{color:#c6d0f5}html.theme--catppuccin-frappe .admonition-header{color:#b5bfe2;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-frappe .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-frappe details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-frappe details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-frappe details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-frappe .admonition-body{color:#c6d0f5;padding:0.5rem .75rem}html.theme--catppuccin-frappe .admonition-body pre{background-color:#292c3c}html.theme--catppuccin-frappe .admonition-body code{background-color:#292c3c}html.theme--catppuccin-frappe .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #626880;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-frappe .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#292c3c;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #626880;overflow:auto}html.theme--catppuccin-frappe .docstring>header code{background-color:transparent}html.theme--catppuccin-frappe .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-frappe .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-frappe .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-frappe .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #626880}html.theme--catppuccin-frappe .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-frappe .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-frappe .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-frappe .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-frappe .documenter-example-output{background-color:#303446}html.theme--catppuccin-frappe .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#292c3c;color:#c6d0f5;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-frappe .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-frappe .outdated-warning-overlay a{color:#8caaee}html.theme--catppuccin-frappe .outdated-warning-overlay a:hover{color:#99d1db}html.theme--catppuccin-frappe .content pre{border:2px solid #626880;border-radius:4px}html.theme--catppuccin-frappe .content code{font-weight:inherit}html.theme--catppuccin-frappe .content a code{color:#8caaee}html.theme--catppuccin-frappe .content a:hover code{color:#99d1db}html.theme--catppuccin-frappe .content h1 code,html.theme--catppuccin-frappe .content h2 code,html.theme--catppuccin-frappe .content h3 code,html.theme--catppuccin-frappe .content h4 code,html.theme--catppuccin-frappe .content h5 code,html.theme--catppuccin-frappe .content h6 code{color:#c6d0f5}html.theme--catppuccin-frappe .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-frappe .content blockquote>ul:first-child,html.theme--catppuccin-frappe .content blockquote>ol:first-child,html.theme--catppuccin-frappe .content .admonition-body>ul:first-child,html.theme--catppuccin-frappe .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-frappe pre,html.theme--catppuccin-frappe code{font-variant-ligatures:no-contextual}html.theme--catppuccin-frappe .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-frappe .breadcrumb a.is-disabled,html.theme--catppuccin-frappe .breadcrumb a.is-disabled:hover{color:#b0bef1}html.theme--catppuccin-frappe .hljs{background:initial !important}html.theme--catppuccin-frappe .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-frappe .katex-display,html.theme--catppuccin-frappe mjx-container,html.theme--catppuccin-frappe .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-frappe html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-frappe li.no-marker{list-style:none}html.theme--catppuccin-frappe #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-frappe #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main{width:100%}html.theme--catppuccin-frappe #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-frappe #documenter .docs-main>header,html.theme--catppuccin-frappe #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar{background-color:#303446;border-bottom:1px solid #626880;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-frappe #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-frappe #documenter .docs-main section.footnotes{border-top:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-frappe #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-frappe .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #626880;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-frappe #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-frappe #documenter .docs-sidebar{display:flex;flex-direction:column;color:#c6d0f5;background-color:#292c3c;border-right:1px solid #626880;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-frappe #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-package-name a:hover{color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #626880;display:none;padding:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #626880;padding-bottom:1.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#c6d0f5;background:#292c3c}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#c6d0f5;background-color:#313548}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #626880;border-bottom:1px solid #626880;background-color:#232634}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#232634;color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#313548;color:#c6d0f5}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #626880}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-frappe #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3a3e54}html.theme--catppuccin-frappe #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4a506c}}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3a3e54}html.theme--catppuccin-frappe #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4a506c}}html.theme--catppuccin-frappe kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-frappe .search-min-width-50{min-width:50%}html.theme--catppuccin-frappe .search-min-height-100{min-height:100%}html.theme--catppuccin-frappe .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-frappe .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover,html.theme--catppuccin-frappe .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-frappe .search-result-link .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-frappe .property-search-result-badge,html.theme--catppuccin-frappe .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-frappe .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:hover .search-filter,html.theme--catppuccin-frappe .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-frappe .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-frappe .search-filter:hover,html.theme--catppuccin-frappe .search-filter:focus{color:#333}html.theme--catppuccin-frappe .search-filter-selected{color:#414559;background-color:#babbf1}html.theme--catppuccin-frappe .search-filter-selected:hover,html.theme--catppuccin-frappe .search-filter-selected:focus{color:#414559}html.theme--catppuccin-frappe .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-frappe .search-divider{border-bottom:1px solid #626880}html.theme--catppuccin-frappe .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-frappe .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-frappe #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-frappe #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-frappe .w-100{width:100%}html.theme--catppuccin-frappe .gap-2{gap:0.5rem}html.theme--catppuccin-frappe .gap-4{gap:1rem}html.theme--catppuccin-frappe .gap-8{gap:2rem}html.theme--catppuccin-frappe{background-color:#303446;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-frappe a{transition:all 200ms ease}html.theme--catppuccin-frappe .label{color:#c6d0f5}html.theme--catppuccin-frappe .button,html.theme--catppuccin-frappe .control.has-icons-left .icon,html.theme--catppuccin-frappe .control.has-icons-right .icon,html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .pagination-ellipsis,html.theme--catppuccin-frappe .pagination-link,html.theme--catppuccin-frappe .pagination-next,html.theme--catppuccin-frappe .pagination-previous,html.theme--catppuccin-frappe .select,html.theme--catppuccin-frappe .select select,html.theme--catppuccin-frappe .textarea{height:2.5em;color:#c6d0f5}html.theme--catppuccin-frappe .input,html.theme--catppuccin-frappe #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-frappe .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#c6d0f5}html.theme--catppuccin-frappe .select:after,html.theme--catppuccin-frappe .select select{border-width:1px}html.theme--catppuccin-frappe .menu-list a{transition:all 300ms ease}html.theme--catppuccin-frappe .modal-card-foot,html.theme--catppuccin-frappe .modal-card-head{border-color:#626880}html.theme--catppuccin-frappe .navbar{border-radius:.4em}html.theme--catppuccin-frappe .navbar.is-transparent{background:none}html.theme--catppuccin-frappe .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-frappe .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8caaee}@media screen and (max-width: 1055px){html.theme--catppuccin-frappe .navbar .navbar-menu{background-color:#8caaee;border-radius:0 0 .4em .4em}}html.theme--catppuccin-frappe .docstring>section>a.docs-sourcelink:not(body){color:#414559}html.theme--catppuccin-frappe .tag.is-link:not(body),html.theme--catppuccin-frappe .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-frappe .content kbd.is-link:not(body){color:#414559}html.theme--catppuccin-frappe .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-frappe .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-frappe .ansi span.sgr3{font-style:italic}html.theme--catppuccin-frappe .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-frappe .ansi span.sgr7{color:#303446;background-color:#c6d0f5}html.theme--catppuccin-frappe .ansi span.sgr8{color:transparent}html.theme--catppuccin-frappe .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-frappe .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-frappe .ansi span.sgr30{color:#51576d}html.theme--catppuccin-frappe .ansi span.sgr31{color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr32{color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr33{color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr34{color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr35{color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr36{color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr37{color:#b5bfe2}html.theme--catppuccin-frappe .ansi span.sgr40{background-color:#51576d}html.theme--catppuccin-frappe .ansi span.sgr41{background-color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr42{background-color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr43{background-color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr44{background-color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr45{background-color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr46{background-color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr47{background-color:#b5bfe2}html.theme--catppuccin-frappe .ansi span.sgr90{color:#626880}html.theme--catppuccin-frappe .ansi span.sgr91{color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr92{color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr93{color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr94{color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr95{color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr96{color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr97{color:#a5adce}html.theme--catppuccin-frappe .ansi span.sgr100{background-color:#626880}html.theme--catppuccin-frappe .ansi span.sgr101{background-color:#e78284}html.theme--catppuccin-frappe .ansi span.sgr102{background-color:#a6d189}html.theme--catppuccin-frappe .ansi span.sgr103{background-color:#e5c890}html.theme--catppuccin-frappe .ansi span.sgr104{background-color:#8caaee}html.theme--catppuccin-frappe .ansi span.sgr105{background-color:#f4b8e4}html.theme--catppuccin-frappe .ansi span.sgr106{background-color:#81c8be}html.theme--catppuccin-frappe .ansi span.sgr107{background-color:#a5adce}html.theme--catppuccin-frappe code.language-julia-repl>span.hljs-meta{color:#a6d189;font-weight:bolder}html.theme--catppuccin-frappe code .hljs{color:#c6d0f5;background:#303446}html.theme--catppuccin-frappe code .hljs-keyword{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-built_in{color:#e78284}html.theme--catppuccin-frappe code .hljs-type{color:#e5c890}html.theme--catppuccin-frappe code .hljs-literal{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-number{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-operator{color:#81c8be}html.theme--catppuccin-frappe code .hljs-punctuation{color:#b5bfe2}html.theme--catppuccin-frappe code .hljs-property{color:#81c8be}html.theme--catppuccin-frappe code .hljs-regexp{color:#f4b8e4}html.theme--catppuccin-frappe code .hljs-string{color:#a6d189}html.theme--catppuccin-frappe code .hljs-char.escape_{color:#a6d189}html.theme--catppuccin-frappe code .hljs-subst{color:#a5adce}html.theme--catppuccin-frappe code .hljs-symbol{color:#eebebe}html.theme--catppuccin-frappe code .hljs-variable{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-variable.language_{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-variable.constant_{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-title{color:#8caaee}html.theme--catppuccin-frappe code .hljs-title.class_{color:#e5c890}html.theme--catppuccin-frappe code .hljs-title.function_{color:#8caaee}html.theme--catppuccin-frappe code .hljs-params{color:#c6d0f5}html.theme--catppuccin-frappe code .hljs-comment{color:#626880}html.theme--catppuccin-frappe code .hljs-doctag{color:#e78284}html.theme--catppuccin-frappe code .hljs-meta{color:#ef9f76}html.theme--catppuccin-frappe code .hljs-section{color:#8caaee}html.theme--catppuccin-frappe code .hljs-tag{color:#a5adce}html.theme--catppuccin-frappe code .hljs-name{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-attr{color:#8caaee}html.theme--catppuccin-frappe code .hljs-attribute{color:#a6d189}html.theme--catppuccin-frappe code .hljs-bullet{color:#81c8be}html.theme--catppuccin-frappe code .hljs-code{color:#a6d189}html.theme--catppuccin-frappe code .hljs-emphasis{color:#e78284;font-style:italic}html.theme--catppuccin-frappe code .hljs-strong{color:#e78284;font-weight:bold}html.theme--catppuccin-frappe code .hljs-formula{color:#81c8be}html.theme--catppuccin-frappe code .hljs-link{color:#85c1dc;font-style:italic}html.theme--catppuccin-frappe code .hljs-quote{color:#a6d189;font-style:italic}html.theme--catppuccin-frappe code .hljs-selector-tag{color:#e5c890}html.theme--catppuccin-frappe code .hljs-selector-id{color:#8caaee}html.theme--catppuccin-frappe code .hljs-selector-class{color:#81c8be}html.theme--catppuccin-frappe code .hljs-selector-attr{color:#ca9ee6}html.theme--catppuccin-frappe code .hljs-selector-pseudo{color:#81c8be}html.theme--catppuccin-frappe code .hljs-template-tag{color:#eebebe}html.theme--catppuccin-frappe code .hljs-template-variable{color:#eebebe}html.theme--catppuccin-frappe code .hljs-addition{color:#a6d189;background:rgba(166,227,161,0.15)}html.theme--catppuccin-frappe code .hljs-deletion{color:#e78284;background:rgba(243,139,168,0.15)}html.theme--catppuccin-frappe .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover,html.theme--catppuccin-frappe .search-result-link:focus{background-color:#414559}html.theme--catppuccin-frappe .search-result-link .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-frappe .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:hover .search-filter,html.theme--catppuccin-frappe .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-frappe .search-result-link:focus .search-filter{color:#414559 !important;background-color:#babbf1 !important}html.theme--catppuccin-frappe .search-result-title{color:#c6d0f5}html.theme--catppuccin-frappe .search-result-highlight{background-color:#e78284;color:#292c3c}html.theme--catppuccin-frappe .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-frappe .w-100{width:100%}html.theme--catppuccin-frappe .gap-2{gap:0.5rem}html.theme--catppuccin-frappe .gap-4{gap:1rem} diff --git a/previews/PR826/assets/themes/catppuccin-latte.css b/previews/PR826/assets/themes/catppuccin-latte.css new file mode 100644 index 0000000000..63160d3449 --- /dev/null +++ b/previews/PR826/assets/themes/catppuccin-latte.css @@ -0,0 +1 @@ +html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte .file-name,html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-latte .pagination-previous:focus,html.theme--catppuccin-latte .pagination-next:focus,html.theme--catppuccin-latte .pagination-link:focus,html.theme--catppuccin-latte .pagination-ellipsis:focus,html.theme--catppuccin-latte .file-cta:focus,html.theme--catppuccin-latte .file-name:focus,html.theme--catppuccin-latte .select select:focus,html.theme--catppuccin-latte .textarea:focus,html.theme--catppuccin-latte .input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-latte .button:focus,html.theme--catppuccin-latte .is-focused.pagination-previous,html.theme--catppuccin-latte .is-focused.pagination-next,html.theme--catppuccin-latte .is-focused.pagination-link,html.theme--catppuccin-latte .is-focused.pagination-ellipsis,html.theme--catppuccin-latte .is-focused.file-cta,html.theme--catppuccin-latte .is-focused.file-name,html.theme--catppuccin-latte .select select.is-focused,html.theme--catppuccin-latte .is-focused.textarea,html.theme--catppuccin-latte .is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-focused.button,html.theme--catppuccin-latte .pagination-previous:active,html.theme--catppuccin-latte .pagination-next:active,html.theme--catppuccin-latte .pagination-link:active,html.theme--catppuccin-latte .pagination-ellipsis:active,html.theme--catppuccin-latte .file-cta:active,html.theme--catppuccin-latte .file-name:active,html.theme--catppuccin-latte .select select:active,html.theme--catppuccin-latte .textarea:active,html.theme--catppuccin-latte .input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-latte .button:active,html.theme--catppuccin-latte .is-active.pagination-previous,html.theme--catppuccin-latte .is-active.pagination-next,html.theme--catppuccin-latte .is-active.pagination-link,html.theme--catppuccin-latte .is-active.pagination-ellipsis,html.theme--catppuccin-latte .is-active.file-cta,html.theme--catppuccin-latte .is-active.file-name,html.theme--catppuccin-latte .select select.is-active,html.theme--catppuccin-latte .is-active.textarea,html.theme--catppuccin-latte .is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .is-active.button{outline:none}html.theme--catppuccin-latte .pagination-previous[disabled],html.theme--catppuccin-latte .pagination-next[disabled],html.theme--catppuccin-latte .pagination-link[disabled],html.theme--catppuccin-latte .pagination-ellipsis[disabled],html.theme--catppuccin-latte .file-cta[disabled],html.theme--catppuccin-latte .file-name[disabled],html.theme--catppuccin-latte .select select[disabled],html.theme--catppuccin-latte .textarea[disabled],html.theme--catppuccin-latte .input[disabled],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-latte .button[disabled],fieldset[disabled] html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-latte .file-name,html.theme--catppuccin-latte fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-latte .select select,fieldset[disabled] html.theme--catppuccin-latte .textarea,fieldset[disabled] html.theme--catppuccin-latte .input,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte fieldset[disabled] .select select,html.theme--catppuccin-latte .select fieldset[disabled] select,html.theme--catppuccin-latte fieldset[disabled] .textarea,html.theme--catppuccin-latte fieldset[disabled] .input,html.theme--catppuccin-latte fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-latte .button,html.theme--catppuccin-latte fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-latte .tabs,html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .breadcrumb,html.theme--catppuccin-latte .file,html.theme--catppuccin-latte .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-latte .admonition:not(:last-child),html.theme--catppuccin-latte .tabs:not(:last-child),html.theme--catppuccin-latte .pagination:not(:last-child),html.theme--catppuccin-latte .message:not(:last-child),html.theme--catppuccin-latte .level:not(:last-child),html.theme--catppuccin-latte .breadcrumb:not(:last-child),html.theme--catppuccin-latte .block:not(:last-child),html.theme--catppuccin-latte .title:not(:last-child),html.theme--catppuccin-latte .subtitle:not(:last-child),html.theme--catppuccin-latte .table-container:not(:last-child),html.theme--catppuccin-latte .table:not(:last-child),html.theme--catppuccin-latte .progress:not(:last-child),html.theme--catppuccin-latte .notification:not(:last-child),html.theme--catppuccin-latte .content:not(:last-child),html.theme--catppuccin-latte .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .modal-close,html.theme--catppuccin-latte .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-latte .modal-close::before,html.theme--catppuccin-latte .delete::before,html.theme--catppuccin-latte .modal-close::after,html.theme--catppuccin-latte .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-latte .modal-close::before,html.theme--catppuccin-latte .delete::before{height:2px;width:50%}html.theme--catppuccin-latte .modal-close::after,html.theme--catppuccin-latte .delete::after{height:50%;width:2px}html.theme--catppuccin-latte .modal-close:hover,html.theme--catppuccin-latte .delete:hover,html.theme--catppuccin-latte .modal-close:focus,html.theme--catppuccin-latte .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-latte .modal-close:active,html.theme--catppuccin-latte .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-latte .is-small.modal-close,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-latte .is-small.delete,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-latte .is-medium.modal-close,html.theme--catppuccin-latte .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-latte .is-large.modal-close,html.theme--catppuccin-latte .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-latte .control.is-loading::after,html.theme--catppuccin-latte .select.is-loading::after,html.theme--catppuccin-latte .loader,html.theme--catppuccin-latte .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #8c8fa1;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-latte .hero-video,html.theme--catppuccin-latte .modal-background,html.theme--catppuccin-latte .modal,html.theme--catppuccin-latte .image.is-square img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-latte .image.is-square .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-latte .image.is-1by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-latte .image.is-1by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-latte .image.is-5by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-latte .image.is-5by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-latte .image.is-4by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-latte .image.is-4by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-latte .image.is-3by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-latte .image.is-5by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-latte .image.is-5by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-latte .image.is-16by9 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-latte .image.is-16by9 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-latte .image.is-2by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-latte .image.is-2by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-latte .image.is-3by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-latte .image.is-3by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-latte .image.is-4by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-latte .image.is-4by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-latte .image.is-3by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-latte .image.is-3by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-latte .image.is-2by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-latte .image.is-2by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-latte .image.is-3by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-latte .image.is-9by16 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-latte .image.is-9by16 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-latte .image.is-1by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-latte .image.is-1by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-latte .image.is-1by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-latte .image.is-1by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-latte .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#ccd0da !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#aeb5c5 !important}.has-background-dark{background-color:#ccd0da !important}.has-text-primary{color:#1e66f5 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#0a4ed6 !important}.has-background-primary{background-color:#1e66f5 !important}.has-text-primary-light{color:#ebf2fe !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bbd1fc !important}.has-background-primary-light{background-color:#ebf2fe !important}.has-text-primary-dark{color:#0a52e1 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#286df5 !important}.has-background-primary-dark{background-color:#0a52e1 !important}.has-text-link{color:#1e66f5 !important}a.has-text-link:hover,a.has-text-link:focus{color:#0a4ed6 !important}.has-background-link{background-color:#1e66f5 !important}.has-text-link-light{color:#ebf2fe !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bbd1fc !important}.has-background-link-light{background-color:#ebf2fe !important}.has-text-link-dark{color:#0a52e1 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#286df5 !important}.has-background-link-dark{background-color:#0a52e1 !important}.has-text-info{color:#179299 !important}a.has-text-info:hover,a.has-text-info:focus{color:#10686d !important}.has-background-info{background-color:#179299 !important}.has-text-info-light{color:#edfcfc !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c1f3f6 !important}.has-background-info-light{background-color:#edfcfc !important}.has-text-info-dark{color:#1cb2ba !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#2ad5df !important}.has-background-info-dark{background-color:#1cb2ba !important}.has-text-success{color:#40a02b !important}a.has-text-success:hover,a.has-text-success:focus{color:#307820 !important}.has-background-success{background-color:#40a02b !important}.has-text-success-light{color:#f1fbef !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#cef0c7 !important}.has-background-success-light{background-color:#f1fbef !important}.has-text-success-dark{color:#40a12b !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#50c936 !important}.has-background-success-dark{background-color:#40a12b !important}.has-text-warning{color:#df8e1d !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#b27117 !important}.has-background-warning{background-color:#df8e1d !important}.has-text-warning-light{color:#fdf6ed !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f7e0c0 !important}.has-background-warning-light{background-color:#fdf6ed !important}.has-text-warning-dark{color:#9e6515 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#cb811a !important}.has-background-warning-dark{background-color:#9e6515 !important}.has-text-danger{color:#d20f39 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a20c2c !important}.has-background-danger{background-color:#d20f39 !important}.has-text-danger-light{color:#feecf0 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fabcca !important}.has-background-danger-light{background-color:#feecf0 !important}.has-text-danger-dark{color:#e9113f !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#f13c63 !important}.has-background-danger-dark{background-color:#e9113f !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#ccd0da !important}.has-background-grey-darker{background-color:#ccd0da !important}.has-text-grey-dark{color:#bcc0cc !important}.has-background-grey-dark{background-color:#bcc0cc !important}.has-text-grey{color:#acb0be !important}.has-background-grey{background-color:#acb0be !important}.has-text-grey-light{color:#9ca0b0 !important}.has-background-grey-light{background-color:#9ca0b0 !important}.has-text-grey-lighter{color:#8c8fa1 !important}.has-background-grey-lighter{background-color:#8c8fa1 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-latte html{background-color:#eff1f5;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-latte article,html.theme--catppuccin-latte aside,html.theme--catppuccin-latte figure,html.theme--catppuccin-latte footer,html.theme--catppuccin-latte header,html.theme--catppuccin-latte hgroup,html.theme--catppuccin-latte section{display:block}html.theme--catppuccin-latte body,html.theme--catppuccin-latte button,html.theme--catppuccin-latte input,html.theme--catppuccin-latte optgroup,html.theme--catppuccin-latte select,html.theme--catppuccin-latte textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-latte code,html.theme--catppuccin-latte pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-latte body{color:#4c4f69;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-latte a{color:#1e66f5;cursor:pointer;text-decoration:none}html.theme--catppuccin-latte a strong{color:currentColor}html.theme--catppuccin-latte a:hover{color:#04a5e5}html.theme--catppuccin-latte code{background-color:#e6e9ef;color:#4c4f69;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-latte hr{background-color:#e6e9ef;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-latte img{height:auto;max-width:100%}html.theme--catppuccin-latte input[type="checkbox"],html.theme--catppuccin-latte input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-latte small{font-size:.875em}html.theme--catppuccin-latte span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-latte strong{color:#41445a;font-weight:700}html.theme--catppuccin-latte fieldset{border:none}html.theme--catppuccin-latte pre{-webkit-overflow-scrolling:touch;background-color:#e6e9ef;color:#4c4f69;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-latte pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-latte table td,html.theme--catppuccin-latte table th{vertical-align:top}html.theme--catppuccin-latte table td:not([align]),html.theme--catppuccin-latte table th:not([align]){text-align:inherit}html.theme--catppuccin-latte table th{color:#41445a}html.theme--catppuccin-latte .box{background-color:#bcc0cc;border-radius:8px;box-shadow:none;color:#4c4f69;display:block;padding:1.25rem}html.theme--catppuccin-latte a.box:hover,html.theme--catppuccin-latte a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1e66f5}html.theme--catppuccin-latte a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1e66f5}html.theme--catppuccin-latte .button{background-color:#e6e9ef;border-color:#fff;border-width:1px;color:#1e66f5;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-latte .button strong{color:inherit}html.theme--catppuccin-latte .button .icon,html.theme--catppuccin-latte .button .icon.is-small,html.theme--catppuccin-latte .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-latte .button .icon.is-medium,html.theme--catppuccin-latte .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-latte .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-latte .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-latte .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-latte .button:hover,html.theme--catppuccin-latte .button.is-hovered{border-color:#9ca0b0;color:#41445a}html.theme--catppuccin-latte .button:focus,html.theme--catppuccin-latte .button.is-focused{border-color:#9ca0b0;color:#0b57ef}html.theme--catppuccin-latte .button:focus:not(:active),html.theme--catppuccin-latte .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button:active,html.theme--catppuccin-latte .button.is-active{border-color:#bcc0cc;color:#41445a}html.theme--catppuccin-latte .button.is-text{background-color:transparent;border-color:transparent;color:#4c4f69;text-decoration:underline}html.theme--catppuccin-latte .button.is-text:hover,html.theme--catppuccin-latte .button.is-text.is-hovered,html.theme--catppuccin-latte .button.is-text:focus,html.theme--catppuccin-latte .button.is-text.is-focused{background-color:#e6e9ef;color:#41445a}html.theme--catppuccin-latte .button.is-text:active,html.theme--catppuccin-latte .button.is-text.is-active{background-color:#d6dbe5;color:#41445a}html.theme--catppuccin-latte .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-latte .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1e66f5;text-decoration:none}html.theme--catppuccin-latte .button.is-ghost:hover,html.theme--catppuccin-latte .button.is-ghost.is-hovered{color:#1e66f5;text-decoration:underline}html.theme--catppuccin-latte .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:hover,html.theme--catppuccin-latte .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:focus,html.theme--catppuccin-latte .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white:focus:not(:active),html.theme--catppuccin-latte .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .button.is-white:active,html.theme--catppuccin-latte .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-latte .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted:hover,html.theme--catppuccin-latte .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-latte .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-white.is-outlined:hover,html.theme--catppuccin-latte .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-white.is-outlined:focus,html.theme--catppuccin-latte .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:hover,html.theme--catppuccin-latte .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:focus,html.theme--catppuccin-latte .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black:focus:not(:active),html.theme--catppuccin-latte .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .button.is-black:active,html.theme--catppuccin-latte .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-latte .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted:hover,html.theme--catppuccin-latte .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-outlined:hover,html.theme--catppuccin-latte .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-black.is-outlined:focus,html.theme--catppuccin-latte .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:hover,html.theme--catppuccin-latte .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:focus,html.theme--catppuccin-latte .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light:focus:not(:active),html.theme--catppuccin-latte .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .button.is-light:active,html.theme--catppuccin-latte .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-latte .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted:hover,html.theme--catppuccin-latte .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-outlined:hover,html.theme--catppuccin-latte .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-light.is-outlined:focus,html.theme--catppuccin-latte .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark,html.theme--catppuccin-latte .content kbd.button{background-color:#ccd0da;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:hover,html.theme--catppuccin-latte .content kbd.button:hover,html.theme--catppuccin-latte .button.is-dark.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-hovered{background-color:#c5c9d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:focus,html.theme--catppuccin-latte .content kbd.button:focus,html.theme--catppuccin-latte .button.is-dark.is-focused,html.theme--catppuccin-latte .content kbd.button.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark:focus:not(:active),html.theme--catppuccin-latte .content kbd.button:focus:not(:active),html.theme--catppuccin-latte .button.is-dark.is-focused:not(:active),html.theme--catppuccin-latte .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .button.is-dark:active,html.theme--catppuccin-latte .content kbd.button:active,html.theme--catppuccin-latte .button.is-dark.is-active,html.theme--catppuccin-latte .content kbd.button.is-active{background-color:#bdc2cf;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark[disabled],html.theme--catppuccin-latte .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button{background-color:#ccd0da;border-color:#ccd0da;box-shadow:none}html.theme--catppuccin-latte .button.is-dark.is-inverted,html.theme--catppuccin-latte .content kbd.button.is-inverted{background-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted:hover,html.theme--catppuccin-latte .content kbd.button.is-inverted:hover,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-inverted[disabled],html.theme--catppuccin-latte .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-loading::after,html.theme--catppuccin-latte .content kbd.button.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-dark.is-outlined,html.theme--catppuccin-latte .content kbd.button.is-outlined{background-color:transparent;border-color:#ccd0da;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-outlined:hover,html.theme--catppuccin-latte .content kbd.button.is-outlined:hover,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-dark.is-outlined:focus,html.theme--catppuccin-latte .content kbd.button.is-outlined:focus,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-focused{background-color:#ccd0da;border-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #ccd0da #ccd0da !important}html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-latte .button.is-dark.is-outlined[disabled],html.theme--catppuccin-latte .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-outlined{background-color:transparent;border-color:#ccd0da;box-shadow:none;color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ccd0da #ccd0da !important}html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .button.is-primary,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:hover,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:focus,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-focused,html.theme--catppuccin-latte .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary:focus:not(:active),html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-latte .button.is-primary.is-focused:not(:active),html.theme--catppuccin-latte .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button.is-primary:active,html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-latte .button.is-primary.is-active,html.theme--catppuccin-latte .docstring>section>a.button.is-active.docs-sourcelink{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-primary[disabled],html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.docs-sourcelink{background-color:#1e66f5;border-color:#1e66f5;box-shadow:none}html.theme--catppuccin-latte .button.is-primary.is-inverted,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-primary.is-inverted[disabled],html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-loading::after,html.theme--catppuccin-latte .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-primary.is-outlined,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-outlined:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-latte .button.is-primary.is-outlined:focus,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-latte .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-primary.is-outlined[disabled],html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#1e66f5;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-latte .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-primary.is-light,html.theme--catppuccin-latte .docstring>section>a.button.is-light.docs-sourcelink{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .button.is-primary.is-light:hover,html.theme--catppuccin-latte .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-latte .button.is-primary.is-light.is-hovered,html.theme--catppuccin-latte .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#dfe9fe;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-primary.is-light:active,html.theme--catppuccin-latte .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-latte .button.is-primary.is-light.is-active,html.theme--catppuccin-latte .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d3e1fd;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-link{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:hover,html.theme--catppuccin-latte .button.is-link.is-hovered{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:focus,html.theme--catppuccin-latte .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link:focus:not(:active),html.theme--catppuccin-latte .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .button.is-link:active,html.theme--catppuccin-latte .button.is-link.is-active{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link{background-color:#1e66f5;border-color:#1e66f5;box-shadow:none}html.theme--catppuccin-latte .button.is-link.is-inverted{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted:hover,html.theme--catppuccin-latte .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-link.is-outlined{background-color:transparent;border-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-outlined:hover,html.theme--catppuccin-latte .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-link.is-outlined:focus,html.theme--catppuccin-latte .button.is-link.is-outlined.is-focused{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-outlined{background-color:transparent;border-color:#1e66f5;box-shadow:none;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1e66f5 #1e66f5 !important}html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-link.is-light{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .button.is-link.is-light:hover,html.theme--catppuccin-latte .button.is-link.is-light.is-hovered{background-color:#dfe9fe;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-link.is-light:active,html.theme--catppuccin-latte .button.is-link.is-light.is-active{background-color:#d3e1fd;border-color:transparent;color:#0a52e1}html.theme--catppuccin-latte .button.is-info{background-color:#179299;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:hover,html.theme--catppuccin-latte .button.is-info.is-hovered{background-color:#15878e;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:focus,html.theme--catppuccin-latte .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info:focus:not(:active),html.theme--catppuccin-latte .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .button.is-info:active,html.theme--catppuccin-latte .button.is-info.is-active{background-color:#147d83;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info{background-color:#179299;border-color:#179299;box-shadow:none}html.theme--catppuccin-latte .button.is-info.is-inverted{background-color:#fff;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted:hover,html.theme--catppuccin-latte .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#179299}html.theme--catppuccin-latte .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-info.is-outlined{background-color:transparent;border-color:#179299;color:#179299}html.theme--catppuccin-latte .button.is-info.is-outlined:hover,html.theme--catppuccin-latte .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-info.is-outlined:focus,html.theme--catppuccin-latte .button.is-info.is-outlined.is-focused{background-color:#179299;border-color:#179299;color:#fff}html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #179299 #179299 !important}html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-outlined{background-color:transparent;border-color:#179299;box-shadow:none;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#179299}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #179299 #179299 !important}html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-info.is-light{background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .button.is-info.is-light:hover,html.theme--catppuccin-latte .button.is-info.is-light.is-hovered{background-color:#e2f9fb;border-color:transparent;color:#1cb2ba}html.theme--catppuccin-latte .button.is-info.is-light:active,html.theme--catppuccin-latte .button.is-info.is-light.is-active{background-color:#d7f7f9;border-color:transparent;color:#1cb2ba}html.theme--catppuccin-latte .button.is-success{background-color:#40a02b;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:hover,html.theme--catppuccin-latte .button.is-success.is-hovered{background-color:#3c9628;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:focus,html.theme--catppuccin-latte .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success:focus:not(:active),html.theme--catppuccin-latte .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .button.is-success:active,html.theme--catppuccin-latte .button.is-success.is-active{background-color:#388c26;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success{background-color:#40a02b;border-color:#40a02b;box-shadow:none}html.theme--catppuccin-latte .button.is-success.is-inverted{background-color:#fff;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted:hover,html.theme--catppuccin-latte .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-success.is-outlined{background-color:transparent;border-color:#40a02b;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-outlined:hover,html.theme--catppuccin-latte .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-success.is-outlined:focus,html.theme--catppuccin-latte .button.is-success.is-outlined.is-focused{background-color:#40a02b;border-color:#40a02b;color:#fff}html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #40a02b #40a02b !important}html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-outlined{background-color:transparent;border-color:#40a02b;box-shadow:none;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#40a02b}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #40a02b #40a02b !important}html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-success.is-light{background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .button.is-success.is-light:hover,html.theme--catppuccin-latte .button.is-success.is-light.is-hovered{background-color:#e8f8e5;border-color:transparent;color:#40a12b}html.theme--catppuccin-latte .button.is-success.is-light:active,html.theme--catppuccin-latte .button.is-success.is-light.is-active{background-color:#e0f5db;border-color:transparent;color:#40a12b}html.theme--catppuccin-latte .button.is-warning{background-color:#df8e1d;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:hover,html.theme--catppuccin-latte .button.is-warning.is-hovered{background-color:#d4871c;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:focus,html.theme--catppuccin-latte .button.is-warning.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning:focus:not(:active),html.theme--catppuccin-latte .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .button.is-warning:active,html.theme--catppuccin-latte .button.is-warning.is-active{background-color:#c8801a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning{background-color:#df8e1d;border-color:#df8e1d;box-shadow:none}html.theme--catppuccin-latte .button.is-warning.is-inverted{background-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted:hover,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-warning.is-outlined{background-color:transparent;border-color:#df8e1d;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-outlined:hover,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-warning.is-outlined:focus,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-focused{background-color:#df8e1d;border-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #df8e1d #df8e1d !important}html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-outlined{background-color:transparent;border-color:#df8e1d;box-shadow:none;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #df8e1d #df8e1d !important}html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-warning.is-light{background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .button.is-warning.is-light:hover,html.theme--catppuccin-latte .button.is-warning.is-light.is-hovered{background-color:#fbf1e2;border-color:transparent;color:#9e6515}html.theme--catppuccin-latte .button.is-warning.is-light:active,html.theme--catppuccin-latte .button.is-warning.is-light.is-active{background-color:#faebd6;border-color:transparent;color:#9e6515}html.theme--catppuccin-latte .button.is-danger{background-color:#d20f39;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:hover,html.theme--catppuccin-latte .button.is-danger.is-hovered{background-color:#c60e36;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:focus,html.theme--catppuccin-latte .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger:focus:not(:active),html.theme--catppuccin-latte .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .button.is-danger:active,html.theme--catppuccin-latte .button.is-danger.is-active{background-color:#ba0d33;border-color:transparent;color:#fff}html.theme--catppuccin-latte .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger{background-color:#d20f39;border-color:#d20f39;box-shadow:none}html.theme--catppuccin-latte .button.is-danger.is-inverted{background-color:#fff;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted:hover,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-latte .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-danger.is-outlined{background-color:transparent;border-color:#d20f39;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-outlined:hover,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-danger.is-outlined:focus,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-focused{background-color:#d20f39;border-color:#d20f39;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #d20f39 #d20f39 !important}html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-latte .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-outlined{background-color:transparent;border-color:#d20f39;box-shadow:none;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#d20f39}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #d20f39 #d20f39 !important}html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-latte .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-latte .button.is-danger.is-light{background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .button.is-danger.is-light:hover,html.theme--catppuccin-latte .button.is-danger.is-light.is-hovered{background-color:#fde0e6;border-color:transparent;color:#e9113f}html.theme--catppuccin-latte .button.is-danger.is-light:active,html.theme--catppuccin-latte .button.is-danger.is-light.is-active{background-color:#fcd4dd;border-color:transparent;color:#e9113f}html.theme--catppuccin-latte .button.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-latte .button.is-small:not(.is-rounded),html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-latte .button.is-normal{font-size:1rem}html.theme--catppuccin-latte .button.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .button.is-large{font-size:1.5rem}html.theme--catppuccin-latte .button[disabled],fieldset[disabled] html.theme--catppuccin-latte .button{background-color:#9ca0b0;border-color:#acb0be;box-shadow:none;opacity:.5}html.theme--catppuccin-latte .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-latte .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-latte .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-latte .button.is-static{background-color:#e6e9ef;border-color:#acb0be;color:#8c8fa1;box-shadow:none;pointer-events:none}html.theme--catppuccin-latte .button.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-latte .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-latte .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-latte .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-latte .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-latte .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-latte .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-latte .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-latte .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-latte .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-latte .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-latte .buttons.has-addons .button:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-latte .buttons.has-addons .button:focus,html.theme--catppuccin-latte .buttons.has-addons .button.is-focused,html.theme--catppuccin-latte .buttons.has-addons .button:active,html.theme--catppuccin-latte .buttons.has-addons .button.is-active,html.theme--catppuccin-latte .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-latte .buttons.has-addons .button:focus:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-latte .buttons.has-addons .button:active:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-latte .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-latte .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .buttons.is-centered{justify-content:center}html.theme--catppuccin-latte .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-latte .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-latte .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .button.is-responsive.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-latte .button.is-responsive,html.theme--catppuccin-latte .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-latte .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-latte .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .button.is-responsive.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-latte .button.is-responsive,html.theme--catppuccin-latte .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-latte .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-latte .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-latte .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-latte .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-latte .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-latte .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-latte .content li+li{margin-top:0.25em}html.theme--catppuccin-latte .content p:not(:last-child),html.theme--catppuccin-latte .content dl:not(:last-child),html.theme--catppuccin-latte .content ol:not(:last-child),html.theme--catppuccin-latte .content ul:not(:last-child),html.theme--catppuccin-latte .content blockquote:not(:last-child),html.theme--catppuccin-latte .content pre:not(:last-child),html.theme--catppuccin-latte .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-latte .content h1,html.theme--catppuccin-latte .content h2,html.theme--catppuccin-latte .content h3,html.theme--catppuccin-latte .content h4,html.theme--catppuccin-latte .content h5,html.theme--catppuccin-latte .content h6{color:#4c4f69;font-weight:600;line-height:1.125}html.theme--catppuccin-latte .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-latte .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-latte .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-latte .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-latte .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-latte .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-latte .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-latte .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-latte .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-latte .content blockquote{background-color:#e6e9ef;border-left:5px solid #acb0be;padding:1.25em 1.5em}html.theme--catppuccin-latte .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-latte .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-latte .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-latte .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-latte .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-latte .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-latte .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-latte .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-latte .content ul ul ul{list-style-type:square}html.theme--catppuccin-latte .content dd{margin-left:2em}html.theme--catppuccin-latte .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-latte .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-latte .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-latte .content figure img{display:inline-block}html.theme--catppuccin-latte .content figure figcaption{font-style:italic}html.theme--catppuccin-latte .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-latte .content sup,html.theme--catppuccin-latte .content sub{font-size:75%}html.theme--catppuccin-latte .content table{width:100%}html.theme--catppuccin-latte .content table td,html.theme--catppuccin-latte .content table th{border:1px solid #acb0be;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-latte .content table th{color:#41445a}html.theme--catppuccin-latte .content table th:not([align]){text-align:inherit}html.theme--catppuccin-latte .content table thead td,html.theme--catppuccin-latte .content table thead th{border-width:0 0 2px;color:#41445a}html.theme--catppuccin-latte .content table tfoot td,html.theme--catppuccin-latte .content table tfoot th{border-width:2px 0 0;color:#41445a}html.theme--catppuccin-latte .content table tbody tr:last-child td,html.theme--catppuccin-latte .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-latte .content .tabs li+li{margin-top:0}html.theme--catppuccin-latte .content.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-latte .content.is-normal{font-size:1rem}html.theme--catppuccin-latte .content.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .content.is-large{font-size:1.5rem}html.theme--catppuccin-latte .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-latte .icon.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-latte .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-latte .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-latte .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-latte .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-latte .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-latte div.icon-text{display:flex}html.theme--catppuccin-latte .image,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-latte .image img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-latte .image img.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-latte .image.is-fullwidth,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-latte .image.is-square img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-latte .image.is-square .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-latte .image.is-1by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-latte .image.is-1by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-latte .image.is-5by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-latte .image.is-5by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-latte .image.is-4by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-latte .image.is-4by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-latte .image.is-3by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-latte .image.is-5by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-latte .image.is-5by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-latte .image.is-16by9 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-latte .image.is-16by9 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-latte .image.is-2by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-latte .image.is-2by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-latte .image.is-3by1 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-latte .image.is-3by1 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-latte .image.is-4by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-latte .image.is-4by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-latte .image.is-3by4 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-latte .image.is-3by4 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-latte .image.is-2by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-latte .image.is-2by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-latte .image.is-3by5 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-latte .image.is-3by5 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-latte .image.is-9by16 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-latte .image.is-9by16 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-latte .image.is-1by2 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-latte .image.is-1by2 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-latte .image.is-1by3 img,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-latte .image.is-1by3 .has-ratio,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-latte .image.is-square,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-latte .image.is-1by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-latte .image.is-5by4,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-latte .image.is-4by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-latte .image.is-3by2,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-latte .image.is-5by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-latte .image.is-16by9,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-latte .image.is-2by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-latte .image.is-3by1,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-latte .image.is-4by5,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-latte .image.is-3by4,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-latte .image.is-2by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-latte .image.is-3by5,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-latte .image.is-9by16,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-latte .image.is-1by2,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-latte .image.is-1by3,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-latte .image.is-16x16,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-latte .image.is-24x24,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-latte .image.is-32x32,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-latte .image.is-48x48,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-latte .image.is-64x64,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-latte .image.is-96x96,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-latte .image.is-128x128,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-latte .notification{background-color:#e6e9ef;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-latte .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-latte .notification strong{color:currentColor}html.theme--catppuccin-latte .notification code,html.theme--catppuccin-latte .notification pre{background:#fff}html.theme--catppuccin-latte .notification pre code{background:transparent}html.theme--catppuccin-latte .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-latte .notification .title,html.theme--catppuccin-latte .notification .subtitle,html.theme--catppuccin-latte .notification .content{color:currentColor}html.theme--catppuccin-latte .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .notification.is-dark,html.theme--catppuccin-latte .content kbd.notification{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .notification.is-primary,html.theme--catppuccin-latte .docstring>section>a.notification.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .notification.is-primary.is-light,html.theme--catppuccin-latte .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .notification.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .notification.is-link.is-light{background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .notification.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .notification.is-info.is-light{background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .notification.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .notification.is-success.is-light{background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .notification.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .notification.is-warning.is-light{background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .notification.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .notification.is-danger.is-light{background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-latte .progress::-webkit-progress-bar{background-color:#bcc0cc}html.theme--catppuccin-latte .progress::-webkit-progress-value{background-color:#8c8fa1}html.theme--catppuccin-latte .progress::-moz-progress-bar{background-color:#8c8fa1}html.theme--catppuccin-latte .progress::-ms-fill{background-color:#8c8fa1;border:none}html.theme--catppuccin-latte .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-latte .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-latte .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-latte .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-latte .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-latte .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-latte .content kbd.progress::-webkit-progress-value{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-latte .content kbd.progress::-moz-progress-bar{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark::-ms-fill,html.theme--catppuccin-latte .content kbd.progress::-ms-fill{background-color:#ccd0da}html.theme--catppuccin-latte .progress.is-dark:indeterminate,html.theme--catppuccin-latte .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #ccd0da 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary::-ms-fill,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-primary:indeterminate,html.theme--catppuccin-latte .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #1e66f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-link::-webkit-progress-value{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link::-moz-progress-bar{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link::-ms-fill{background-color:#1e66f5}html.theme--catppuccin-latte .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1e66f5 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-info::-webkit-progress-value{background-color:#179299}html.theme--catppuccin-latte .progress.is-info::-moz-progress-bar{background-color:#179299}html.theme--catppuccin-latte .progress.is-info::-ms-fill{background-color:#179299}html.theme--catppuccin-latte .progress.is-info:indeterminate{background-image:linear-gradient(to right, #179299 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-success::-webkit-progress-value{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success::-moz-progress-bar{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success::-ms-fill{background-color:#40a02b}html.theme--catppuccin-latte .progress.is-success:indeterminate{background-image:linear-gradient(to right, #40a02b 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-warning::-webkit-progress-value{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning::-moz-progress-bar{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning::-ms-fill{background-color:#df8e1d}html.theme--catppuccin-latte .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #df8e1d 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress.is-danger::-webkit-progress-value{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger::-moz-progress-bar{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger::-ms-fill{background-color:#d20f39}html.theme--catppuccin-latte .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #d20f39 30%, #bcc0cc 30%)}html.theme--catppuccin-latte .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#bcc0cc;background-image:linear-gradient(to right, #4c4f69 30%, #bcc0cc 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-latte .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-latte .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-latte .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-latte .progress.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-latte .progress.is-medium{height:1.25rem}html.theme--catppuccin-latte .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-latte .table{background-color:#bcc0cc;color:#4c4f69}html.theme--catppuccin-latte .table td,html.theme--catppuccin-latte .table th{border:1px solid #acb0be;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-latte .table td.is-white,html.theme--catppuccin-latte .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .table td.is-black,html.theme--catppuccin-latte .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .table td.is-light,html.theme--catppuccin-latte .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .table td.is-dark,html.theme--catppuccin-latte .table th.is-dark{background-color:#ccd0da;border-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .table td.is-primary,html.theme--catppuccin-latte .table th.is-primary{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-link,html.theme--catppuccin-latte .table th.is-link{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-info,html.theme--catppuccin-latte .table th.is-info{background-color:#179299;border-color:#179299;color:#fff}html.theme--catppuccin-latte .table td.is-success,html.theme--catppuccin-latte .table th.is-success{background-color:#40a02b;border-color:#40a02b;color:#fff}html.theme--catppuccin-latte .table td.is-warning,html.theme--catppuccin-latte .table th.is-warning{background-color:#df8e1d;border-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .table td.is-danger,html.theme--catppuccin-latte .table th.is-danger{background-color:#d20f39;border-color:#d20f39;color:#fff}html.theme--catppuccin-latte .table td.is-narrow,html.theme--catppuccin-latte .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-latte .table td.is-selected,html.theme--catppuccin-latte .table th.is-selected{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table td.is-selected a,html.theme--catppuccin-latte .table td.is-selected strong,html.theme--catppuccin-latte .table th.is-selected a,html.theme--catppuccin-latte .table th.is-selected strong{color:currentColor}html.theme--catppuccin-latte .table td.is-vcentered,html.theme--catppuccin-latte .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-latte .table th{color:#41445a}html.theme--catppuccin-latte .table th:not([align]){text-align:left}html.theme--catppuccin-latte .table tr.is-selected{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .table tr.is-selected a,html.theme--catppuccin-latte .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-latte .table tr.is-selected td,html.theme--catppuccin-latte .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-latte .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table thead td,html.theme--catppuccin-latte .table thead th{border-width:0 0 2px;color:#41445a}html.theme--catppuccin-latte .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table tfoot td,html.theme--catppuccin-latte .table tfoot th{border-width:2px 0 0;color:#41445a}html.theme--catppuccin-latte .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .table tbody tr:last-child td,html.theme--catppuccin-latte .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-latte .table.is-bordered td,html.theme--catppuccin-latte .table.is-bordered th{border-width:1px}html.theme--catppuccin-latte .table.is-bordered tr:last-child td,html.theme--catppuccin-latte .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-latte .table.is-fullwidth{width:100%}html.theme--catppuccin-latte .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#ccd0da}html.theme--catppuccin-latte .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#ccd0da}html.theme--catppuccin-latte .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#d2d5de}html.theme--catppuccin-latte .table.is-narrow td,html.theme--catppuccin-latte .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-latte .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#ccd0da}html.theme--catppuccin-latte .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-latte .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .tags .tag,html.theme--catppuccin-latte .tags .content kbd,html.theme--catppuccin-latte .content .tags kbd,html.theme--catppuccin-latte .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-latte .tags .tag:not(:last-child),html.theme--catppuccin-latte .tags .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags kbd:not(:last-child),html.theme--catppuccin-latte .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-latte .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-latte .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-latte .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-latte .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-latte .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-latte .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-latte .tags.is-centered{justify-content:center}html.theme--catppuccin-latte .tags.is-centered .tag,html.theme--catppuccin-latte .tags.is-centered .content kbd,html.theme--catppuccin-latte .content .tags.is-centered kbd,html.theme--catppuccin-latte .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-latte .tags.is-right{justify-content:flex-end}html.theme--catppuccin-latte .tags.is-right .tag:not(:first-child),html.theme--catppuccin-latte .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-latte .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-latte .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-latte .tags.is-right .tag:not(:last-child),html.theme--catppuccin-latte .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-latte .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-latte .tags.has-addons .tag,html.theme--catppuccin-latte .tags.has-addons .content kbd,html.theme--catppuccin-latte .content .tags.has-addons kbd,html.theme--catppuccin-latte .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-latte .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-latte .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-latte .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-latte .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-latte .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-latte .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-latte .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-latte .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-latte .tag:not(body),html.theme--catppuccin-latte .content kbd:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#e6e9ef;border-radius:.4em;color:#4c4f69;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-latte .tag:not(body) .delete,html.theme--catppuccin-latte .content kbd:not(body) .delete,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-latte .tag.is-white:not(body),html.theme--catppuccin-latte .content kbd.is-white:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .tag.is-black:not(body),html.theme--catppuccin-latte .content kbd.is-black:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .tag.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .tag.is-dark:not(body),html.theme--catppuccin-latte .content kbd:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-latte .content .docstring>section>kbd:not(body){background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .tag.is-primary:not(body),html.theme--catppuccin-latte .content kbd.is-primary:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .tag.is-primary.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .tag.is-link:not(body),html.theme--catppuccin-latte .content kbd.is-link:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .tag.is-link.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-link.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ebf2fe;color:#0a52e1}html.theme--catppuccin-latte .tag.is-info:not(body),html.theme--catppuccin-latte .content kbd.is-info:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#179299;color:#fff}html.theme--catppuccin-latte .tag.is-info.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-info.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#edfcfc;color:#1cb2ba}html.theme--catppuccin-latte .tag.is-success:not(body),html.theme--catppuccin-latte .content kbd.is-success:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .tag.is-success.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-success.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f1fbef;color:#40a12b}html.theme--catppuccin-latte .tag.is-warning:not(body),html.theme--catppuccin-latte .content kbd.is-warning:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .tag.is-warning.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fdf6ed;color:#9e6515}html.theme--catppuccin-latte .tag.is-danger:not(body),html.theme--catppuccin-latte .content kbd.is-danger:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .tag.is-danger.is-light:not(body),html.theme--catppuccin-latte .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#feecf0;color:#e9113f}html.theme--catppuccin-latte .tag.is-normal:not(body),html.theme--catppuccin-latte .content kbd.is-normal:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-latte .tag.is-medium:not(body),html.theme--catppuccin-latte .content kbd.is-medium:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-latte .tag.is-large:not(body),html.theme--catppuccin-latte .content kbd.is-large:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-latte .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-latte .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-latte .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-latte .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-latte .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-latte .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-latte .tag.is-delete:not(body),html.theme--catppuccin-latte .content kbd.is-delete:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-latte .tag.is-delete:not(body)::before,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::before,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-latte .tag.is-delete:not(body)::after,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::after,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-latte .tag.is-delete:not(body)::before,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::before,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-latte .tag.is-delete:not(body)::after,html.theme--catppuccin-latte .content kbd.is-delete:not(body)::after,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-latte .tag.is-delete:not(body):hover,html.theme--catppuccin-latte .content kbd.is-delete:not(body):hover,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-latte .tag.is-delete:not(body):focus,html.theme--catppuccin-latte .content kbd.is-delete:not(body):focus,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#d6dbe5}html.theme--catppuccin-latte .tag.is-delete:not(body):active,html.theme--catppuccin-latte .content kbd.is-delete:not(body):active,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#c7cedb}html.theme--catppuccin-latte .tag.is-rounded:not(body),html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-latte .content kbd.is-rounded:not(body),html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-latte a.tag:hover,html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-latte .title,html.theme--catppuccin-latte .subtitle{word-break:break-word}html.theme--catppuccin-latte .title em,html.theme--catppuccin-latte .title span,html.theme--catppuccin-latte .subtitle em,html.theme--catppuccin-latte .subtitle span{font-weight:inherit}html.theme--catppuccin-latte .title sub,html.theme--catppuccin-latte .subtitle sub{font-size:.75em}html.theme--catppuccin-latte .title sup,html.theme--catppuccin-latte .subtitle sup{font-size:.75em}html.theme--catppuccin-latte .title .tag,html.theme--catppuccin-latte .title .content kbd,html.theme--catppuccin-latte .content .title kbd,html.theme--catppuccin-latte .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-latte .subtitle .tag,html.theme--catppuccin-latte .subtitle .content kbd,html.theme--catppuccin-latte .content .subtitle kbd,html.theme--catppuccin-latte .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-latte .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-latte .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-latte .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-latte .title.is-1{font-size:3rem}html.theme--catppuccin-latte .title.is-2{font-size:2.5rem}html.theme--catppuccin-latte .title.is-3{font-size:2rem}html.theme--catppuccin-latte .title.is-4{font-size:1.5rem}html.theme--catppuccin-latte .title.is-5{font-size:1.25rem}html.theme--catppuccin-latte .title.is-6{font-size:1rem}html.theme--catppuccin-latte .title.is-7{font-size:.75rem}html.theme--catppuccin-latte .subtitle{color:#9ca0b0;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-latte .subtitle strong{color:#9ca0b0;font-weight:600}html.theme--catppuccin-latte .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-latte .subtitle.is-1{font-size:3rem}html.theme--catppuccin-latte .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-latte .subtitle.is-3{font-size:2rem}html.theme--catppuccin-latte .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-latte .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-latte .subtitle.is-6{font-size:1rem}html.theme--catppuccin-latte .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-latte .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-latte .number{align-items:center;background-color:#e6e9ef;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{background-color:#eff1f5;border-color:#acb0be;border-radius:.4em;color:#8c8fa1}html.theme--catppuccin-latte .select select::-moz-placeholder,html.theme--catppuccin-latte .textarea::-moz-placeholder,html.theme--catppuccin-latte .input::-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-latte .select select::-webkit-input-placeholder,html.theme--catppuccin-latte .textarea::-webkit-input-placeholder,html.theme--catppuccin-latte .input::-webkit-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:-moz-placeholder,html.theme--catppuccin-latte .textarea:-moz-placeholder,html.theme--catppuccin-latte .input:-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:-ms-input-placeholder,html.theme--catppuccin-latte .textarea:-ms-input-placeholder,html.theme--catppuccin-latte .input:-ms-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-latte .select select:hover,html.theme--catppuccin-latte .textarea:hover,html.theme--catppuccin-latte .input:hover,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-latte .select select.is-hovered,html.theme--catppuccin-latte .is-hovered.textarea,html.theme--catppuccin-latte .is-hovered.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#9ca0b0}html.theme--catppuccin-latte .select select:focus,html.theme--catppuccin-latte .textarea:focus,html.theme--catppuccin-latte .input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-latte .select select.is-focused,html.theme--catppuccin-latte .is-focused.textarea,html.theme--catppuccin-latte .is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .select select:active,html.theme--catppuccin-latte .textarea:active,html.theme--catppuccin-latte .input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-latte .select select.is-active,html.theme--catppuccin-latte .is-active.textarea,html.theme--catppuccin-latte .is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1e66f5;box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select select[disabled],html.theme--catppuccin-latte .textarea[disabled],html.theme--catppuccin-latte .input[disabled],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-latte .select select,fieldset[disabled] html.theme--catppuccin-latte .textarea,fieldset[disabled] html.theme--catppuccin-latte .input,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{background-color:#9ca0b0;border-color:#e6e9ef;box-shadow:none;color:#616587}html.theme--catppuccin-latte .select select[disabled]::-moz-placeholder,html.theme--catppuccin-latte .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-latte .input[disabled]::-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]:-moz-placeholder,html.theme--catppuccin-latte .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-latte .input[disabled]:-moz-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(97,101,135,0.3)}html.theme--catppuccin-latte .textarea,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-latte .textarea[readonly],html.theme--catppuccin-latte .input[readonly],html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-latte .is-white.textarea,html.theme--catppuccin-latte .is-white.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-latte .is-white.textarea:focus,html.theme--catppuccin-latte .is-white.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-latte .is-white.is-focused.textarea,html.theme--catppuccin-latte .is-white.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-white.textarea:active,html.theme--catppuccin-latte .is-white.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-latte .is-white.is-active.textarea,html.theme--catppuccin-latte .is-white.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .is-black.textarea,html.theme--catppuccin-latte .is-black.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-latte .is-black.textarea:focus,html.theme--catppuccin-latte .is-black.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-latte .is-black.is-focused.textarea,html.theme--catppuccin-latte .is-black.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-black.textarea:active,html.theme--catppuccin-latte .is-black.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-latte .is-black.is-active.textarea,html.theme--catppuccin-latte .is-black.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .is-light.textarea,html.theme--catppuccin-latte .is-light.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-latte .is-light.textarea:focus,html.theme--catppuccin-latte .is-light.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-latte .is-light.is-focused.textarea,html.theme--catppuccin-latte .is-light.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-light.textarea:active,html.theme--catppuccin-latte .is-light.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-latte .is-light.is-active.textarea,html.theme--catppuccin-latte .is-light.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .is-dark.textarea,html.theme--catppuccin-latte .content kbd.textarea,html.theme--catppuccin-latte .is-dark.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-latte .content kbd.input{border-color:#ccd0da}html.theme--catppuccin-latte .is-dark.textarea:focus,html.theme--catppuccin-latte .content kbd.textarea:focus,html.theme--catppuccin-latte .is-dark.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-latte .content kbd.input:focus,html.theme--catppuccin-latte .is-dark.is-focused.textarea,html.theme--catppuccin-latte .content kbd.is-focused.textarea,html.theme--catppuccin-latte .is-dark.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .content kbd.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-dark.textarea:active,html.theme--catppuccin-latte .content kbd.textarea:active,html.theme--catppuccin-latte .is-dark.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-latte .content kbd.input:active,html.theme--catppuccin-latte .is-dark.is-active.textarea,html.theme--catppuccin-latte .content kbd.is-active.textarea,html.theme--catppuccin-latte .is-dark.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .content kbd.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .is-primary.textarea,html.theme--catppuccin-latte .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-latte .docstring>section>a.input.docs-sourcelink{border-color:#1e66f5}html.theme--catppuccin-latte .is-primary.textarea:focus,html.theme--catppuccin-latte .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-latte .is-primary.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-latte .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-latte .is-primary.is-focused.textarea,html.theme--catppuccin-latte .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-latte .is-primary.textarea:active,html.theme--catppuccin-latte .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-latte .is-primary.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-latte .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-latte .is-primary.is-active.textarea,html.theme--catppuccin-latte .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-latte .is-primary.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-latte .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .is-link.textarea,html.theme--catppuccin-latte .is-link.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1e66f5}html.theme--catppuccin-latte .is-link.textarea:focus,html.theme--catppuccin-latte .is-link.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-latte .is-link.is-focused.textarea,html.theme--catppuccin-latte .is-link.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-link.textarea:active,html.theme--catppuccin-latte .is-link.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-latte .is-link.is-active.textarea,html.theme--catppuccin-latte .is-link.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .is-info.textarea,html.theme--catppuccin-latte .is-info.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#179299}html.theme--catppuccin-latte .is-info.textarea:focus,html.theme--catppuccin-latte .is-info.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-latte .is-info.is-focused.textarea,html.theme--catppuccin-latte .is-info.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-info.textarea:active,html.theme--catppuccin-latte .is-info.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-latte .is-info.is-active.textarea,html.theme--catppuccin-latte .is-info.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .is-success.textarea,html.theme--catppuccin-latte .is-success.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#40a02b}html.theme--catppuccin-latte .is-success.textarea:focus,html.theme--catppuccin-latte .is-success.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-latte .is-success.is-focused.textarea,html.theme--catppuccin-latte .is-success.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-success.textarea:active,html.theme--catppuccin-latte .is-success.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-latte .is-success.is-active.textarea,html.theme--catppuccin-latte .is-success.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .is-warning.textarea,html.theme--catppuccin-latte .is-warning.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#df8e1d}html.theme--catppuccin-latte .is-warning.textarea:focus,html.theme--catppuccin-latte .is-warning.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-latte .is-warning.is-focused.textarea,html.theme--catppuccin-latte .is-warning.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-warning.textarea:active,html.theme--catppuccin-latte .is-warning.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-latte .is-warning.is-active.textarea,html.theme--catppuccin-latte .is-warning.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .is-danger.textarea,html.theme--catppuccin-latte .is-danger.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#d20f39}html.theme--catppuccin-latte .is-danger.textarea:focus,html.theme--catppuccin-latte .is-danger.input:focus,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-latte .is-danger.is-focused.textarea,html.theme--catppuccin-latte .is-danger.is-focused.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-latte .is-danger.textarea:active,html.theme--catppuccin-latte .is-danger.input:active,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-latte .is-danger.is-active.textarea,html.theme--catppuccin-latte .is-danger.is-active.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .is-small.textarea,html.theme--catppuccin-latte .is-small.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-latte .is-medium.textarea,html.theme--catppuccin-latte .is-medium.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .is-large.textarea,html.theme--catppuccin-latte .is-large.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-latte .is-fullwidth.textarea,html.theme--catppuccin-latte .is-fullwidth.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-latte .is-inline.textarea,html.theme--catppuccin-latte .is-inline.input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-latte .input.is-rounded,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-latte .input.is-static,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-latte .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-latte .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-latte .textarea[rows]{height:initial}html.theme--catppuccin-latte .textarea.has-fixed-size{resize:none}html.theme--catppuccin-latte .radio,html.theme--catppuccin-latte .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-latte .radio input,html.theme--catppuccin-latte .checkbox input{cursor:pointer}html.theme--catppuccin-latte .radio:hover,html.theme--catppuccin-latte .checkbox:hover{color:#04a5e5}html.theme--catppuccin-latte .radio[disabled],html.theme--catppuccin-latte .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-latte .radio,fieldset[disabled] html.theme--catppuccin-latte .checkbox,html.theme--catppuccin-latte .radio input[disabled],html.theme--catppuccin-latte .checkbox input[disabled]{color:#616587;cursor:not-allowed}html.theme--catppuccin-latte .radio+.radio{margin-left:.5em}html.theme--catppuccin-latte .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-latte .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading)::after{border-color:#1e66f5;right:1.125em;z-index:4}html.theme--catppuccin-latte .select.is-rounded select,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-latte .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-latte .select select::-ms-expand{display:none}html.theme--catppuccin-latte .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-latte .select select:hover{border-color:#e6e9ef}html.theme--catppuccin-latte .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-latte .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-latte .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-latte .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#04a5e5}html.theme--catppuccin-latte .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-latte .select.is-white select{border-color:#fff}html.theme--catppuccin-latte .select.is-white select:hover,html.theme--catppuccin-latte .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-latte .select.is-white select:focus,html.theme--catppuccin-latte .select.is-white select.is-focused,html.theme--catppuccin-latte .select.is-white select:active,html.theme--catppuccin-latte .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-latte .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-latte .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-latte .select.is-black select:hover,html.theme--catppuccin-latte .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-latte .select.is-black select:focus,html.theme--catppuccin-latte .select.is-black select.is-focused,html.theme--catppuccin-latte .select.is-black select:active,html.theme--catppuccin-latte .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-latte .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-latte .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-latte .select.is-light select:hover,html.theme--catppuccin-latte .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-latte .select.is-light select:focus,html.theme--catppuccin-latte .select.is-light select.is-focused,html.theme--catppuccin-latte .select.is-light select:active,html.theme--catppuccin-latte .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-latte .select.is-dark:not(:hover)::after,html.theme--catppuccin-latte .content kbd.select:not(:hover)::after{border-color:#ccd0da}html.theme--catppuccin-latte .select.is-dark select,html.theme--catppuccin-latte .content kbd.select select{border-color:#ccd0da}html.theme--catppuccin-latte .select.is-dark select:hover,html.theme--catppuccin-latte .content kbd.select select:hover,html.theme--catppuccin-latte .select.is-dark select.is-hovered,html.theme--catppuccin-latte .content kbd.select select.is-hovered{border-color:#bdc2cf}html.theme--catppuccin-latte .select.is-dark select:focus,html.theme--catppuccin-latte .content kbd.select select:focus,html.theme--catppuccin-latte .select.is-dark select.is-focused,html.theme--catppuccin-latte .content kbd.select select.is-focused,html.theme--catppuccin-latte .select.is-dark select:active,html.theme--catppuccin-latte .content kbd.select select:active,html.theme--catppuccin-latte .select.is-dark select.is-active,html.theme--catppuccin-latte .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(204,208,218,0.25)}html.theme--catppuccin-latte .select.is-primary:not(:hover)::after,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-primary select,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-primary select:hover,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-latte .select.is-primary select.is-hovered,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#0b57ef}html.theme--catppuccin-latte .select.is-primary select:focus,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-latte .select.is-primary select.is-focused,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-latte .select.is-primary select:active,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-latte .select.is-primary select.is-active,html.theme--catppuccin-latte .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select.is-link:not(:hover)::after{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-link select{border-color:#1e66f5}html.theme--catppuccin-latte .select.is-link select:hover,html.theme--catppuccin-latte .select.is-link select.is-hovered{border-color:#0b57ef}html.theme--catppuccin-latte .select.is-link select:focus,html.theme--catppuccin-latte .select.is-link select.is-focused,html.theme--catppuccin-latte .select.is-link select:active,html.theme--catppuccin-latte .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(30,102,245,0.25)}html.theme--catppuccin-latte .select.is-info:not(:hover)::after{border-color:#179299}html.theme--catppuccin-latte .select.is-info select{border-color:#179299}html.theme--catppuccin-latte .select.is-info select:hover,html.theme--catppuccin-latte .select.is-info select.is-hovered{border-color:#147d83}html.theme--catppuccin-latte .select.is-info select:focus,html.theme--catppuccin-latte .select.is-info select.is-focused,html.theme--catppuccin-latte .select.is-info select:active,html.theme--catppuccin-latte .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(23,146,153,0.25)}html.theme--catppuccin-latte .select.is-success:not(:hover)::after{border-color:#40a02b}html.theme--catppuccin-latte .select.is-success select{border-color:#40a02b}html.theme--catppuccin-latte .select.is-success select:hover,html.theme--catppuccin-latte .select.is-success select.is-hovered{border-color:#388c26}html.theme--catppuccin-latte .select.is-success select:focus,html.theme--catppuccin-latte .select.is-success select.is-focused,html.theme--catppuccin-latte .select.is-success select:active,html.theme--catppuccin-latte .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(64,160,43,0.25)}html.theme--catppuccin-latte .select.is-warning:not(:hover)::after{border-color:#df8e1d}html.theme--catppuccin-latte .select.is-warning select{border-color:#df8e1d}html.theme--catppuccin-latte .select.is-warning select:hover,html.theme--catppuccin-latte .select.is-warning select.is-hovered{border-color:#c8801a}html.theme--catppuccin-latte .select.is-warning select:focus,html.theme--catppuccin-latte .select.is-warning select.is-focused,html.theme--catppuccin-latte .select.is-warning select:active,html.theme--catppuccin-latte .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(223,142,29,0.25)}html.theme--catppuccin-latte .select.is-danger:not(:hover)::after{border-color:#d20f39}html.theme--catppuccin-latte .select.is-danger select{border-color:#d20f39}html.theme--catppuccin-latte .select.is-danger select:hover,html.theme--catppuccin-latte .select.is-danger select.is-hovered{border-color:#ba0d33}html.theme--catppuccin-latte .select.is-danger select:focus,html.theme--catppuccin-latte .select.is-danger select.is-focused,html.theme--catppuccin-latte .select.is-danger select:active,html.theme--catppuccin-latte .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(210,15,57,0.25)}html.theme--catppuccin-latte .select.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-latte .select.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .select.is-large{font-size:1.5rem}html.theme--catppuccin-latte .select.is-disabled::after{border-color:#616587 !important;opacity:0.5}html.theme--catppuccin-latte .select.is-fullwidth{width:100%}html.theme--catppuccin-latte .select.is-fullwidth select{width:100%}html.theme--catppuccin-latte .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-latte .select.is-loading.is-small:after,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-latte .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-latte .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-latte .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-latte .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:hover .file-cta,html.theme--catppuccin-latte .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:focus .file-cta,html.theme--catppuccin-latte .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-latte .file.is-white:active .file-cta,html.theme--catppuccin-latte .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-latte .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-black:hover .file-cta,html.theme--catppuccin-latte .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-black:focus .file-cta,html.theme--catppuccin-latte .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-latte .file.is-black:active .file-cta,html.theme--catppuccin-latte .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:hover .file-cta,html.theme--catppuccin-latte .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:focus .file-cta,html.theme--catppuccin-latte .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-light:active .file-cta,html.theme--catppuccin-latte .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark .file-cta,html.theme--catppuccin-latte .content kbd.file .file-cta{background-color:#ccd0da;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:hover .file-cta,html.theme--catppuccin-latte .content kbd.file:hover .file-cta,html.theme--catppuccin-latte .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-latte .content kbd.file.is-hovered .file-cta{background-color:#c5c9d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:focus .file-cta,html.theme--catppuccin-latte .content kbd.file:focus .file-cta,html.theme--catppuccin-latte .file.is-dark.is-focused .file-cta,html.theme--catppuccin-latte .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(204,208,218,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-dark:active .file-cta,html.theme--catppuccin-latte .content kbd.file:active .file-cta,html.theme--catppuccin-latte .file.is-dark.is-active .file-cta,html.theme--catppuccin-latte .content kbd.file.is-active .file-cta{background-color:#bdc2cf;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .file.is-primary .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-primary:hover .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-latte .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-primary:focus .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-latte .file.is-primary.is-focused .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(30,102,245,0.25);color:#fff}html.theme--catppuccin-latte .file.is-primary:active .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-latte .file.is-primary.is-active .file-cta,html.theme--catppuccin-latte .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link .file-cta{background-color:#1e66f5;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link:hover .file-cta,html.theme--catppuccin-latte .file.is-link.is-hovered .file-cta{background-color:#125ef4;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-link:focus .file-cta,html.theme--catppuccin-latte .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(30,102,245,0.25);color:#fff}html.theme--catppuccin-latte .file.is-link:active .file-cta,html.theme--catppuccin-latte .file.is-link.is-active .file-cta{background-color:#0b57ef;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info .file-cta{background-color:#179299;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info:hover .file-cta,html.theme--catppuccin-latte .file.is-info.is-hovered .file-cta{background-color:#15878e;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-info:focus .file-cta,html.theme--catppuccin-latte .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(23,146,153,0.25);color:#fff}html.theme--catppuccin-latte .file.is-info:active .file-cta,html.theme--catppuccin-latte .file.is-info.is-active .file-cta{background-color:#147d83;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success .file-cta{background-color:#40a02b;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success:hover .file-cta,html.theme--catppuccin-latte .file.is-success.is-hovered .file-cta{background-color:#3c9628;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-success:focus .file-cta,html.theme--catppuccin-latte .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(64,160,43,0.25);color:#fff}html.theme--catppuccin-latte .file.is-success:active .file-cta,html.theme--catppuccin-latte .file.is-success.is-active .file-cta{background-color:#388c26;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning .file-cta{background-color:#df8e1d;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning:hover .file-cta,html.theme--catppuccin-latte .file.is-warning.is-hovered .file-cta{background-color:#d4871c;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-warning:focus .file-cta,html.theme--catppuccin-latte .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(223,142,29,0.25);color:#fff}html.theme--catppuccin-latte .file.is-warning:active .file-cta,html.theme--catppuccin-latte .file.is-warning.is-active .file-cta{background-color:#c8801a;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger .file-cta{background-color:#d20f39;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger:hover .file-cta,html.theme--catppuccin-latte .file.is-danger.is-hovered .file-cta{background-color:#c60e36;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-danger:focus .file-cta,html.theme--catppuccin-latte .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(210,15,57,0.25);color:#fff}html.theme--catppuccin-latte .file.is-danger:active .file-cta,html.theme--catppuccin-latte .file.is-danger.is-active .file-cta{background-color:#ba0d33;border-color:transparent;color:#fff}html.theme--catppuccin-latte .file.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-latte .file.is-normal{font-size:1rem}html.theme--catppuccin-latte .file.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-latte .file.is-large{font-size:1.5rem}html.theme--catppuccin-latte .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-latte .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-latte .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-latte .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-latte .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-latte .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-latte .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-latte .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-latte .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-latte .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-latte .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-latte .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-latte .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-latte .file.is-centered{justify-content:center}html.theme--catppuccin-latte .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-latte .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-latte .file.is-right{justify-content:flex-end}html.theme--catppuccin-latte .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-latte .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-latte .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-latte .file-label:hover .file-cta{background-color:#c5c9d5;color:#41445a}html.theme--catppuccin-latte .file-label:hover .file-name{border-color:#a5a9b8}html.theme--catppuccin-latte .file-label:active .file-cta{background-color:#bdc2cf;color:#41445a}html.theme--catppuccin-latte .file-label:active .file-name{border-color:#9ea2b3}html.theme--catppuccin-latte .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-latte .file-cta,html.theme--catppuccin-latte .file-name{border-color:#acb0be;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-latte .file-cta{background-color:#ccd0da;color:#4c4f69}html.theme--catppuccin-latte .file-name{border-color:#acb0be;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-latte .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-latte .file-icon .fa{font-size:14px}html.theme--catppuccin-latte .label{color:#41445a;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-latte .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-latte .label.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-latte .label.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .label.is-large{font-size:1.5rem}html.theme--catppuccin-latte .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-latte .help.is-white{color:#fff}html.theme--catppuccin-latte .help.is-black{color:#0a0a0a}html.theme--catppuccin-latte .help.is-light{color:#f5f5f5}html.theme--catppuccin-latte .help.is-dark,html.theme--catppuccin-latte .content kbd.help{color:#ccd0da}html.theme--catppuccin-latte .help.is-primary,html.theme--catppuccin-latte .docstring>section>a.help.docs-sourcelink{color:#1e66f5}html.theme--catppuccin-latte .help.is-link{color:#1e66f5}html.theme--catppuccin-latte .help.is-info{color:#179299}html.theme--catppuccin-latte .help.is-success{color:#40a02b}html.theme--catppuccin-latte .help.is-warning{color:#df8e1d}html.theme--catppuccin-latte .help.is-danger{color:#d20f39}html.theme--catppuccin-latte .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-latte .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-latte .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-latte .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-latte .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-latte .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-latte #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-latte .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-latte .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-latte .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-latte .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-latte .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-latte .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-latte .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-latte .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-latte .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-latte .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field.is-horizontal{display:flex}}html.theme--catppuccin-latte .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-latte .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-latte .field-label.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-latte .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-latte .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-latte .field-body .field{margin-bottom:0}html.theme--catppuccin-latte .field-body>.field{flex-shrink:1}html.theme--catppuccin-latte .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-latte .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-latte .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-latte .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-latte .control.has-icons-right .select:focus~.icon{color:#ccd0da}html.theme--catppuccin-latte .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-latte .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-latte .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-latte .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-latte .control.has-icons-left .icon,html.theme--catppuccin-latte .control.has-icons-right .icon{color:#acb0be;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-latte .control.has-icons-left .input,html.theme--catppuccin-latte .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-latte .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-latte .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-latte .control.has-icons-right .input,html.theme--catppuccin-latte .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-latte .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-latte .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-latte .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-latte .control.is-loading.is-small:after,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-latte .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-latte .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-latte .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-latte .breadcrumb a{align-items:center;color:#1e66f5;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-latte .breadcrumb a:hover{color:#04a5e5}html.theme--catppuccin-latte .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-latte .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-latte .breadcrumb li.is-active a{color:#41445a;cursor:default;pointer-events:none}html.theme--catppuccin-latte .breadcrumb li+li::before{color:#9ca0b0;content:"\0002f"}html.theme--catppuccin-latte .breadcrumb ul,html.theme--catppuccin-latte .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-latte .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-latte .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-latte .breadcrumb.is-centered ol,html.theme--catppuccin-latte .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-latte .breadcrumb.is-right ol,html.theme--catppuccin-latte .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-latte .breadcrumb.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-latte .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-latte .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-latte .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-latte .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-latte .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-latte .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#4c4f69;max-width:100%;position:relative}html.theme--catppuccin-latte .card-footer:first-child,html.theme--catppuccin-latte .card-content:first-child,html.theme--catppuccin-latte .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-latte .card-footer:last-child,html.theme--catppuccin-latte .card-content:last-child,html.theme--catppuccin-latte .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-latte .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-latte .card-header-title{align-items:center;color:#41445a;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-latte .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-latte .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-latte .card-image{display:block;position:relative}html.theme--catppuccin-latte .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-latte .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-latte .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-latte .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-latte .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-latte .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-latte .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-latte .dropdown.is-active .dropdown-menu,html.theme--catppuccin-latte .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-latte .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-latte .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-latte .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-latte .dropdown-content{background-color:#e6e9ef;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-latte .dropdown-item{color:#4c4f69;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-latte a.dropdown-item,html.theme--catppuccin-latte button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-latte a.dropdown-item:hover,html.theme--catppuccin-latte button.dropdown-item:hover{background-color:#e6e9ef;color:#0a0a0a}html.theme--catppuccin-latte a.dropdown-item.is-active,html.theme--catppuccin-latte button.dropdown-item.is-active{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-latte .level{align-items:center;justify-content:space-between}html.theme--catppuccin-latte .level code{border-radius:.4em}html.theme--catppuccin-latte .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-latte .level.is-mobile{display:flex}html.theme--catppuccin-latte .level.is-mobile .level-left,html.theme--catppuccin-latte .level.is-mobile .level-right{display:flex}html.theme--catppuccin-latte .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-latte .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-latte .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level{display:flex}html.theme--catppuccin-latte .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-latte .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-latte .level-item .title,html.theme--catppuccin-latte .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-latte .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-latte .level-left,html.theme--catppuccin-latte .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .level-left .level-item.is-flexible,html.theme--catppuccin-latte .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-left .level-item:not(:last-child),html.theme--catppuccin-latte .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-latte .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-latte .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-left{display:flex}}html.theme--catppuccin-latte .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .level-right{display:flex}}html.theme--catppuccin-latte .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-latte .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-latte .media .media{border-top:1px solid rgba(172,176,190,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-latte .media .media .content:not(:last-child),html.theme--catppuccin-latte .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-latte .media .media .media{padding-top:.5rem}html.theme--catppuccin-latte .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-latte .media+.media{border-top:1px solid rgba(172,176,190,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-latte .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-latte .media-left,html.theme--catppuccin-latte .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .media-left{margin-right:1rem}html.theme--catppuccin-latte .media-right{margin-left:1rem}html.theme--catppuccin-latte .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-latte .media-content{overflow-x:auto}}html.theme--catppuccin-latte .menu{font-size:1rem}html.theme--catppuccin-latte .menu.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-latte .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .menu.is-large{font-size:1.5rem}html.theme--catppuccin-latte .menu-list{line-height:1.25}html.theme--catppuccin-latte .menu-list a{border-radius:3px;color:#4c4f69;display:block;padding:0.5em 0.75em}html.theme--catppuccin-latte .menu-list a:hover{background-color:#e6e9ef;color:#41445a}html.theme--catppuccin-latte .menu-list a.is-active{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .menu-list li ul{border-left:1px solid #acb0be;margin:.75em;padding-left:.75em}html.theme--catppuccin-latte .menu-label{color:#616587;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-latte .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-latte .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-latte .message{background-color:#e6e9ef;border-radius:.4em;font-size:1rem}html.theme--catppuccin-latte .message strong{color:currentColor}html.theme--catppuccin-latte .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-latte .message.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-latte .message.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .message.is-large{font-size:1.5rem}html.theme--catppuccin-latte .message.is-white{background-color:#fff}html.theme--catppuccin-latte .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-latte .message.is-black{background-color:#fafafa}html.theme--catppuccin-latte .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-latte .message.is-light{background-color:#fafafa}html.theme--catppuccin-latte .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-latte .message.is-dark,html.theme--catppuccin-latte .content kbd.message{background-color:#f9fafb}html.theme--catppuccin-latte .message.is-dark .message-header,html.theme--catppuccin-latte .content kbd.message .message-header{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .message.is-dark .message-body,html.theme--catppuccin-latte .content kbd.message .message-body{border-color:#ccd0da}html.theme--catppuccin-latte .message.is-primary,html.theme--catppuccin-latte .docstring>section>a.message.docs-sourcelink{background-color:#ebf2fe}html.theme--catppuccin-latte .message.is-primary .message-header,html.theme--catppuccin-latte .docstring>section>a.message.docs-sourcelink .message-header{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .message.is-primary .message-body,html.theme--catppuccin-latte .docstring>section>a.message.docs-sourcelink .message-body{border-color:#1e66f5;color:#0a52e1}html.theme--catppuccin-latte .message.is-link{background-color:#ebf2fe}html.theme--catppuccin-latte .message.is-link .message-header{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .message.is-link .message-body{border-color:#1e66f5;color:#0a52e1}html.theme--catppuccin-latte .message.is-info{background-color:#edfcfc}html.theme--catppuccin-latte .message.is-info .message-header{background-color:#179299;color:#fff}html.theme--catppuccin-latte .message.is-info .message-body{border-color:#179299;color:#1cb2ba}html.theme--catppuccin-latte .message.is-success{background-color:#f1fbef}html.theme--catppuccin-latte .message.is-success .message-header{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .message.is-success .message-body{border-color:#40a02b;color:#40a12b}html.theme--catppuccin-latte .message.is-warning{background-color:#fdf6ed}html.theme--catppuccin-latte .message.is-warning .message-header{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .message.is-warning .message-body{border-color:#df8e1d;color:#9e6515}html.theme--catppuccin-latte .message.is-danger{background-color:#feecf0}html.theme--catppuccin-latte .message.is-danger .message-header{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .message.is-danger .message-body{border-color:#d20f39;color:#e9113f}html.theme--catppuccin-latte .message-header{align-items:center;background-color:#4c4f69;border-radius:.4em .4em 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-latte .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-latte .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-latte .message-body{border-color:#acb0be;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#4c4f69;padding:1.25em 1.5em}html.theme--catppuccin-latte .message-body code,html.theme--catppuccin-latte .message-body pre{background-color:#fff}html.theme--catppuccin-latte .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-latte .modal.is-active{display:flex}html.theme--catppuccin-latte .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-latte .modal-content,html.theme--catppuccin-latte .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-latte .modal-content,html.theme--catppuccin-latte .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-latte .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-latte .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-latte .modal-card-head,html.theme--catppuccin-latte .modal-card-foot{align-items:center;background-color:#e6e9ef;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-latte .modal-card-head{border-bottom:1px solid #acb0be;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-latte .modal-card-title{color:#4c4f69;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-latte .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #acb0be}html.theme--catppuccin-latte .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-latte .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#eff1f5;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-latte .navbar{background-color:#1e66f5;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-latte .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-latte .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-latte .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-latte .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-latte .navbar.is-dark,html.theme--catppuccin-latte .content kbd.navbar{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-burger,html.theme--catppuccin-latte .content kbd.navbar .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-latte .content kbd.navbar .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#ccd0da;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-latte .navbar.is-primary,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-burger,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5;color:#fff}}html.theme--catppuccin-latte .navbar.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5;color:#fff}}html.theme--catppuccin-latte .navbar.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#179299;color:#fff}}html.theme--catppuccin-latte .navbar.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#40a02b;color:#fff}}html.theme--catppuccin-latte .navbar.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#df8e1d;color:#fff}}html.theme--catppuccin-latte .navbar.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-latte .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#d20f39;color:#fff}}html.theme--catppuccin-latte .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-latte .navbar.has-shadow{box-shadow:0 2px 0 0 #e6e9ef}html.theme--catppuccin-latte .navbar.is-fixed-bottom,html.theme--catppuccin-latte .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #e6e9ef}html.theme--catppuccin-latte .navbar.is-fixed-top{top:0}html.theme--catppuccin-latte html.has-navbar-fixed-top,html.theme--catppuccin-latte body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom,html.theme--catppuccin-latte body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-latte .navbar-brand,html.theme--catppuccin-latte .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-latte .navbar-brand a.navbar-item:focus,html.theme--catppuccin-latte .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-latte .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-latte .navbar-burger{color:#4c4f69;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-latte .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-latte .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-latte .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-latte .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-latte .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-latte .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-latte .navbar-menu{display:none}html.theme--catppuccin-latte .navbar-item,html.theme--catppuccin-latte .navbar-link{color:#4c4f69;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-latte .navbar-item .icon:only-child,html.theme--catppuccin-latte .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-latte a.navbar-item,html.theme--catppuccin-latte .navbar-link{cursor:pointer}html.theme--catppuccin-latte a.navbar-item:focus,html.theme--catppuccin-latte a.navbar-item:focus-within,html.theme--catppuccin-latte a.navbar-item:hover,html.theme--catppuccin-latte a.navbar-item.is-active,html.theme--catppuccin-latte .navbar-link:focus,html.theme--catppuccin-latte .navbar-link:focus-within,html.theme--catppuccin-latte .navbar-link:hover,html.theme--catppuccin-latte .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}html.theme--catppuccin-latte .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .navbar-item img{max-height:1.75rem}html.theme--catppuccin-latte .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-latte .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-latte .navbar-item.is-tab:focus,html.theme--catppuccin-latte .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1e66f5}html.theme--catppuccin-latte .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1e66f5;border-bottom-style:solid;border-bottom-width:3px;color:#1e66f5;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-latte .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-latte .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-latte .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-latte .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-latte .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .navbar>.container{display:block}html.theme--catppuccin-latte .navbar-brand .navbar-item,html.theme--catppuccin-latte .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-latte .navbar-link::after{display:none}html.theme--catppuccin-latte .navbar-menu{background-color:#1e66f5;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-latte .navbar-menu.is-active{display:block}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch,html.theme--catppuccin-latte .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-latte .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-latte .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-latte .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-latte html.has-navbar-fixed-top-touch,html.theme--catppuccin-latte body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-latte body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .navbar,html.theme--catppuccin-latte .navbar-menu,html.theme--catppuccin-latte .navbar-start,html.theme--catppuccin-latte .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-latte .navbar{min-height:4rem}html.theme--catppuccin-latte .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-latte .navbar.is-spaced .navbar-start,html.theme--catppuccin-latte .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-latte .navbar.is-spaced a.navbar-item,html.theme--catppuccin-latte .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-latte .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-latte .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-latte .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8c8fa1}html.theme--catppuccin-latte .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}html.theme--catppuccin-latte .navbar-burger{display:none}html.theme--catppuccin-latte .navbar-item,html.theme--catppuccin-latte .navbar-link{align-items:center;display:flex}html.theme--catppuccin-latte .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-latte .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-latte .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-latte .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-latte .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-latte .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-latte .navbar-dropdown{background-color:#1e66f5;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-latte .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-latte .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8c8fa1}html.theme--catppuccin-latte .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1e66f5}.navbar.is-spaced html.theme--catppuccin-latte .navbar-dropdown,html.theme--catppuccin-latte .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-latte .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-latte .navbar-divider{display:block}html.theme--catppuccin-latte .navbar>.container .navbar-brand,html.theme--catppuccin-latte .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-latte .navbar>.container .navbar-menu,html.theme--catppuccin-latte .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-latte .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-latte .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-latte .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-latte html.has-navbar-fixed-top-desktop,html.theme--catppuccin-latte body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-latte html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-latte body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-latte html.has-spaced-navbar-fixed-top,html.theme--catppuccin-latte body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-latte html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-latte body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-latte a.navbar-item.is-active,html.theme--catppuccin-latte .navbar-link.is-active{color:#1e66f5}html.theme--catppuccin-latte a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-latte .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-latte .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-latte .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-latte .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-latte .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-latte .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-latte .pagination.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-latte .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-latte .pagination.is-rounded .pagination-previous,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-latte .pagination.is-rounded .pagination-next,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-latte .pagination.is-rounded .pagination-link,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-latte .pagination,html.theme--catppuccin-latte .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link{border-color:#acb0be;color:#1e66f5;min-width:2.5em}html.theme--catppuccin-latte .pagination-previous:hover,html.theme--catppuccin-latte .pagination-next:hover,html.theme--catppuccin-latte .pagination-link:hover{border-color:#9ca0b0;color:#04a5e5}html.theme--catppuccin-latte .pagination-previous:focus,html.theme--catppuccin-latte .pagination-next:focus,html.theme--catppuccin-latte .pagination-link:focus{border-color:#9ca0b0}html.theme--catppuccin-latte .pagination-previous:active,html.theme--catppuccin-latte .pagination-next:active,html.theme--catppuccin-latte .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-latte .pagination-previous[disabled],html.theme--catppuccin-latte .pagination-previous.is-disabled,html.theme--catppuccin-latte .pagination-next[disabled],html.theme--catppuccin-latte .pagination-next.is-disabled,html.theme--catppuccin-latte .pagination-link[disabled],html.theme--catppuccin-latte .pagination-link.is-disabled{background-color:#acb0be;border-color:#acb0be;box-shadow:none;color:#616587;opacity:0.5}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-latte .pagination-link.is-current{background-color:#1e66f5;border-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .pagination-ellipsis{color:#9ca0b0;pointer-events:none}html.theme--catppuccin-latte .pagination-list{flex-wrap:wrap}html.theme--catppuccin-latte .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-latte .pagination{flex-wrap:wrap}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-latte .pagination-previous{order:2}html.theme--catppuccin-latte .pagination-next{order:3}html.theme--catppuccin-latte .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-latte .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-latte .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-latte .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-latte .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-latte .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-latte .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-latte .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-latte .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-latte .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-latte .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-latte .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-latte .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-latte .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-latte .panel.is-dark .panel-heading,html.theme--catppuccin-latte .content kbd.panel .panel-heading{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-latte .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#ccd0da}html.theme--catppuccin-latte .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-latte .content kbd.panel .panel-block.is-active .panel-icon{color:#ccd0da}html.theme--catppuccin-latte .panel.is-primary .panel-heading,html.theme--catppuccin-latte .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-latte .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#1e66f5}html.theme--catppuccin-latte .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-latte .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel.is-link .panel-heading{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1e66f5}html.theme--catppuccin-latte .panel.is-link .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel.is-info .panel-heading{background-color:#179299;color:#fff}html.theme--catppuccin-latte .panel.is-info .panel-tabs a.is-active{border-bottom-color:#179299}html.theme--catppuccin-latte .panel.is-info .panel-block.is-active .panel-icon{color:#179299}html.theme--catppuccin-latte .panel.is-success .panel-heading{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .panel.is-success .panel-tabs a.is-active{border-bottom-color:#40a02b}html.theme--catppuccin-latte .panel.is-success .panel-block.is-active .panel-icon{color:#40a02b}html.theme--catppuccin-latte .panel.is-warning .panel-heading{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#df8e1d}html.theme--catppuccin-latte .panel.is-warning .panel-block.is-active .panel-icon{color:#df8e1d}html.theme--catppuccin-latte .panel.is-danger .panel-heading{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#d20f39}html.theme--catppuccin-latte .panel.is-danger .panel-block.is-active .panel-icon{color:#d20f39}html.theme--catppuccin-latte .panel-tabs:not(:last-child),html.theme--catppuccin-latte .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-latte .panel-heading{background-color:#bcc0cc;border-radius:8px 8px 0 0;color:#41445a;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-latte .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-latte .panel-tabs a{border-bottom:1px solid #acb0be;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-latte .panel-tabs a.is-active{border-bottom-color:#bcc0cc;color:#0b57ef}html.theme--catppuccin-latte .panel-list a{color:#4c4f69}html.theme--catppuccin-latte .panel-list a:hover{color:#1e66f5}html.theme--catppuccin-latte .panel-block{align-items:center;color:#41445a;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-latte .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-latte .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-latte .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-latte .panel-block.is-active{border-left-color:#1e66f5;color:#0b57ef}html.theme--catppuccin-latte .panel-block.is-active .panel-icon{color:#1e66f5}html.theme--catppuccin-latte .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-latte a.panel-block,html.theme--catppuccin-latte label.panel-block{cursor:pointer}html.theme--catppuccin-latte a.panel-block:hover,html.theme--catppuccin-latte label.panel-block:hover{background-color:#e6e9ef}html.theme--catppuccin-latte .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#616587;margin-right:.75em}html.theme--catppuccin-latte .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-latte .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-latte .tabs a{align-items:center;border-bottom-color:#acb0be;border-bottom-style:solid;border-bottom-width:1px;color:#4c4f69;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-latte .tabs a:hover{border-bottom-color:#41445a;color:#41445a}html.theme--catppuccin-latte .tabs li{display:block}html.theme--catppuccin-latte .tabs li.is-active a{border-bottom-color:#1e66f5;color:#1e66f5}html.theme--catppuccin-latte .tabs ul{align-items:center;border-bottom-color:#acb0be;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-latte .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-latte .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-latte .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-latte .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-latte .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-latte .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-latte .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-latte .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-latte .tabs.is-boxed a:hover{background-color:#e6e9ef;border-bottom-color:#acb0be}html.theme--catppuccin-latte .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#acb0be;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-latte .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-latte .tabs.is-toggle a{border-color:#acb0be;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-latte .tabs.is-toggle a:hover{background-color:#e6e9ef;border-color:#9ca0b0;z-index:2}html.theme--catppuccin-latte .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-latte .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-latte .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-latte .tabs.is-toggle li.is-active a{background-color:#1e66f5;border-color:#1e66f5;color:#fff;z-index:1}html.theme--catppuccin-latte .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-latte .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-latte .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-latte .tabs.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-latte .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-latte .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-latte .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-latte .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-latte .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-latte .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-latte .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-latte .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-latte .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-latte .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-latte .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .column.is-narrow,html.theme--catppuccin-latte .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full,html.theme--catppuccin-latte .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters,html.theme--catppuccin-latte .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds,html.theme--catppuccin-latte .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half,html.theme--catppuccin-latte .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third,html.theme--catppuccin-latte .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter,html.theme--catppuccin-latte .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth,html.theme--catppuccin-latte .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths,html.theme--catppuccin-latte .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths,html.theme--catppuccin-latte .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths,html.theme--catppuccin-latte .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters,html.theme--catppuccin-latte .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds,html.theme--catppuccin-latte .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half,html.theme--catppuccin-latte .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third,html.theme--catppuccin-latte .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter,html.theme--catppuccin-latte .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth,html.theme--catppuccin-latte .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths,html.theme--catppuccin-latte .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths,html.theme--catppuccin-latte .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths,html.theme--catppuccin-latte .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-latte .column.is-0,html.theme--catppuccin-latte .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0,html.theme--catppuccin-latte .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-latte .column.is-1,html.theme--catppuccin-latte .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1,html.theme--catppuccin-latte .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2,html.theme--catppuccin-latte .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2,html.theme--catppuccin-latte .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3,html.theme--catppuccin-latte .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3,html.theme--catppuccin-latte .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-latte .column.is-4,html.theme--catppuccin-latte .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4,html.theme--catppuccin-latte .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5,html.theme--catppuccin-latte .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5,html.theme--catppuccin-latte .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6,html.theme--catppuccin-latte .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6,html.theme--catppuccin-latte .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-latte .column.is-7,html.theme--catppuccin-latte .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7,html.theme--catppuccin-latte .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8,html.theme--catppuccin-latte .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8,html.theme--catppuccin-latte .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9,html.theme--catppuccin-latte .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9,html.theme--catppuccin-latte .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-latte .column.is-10,html.theme--catppuccin-latte .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10,html.theme--catppuccin-latte .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11,html.theme--catppuccin-latte .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11,html.theme--catppuccin-latte .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12,html.theme--catppuccin-latte .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12,html.theme--catppuccin-latte .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-latte .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-latte .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-latte .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-latte .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-latte .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-latte .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-latte .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-latte .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-latte .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-latte .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-latte .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-latte .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-latte .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-latte .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-latte .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-latte .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-latte .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-latte .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-latte .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-latte .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-latte .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-latte .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-latte .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-latte .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-latte .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-latte .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-latte .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-latte .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-latte .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-latte .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-latte .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-latte .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-latte .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-latte .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-latte .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-latte .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-latte .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-latte .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-latte .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-latte .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-latte .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-latte .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-latte .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-latte .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-latte .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-latte .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-latte .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-latte .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-latte .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-latte .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-latte .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-latte .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-latte .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-latte .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-latte .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-latte .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-latte .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-latte .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-latte .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-latte .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-latte .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-latte .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-latte .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-latte .columns.is-centered{justify-content:center}html.theme--catppuccin-latte .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-latte .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-latte .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-latte .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-latte .columns.is-mobile{display:flex}html.theme--catppuccin-latte .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-latte .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-desktop{display:flex}}html.theme--catppuccin-latte .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-latte .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-latte .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-latte .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-latte .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-latte .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-latte .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-latte .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-latte .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-latte .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-latte .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-latte .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-latte .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-latte .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-latte .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-latte .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-latte .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-latte .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-latte .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-latte .tile.is-child{margin:0 !important}html.theme--catppuccin-latte .tile.is-parent{padding:.75rem}html.theme--catppuccin-latte .tile.is-vertical{flex-direction:column}html.theme--catppuccin-latte .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .tile:not(.is-child){display:flex}html.theme--catppuccin-latte .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-latte .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-latte .tile.is-3{flex:none;width:25%}html.theme--catppuccin-latte .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-latte .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-latte .tile.is-6{flex:none;width:50%}html.theme--catppuccin-latte .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-latte .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-latte .tile.is-9{flex:none;width:75%}html.theme--catppuccin-latte .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-latte .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-latte .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-latte .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-latte .hero .navbar{background:none}html.theme--catppuccin-latte .hero .tabs ul{border-bottom:none}html.theme--catppuccin-latte .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-white strong{color:inherit}html.theme--catppuccin-latte .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-latte .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-latte .hero.is-white .navbar-item,html.theme--catppuccin-latte .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-latte .hero.is-white a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-white .navbar-link:hover,html.theme--catppuccin-latte .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-latte .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-latte .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-latte .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-black strong{color:inherit}html.theme--catppuccin-latte .hero.is-black .title{color:#fff}html.theme--catppuccin-latte .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-latte .hero.is-black .navbar-item,html.theme--catppuccin-latte .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-black a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-black .navbar-link:hover,html.theme--catppuccin-latte .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-latte .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-latte .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-latte .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-light strong{color:inherit}html.theme--catppuccin-latte .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-latte .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-latte .hero.is-light .navbar-item,html.theme--catppuccin-latte .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-light .navbar-link:hover,html.theme--catppuccin-latte .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-latte .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-latte .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-latte .hero.is-dark,html.theme--catppuccin-latte .content kbd.hero{background-color:#ccd0da;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-dark strong,html.theme--catppuccin-latte .content kbd.hero strong{color:inherit}html.theme--catppuccin-latte .hero.is-dark .title,html.theme--catppuccin-latte .content kbd.hero .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .subtitle,html.theme--catppuccin-latte .content kbd.hero .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-latte .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-latte .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-dark .subtitle strong,html.theme--catppuccin-latte .content kbd.hero .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-dark .navbar-menu,html.theme--catppuccin-latte .content kbd.hero .navbar-menu{background-color:#ccd0da}}html.theme--catppuccin-latte .hero.is-dark .navbar-item,html.theme--catppuccin-latte .content kbd.hero .navbar-item,html.theme--catppuccin-latte .hero.is-dark .navbar-link,html.theme--catppuccin-latte .content kbd.hero .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-latte .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-latte .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-dark .navbar-link:hover,html.theme--catppuccin-latte .content kbd.hero .navbar-link:hover,html.theme--catppuccin-latte .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-latte .content kbd.hero .navbar-link.is-active{background-color:#bdc2cf;color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .tabs a,html.theme--catppuccin-latte .content kbd.hero .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-latte .hero.is-dark .tabs a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs li.is-active a{color:#ccd0da !important;opacity:1}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ccd0da}html.theme--catppuccin-latte .hero.is-dark.is-bold,html.theme--catppuccin-latte .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #a7b8cc 0%, #ccd0da 71%, #d9dbe6 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-latte .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #a7b8cc 0%, #ccd0da 71%, #d9dbe6 100%)}}html.theme--catppuccin-latte .hero.is-primary,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-primary strong,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-latte .hero.is-primary .title,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-latte .hero.is-primary .subtitle,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-primary .subtitle strong,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-primary .navbar-menu,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#1e66f5}}html.theme--catppuccin-latte .hero.is-primary .navbar-item,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-latte .hero.is-primary .navbar-link,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-primary .navbar-link:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-latte .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .hero.is-primary .tabs a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-primary .tabs a:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#1e66f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .hero.is-primary.is-bold,html.theme--catppuccin-latte .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-latte .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}}html.theme--catppuccin-latte .hero.is-link{background-color:#1e66f5;color:#fff}html.theme--catppuccin-latte .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-link strong{color:inherit}html.theme--catppuccin-latte .hero.is-link .title{color:#fff}html.theme--catppuccin-latte .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-link .navbar-menu{background-color:#1e66f5}}html.theme--catppuccin-latte .hero.is-link .navbar-item,html.theme--catppuccin-latte .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-link a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-link .navbar-link:hover,html.theme--catppuccin-latte .hero.is-link .navbar-link.is-active{background-color:#0b57ef;color:#fff}html.theme--catppuccin-latte .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-link .tabs li.is-active a{color:#1e66f5 !important;opacity:1}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1e66f5}html.theme--catppuccin-latte .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0070e0 0%, #1e66f5 71%, #3153fb 100%)}}html.theme--catppuccin-latte .hero.is-info{background-color:#179299;color:#fff}html.theme--catppuccin-latte .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-info strong{color:inherit}html.theme--catppuccin-latte .hero.is-info .title{color:#fff}html.theme--catppuccin-latte .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-info .navbar-menu{background-color:#179299}}html.theme--catppuccin-latte .hero.is-info .navbar-item,html.theme--catppuccin-latte .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-info a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-info .navbar-link:hover,html.theme--catppuccin-latte .hero.is-info .navbar-link.is-active{background-color:#147d83;color:#fff}html.theme--catppuccin-latte .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-info .tabs li.is-active a{color:#179299 !important;opacity:1}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#179299}html.theme--catppuccin-latte .hero.is-info.is-bold{background-image:linear-gradient(141deg, #0a7367 0%, #179299 71%, #1591b4 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0a7367 0%, #179299 71%, #1591b4 100%)}}html.theme--catppuccin-latte .hero.is-success{background-color:#40a02b;color:#fff}html.theme--catppuccin-latte .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-success strong{color:inherit}html.theme--catppuccin-latte .hero.is-success .title{color:#fff}html.theme--catppuccin-latte .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-success .navbar-menu{background-color:#40a02b}}html.theme--catppuccin-latte .hero.is-success .navbar-item,html.theme--catppuccin-latte .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-success a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-success .navbar-link:hover,html.theme--catppuccin-latte .hero.is-success .navbar-link.is-active{background-color:#388c26;color:#fff}html.theme--catppuccin-latte .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-success .tabs li.is-active a{color:#40a02b !important;opacity:1}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#40a02b}html.theme--catppuccin-latte .hero.is-success.is-bold{background-image:linear-gradient(141deg, #3c7f19 0%, #40a02b 71%, #2dba2b 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #3c7f19 0%, #40a02b 71%, #2dba2b 100%)}}html.theme--catppuccin-latte .hero.is-warning{background-color:#df8e1d;color:#fff}html.theme--catppuccin-latte .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-warning strong{color:inherit}html.theme--catppuccin-latte .hero.is-warning .title{color:#fff}html.theme--catppuccin-latte .hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-warning .navbar-menu{background-color:#df8e1d}}html.theme--catppuccin-latte .hero.is-warning .navbar-item,html.theme--catppuccin-latte .hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-warning .navbar-link:hover,html.theme--catppuccin-latte .hero.is-warning .navbar-link.is-active{background-color:#c8801a;color:#fff}html.theme--catppuccin-latte .hero.is-warning .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-warning .tabs li.is-active a{color:#df8e1d !important;opacity:1}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#df8e1d}html.theme--catppuccin-latte .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #bc560d 0%, #df8e1d 71%, #eaba2b 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #bc560d 0%, #df8e1d 71%, #eaba2b 100%)}}html.theme--catppuccin-latte .hero.is-danger{background-color:#d20f39;color:#fff}html.theme--catppuccin-latte .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-latte .hero.is-danger strong{color:inherit}html.theme--catppuccin-latte .hero.is-danger .title{color:#fff}html.theme--catppuccin-latte .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-latte .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-latte .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .hero.is-danger .navbar-menu{background-color:#d20f39}}html.theme--catppuccin-latte .hero.is-danger .navbar-item,html.theme--catppuccin-latte .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-latte .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-latte .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-latte .hero.is-danger .navbar-link:hover,html.theme--catppuccin-latte .hero.is-danger .navbar-link.is-active{background-color:#ba0d33;color:#fff}html.theme--catppuccin-latte .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-latte .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-latte .hero.is-danger .tabs li.is-active a{color:#d20f39 !important;opacity:1}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-latte .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#d20f39}html.theme--catppuccin-latte .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ab0343 0%, #d20f39 71%, #f00a16 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ab0343 0%, #d20f39 71%, #f00a16 100%)}}html.theme--catppuccin-latte .hero.is-small .hero-body,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-latte .hero.is-halfheight .hero-body,html.theme--catppuccin-latte .hero.is-fullheight .hero-body,html.theme--catppuccin-latte .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-latte .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-latte .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-latte .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-latte .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-latte .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-latte .hero-video{overflow:hidden}html.theme--catppuccin-latte .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-latte .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero-video{display:none}}html.theme--catppuccin-latte .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-latte .hero-buttons .button{display:flex}html.theme--catppuccin-latte .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-latte .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-latte .hero-head,html.theme--catppuccin-latte .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-latte .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-latte .hero-body{padding:3rem 3rem}}html.theme--catppuccin-latte .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-latte .section{padding:3rem 3rem}html.theme--catppuccin-latte .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-latte .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-latte .footer{background-color:#e6e9ef;padding:3rem 1.5rem 6rem}html.theme--catppuccin-latte h1 .docs-heading-anchor,html.theme--catppuccin-latte h1 .docs-heading-anchor:hover,html.theme--catppuccin-latte h1 .docs-heading-anchor:visited,html.theme--catppuccin-latte h2 .docs-heading-anchor,html.theme--catppuccin-latte h2 .docs-heading-anchor:hover,html.theme--catppuccin-latte h2 .docs-heading-anchor:visited,html.theme--catppuccin-latte h3 .docs-heading-anchor,html.theme--catppuccin-latte h3 .docs-heading-anchor:hover,html.theme--catppuccin-latte h3 .docs-heading-anchor:visited,html.theme--catppuccin-latte h4 .docs-heading-anchor,html.theme--catppuccin-latte h4 .docs-heading-anchor:hover,html.theme--catppuccin-latte h4 .docs-heading-anchor:visited,html.theme--catppuccin-latte h5 .docs-heading-anchor,html.theme--catppuccin-latte h5 .docs-heading-anchor:hover,html.theme--catppuccin-latte h5 .docs-heading-anchor:visited,html.theme--catppuccin-latte h6 .docs-heading-anchor,html.theme--catppuccin-latte h6 .docs-heading-anchor:hover,html.theme--catppuccin-latte h6 .docs-heading-anchor:visited{color:#4c4f69}html.theme--catppuccin-latte h1 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h2 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h3 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h4 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h5 .docs-heading-anchor-permalink,html.theme--catppuccin-latte h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-latte h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-latte h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-latte h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-latte h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-latte .docs-dark-only{display:none !important}html.theme--catppuccin-latte pre{position:relative;overflow:hidden}html.theme--catppuccin-latte pre code,html.theme--catppuccin-latte pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-latte pre code:first-of-type,html.theme--catppuccin-latte pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-latte pre code:last-of-type,html.theme--catppuccin-latte pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-latte pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#4c4f69;cursor:pointer;text-align:center}html.theme--catppuccin-latte pre .copy-button:focus,html.theme--catppuccin-latte pre .copy-button:hover{opacity:1;background:rgba(76,79,105,0.1);color:#1e66f5}html.theme--catppuccin-latte pre .copy-button.success{color:#40a02b;opacity:1}html.theme--catppuccin-latte pre .copy-button.error{color:#d20f39;opacity:1}html.theme--catppuccin-latte pre:hover .copy-button{opacity:1}html.theme--catppuccin-latte .admonition{background-color:#e6e9ef;border-style:solid;border-width:2px;border-color:#5c5f77;border-radius:4px;font-size:1rem}html.theme--catppuccin-latte .admonition strong{color:currentColor}html.theme--catppuccin-latte .admonition.is-small,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-latte .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-latte .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-latte .admonition.is-default{background-color:#e6e9ef;border-color:#5c5f77}html.theme--catppuccin-latte .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#5c5f77}html.theme--catppuccin-latte .admonition.is-default>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-info{background-color:#e6e9ef;border-color:#179299}html.theme--catppuccin-latte .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#179299}html.theme--catppuccin-latte .admonition.is-info>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-success{background-color:#e6e9ef;border-color:#40a02b}html.theme--catppuccin-latte .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#40a02b}html.theme--catppuccin-latte .admonition.is-success>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-warning{background-color:#e6e9ef;border-color:#df8e1d}html.theme--catppuccin-latte .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#df8e1d}html.theme--catppuccin-latte .admonition.is-warning>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-danger{background-color:#e6e9ef;border-color:#d20f39}html.theme--catppuccin-latte .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#d20f39}html.theme--catppuccin-latte .admonition.is-danger>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-compat{background-color:#e6e9ef;border-color:#04a5e5}html.theme--catppuccin-latte .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#04a5e5}html.theme--catppuccin-latte .admonition.is-compat>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition.is-todo{background-color:#e6e9ef;border-color:#8839ef}html.theme--catppuccin-latte .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#8839ef}html.theme--catppuccin-latte .admonition.is-todo>.admonition-body{color:#4c4f69}html.theme--catppuccin-latte .admonition-header{color:#5c5f77;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-latte .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-latte details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-latte details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-latte details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-latte .admonition-body{color:#4c4f69;padding:0.5rem .75rem}html.theme--catppuccin-latte .admonition-body pre{background-color:#e6e9ef}html.theme--catppuccin-latte .admonition-body code{background-color:#e6e9ef}html.theme--catppuccin-latte .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #acb0be;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-latte .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#e6e9ef;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #acb0be;overflow:auto}html.theme--catppuccin-latte .docstring>header code{background-color:transparent}html.theme--catppuccin-latte .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-latte .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-latte .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-latte .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #acb0be}html.theme--catppuccin-latte .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-latte .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-latte .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-latte .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-latte .documenter-example-output{background-color:#eff1f5}html.theme--catppuccin-latte .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#e6e9ef;color:#4c4f69;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-latte .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-latte .outdated-warning-overlay a{color:#1e66f5}html.theme--catppuccin-latte .outdated-warning-overlay a:hover{color:#04a5e5}html.theme--catppuccin-latte .content pre{border:2px solid #acb0be;border-radius:4px}html.theme--catppuccin-latte .content code{font-weight:inherit}html.theme--catppuccin-latte .content a code{color:#1e66f5}html.theme--catppuccin-latte .content a:hover code{color:#04a5e5}html.theme--catppuccin-latte .content h1 code,html.theme--catppuccin-latte .content h2 code,html.theme--catppuccin-latte .content h3 code,html.theme--catppuccin-latte .content h4 code,html.theme--catppuccin-latte .content h5 code,html.theme--catppuccin-latte .content h6 code{color:#4c4f69}html.theme--catppuccin-latte .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-latte .content blockquote>ul:first-child,html.theme--catppuccin-latte .content blockquote>ol:first-child,html.theme--catppuccin-latte .content .admonition-body>ul:first-child,html.theme--catppuccin-latte .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-latte pre,html.theme--catppuccin-latte code{font-variant-ligatures:no-contextual}html.theme--catppuccin-latte .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-latte .breadcrumb a.is-disabled,html.theme--catppuccin-latte .breadcrumb a.is-disabled:hover{color:#41445a}html.theme--catppuccin-latte .hljs{background:initial !important}html.theme--catppuccin-latte .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-latte .katex-display,html.theme--catppuccin-latte mjx-container,html.theme--catppuccin-latte .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-latte html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-latte li.no-marker{list-style:none}html.theme--catppuccin-latte #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-latte #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main{width:100%}html.theme--catppuccin-latte #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-latte #documenter .docs-main>header,html.theme--catppuccin-latte #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar{background-color:#eff1f5;border-bottom:1px solid #acb0be;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-latte #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-latte #documenter .docs-main section.footnotes{border-top:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-latte #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-latte .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-latte #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #acb0be;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-latte #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-latte #documenter .docs-sidebar{display:flex;flex-direction:column;color:#4c4f69;background-color:#e6e9ef;border-right:1px solid #acb0be;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-latte #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-latte #documenter .docs-sidebar .docs-package-name a:hover{color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #acb0be;display:none;padding:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #acb0be;padding-bottom:1.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#4c4f69;background:#e6e9ef}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#4c4f69;background-color:#f2f4f7}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #acb0be;border-bottom:1px solid #acb0be;background-color:#dce0e8}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#dce0e8;color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#f2f4f7;color:#4c4f69}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #acb0be}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-latte #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#fff}html.theme--catppuccin-latte #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#fff}}@media screen and (max-width: 1055px){html.theme--catppuccin-latte #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#fff}html.theme--catppuccin-latte #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#fff}}html.theme--catppuccin-latte kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-latte .search-min-width-50{min-width:50%}html.theme--catppuccin-latte .search-min-height-100{min-height:100%}html.theme--catppuccin-latte .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-latte .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover,html.theme--catppuccin-latte .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-latte .search-result-link .property-search-result-badge,html.theme--catppuccin-latte .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-latte .property-search-result-badge,html.theme--catppuccin-latte .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-latte .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:hover .search-filter,html.theme--catppuccin-latte .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-latte .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-latte .search-filter:hover,html.theme--catppuccin-latte .search-filter:focus{color:#333}html.theme--catppuccin-latte .search-filter-selected{color:#ccd0da;background-color:#7287fd}html.theme--catppuccin-latte .search-filter-selected:hover,html.theme--catppuccin-latte .search-filter-selected:focus{color:#ccd0da}html.theme--catppuccin-latte .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-latte .search-divider{border-bottom:1px solid #acb0be}html.theme--catppuccin-latte .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-latte .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-latte #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-latte #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-latte .w-100{width:100%}html.theme--catppuccin-latte .gap-2{gap:0.5rem}html.theme--catppuccin-latte .gap-4{gap:1rem}html.theme--catppuccin-latte .gap-8{gap:2rem}html.theme--catppuccin-latte{background-color:#eff1f5;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-latte a{transition:all 200ms ease}html.theme--catppuccin-latte .label{color:#4c4f69}html.theme--catppuccin-latte .button,html.theme--catppuccin-latte .control.has-icons-left .icon,html.theme--catppuccin-latte .control.has-icons-right .icon,html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .pagination-ellipsis,html.theme--catppuccin-latte .pagination-link,html.theme--catppuccin-latte .pagination-next,html.theme--catppuccin-latte .pagination-previous,html.theme--catppuccin-latte .select,html.theme--catppuccin-latte .select select,html.theme--catppuccin-latte .textarea{height:2.5em;color:#4c4f69}html.theme--catppuccin-latte .input,html.theme--catppuccin-latte #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-latte .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#4c4f69}html.theme--catppuccin-latte .select:after,html.theme--catppuccin-latte .select select{border-width:1px}html.theme--catppuccin-latte .menu-list a{transition:all 300ms ease}html.theme--catppuccin-latte .modal-card-foot,html.theme--catppuccin-latte .modal-card-head{border-color:#acb0be}html.theme--catppuccin-latte .navbar{border-radius:.4em}html.theme--catppuccin-latte .navbar.is-transparent{background:none}html.theme--catppuccin-latte .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-latte .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1e66f5}@media screen and (max-width: 1055px){html.theme--catppuccin-latte .navbar .navbar-menu{background-color:#1e66f5;border-radius:0 0 .4em .4em}}html.theme--catppuccin-latte .docstring>section>a.docs-sourcelink:not(body){color:#ccd0da}html.theme--catppuccin-latte .tag.is-link:not(body),html.theme--catppuccin-latte .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-latte .content kbd.is-link:not(body){color:#ccd0da}html.theme--catppuccin-latte .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-latte .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-latte .ansi span.sgr3{font-style:italic}html.theme--catppuccin-latte .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-latte .ansi span.sgr7{color:#eff1f5;background-color:#4c4f69}html.theme--catppuccin-latte .ansi span.sgr8{color:transparent}html.theme--catppuccin-latte .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-latte .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-latte .ansi span.sgr30{color:#5c5f77}html.theme--catppuccin-latte .ansi span.sgr31{color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr32{color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr33{color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr34{color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr35{color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr36{color:#179299}html.theme--catppuccin-latte .ansi span.sgr37{color:#acb0be}html.theme--catppuccin-latte .ansi span.sgr40{background-color:#5c5f77}html.theme--catppuccin-latte .ansi span.sgr41{background-color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr42{background-color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr43{background-color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr44{background-color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr45{background-color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr46{background-color:#179299}html.theme--catppuccin-latte .ansi span.sgr47{background-color:#acb0be}html.theme--catppuccin-latte .ansi span.sgr90{color:#6c6f85}html.theme--catppuccin-latte .ansi span.sgr91{color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr92{color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr93{color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr94{color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr95{color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr96{color:#179299}html.theme--catppuccin-latte .ansi span.sgr97{color:#bcc0cc}html.theme--catppuccin-latte .ansi span.sgr100{background-color:#6c6f85}html.theme--catppuccin-latte .ansi span.sgr101{background-color:#d20f39}html.theme--catppuccin-latte .ansi span.sgr102{background-color:#40a02b}html.theme--catppuccin-latte .ansi span.sgr103{background-color:#df8e1d}html.theme--catppuccin-latte .ansi span.sgr104{background-color:#1e66f5}html.theme--catppuccin-latte .ansi span.sgr105{background-color:#ea76cb}html.theme--catppuccin-latte .ansi span.sgr106{background-color:#179299}html.theme--catppuccin-latte .ansi span.sgr107{background-color:#bcc0cc}html.theme--catppuccin-latte code.language-julia-repl>span.hljs-meta{color:#40a02b;font-weight:bolder}html.theme--catppuccin-latte code .hljs{color:#4c4f69;background:#eff1f5}html.theme--catppuccin-latte code .hljs-keyword{color:#8839ef}html.theme--catppuccin-latte code .hljs-built_in{color:#d20f39}html.theme--catppuccin-latte code .hljs-type{color:#df8e1d}html.theme--catppuccin-latte code .hljs-literal{color:#fe640b}html.theme--catppuccin-latte code .hljs-number{color:#fe640b}html.theme--catppuccin-latte code .hljs-operator{color:#179299}html.theme--catppuccin-latte code .hljs-punctuation{color:#5c5f77}html.theme--catppuccin-latte code .hljs-property{color:#179299}html.theme--catppuccin-latte code .hljs-regexp{color:#ea76cb}html.theme--catppuccin-latte code .hljs-string{color:#40a02b}html.theme--catppuccin-latte code .hljs-char.escape_{color:#40a02b}html.theme--catppuccin-latte code .hljs-subst{color:#6c6f85}html.theme--catppuccin-latte code .hljs-symbol{color:#dd7878}html.theme--catppuccin-latte code .hljs-variable{color:#8839ef}html.theme--catppuccin-latte code .hljs-variable.language_{color:#8839ef}html.theme--catppuccin-latte code .hljs-variable.constant_{color:#fe640b}html.theme--catppuccin-latte code .hljs-title{color:#1e66f5}html.theme--catppuccin-latte code .hljs-title.class_{color:#df8e1d}html.theme--catppuccin-latte code .hljs-title.function_{color:#1e66f5}html.theme--catppuccin-latte code .hljs-params{color:#4c4f69}html.theme--catppuccin-latte code .hljs-comment{color:#acb0be}html.theme--catppuccin-latte code .hljs-doctag{color:#d20f39}html.theme--catppuccin-latte code .hljs-meta{color:#fe640b}html.theme--catppuccin-latte code .hljs-section{color:#1e66f5}html.theme--catppuccin-latte code .hljs-tag{color:#6c6f85}html.theme--catppuccin-latte code .hljs-name{color:#8839ef}html.theme--catppuccin-latte code .hljs-attr{color:#1e66f5}html.theme--catppuccin-latte code .hljs-attribute{color:#40a02b}html.theme--catppuccin-latte code .hljs-bullet{color:#179299}html.theme--catppuccin-latte code .hljs-code{color:#40a02b}html.theme--catppuccin-latte code .hljs-emphasis{color:#d20f39;font-style:italic}html.theme--catppuccin-latte code .hljs-strong{color:#d20f39;font-weight:bold}html.theme--catppuccin-latte code .hljs-formula{color:#179299}html.theme--catppuccin-latte code .hljs-link{color:#209fb5;font-style:italic}html.theme--catppuccin-latte code .hljs-quote{color:#40a02b;font-style:italic}html.theme--catppuccin-latte code .hljs-selector-tag{color:#df8e1d}html.theme--catppuccin-latte code .hljs-selector-id{color:#1e66f5}html.theme--catppuccin-latte code .hljs-selector-class{color:#179299}html.theme--catppuccin-latte code .hljs-selector-attr{color:#8839ef}html.theme--catppuccin-latte code .hljs-selector-pseudo{color:#179299}html.theme--catppuccin-latte code .hljs-template-tag{color:#dd7878}html.theme--catppuccin-latte code .hljs-template-variable{color:#dd7878}html.theme--catppuccin-latte code .hljs-addition{color:#40a02b;background:rgba(166,227,161,0.15)}html.theme--catppuccin-latte code .hljs-deletion{color:#d20f39;background:rgba(243,139,168,0.15)}html.theme--catppuccin-latte .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover,html.theme--catppuccin-latte .search-result-link:focus{background-color:#ccd0da}html.theme--catppuccin-latte .search-result-link .property-search-result-badge,html.theme--catppuccin-latte .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-latte .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:hover .search-filter,html.theme--catppuccin-latte .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-latte .search-result-link:focus .search-filter{color:#ccd0da !important;background-color:#7287fd !important}html.theme--catppuccin-latte .search-result-title{color:#4c4f69}html.theme--catppuccin-latte .search-result-highlight{background-color:#d20f39;color:#e6e9ef}html.theme--catppuccin-latte .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-latte .w-100{width:100%}html.theme--catppuccin-latte .gap-2{gap:0.5rem}html.theme--catppuccin-latte .gap-4{gap:1rem} diff --git a/previews/PR826/assets/themes/catppuccin-macchiato.css b/previews/PR826/assets/themes/catppuccin-macchiato.css new file mode 100644 index 0000000000..a9cf9c573f --- /dev/null +++ b/previews/PR826/assets/themes/catppuccin-macchiato.css @@ -0,0 +1 @@ +html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato .file-name,html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-macchiato .pagination-previous:focus,html.theme--catppuccin-macchiato .pagination-next:focus,html.theme--catppuccin-macchiato .pagination-link:focus,html.theme--catppuccin-macchiato .pagination-ellipsis:focus,html.theme--catppuccin-macchiato .file-cta:focus,html.theme--catppuccin-macchiato .file-name:focus,html.theme--catppuccin-macchiato .select select:focus,html.theme--catppuccin-macchiato .textarea:focus,html.theme--catppuccin-macchiato .input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-macchiato .button:focus,html.theme--catppuccin-macchiato .is-focused.pagination-previous,html.theme--catppuccin-macchiato .is-focused.pagination-next,html.theme--catppuccin-macchiato .is-focused.pagination-link,html.theme--catppuccin-macchiato .is-focused.pagination-ellipsis,html.theme--catppuccin-macchiato .is-focused.file-cta,html.theme--catppuccin-macchiato .is-focused.file-name,html.theme--catppuccin-macchiato .select select.is-focused,html.theme--catppuccin-macchiato .is-focused.textarea,html.theme--catppuccin-macchiato .is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-focused.button,html.theme--catppuccin-macchiato .pagination-previous:active,html.theme--catppuccin-macchiato .pagination-next:active,html.theme--catppuccin-macchiato .pagination-link:active,html.theme--catppuccin-macchiato .pagination-ellipsis:active,html.theme--catppuccin-macchiato .file-cta:active,html.theme--catppuccin-macchiato .file-name:active,html.theme--catppuccin-macchiato .select select:active,html.theme--catppuccin-macchiato .textarea:active,html.theme--catppuccin-macchiato .input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-macchiato .button:active,html.theme--catppuccin-macchiato .is-active.pagination-previous,html.theme--catppuccin-macchiato .is-active.pagination-next,html.theme--catppuccin-macchiato .is-active.pagination-link,html.theme--catppuccin-macchiato .is-active.pagination-ellipsis,html.theme--catppuccin-macchiato .is-active.file-cta,html.theme--catppuccin-macchiato .is-active.file-name,html.theme--catppuccin-macchiato .select select.is-active,html.theme--catppuccin-macchiato .is-active.textarea,html.theme--catppuccin-macchiato .is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .is-active.button{outline:none}html.theme--catppuccin-macchiato .pagination-previous[disabled],html.theme--catppuccin-macchiato .pagination-next[disabled],html.theme--catppuccin-macchiato .pagination-link[disabled],html.theme--catppuccin-macchiato .pagination-ellipsis[disabled],html.theme--catppuccin-macchiato .file-cta[disabled],html.theme--catppuccin-macchiato .file-name[disabled],html.theme--catppuccin-macchiato .select select[disabled],html.theme--catppuccin-macchiato .textarea[disabled],html.theme--catppuccin-macchiato .input[disabled],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-macchiato .button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-macchiato .file-name,html.theme--catppuccin-macchiato fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-macchiato .select select,fieldset[disabled] html.theme--catppuccin-macchiato .textarea,fieldset[disabled] html.theme--catppuccin-macchiato .input,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato fieldset[disabled] .select select,html.theme--catppuccin-macchiato .select fieldset[disabled] select,html.theme--catppuccin-macchiato fieldset[disabled] .textarea,html.theme--catppuccin-macchiato fieldset[disabled] .input,html.theme--catppuccin-macchiato fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-macchiato .button,html.theme--catppuccin-macchiato fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-macchiato .tabs,html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .breadcrumb,html.theme--catppuccin-macchiato .file,html.theme--catppuccin-macchiato .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-macchiato .admonition:not(:last-child),html.theme--catppuccin-macchiato .tabs:not(:last-child),html.theme--catppuccin-macchiato .pagination:not(:last-child),html.theme--catppuccin-macchiato .message:not(:last-child),html.theme--catppuccin-macchiato .level:not(:last-child),html.theme--catppuccin-macchiato .breadcrumb:not(:last-child),html.theme--catppuccin-macchiato .block:not(:last-child),html.theme--catppuccin-macchiato .title:not(:last-child),html.theme--catppuccin-macchiato .subtitle:not(:last-child),html.theme--catppuccin-macchiato .table-container:not(:last-child),html.theme--catppuccin-macchiato .table:not(:last-child),html.theme--catppuccin-macchiato .progress:not(:last-child),html.theme--catppuccin-macchiato .notification:not(:last-child),html.theme--catppuccin-macchiato .content:not(:last-child),html.theme--catppuccin-macchiato .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .modal-close,html.theme--catppuccin-macchiato .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-macchiato .modal-close::before,html.theme--catppuccin-macchiato .delete::before,html.theme--catppuccin-macchiato .modal-close::after,html.theme--catppuccin-macchiato .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-macchiato .modal-close::before,html.theme--catppuccin-macchiato .delete::before{height:2px;width:50%}html.theme--catppuccin-macchiato .modal-close::after,html.theme--catppuccin-macchiato .delete::after{height:50%;width:2px}html.theme--catppuccin-macchiato .modal-close:hover,html.theme--catppuccin-macchiato .delete:hover,html.theme--catppuccin-macchiato .modal-close:focus,html.theme--catppuccin-macchiato .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-macchiato .modal-close:active,html.theme--catppuccin-macchiato .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-macchiato .is-small.modal-close,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-macchiato .is-small.delete,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-macchiato .is-medium.modal-close,html.theme--catppuccin-macchiato .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-macchiato .is-large.modal-close,html.theme--catppuccin-macchiato .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-macchiato .control.is-loading::after,html.theme--catppuccin-macchiato .select.is-loading::after,html.theme--catppuccin-macchiato .loader,html.theme--catppuccin-macchiato .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #8087a2;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-macchiato .hero-video,html.theme--catppuccin-macchiato .modal-background,html.theme--catppuccin-macchiato .modal,html.theme--catppuccin-macchiato .image.is-square img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-macchiato .image.is-square .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-macchiato .image.is-1by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-macchiato .image.is-1by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-macchiato .image.is-5by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-macchiato .image.is-4by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-macchiato .image.is-3by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-macchiato .image.is-5by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-16by9 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-macchiato .image.is-16by9 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-macchiato .image.is-2by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-macchiato .image.is-3by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-macchiato .image.is-4by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-macchiato .image.is-3by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-macchiato .image.is-2by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-macchiato .image.is-3by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-9by16 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-macchiato .image.is-9by16 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-macchiato .image.is-1by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-macchiato .image.is-1by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-macchiato .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363a4f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#212431 !important}.has-background-dark{background-color:#363a4f !important}.has-text-primary{color:#8aadf4 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#5b8cf0 !important}.has-background-primary{background-color:#8aadf4 !important}.has-text-primary-light{color:#ecf2fd !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bed1f9 !important}.has-background-primary-light{background-color:#ecf2fd !important}.has-text-primary-dark{color:#0e3b95 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#124dc4 !important}.has-background-primary-dark{background-color:#0e3b95 !important}.has-text-link{color:#8aadf4 !important}a.has-text-link:hover,a.has-text-link:focus{color:#5b8cf0 !important}.has-background-link{background-color:#8aadf4 !important}.has-text-link-light{color:#ecf2fd !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bed1f9 !important}.has-background-link-light{background-color:#ecf2fd !important}.has-text-link-dark{color:#0e3b95 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#124dc4 !important}.has-background-link-dark{background-color:#0e3b95 !important}.has-text-info{color:#8bd5ca !important}a.has-text-info:hover,a.has-text-info:focus{color:#66c7b9 !important}.has-background-info{background-color:#8bd5ca !important}.has-text-info-light{color:#f0faf8 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#cbece7 !important}.has-background-info-light{background-color:#f0faf8 !important}.has-text-info-dark{color:#276d62 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#359284 !important}.has-background-info-dark{background-color:#276d62 !important}.has-text-success{color:#a6da95 !important}a.has-text-success:hover,a.has-text-success:focus{color:#86cd6f !important}.has-background-success{background-color:#a6da95 !important}.has-text-success-light{color:#f2faf0 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#d3edca !important}.has-background-success-light{background-color:#f2faf0 !important}.has-text-success-dark{color:#386e26 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#4b9333 !important}.has-background-success-dark{background-color:#386e26 !important}.has-text-warning{color:#eed49f !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#e6c174 !important}.has-background-warning{background-color:#eed49f !important}.has-text-warning-light{color:#fcf7ee !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#f4e4c2 !important}.has-background-warning-light{background-color:#fcf7ee !important}.has-text-warning-dark{color:#7e5c16 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#a97b1e !important}.has-background-warning-dark{background-color:#7e5c16 !important}.has-text-danger{color:#ed8796 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#e65b6f !important}.has-background-danger{background-color:#ed8796 !important}.has-text-danger-light{color:#fcedef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f6c1c9 !important}.has-background-danger-light{background-color:#fcedef !important}.has-text-danger-dark{color:#971729 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c31d36 !important}.has-background-danger-dark{background-color:#971729 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363a4f !important}.has-background-grey-darker{background-color:#363a4f !important}.has-text-grey-dark{color:#494d64 !important}.has-background-grey-dark{background-color:#494d64 !important}.has-text-grey{color:#5b6078 !important}.has-background-grey{background-color:#5b6078 !important}.has-text-grey-light{color:#6e738d !important}.has-background-grey-light{background-color:#6e738d !important}.has-text-grey-lighter{color:#8087a2 !important}.has-background-grey-lighter{background-color:#8087a2 !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-macchiato html{background-color:#24273a;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-macchiato article,html.theme--catppuccin-macchiato aside,html.theme--catppuccin-macchiato figure,html.theme--catppuccin-macchiato footer,html.theme--catppuccin-macchiato header,html.theme--catppuccin-macchiato hgroup,html.theme--catppuccin-macchiato section{display:block}html.theme--catppuccin-macchiato body,html.theme--catppuccin-macchiato button,html.theme--catppuccin-macchiato input,html.theme--catppuccin-macchiato optgroup,html.theme--catppuccin-macchiato select,html.theme--catppuccin-macchiato textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-macchiato code,html.theme--catppuccin-macchiato pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-macchiato body{color:#cad3f5;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-macchiato a{color:#8aadf4;cursor:pointer;text-decoration:none}html.theme--catppuccin-macchiato a strong{color:currentColor}html.theme--catppuccin-macchiato a:hover{color:#91d7e3}html.theme--catppuccin-macchiato code{background-color:#1e2030;color:#cad3f5;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-macchiato hr{background-color:#1e2030;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-macchiato img{height:auto;max-width:100%}html.theme--catppuccin-macchiato input[type="checkbox"],html.theme--catppuccin-macchiato input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-macchiato small{font-size:.875em}html.theme--catppuccin-macchiato span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-macchiato strong{color:#b5c1f1;font-weight:700}html.theme--catppuccin-macchiato fieldset{border:none}html.theme--catppuccin-macchiato pre{-webkit-overflow-scrolling:touch;background-color:#1e2030;color:#cad3f5;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-macchiato pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-macchiato table td,html.theme--catppuccin-macchiato table th{vertical-align:top}html.theme--catppuccin-macchiato table td:not([align]),html.theme--catppuccin-macchiato table th:not([align]){text-align:inherit}html.theme--catppuccin-macchiato table th{color:#b5c1f1}html.theme--catppuccin-macchiato .box{background-color:#494d64;border-radius:8px;box-shadow:none;color:#cad3f5;display:block;padding:1.25rem}html.theme--catppuccin-macchiato a.box:hover,html.theme--catppuccin-macchiato a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #8aadf4}html.theme--catppuccin-macchiato a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #8aadf4}html.theme--catppuccin-macchiato .button{background-color:#1e2030;border-color:#3b3f5f;border-width:1px;color:#8aadf4;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-macchiato .button strong{color:inherit}html.theme--catppuccin-macchiato .button .icon,html.theme--catppuccin-macchiato .button .icon.is-small,html.theme--catppuccin-macchiato .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-macchiato .button .icon.is-medium,html.theme--catppuccin-macchiato .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-macchiato .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-macchiato .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-macchiato .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-macchiato .button:hover,html.theme--catppuccin-macchiato .button.is-hovered{border-color:#6e738d;color:#b5c1f1}html.theme--catppuccin-macchiato .button:focus,html.theme--catppuccin-macchiato .button.is-focused{border-color:#6e738d;color:#739df2}html.theme--catppuccin-macchiato .button:focus:not(:active),html.theme--catppuccin-macchiato .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button:active,html.theme--catppuccin-macchiato .button.is-active{border-color:#494d64;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text{background-color:transparent;border-color:transparent;color:#cad3f5;text-decoration:underline}html.theme--catppuccin-macchiato .button.is-text:hover,html.theme--catppuccin-macchiato .button.is-text.is-hovered,html.theme--catppuccin-macchiato .button.is-text:focus,html.theme--catppuccin-macchiato .button.is-text.is-focused{background-color:#1e2030;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text:active,html.theme--catppuccin-macchiato .button.is-text.is-active{background-color:#141620;color:#b5c1f1}html.theme--catppuccin-macchiato .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-macchiato .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#8aadf4;text-decoration:none}html.theme--catppuccin-macchiato .button.is-ghost:hover,html.theme--catppuccin-macchiato .button.is-ghost.is-hovered{color:#8aadf4;text-decoration:underline}html.theme--catppuccin-macchiato .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:hover,html.theme--catppuccin-macchiato .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:focus,html.theme--catppuccin-macchiato .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white:focus:not(:active),html.theme--catppuccin-macchiato .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .button.is-white:active,html.theme--catppuccin-macchiato .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-macchiato .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-macchiato .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-white.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:hover,html.theme--catppuccin-macchiato .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:focus,html.theme--catppuccin-macchiato .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black:focus:not(:active),html.theme--catppuccin-macchiato .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .button.is-black:active,html.theme--catppuccin-macchiato .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-macchiato .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-black.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:hover,html.theme--catppuccin-macchiato .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:focus,html.theme--catppuccin-macchiato .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light:focus:not(:active),html.theme--catppuccin-macchiato .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .button.is-light:active,html.theme--catppuccin-macchiato .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-macchiato .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-light.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-dark,html.theme--catppuccin-macchiato .content kbd.button{background-color:#363a4f;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:hover,html.theme--catppuccin-macchiato .content kbd.button:hover,html.theme--catppuccin-macchiato .button.is-dark.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-hovered{background-color:#313447;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:focus,html.theme--catppuccin-macchiato .content kbd.button:focus,html.theme--catppuccin-macchiato .button.is-dark.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark:focus:not(:active),html.theme--catppuccin-macchiato .content kbd.button:focus:not(:active),html.theme--catppuccin-macchiato .button.is-dark.is-focused:not(:active),html.theme--catppuccin-macchiato .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .button.is-dark:active,html.theme--catppuccin-macchiato .content kbd.button:active,html.theme--catppuccin-macchiato .button.is-dark.is-active,html.theme--catppuccin-macchiato .content kbd.button.is-active{background-color:#2c2f40;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-dark[disabled],html.theme--catppuccin-macchiato .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button{background-color:#363a4f;border-color:#363a4f;box-shadow:none}html.theme--catppuccin-macchiato .button.is-dark.is-inverted,html.theme--catppuccin-macchiato .content kbd.button.is-inverted{background-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted:hover,html.theme--catppuccin-macchiato .content kbd.button.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-dark.is-inverted[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-loading::after,html.theme--catppuccin-macchiato .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined,html.theme--catppuccin-macchiato .content kbd.button.is-outlined{background-color:transparent;border-color:#363a4f;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-outlined:hover,html.theme--catppuccin-macchiato .content kbd.button.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-dark.is-outlined:focus,html.theme--catppuccin-macchiato .content kbd.button.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-focused{background-color:#363a4f;border-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363a4f #363a4f !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-dark.is-outlined[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-outlined{background-color:transparent;border-color:#363a4f;box-shadow:none;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363a4f #363a4f !important}html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:focus,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary:focus:not(:active),html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-macchiato .button.is-primary.is-focused:not(:active),html.theme--catppuccin-macchiato .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button.is-primary:active,html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-macchiato .button.is-primary.is-active,html.theme--catppuccin-macchiato .docstring>section>a.button.is-active.docs-sourcelink{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-primary[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.docs-sourcelink{background-color:#8aadf4;border-color:#8aadf4;box-shadow:none}html.theme--catppuccin-macchiato .button.is-primary.is-inverted,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-primary.is-inverted[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-loading::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-outlined:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-macchiato .button.is-primary.is-outlined:focus,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-macchiato .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-primary.is-outlined[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#8aadf4;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-macchiato .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-primary.is-light,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.docs-sourcelink{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-primary.is-light:hover,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-macchiato .button.is-primary.is-light.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e1eafc;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-primary.is-light:active,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-macchiato .button.is-primary.is-light.is-active,html.theme--catppuccin-macchiato .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d5e2fb;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:hover,html.theme--catppuccin-macchiato .button.is-link.is-hovered{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:focus,html.theme--catppuccin-macchiato .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link:focus:not(:active),html.theme--catppuccin-macchiato .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .button.is-link:active,html.theme--catppuccin-macchiato .button.is-link.is-active{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link{background-color:#8aadf4;border-color:#8aadf4;box-shadow:none}html.theme--catppuccin-macchiato .button.is-link.is-inverted{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined{background-color:transparent;border-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-link.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-focused{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-outlined{background-color:transparent;border-color:#8aadf4;box-shadow:none;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8aadf4 #8aadf4 !important}html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-link.is-light{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link.is-light:hover,html.theme--catppuccin-macchiato .button.is-link.is-light.is-hovered{background-color:#e1eafc;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-link.is-light:active,html.theme--catppuccin-macchiato .button.is-link.is-light.is-active{background-color:#d5e2fb;border-color:transparent;color:#0e3b95}html.theme--catppuccin-macchiato .button.is-info{background-color:#8bd5ca;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:hover,html.theme--catppuccin-macchiato .button.is-info.is-hovered{background-color:#82d2c6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:focus,html.theme--catppuccin-macchiato .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info:focus:not(:active),html.theme--catppuccin-macchiato .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .button.is-info:active,html.theme--catppuccin-macchiato .button.is-info.is-active{background-color:#78cec1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info{background-color:#8bd5ca;border-color:#8bd5ca;box-shadow:none}html.theme--catppuccin-macchiato .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined{background-color:transparent;border-color:#8bd5ca;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-info.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-focused{background-color:#8bd5ca;border-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #8bd5ca #8bd5ca !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-outlined{background-color:transparent;border-color:#8bd5ca;box-shadow:none;color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #8bd5ca #8bd5ca !important}html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-info.is-light{background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .button.is-info.is-light:hover,html.theme--catppuccin-macchiato .button.is-info.is-light.is-hovered{background-color:#e7f6f4;border-color:transparent;color:#276d62}html.theme--catppuccin-macchiato .button.is-info.is-light:active,html.theme--catppuccin-macchiato .button.is-info.is-light.is-active{background-color:#ddf3f0;border-color:transparent;color:#276d62}html.theme--catppuccin-macchiato .button.is-success{background-color:#a6da95;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:hover,html.theme--catppuccin-macchiato .button.is-success.is-hovered{background-color:#9ed78c;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:focus,html.theme--catppuccin-macchiato .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success:focus:not(:active),html.theme--catppuccin-macchiato .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .button.is-success:active,html.theme--catppuccin-macchiato .button.is-success.is-active{background-color:#96d382;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success{background-color:#a6da95;border-color:#a6da95;box-shadow:none}html.theme--catppuccin-macchiato .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined{background-color:transparent;border-color:#a6da95;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-success.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-focused{background-color:#a6da95;border-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6da95 #a6da95 !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-outlined{background-color:transparent;border-color:#a6da95;box-shadow:none;color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6da95 #a6da95 !important}html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-success.is-light{background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .button.is-success.is-light:hover,html.theme--catppuccin-macchiato .button.is-success.is-light.is-hovered{background-color:#eaf6e6;border-color:transparent;color:#386e26}html.theme--catppuccin-macchiato .button.is-success.is-light:active,html.theme--catppuccin-macchiato .button.is-success.is-light.is-active{background-color:#e2f3dd;border-color:transparent;color:#386e26}html.theme--catppuccin-macchiato .button.is-warning{background-color:#eed49f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:hover,html.theme--catppuccin-macchiato .button.is-warning.is-hovered{background-color:#eccf94;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:focus,html.theme--catppuccin-macchiato .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning:focus:not(:active),html.theme--catppuccin-macchiato .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .button.is-warning:active,html.theme--catppuccin-macchiato .button.is-warning.is-active{background-color:#eaca89;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning{background-color:#eed49f;border-color:#eed49f;box-shadow:none}html.theme--catppuccin-macchiato .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined{background-color:transparent;border-color:#eed49f;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-warning.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-focused{background-color:#eed49f;border-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #eed49f #eed49f !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-macchiato .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-outlined{background-color:transparent;border-color:#eed49f;box-shadow:none;color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #eed49f #eed49f !important}html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .button.is-warning.is-light{background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-warning.is-light:hover,html.theme--catppuccin-macchiato .button.is-warning.is-light.is-hovered{background-color:#faf2e3;border-color:transparent;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-warning.is-light:active,html.theme--catppuccin-macchiato .button.is-warning.is-light.is-active{background-color:#f8eed8;border-color:transparent;color:#7e5c16}html.theme--catppuccin-macchiato .button.is-danger{background-color:#ed8796;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:hover,html.theme--catppuccin-macchiato .button.is-danger.is-hovered{background-color:#eb7c8c;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:focus,html.theme--catppuccin-macchiato .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger:focus:not(:active),html.theme--catppuccin-macchiato .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .button.is-danger:active,html.theme--catppuccin-macchiato .button.is-danger.is-active{background-color:#ea7183;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger{background-color:#ed8796;border-color:#ed8796;box-shadow:none}html.theme--catppuccin-macchiato .button.is-danger.is-inverted{background-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted:hover,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-macchiato .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined{background-color:transparent;border-color:#ed8796;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-danger.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-focused{background-color:#ed8796;border-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #ed8796 #ed8796 !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-macchiato .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-outlined{background-color:transparent;border-color:#ed8796;box-shadow:none;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ed8796 #ed8796 !important}html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-macchiato .button.is-danger.is-light{background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .button.is-danger.is-light:hover,html.theme--catppuccin-macchiato .button.is-danger.is-light.is-hovered{background-color:#fbe2e6;border-color:transparent;color:#971729}html.theme--catppuccin-macchiato .button.is-danger.is-light:active,html.theme--catppuccin-macchiato .button.is-danger.is-light.is-active{background-color:#f9d7dc;border-color:transparent;color:#971729}html.theme--catppuccin-macchiato .button.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-small:not(.is-rounded),html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-macchiato .button.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .button.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .button.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .button[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .button{background-color:#6e738d;border-color:#5b6078;box-shadow:none;opacity:.5}html.theme--catppuccin-macchiato .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-macchiato .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-macchiato .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-macchiato .button.is-static{background-color:#1e2030;border-color:#5b6078;color:#8087a2;box-shadow:none;pointer-events:none}html.theme--catppuccin-macchiato .button.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-macchiato .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-macchiato .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-macchiato .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-macchiato .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-macchiato .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-macchiato .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-macchiato .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-macchiato .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-macchiato .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-macchiato .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-macchiato .buttons.has-addons .button:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-macchiato .buttons.has-addons .button:focus,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-focused,html.theme--catppuccin-macchiato .buttons.has-addons .button:active,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-active,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-macchiato .buttons.has-addons .button:focus:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button:active:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-macchiato .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-macchiato .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .buttons.is-centered{justify-content:center}html.theme--catppuccin-macchiato .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-macchiato .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .button.is-responsive.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-macchiato .button.is-responsive,html.theme--catppuccin-macchiato .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-macchiato .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .button.is-responsive.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-macchiato .button.is-responsive,html.theme--catppuccin-macchiato .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-macchiato .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-macchiato .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-macchiato .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-macchiato .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-macchiato .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-macchiato .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-macchiato .content li+li{margin-top:0.25em}html.theme--catppuccin-macchiato .content p:not(:last-child),html.theme--catppuccin-macchiato .content dl:not(:last-child),html.theme--catppuccin-macchiato .content ol:not(:last-child),html.theme--catppuccin-macchiato .content ul:not(:last-child),html.theme--catppuccin-macchiato .content blockquote:not(:last-child),html.theme--catppuccin-macchiato .content pre:not(:last-child),html.theme--catppuccin-macchiato .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-macchiato .content h1,html.theme--catppuccin-macchiato .content h2,html.theme--catppuccin-macchiato .content h3,html.theme--catppuccin-macchiato .content h4,html.theme--catppuccin-macchiato .content h5,html.theme--catppuccin-macchiato .content h6{color:#cad3f5;font-weight:600;line-height:1.125}html.theme--catppuccin-macchiato .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-macchiato .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-macchiato .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-macchiato .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-macchiato .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-macchiato .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-macchiato .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-macchiato .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-macchiato .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-macchiato .content blockquote{background-color:#1e2030;border-left:5px solid #5b6078;padding:1.25em 1.5em}html.theme--catppuccin-macchiato .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-macchiato .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-macchiato .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-macchiato .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-macchiato .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-macchiato .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-macchiato .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-macchiato .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-macchiato .content ul ul ul{list-style-type:square}html.theme--catppuccin-macchiato .content dd{margin-left:2em}html.theme--catppuccin-macchiato .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-macchiato .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-macchiato .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-macchiato .content figure img{display:inline-block}html.theme--catppuccin-macchiato .content figure figcaption{font-style:italic}html.theme--catppuccin-macchiato .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-macchiato .content sup,html.theme--catppuccin-macchiato .content sub{font-size:75%}html.theme--catppuccin-macchiato .content table{width:100%}html.theme--catppuccin-macchiato .content table td,html.theme--catppuccin-macchiato .content table th{border:1px solid #5b6078;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-macchiato .content table th{color:#b5c1f1}html.theme--catppuccin-macchiato .content table th:not([align]){text-align:inherit}html.theme--catppuccin-macchiato .content table thead td,html.theme--catppuccin-macchiato .content table thead th{border-width:0 0 2px;color:#b5c1f1}html.theme--catppuccin-macchiato .content table tfoot td,html.theme--catppuccin-macchiato .content table tfoot th{border-width:2px 0 0;color:#b5c1f1}html.theme--catppuccin-macchiato .content table tbody tr:last-child td,html.theme--catppuccin-macchiato .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-macchiato .content .tabs li+li{margin-top:0}html.theme--catppuccin-macchiato .content.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-macchiato .content.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .content.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .content.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-macchiato .icon.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-macchiato .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-macchiato .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-macchiato .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-macchiato .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-macchiato .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-macchiato div.icon-text{display:flex}html.theme--catppuccin-macchiato .image,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-macchiato .image img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-macchiato .image img.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-macchiato .image.is-fullwidth,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .image.is-square img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-macchiato .image.is-square .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-macchiato .image.is-1by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-macchiato .image.is-1by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-macchiato .image.is-5by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-macchiato .image.is-4by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-macchiato .image.is-3by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-5by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-macchiato .image.is-5by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-16by9 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-macchiato .image.is-16by9 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-macchiato .image.is-2by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by1 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-macchiato .image.is-3by1 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-macchiato .image.is-4by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-macchiato .image.is-4by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by4 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-macchiato .image.is-3by4 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-macchiato .image.is-2by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-macchiato .image.is-2by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-macchiato .image.is-3by5 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-macchiato .image.is-3by5 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-macchiato .image.is-9by16 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-macchiato .image.is-9by16 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by2 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-macchiato .image.is-1by2 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-macchiato .image.is-1by3 img,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-macchiato .image.is-1by3 .has-ratio,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-macchiato .image.is-square,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-macchiato .image.is-1by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-macchiato .image.is-5by4,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-macchiato .image.is-4by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-macchiato .image.is-3by2,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-macchiato .image.is-5by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-macchiato .image.is-16by9,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-macchiato .image.is-2by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-macchiato .image.is-3by1,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-macchiato .image.is-4by5,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-macchiato .image.is-3by4,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-macchiato .image.is-2by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-macchiato .image.is-3by5,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-macchiato .image.is-9by16,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-macchiato .image.is-1by2,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-macchiato .image.is-1by3,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-macchiato .image.is-16x16,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-macchiato .image.is-24x24,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-macchiato .image.is-32x32,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-macchiato .image.is-48x48,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-macchiato .image.is-64x64,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-macchiato .image.is-96x96,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-macchiato .image.is-128x128,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-macchiato .notification{background-color:#1e2030;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-macchiato .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-macchiato .notification strong{color:currentColor}html.theme--catppuccin-macchiato .notification code,html.theme--catppuccin-macchiato .notification pre{background:#fff}html.theme--catppuccin-macchiato .notification pre code{background:transparent}html.theme--catppuccin-macchiato .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-macchiato .notification .title,html.theme--catppuccin-macchiato .notification .subtitle,html.theme--catppuccin-macchiato .notification .content{color:currentColor}html.theme--catppuccin-macchiato .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-dark,html.theme--catppuccin-macchiato .content kbd.notification{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .notification.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.notification.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .notification.is-primary.is-light,html.theme--catppuccin-macchiato .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .notification.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .notification.is-link.is-light{background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .notification.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-info.is-light{background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .notification.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-success.is-light{background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .notification.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .notification.is-warning.is-light{background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .notification.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .notification.is-danger.is-light{background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-macchiato .progress::-webkit-progress-bar{background-color:#494d64}html.theme--catppuccin-macchiato .progress::-webkit-progress-value{background-color:#8087a2}html.theme--catppuccin-macchiato .progress::-moz-progress-bar{background-color:#8087a2}html.theme--catppuccin-macchiato .progress::-ms-fill{background-color:#8087a2;border:none}html.theme--catppuccin-macchiato .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-macchiato .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-macchiato .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-macchiato .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-macchiato .content kbd.progress::-webkit-progress-value{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-macchiato .content kbd.progress::-moz-progress-bar{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark::-ms-fill,html.theme--catppuccin-macchiato .content kbd.progress::-ms-fill{background-color:#363a4f}html.theme--catppuccin-macchiato .progress.is-dark:indeterminate,html.theme--catppuccin-macchiato .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363a4f 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary::-ms-fill,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-primary:indeterminate,html.theme--catppuccin-macchiato .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #8aadf4 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-link::-webkit-progress-value{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link::-moz-progress-bar{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link::-ms-fill{background-color:#8aadf4}html.theme--catppuccin-macchiato .progress.is-link:indeterminate{background-image:linear-gradient(to right, #8aadf4 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-info::-webkit-progress-value{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info::-moz-progress-bar{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info::-ms-fill{background-color:#8bd5ca}html.theme--catppuccin-macchiato .progress.is-info:indeterminate{background-image:linear-gradient(to right, #8bd5ca 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-success::-webkit-progress-value{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success::-moz-progress-bar{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success::-ms-fill{background-color:#a6da95}html.theme--catppuccin-macchiato .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6da95 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-warning::-webkit-progress-value{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning::-moz-progress-bar{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning::-ms-fill{background-color:#eed49f}html.theme--catppuccin-macchiato .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #eed49f 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress.is-danger::-webkit-progress-value{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger::-moz-progress-bar{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger::-ms-fill{background-color:#ed8796}html.theme--catppuccin-macchiato .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #ed8796 30%, #494d64 30%)}html.theme--catppuccin-macchiato .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#494d64;background-image:linear-gradient(to right, #cad3f5 30%, #494d64 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-macchiato .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-macchiato .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-macchiato .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-macchiato .progress.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-macchiato .progress.is-medium{height:1.25rem}html.theme--catppuccin-macchiato .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-macchiato .table{background-color:#494d64;color:#cad3f5}html.theme--catppuccin-macchiato .table td,html.theme--catppuccin-macchiato .table th{border:1px solid #5b6078;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-macchiato .table td.is-white,html.theme--catppuccin-macchiato .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .table td.is-black,html.theme--catppuccin-macchiato .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .table td.is-light,html.theme--catppuccin-macchiato .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-dark,html.theme--catppuccin-macchiato .table th.is-dark{background-color:#363a4f;border-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .table td.is-primary,html.theme--catppuccin-macchiato .table th.is-primary{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-link,html.theme--catppuccin-macchiato .table th.is-link{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-info,html.theme--catppuccin-macchiato .table th.is-info{background-color:#8bd5ca;border-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-success,html.theme--catppuccin-macchiato .table th.is-success{background-color:#a6da95;border-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-warning,html.theme--catppuccin-macchiato .table th.is-warning{background-color:#eed49f;border-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .table td.is-danger,html.theme--catppuccin-macchiato .table th.is-danger{background-color:#ed8796;border-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .table td.is-narrow,html.theme--catppuccin-macchiato .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-macchiato .table td.is-selected,html.theme--catppuccin-macchiato .table th.is-selected{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table td.is-selected a,html.theme--catppuccin-macchiato .table td.is-selected strong,html.theme--catppuccin-macchiato .table th.is-selected a,html.theme--catppuccin-macchiato .table th.is-selected strong{color:currentColor}html.theme--catppuccin-macchiato .table td.is-vcentered,html.theme--catppuccin-macchiato .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-macchiato .table th{color:#b5c1f1}html.theme--catppuccin-macchiato .table th:not([align]){text-align:left}html.theme--catppuccin-macchiato .table tr.is-selected{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .table tr.is-selected a,html.theme--catppuccin-macchiato .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-macchiato .table tr.is-selected td,html.theme--catppuccin-macchiato .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-macchiato .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table thead td,html.theme--catppuccin-macchiato .table thead th{border-width:0 0 2px;color:#b5c1f1}html.theme--catppuccin-macchiato .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table tfoot td,html.theme--catppuccin-macchiato .table tfoot th{border-width:2px 0 0;color:#b5c1f1}html.theme--catppuccin-macchiato .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .table tbody tr:last-child td,html.theme--catppuccin-macchiato .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-macchiato .table.is-bordered td,html.theme--catppuccin-macchiato .table.is-bordered th{border-width:1px}html.theme--catppuccin-macchiato .table.is-bordered tr:last-child td,html.theme--catppuccin-macchiato .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-macchiato .table.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#363a4f}html.theme--catppuccin-macchiato .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#363a4f}html.theme--catppuccin-macchiato .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#3a3e55}html.theme--catppuccin-macchiato .table.is-narrow td,html.theme--catppuccin-macchiato .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-macchiato .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#363a4f}html.theme--catppuccin-macchiato .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-macchiato .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .tags .tag,html.theme--catppuccin-macchiato .tags .content kbd,html.theme--catppuccin-macchiato .content .tags kbd,html.theme--catppuccin-macchiato .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-macchiato .tags .tag:not(:last-child),html.theme--catppuccin-macchiato .tags .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags kbd:not(:last-child),html.theme--catppuccin-macchiato .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-macchiato .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-macchiato .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-macchiato .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-macchiato .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-macchiato .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-macchiato .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-macchiato .tags.is-centered{justify-content:center}html.theme--catppuccin-macchiato .tags.is-centered .tag,html.theme--catppuccin-macchiato .tags.is-centered .content kbd,html.theme--catppuccin-macchiato .content .tags.is-centered kbd,html.theme--catppuccin-macchiato .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-macchiato .tags.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .tags.is-right .tag:not(:first-child),html.theme--catppuccin-macchiato .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-macchiato .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-macchiato .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-macchiato .tags.is-right .tag:not(:last-child),html.theme--catppuccin-macchiato .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-macchiato .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-macchiato .tags.has-addons .tag,html.theme--catppuccin-macchiato .tags.has-addons .content kbd,html.theme--catppuccin-macchiato .content .tags.has-addons kbd,html.theme--catppuccin-macchiato .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-macchiato .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-macchiato .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-macchiato .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-macchiato .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-macchiato .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-macchiato .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-macchiato .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-macchiato .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-macchiato .tag:not(body),html.theme--catppuccin-macchiato .content kbd:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#1e2030;border-radius:.4em;color:#cad3f5;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-macchiato .tag:not(body) .delete,html.theme--catppuccin-macchiato .content kbd:not(body) .delete,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-macchiato .tag.is-white:not(body),html.theme--catppuccin-macchiato .content kbd.is-white:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .tag.is-black:not(body),html.theme--catppuccin-macchiato .content kbd.is-black:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .tag.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-dark:not(body),html.theme--catppuccin-macchiato .content kbd:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-macchiato .content .docstring>section>kbd:not(body){background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .tag.is-primary:not(body),html.theme--catppuccin-macchiato .content kbd.is-primary:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .tag.is-primary.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .tag.is-link:not(body),html.theme--catppuccin-macchiato .content kbd.is-link:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .tag.is-link.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-link.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ecf2fd;color:#0e3b95}html.theme--catppuccin-macchiato .tag.is-info:not(body),html.theme--catppuccin-macchiato .content kbd.is-info:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-info.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-info.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#f0faf8;color:#276d62}html.theme--catppuccin-macchiato .tag.is-success:not(body),html.theme--catppuccin-macchiato .content kbd.is-success:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-success.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-success.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f2faf0;color:#386e26}html.theme--catppuccin-macchiato .tag.is-warning:not(body),html.theme--catppuccin-macchiato .content kbd.is-warning:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .tag.is-warning.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fcf7ee;color:#7e5c16}html.theme--catppuccin-macchiato .tag.is-danger:not(body),html.theme--catppuccin-macchiato .content kbd.is-danger:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .tag.is-danger.is-light:not(body),html.theme--catppuccin-macchiato .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fcedef;color:#971729}html.theme--catppuccin-macchiato .tag.is-normal:not(body),html.theme--catppuccin-macchiato .content kbd.is-normal:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-macchiato .tag.is-medium:not(body),html.theme--catppuccin-macchiato .content kbd.is-medium:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-macchiato .tag.is-large:not(body),html.theme--catppuccin-macchiato .content kbd.is-large:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-macchiato .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-macchiato .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-macchiato .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-macchiato .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-macchiato .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-macchiato .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-macchiato .tag.is-delete:not(body),html.theme--catppuccin-macchiato .content kbd.is-delete:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::before,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::before,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-macchiato .tag.is-delete:not(body)::after,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::after,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::before,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::before,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-macchiato .tag.is-delete:not(body)::after,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body)::after,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-macchiato .tag.is-delete:not(body):hover,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):hover,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-macchiato .tag.is-delete:not(body):focus,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):focus,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#141620}html.theme--catppuccin-macchiato .tag.is-delete:not(body):active,html.theme--catppuccin-macchiato .content kbd.is-delete:not(body):active,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#0a0b11}html.theme--catppuccin-macchiato .tag.is-rounded:not(body),html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-macchiato .content kbd.is-rounded:not(body),html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-macchiato a.tag:hover,html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-macchiato .title,html.theme--catppuccin-macchiato .subtitle{word-break:break-word}html.theme--catppuccin-macchiato .title em,html.theme--catppuccin-macchiato .title span,html.theme--catppuccin-macchiato .subtitle em,html.theme--catppuccin-macchiato .subtitle span{font-weight:inherit}html.theme--catppuccin-macchiato .title sub,html.theme--catppuccin-macchiato .subtitle sub{font-size:.75em}html.theme--catppuccin-macchiato .title sup,html.theme--catppuccin-macchiato .subtitle sup{font-size:.75em}html.theme--catppuccin-macchiato .title .tag,html.theme--catppuccin-macchiato .title .content kbd,html.theme--catppuccin-macchiato .content .title kbd,html.theme--catppuccin-macchiato .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-macchiato .subtitle .tag,html.theme--catppuccin-macchiato .subtitle .content kbd,html.theme--catppuccin-macchiato .content .subtitle kbd,html.theme--catppuccin-macchiato .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-macchiato .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-macchiato .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-macchiato .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-macchiato .title.is-1{font-size:3rem}html.theme--catppuccin-macchiato .title.is-2{font-size:2.5rem}html.theme--catppuccin-macchiato .title.is-3{font-size:2rem}html.theme--catppuccin-macchiato .title.is-4{font-size:1.5rem}html.theme--catppuccin-macchiato .title.is-5{font-size:1.25rem}html.theme--catppuccin-macchiato .title.is-6{font-size:1rem}html.theme--catppuccin-macchiato .title.is-7{font-size:.75rem}html.theme--catppuccin-macchiato .subtitle{color:#6e738d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-macchiato .subtitle strong{color:#6e738d;font-weight:600}html.theme--catppuccin-macchiato .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-macchiato .subtitle.is-1{font-size:3rem}html.theme--catppuccin-macchiato .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-macchiato .subtitle.is-3{font-size:2rem}html.theme--catppuccin-macchiato .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-macchiato .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-macchiato .subtitle.is-6{font-size:1rem}html.theme--catppuccin-macchiato .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-macchiato .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-macchiato .number{align-items:center;background-color:#1e2030;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{background-color:#24273a;border-color:#5b6078;border-radius:.4em;color:#8087a2}html.theme--catppuccin-macchiato .select select::-moz-placeholder,html.theme--catppuccin-macchiato .textarea::-moz-placeholder,html.theme--catppuccin-macchiato .input::-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select::-webkit-input-placeholder,html.theme--catppuccin-macchiato .textarea::-webkit-input-placeholder,html.theme--catppuccin-macchiato .input::-webkit-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:-moz-placeholder,html.theme--catppuccin-macchiato .textarea:-moz-placeholder,html.theme--catppuccin-macchiato .input:-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:-ms-input-placeholder,html.theme--catppuccin-macchiato .textarea:-ms-input-placeholder,html.theme--catppuccin-macchiato .input:-ms-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-macchiato .select select:hover,html.theme--catppuccin-macchiato .textarea:hover,html.theme--catppuccin-macchiato .input:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-macchiato .select select.is-hovered,html.theme--catppuccin-macchiato .is-hovered.textarea,html.theme--catppuccin-macchiato .is-hovered.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#6e738d}html.theme--catppuccin-macchiato .select select:focus,html.theme--catppuccin-macchiato .textarea:focus,html.theme--catppuccin-macchiato .input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-macchiato .select select.is-focused,html.theme--catppuccin-macchiato .is-focused.textarea,html.theme--catppuccin-macchiato .is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .select select:active,html.theme--catppuccin-macchiato .textarea:active,html.theme--catppuccin-macchiato .input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-macchiato .select select.is-active,html.theme--catppuccin-macchiato .is-active.textarea,html.theme--catppuccin-macchiato .is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#8aadf4;box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select select[disabled],html.theme--catppuccin-macchiato .textarea[disabled],html.theme--catppuccin-macchiato .input[disabled],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .select select,fieldset[disabled] html.theme--catppuccin-macchiato .textarea,fieldset[disabled] html.theme--catppuccin-macchiato .input,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{background-color:#6e738d;border-color:#1e2030;box-shadow:none;color:#f5f7fd}html.theme--catppuccin-macchiato .select select[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato .input[disabled]::-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato .input[disabled]:-moz-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(245,247,253,0.3)}html.theme--catppuccin-macchiato .textarea,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-macchiato .textarea[readonly],html.theme--catppuccin-macchiato .input[readonly],html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-macchiato .is-white.textarea,html.theme--catppuccin-macchiato .is-white.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-macchiato .is-white.textarea:focus,html.theme--catppuccin-macchiato .is-white.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-macchiato .is-white.is-focused.textarea,html.theme--catppuccin-macchiato .is-white.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-white.textarea:active,html.theme--catppuccin-macchiato .is-white.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-macchiato .is-white.is-active.textarea,html.theme--catppuccin-macchiato .is-white.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .is-black.textarea,html.theme--catppuccin-macchiato .is-black.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-macchiato .is-black.textarea:focus,html.theme--catppuccin-macchiato .is-black.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-macchiato .is-black.is-focused.textarea,html.theme--catppuccin-macchiato .is-black.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-black.textarea:active,html.theme--catppuccin-macchiato .is-black.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-macchiato .is-black.is-active.textarea,html.theme--catppuccin-macchiato .is-black.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .is-light.textarea,html.theme--catppuccin-macchiato .is-light.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-macchiato .is-light.textarea:focus,html.theme--catppuccin-macchiato .is-light.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-macchiato .is-light.is-focused.textarea,html.theme--catppuccin-macchiato .is-light.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-light.textarea:active,html.theme--catppuccin-macchiato .is-light.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-macchiato .is-light.is-active.textarea,html.theme--catppuccin-macchiato .is-light.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .is-dark.textarea,html.theme--catppuccin-macchiato .content kbd.textarea,html.theme--catppuccin-macchiato .is-dark.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-macchiato .content kbd.input{border-color:#363a4f}html.theme--catppuccin-macchiato .is-dark.textarea:focus,html.theme--catppuccin-macchiato .content kbd.textarea:focus,html.theme--catppuccin-macchiato .is-dark.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-macchiato .content kbd.input:focus,html.theme--catppuccin-macchiato .is-dark.is-focused.textarea,html.theme--catppuccin-macchiato .content kbd.is-focused.textarea,html.theme--catppuccin-macchiato .is-dark.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .content kbd.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-dark.textarea:active,html.theme--catppuccin-macchiato .content kbd.textarea:active,html.theme--catppuccin-macchiato .is-dark.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-macchiato .content kbd.input:active,html.theme--catppuccin-macchiato .is-dark.is-active.textarea,html.theme--catppuccin-macchiato .content kbd.is-active.textarea,html.theme--catppuccin-macchiato .is-dark.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .content kbd.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .is-primary.textarea,html.theme--catppuccin-macchiato .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.input.docs-sourcelink{border-color:#8aadf4}html.theme--catppuccin-macchiato .is-primary.textarea:focus,html.theme--catppuccin-macchiato .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-macchiato .is-primary.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-macchiato .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-macchiato .is-primary.is-focused.textarea,html.theme--catppuccin-macchiato .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.textarea:active,html.theme--catppuccin-macchiato .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-macchiato .is-primary.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-macchiato .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-macchiato .is-primary.is-active.textarea,html.theme--catppuccin-macchiato .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-macchiato .is-primary.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-macchiato .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .is-link.textarea,html.theme--catppuccin-macchiato .is-link.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#8aadf4}html.theme--catppuccin-macchiato .is-link.textarea:focus,html.theme--catppuccin-macchiato .is-link.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-macchiato .is-link.is-focused.textarea,html.theme--catppuccin-macchiato .is-link.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-link.textarea:active,html.theme--catppuccin-macchiato .is-link.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-macchiato .is-link.is-active.textarea,html.theme--catppuccin-macchiato .is-link.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .is-info.textarea,html.theme--catppuccin-macchiato .is-info.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#8bd5ca}html.theme--catppuccin-macchiato .is-info.textarea:focus,html.theme--catppuccin-macchiato .is-info.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-macchiato .is-info.is-focused.textarea,html.theme--catppuccin-macchiato .is-info.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-info.textarea:active,html.theme--catppuccin-macchiato .is-info.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-macchiato .is-info.is-active.textarea,html.theme--catppuccin-macchiato .is-info.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .is-success.textarea,html.theme--catppuccin-macchiato .is-success.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6da95}html.theme--catppuccin-macchiato .is-success.textarea:focus,html.theme--catppuccin-macchiato .is-success.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-macchiato .is-success.is-focused.textarea,html.theme--catppuccin-macchiato .is-success.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-success.textarea:active,html.theme--catppuccin-macchiato .is-success.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-macchiato .is-success.is-active.textarea,html.theme--catppuccin-macchiato .is-success.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .is-warning.textarea,html.theme--catppuccin-macchiato .is-warning.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#eed49f}html.theme--catppuccin-macchiato .is-warning.textarea:focus,html.theme--catppuccin-macchiato .is-warning.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-macchiato .is-warning.is-focused.textarea,html.theme--catppuccin-macchiato .is-warning.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-warning.textarea:active,html.theme--catppuccin-macchiato .is-warning.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-macchiato .is-warning.is-active.textarea,html.theme--catppuccin-macchiato .is-warning.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .is-danger.textarea,html.theme--catppuccin-macchiato .is-danger.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#ed8796}html.theme--catppuccin-macchiato .is-danger.textarea:focus,html.theme--catppuccin-macchiato .is-danger.input:focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-macchiato .is-danger.is-focused.textarea,html.theme--catppuccin-macchiato .is-danger.is-focused.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-macchiato .is-danger.textarea:active,html.theme--catppuccin-macchiato .is-danger.input:active,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-macchiato .is-danger.is-active.textarea,html.theme--catppuccin-macchiato .is-danger.is-active.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .is-small.textarea,html.theme--catppuccin-macchiato .is-small.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-macchiato .is-medium.textarea,html.theme--catppuccin-macchiato .is-medium.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .is-large.textarea,html.theme--catppuccin-macchiato .is-large.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .is-fullwidth.textarea,html.theme--catppuccin-macchiato .is-fullwidth.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-macchiato .is-inline.textarea,html.theme--catppuccin-macchiato .is-inline.input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-macchiato .input.is-rounded,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-macchiato .input.is-static,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-macchiato .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-macchiato .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-macchiato .textarea[rows]{height:initial}html.theme--catppuccin-macchiato .textarea.has-fixed-size{resize:none}html.theme--catppuccin-macchiato .radio,html.theme--catppuccin-macchiato .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-macchiato .radio input,html.theme--catppuccin-macchiato .checkbox input{cursor:pointer}html.theme--catppuccin-macchiato .radio:hover,html.theme--catppuccin-macchiato .checkbox:hover{color:#91d7e3}html.theme--catppuccin-macchiato .radio[disabled],html.theme--catppuccin-macchiato .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-macchiato .radio,fieldset[disabled] html.theme--catppuccin-macchiato .checkbox,html.theme--catppuccin-macchiato .radio input[disabled],html.theme--catppuccin-macchiato .checkbox input[disabled]{color:#f5f7fd;cursor:not-allowed}html.theme--catppuccin-macchiato .radio+.radio{margin-left:.5em}html.theme--catppuccin-macchiato .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-macchiato .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading)::after{border-color:#8aadf4;right:1.125em;z-index:4}html.theme--catppuccin-macchiato .select.is-rounded select,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-macchiato .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-macchiato .select select::-ms-expand{display:none}html.theme--catppuccin-macchiato .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-macchiato .select select:hover{border-color:#1e2030}html.theme--catppuccin-macchiato .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-macchiato .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-macchiato .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-macchiato .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#91d7e3}html.theme--catppuccin-macchiato .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-macchiato .select.is-white select{border-color:#fff}html.theme--catppuccin-macchiato .select.is-white select:hover,html.theme--catppuccin-macchiato .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-macchiato .select.is-white select:focus,html.theme--catppuccin-macchiato .select.is-white select.is-focused,html.theme--catppuccin-macchiato .select.is-white select:active,html.theme--catppuccin-macchiato .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-macchiato .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-macchiato .select.is-black select:hover,html.theme--catppuccin-macchiato .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-macchiato .select.is-black select:focus,html.theme--catppuccin-macchiato .select.is-black select.is-focused,html.theme--catppuccin-macchiato .select.is-black select:active,html.theme--catppuccin-macchiato .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-macchiato .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-macchiato .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-macchiato .select.is-light select:hover,html.theme--catppuccin-macchiato .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-macchiato .select.is-light select:focus,html.theme--catppuccin-macchiato .select.is-light select.is-focused,html.theme--catppuccin-macchiato .select.is-light select:active,html.theme--catppuccin-macchiato .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-macchiato .select.is-dark:not(:hover)::after,html.theme--catppuccin-macchiato .content kbd.select:not(:hover)::after{border-color:#363a4f}html.theme--catppuccin-macchiato .select.is-dark select,html.theme--catppuccin-macchiato .content kbd.select select{border-color:#363a4f}html.theme--catppuccin-macchiato .select.is-dark select:hover,html.theme--catppuccin-macchiato .content kbd.select select:hover,html.theme--catppuccin-macchiato .select.is-dark select.is-hovered,html.theme--catppuccin-macchiato .content kbd.select select.is-hovered{border-color:#2c2f40}html.theme--catppuccin-macchiato .select.is-dark select:focus,html.theme--catppuccin-macchiato .content kbd.select select:focus,html.theme--catppuccin-macchiato .select.is-dark select.is-focused,html.theme--catppuccin-macchiato .content kbd.select select.is-focused,html.theme--catppuccin-macchiato .select.is-dark select:active,html.theme--catppuccin-macchiato .content kbd.select select:active,html.theme--catppuccin-macchiato .select.is-dark select.is-active,html.theme--catppuccin-macchiato .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,58,79,0.25)}html.theme--catppuccin-macchiato .select.is-primary:not(:hover)::after,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-primary select,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-primary select:hover,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-macchiato .select.is-primary select.is-hovered,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#739df2}html.theme--catppuccin-macchiato .select.is-primary select:focus,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-macchiato .select.is-primary select.is-focused,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-macchiato .select.is-primary select:active,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-macchiato .select.is-primary select.is-active,html.theme--catppuccin-macchiato .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select.is-link:not(:hover)::after{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-link select{border-color:#8aadf4}html.theme--catppuccin-macchiato .select.is-link select:hover,html.theme--catppuccin-macchiato .select.is-link select.is-hovered{border-color:#739df2}html.theme--catppuccin-macchiato .select.is-link select:focus,html.theme--catppuccin-macchiato .select.is-link select.is-focused,html.theme--catppuccin-macchiato .select.is-link select:active,html.theme--catppuccin-macchiato .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(138,173,244,0.25)}html.theme--catppuccin-macchiato .select.is-info:not(:hover)::after{border-color:#8bd5ca}html.theme--catppuccin-macchiato .select.is-info select{border-color:#8bd5ca}html.theme--catppuccin-macchiato .select.is-info select:hover,html.theme--catppuccin-macchiato .select.is-info select.is-hovered{border-color:#78cec1}html.theme--catppuccin-macchiato .select.is-info select:focus,html.theme--catppuccin-macchiato .select.is-info select.is-focused,html.theme--catppuccin-macchiato .select.is-info select:active,html.theme--catppuccin-macchiato .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(139,213,202,0.25)}html.theme--catppuccin-macchiato .select.is-success:not(:hover)::after{border-color:#a6da95}html.theme--catppuccin-macchiato .select.is-success select{border-color:#a6da95}html.theme--catppuccin-macchiato .select.is-success select:hover,html.theme--catppuccin-macchiato .select.is-success select.is-hovered{border-color:#96d382}html.theme--catppuccin-macchiato .select.is-success select:focus,html.theme--catppuccin-macchiato .select.is-success select.is-focused,html.theme--catppuccin-macchiato .select.is-success select:active,html.theme--catppuccin-macchiato .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,218,149,0.25)}html.theme--catppuccin-macchiato .select.is-warning:not(:hover)::after{border-color:#eed49f}html.theme--catppuccin-macchiato .select.is-warning select{border-color:#eed49f}html.theme--catppuccin-macchiato .select.is-warning select:hover,html.theme--catppuccin-macchiato .select.is-warning select.is-hovered{border-color:#eaca89}html.theme--catppuccin-macchiato .select.is-warning select:focus,html.theme--catppuccin-macchiato .select.is-warning select.is-focused,html.theme--catppuccin-macchiato .select.is-warning select:active,html.theme--catppuccin-macchiato .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(238,212,159,0.25)}html.theme--catppuccin-macchiato .select.is-danger:not(:hover)::after{border-color:#ed8796}html.theme--catppuccin-macchiato .select.is-danger select{border-color:#ed8796}html.theme--catppuccin-macchiato .select.is-danger select:hover,html.theme--catppuccin-macchiato .select.is-danger select.is-hovered{border-color:#ea7183}html.theme--catppuccin-macchiato .select.is-danger select:focus,html.theme--catppuccin-macchiato .select.is-danger select.is-focused,html.theme--catppuccin-macchiato .select.is-danger select:active,html.theme--catppuccin-macchiato .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(237,135,150,0.25)}html.theme--catppuccin-macchiato .select.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-macchiato .select.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .select.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .select.is-disabled::after{border-color:#f5f7fd !important;opacity:0.5}html.theme--catppuccin-macchiato .select.is-fullwidth{width:100%}html.theme--catppuccin-macchiato .select.is-fullwidth select{width:100%}html.theme--catppuccin-macchiato .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-macchiato .select.is-loading.is-small:after,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-macchiato .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-macchiato .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-macchiato .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-macchiato .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:hover .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:focus .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-white:active .file-cta,html.theme--catppuccin-macchiato .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-macchiato .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-black:hover .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-black:focus .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-black:active .file-cta,html.theme--catppuccin-macchiato .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:hover .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:focus .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-light:active .file-cta,html.theme--catppuccin-macchiato .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-dark .file-cta,html.theme--catppuccin-macchiato .content kbd.file .file-cta{background-color:#363a4f;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-dark:hover .file-cta,html.theme--catppuccin-macchiato .content kbd.file:hover .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-hovered .file-cta{background-color:#313447;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-dark:focus .file-cta,html.theme--catppuccin-macchiato .content kbd.file:focus .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-focused .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,58,79,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-dark:active .file-cta,html.theme--catppuccin-macchiato .content kbd.file:active .file-cta,html.theme--catppuccin-macchiato .file.is-dark.is-active .file-cta,html.theme--catppuccin-macchiato .content kbd.file.is-active .file-cta{background-color:#2c2f40;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary:hover .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-primary:focus .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-focused .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(138,173,244,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-primary:active .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-macchiato .file.is-primary.is-active .file-cta,html.theme--catppuccin-macchiato .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link .file-cta{background-color:#8aadf4;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link:hover .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-hovered .file-cta{background-color:#7ea5f3;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-link:focus .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(138,173,244,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-link:active .file-cta,html.theme--catppuccin-macchiato .file.is-link.is-active .file-cta{background-color:#739df2;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-info .file-cta{background-color:#8bd5ca;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:hover .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-hovered .file-cta{background-color:#82d2c6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:focus .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(139,213,202,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-info:active .file-cta,html.theme--catppuccin-macchiato .file.is-info.is-active .file-cta{background-color:#78cec1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success .file-cta{background-color:#a6da95;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:hover .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-hovered .file-cta{background-color:#9ed78c;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:focus .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,218,149,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-success:active .file-cta,html.theme--catppuccin-macchiato .file.is-success.is-active .file-cta{background-color:#96d382;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning .file-cta{background-color:#eed49f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:hover .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-hovered .file-cta{background-color:#eccf94;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:focus .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(238,212,159,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-warning:active .file-cta,html.theme--catppuccin-macchiato .file.is-warning.is-active .file-cta{background-color:#eaca89;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .file.is-danger .file-cta{background-color:#ed8796;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-danger:hover .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-hovered .file-cta{background-color:#eb7c8c;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-danger:focus .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(237,135,150,0.25);color:#fff}html.theme--catppuccin-macchiato .file.is-danger:active .file-cta,html.theme--catppuccin-macchiato .file.is-danger.is-active .file-cta{background-color:#ea7183;border-color:transparent;color:#fff}html.theme--catppuccin-macchiato .file.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-macchiato .file.is-normal{font-size:1rem}html.theme--catppuccin-macchiato .file.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-macchiato .file.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-macchiato .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-macchiato .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-macchiato .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-macchiato .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-macchiato .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-macchiato .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-macchiato .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-macchiato .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-macchiato .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-macchiato .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-macchiato .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-macchiato .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-macchiato .file.is-centered{justify-content:center}html.theme--catppuccin-macchiato .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-macchiato .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-macchiato .file.is-right{justify-content:flex-end}html.theme--catppuccin-macchiato .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-macchiato .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-macchiato .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-macchiato .file-label:hover .file-cta{background-color:#313447;color:#b5c1f1}html.theme--catppuccin-macchiato .file-label:hover .file-name{border-color:#565a71}html.theme--catppuccin-macchiato .file-label:active .file-cta{background-color:#2c2f40;color:#b5c1f1}html.theme--catppuccin-macchiato .file-label:active .file-name{border-color:#505469}html.theme--catppuccin-macchiato .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-macchiato .file-cta,html.theme--catppuccin-macchiato .file-name{border-color:#5b6078;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-macchiato .file-cta{background-color:#363a4f;color:#cad3f5}html.theme--catppuccin-macchiato .file-name{border-color:#5b6078;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-macchiato .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-macchiato .file-icon .fa{font-size:14px}html.theme--catppuccin-macchiato .label{color:#b5c1f1;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-macchiato .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-macchiato .label.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-macchiato .label.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .label.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-macchiato .help.is-white{color:#fff}html.theme--catppuccin-macchiato .help.is-black{color:#0a0a0a}html.theme--catppuccin-macchiato .help.is-light{color:#f5f5f5}html.theme--catppuccin-macchiato .help.is-dark,html.theme--catppuccin-macchiato .content kbd.help{color:#363a4f}html.theme--catppuccin-macchiato .help.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.help.docs-sourcelink{color:#8aadf4}html.theme--catppuccin-macchiato .help.is-link{color:#8aadf4}html.theme--catppuccin-macchiato .help.is-info{color:#8bd5ca}html.theme--catppuccin-macchiato .help.is-success{color:#a6da95}html.theme--catppuccin-macchiato .help.is-warning{color:#eed49f}html.theme--catppuccin-macchiato .help.is-danger{color:#ed8796}html.theme--catppuccin-macchiato .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-macchiato .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-macchiato .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-macchiato .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-macchiato .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-macchiato .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-macchiato .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-macchiato .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-macchiato .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-macchiato .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-macchiato .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-macchiato .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field.is-horizontal{display:flex}}html.theme--catppuccin-macchiato .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-macchiato .field-label.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-macchiato .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-macchiato .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-macchiato .field-body .field{margin-bottom:0}html.theme--catppuccin-macchiato .field-body>.field{flex-shrink:1}html.theme--catppuccin-macchiato .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-macchiato .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-macchiato .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-macchiato .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select:focus~.icon{color:#363a4f}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-macchiato .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-macchiato .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-macchiato .control.has-icons-left .icon,html.theme--catppuccin-macchiato .control.has-icons-right .icon{color:#5b6078;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-macchiato .control.has-icons-left .input,html.theme--catppuccin-macchiato .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-macchiato .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-macchiato .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-macchiato .control.has-icons-right .input,html.theme--catppuccin-macchiato .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-macchiato .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-macchiato .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-macchiato .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-macchiato .control.is-loading.is-small:after,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-macchiato .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-macchiato .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-macchiato .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-macchiato .breadcrumb a{align-items:center;color:#8aadf4;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-macchiato .breadcrumb a:hover{color:#91d7e3}html.theme--catppuccin-macchiato .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-macchiato .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-macchiato .breadcrumb li.is-active a{color:#b5c1f1;cursor:default;pointer-events:none}html.theme--catppuccin-macchiato .breadcrumb li+li::before{color:#6e738d;content:"\0002f"}html.theme--catppuccin-macchiato .breadcrumb ul,html.theme--catppuccin-macchiato .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-macchiato .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-macchiato .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-macchiato .breadcrumb.is-centered ol,html.theme--catppuccin-macchiato .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-macchiato .breadcrumb.is-right ol,html.theme--catppuccin-macchiato .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-macchiato .breadcrumb.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-macchiato .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-macchiato .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-macchiato .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-macchiato .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-macchiato .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#cad3f5;max-width:100%;position:relative}html.theme--catppuccin-macchiato .card-footer:first-child,html.theme--catppuccin-macchiato .card-content:first-child,html.theme--catppuccin-macchiato .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-macchiato .card-footer:last-child,html.theme--catppuccin-macchiato .card-content:last-child,html.theme--catppuccin-macchiato .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-macchiato .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-macchiato .card-header-title{align-items:center;color:#b5c1f1;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-macchiato .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-macchiato .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-macchiato .card-image{display:block;position:relative}html.theme--catppuccin-macchiato .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-macchiato .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-macchiato .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-macchiato .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-macchiato .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-macchiato .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-macchiato .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-macchiato .dropdown.is-active .dropdown-menu,html.theme--catppuccin-macchiato .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-macchiato .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-macchiato .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-macchiato .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-macchiato .dropdown-content{background-color:#1e2030;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-macchiato .dropdown-item{color:#cad3f5;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-macchiato a.dropdown-item,html.theme--catppuccin-macchiato button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-macchiato a.dropdown-item:hover,html.theme--catppuccin-macchiato button.dropdown-item:hover{background-color:#1e2030;color:#0a0a0a}html.theme--catppuccin-macchiato a.dropdown-item.is-active,html.theme--catppuccin-macchiato button.dropdown-item.is-active{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-macchiato .level{align-items:center;justify-content:space-between}html.theme--catppuccin-macchiato .level code{border-radius:.4em}html.theme--catppuccin-macchiato .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-macchiato .level.is-mobile{display:flex}html.theme--catppuccin-macchiato .level.is-mobile .level-left,html.theme--catppuccin-macchiato .level.is-mobile .level-right{display:flex}html.theme--catppuccin-macchiato .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-macchiato .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-macchiato .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level{display:flex}html.theme--catppuccin-macchiato .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-macchiato .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-macchiato .level-item .title,html.theme--catppuccin-macchiato .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-macchiato .level-left,html.theme--catppuccin-macchiato .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .level-left .level-item.is-flexible,html.theme--catppuccin-macchiato .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-left .level-item:not(:last-child),html.theme--catppuccin-macchiato .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-macchiato .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-left{display:flex}}html.theme--catppuccin-macchiato .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .level-right{display:flex}}html.theme--catppuccin-macchiato .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-macchiato .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-macchiato .media .media{border-top:1px solid rgba(91,96,120,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-macchiato .media .media .content:not(:last-child),html.theme--catppuccin-macchiato .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-macchiato .media .media .media{padding-top:.5rem}html.theme--catppuccin-macchiato .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-macchiato .media+.media{border-top:1px solid rgba(91,96,120,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-macchiato .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-macchiato .media-left,html.theme--catppuccin-macchiato .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .media-left{margin-right:1rem}html.theme--catppuccin-macchiato .media-right{margin-left:1rem}html.theme--catppuccin-macchiato .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .media-content{overflow-x:auto}}html.theme--catppuccin-macchiato .menu{font-size:1rem}html.theme--catppuccin-macchiato .menu.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-macchiato .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .menu.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .menu-list{line-height:1.25}html.theme--catppuccin-macchiato .menu-list a{border-radius:3px;color:#cad3f5;display:block;padding:0.5em 0.75em}html.theme--catppuccin-macchiato .menu-list a:hover{background-color:#1e2030;color:#b5c1f1}html.theme--catppuccin-macchiato .menu-list a.is-active{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .menu-list li ul{border-left:1px solid #5b6078;margin:.75em;padding-left:.75em}html.theme--catppuccin-macchiato .menu-label{color:#f5f7fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-macchiato .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-macchiato .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-macchiato .message{background-color:#1e2030;border-radius:.4em;font-size:1rem}html.theme--catppuccin-macchiato .message strong{color:currentColor}html.theme--catppuccin-macchiato .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-macchiato .message.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-macchiato .message.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .message.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .message.is-white{background-color:#fff}html.theme--catppuccin-macchiato .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-macchiato .message.is-black{background-color:#fafafa}html.theme--catppuccin-macchiato .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-macchiato .message.is-light{background-color:#fafafa}html.theme--catppuccin-macchiato .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-macchiato .message.is-dark,html.theme--catppuccin-macchiato .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-macchiato .message.is-dark .message-header,html.theme--catppuccin-macchiato .content kbd.message .message-header{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .message.is-dark .message-body,html.theme--catppuccin-macchiato .content kbd.message .message-body{border-color:#363a4f}html.theme--catppuccin-macchiato .message.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.message.docs-sourcelink{background-color:#ecf2fd}html.theme--catppuccin-macchiato .message.is-primary .message-header,html.theme--catppuccin-macchiato .docstring>section>a.message.docs-sourcelink .message-header{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .message.is-primary .message-body,html.theme--catppuccin-macchiato .docstring>section>a.message.docs-sourcelink .message-body{border-color:#8aadf4;color:#0e3b95}html.theme--catppuccin-macchiato .message.is-link{background-color:#ecf2fd}html.theme--catppuccin-macchiato .message.is-link .message-header{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .message.is-link .message-body{border-color:#8aadf4;color:#0e3b95}html.theme--catppuccin-macchiato .message.is-info{background-color:#f0faf8}html.theme--catppuccin-macchiato .message.is-info .message-header{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-info .message-body{border-color:#8bd5ca;color:#276d62}html.theme--catppuccin-macchiato .message.is-success{background-color:#f2faf0}html.theme--catppuccin-macchiato .message.is-success .message-header{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-success .message-body{border-color:#a6da95;color:#386e26}html.theme--catppuccin-macchiato .message.is-warning{background-color:#fcf7ee}html.theme--catppuccin-macchiato .message.is-warning .message-header{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .message.is-warning .message-body{border-color:#eed49f;color:#7e5c16}html.theme--catppuccin-macchiato .message.is-danger{background-color:#fcedef}html.theme--catppuccin-macchiato .message.is-danger .message-header{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .message.is-danger .message-body{border-color:#ed8796;color:#971729}html.theme--catppuccin-macchiato .message-header{align-items:center;background-color:#cad3f5;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-macchiato .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-macchiato .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-macchiato .message-body{border-color:#5b6078;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#cad3f5;padding:1.25em 1.5em}html.theme--catppuccin-macchiato .message-body code,html.theme--catppuccin-macchiato .message-body pre{background-color:#fff}html.theme--catppuccin-macchiato .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-macchiato .modal.is-active{display:flex}html.theme--catppuccin-macchiato .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-macchiato .modal-content,html.theme--catppuccin-macchiato .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-macchiato .modal-content,html.theme--catppuccin-macchiato .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-macchiato .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-macchiato .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-macchiato .modal-card-head,html.theme--catppuccin-macchiato .modal-card-foot{align-items:center;background-color:#1e2030;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-macchiato .modal-card-head{border-bottom:1px solid #5b6078;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-macchiato .modal-card-title{color:#cad3f5;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-macchiato .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5b6078}html.theme--catppuccin-macchiato .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-macchiato .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#24273a;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-macchiato .navbar{background-color:#8aadf4;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-macchiato .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-macchiato .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-dark,html.theme--catppuccin-macchiato .content kbd.navbar{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-burger,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363a4f;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-burger,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4;color:#fff}}html.theme--catppuccin-macchiato .navbar.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6da95;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#eed49f;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-macchiato .navbar.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#ed8796;color:#fff}}html.theme--catppuccin-macchiato .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-macchiato .navbar.has-shadow{box-shadow:0 2px 0 0 #1e2030}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom,html.theme--catppuccin-macchiato .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #1e2030}html.theme--catppuccin-macchiato .navbar.is-fixed-top{top:0}html.theme--catppuccin-macchiato html.has-navbar-fixed-top,html.theme--catppuccin-macchiato body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-macchiato .navbar-brand,html.theme--catppuccin-macchiato .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-macchiato .navbar-brand a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-macchiato .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-macchiato .navbar-burger{color:#cad3f5;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-macchiato .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-macchiato .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-macchiato .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-macchiato .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-macchiato .navbar-menu{display:none}html.theme--catppuccin-macchiato .navbar-item,html.theme--catppuccin-macchiato .navbar-link{color:#cad3f5;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-macchiato .navbar-item .icon:only-child,html.theme--catppuccin-macchiato .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-macchiato a.navbar-item,html.theme--catppuccin-macchiato .navbar-link{cursor:pointer}html.theme--catppuccin-macchiato a.navbar-item:focus,html.theme--catppuccin-macchiato a.navbar-item:focus-within,html.theme--catppuccin-macchiato a.navbar-item:hover,html.theme--catppuccin-macchiato a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar-link:focus,html.theme--catppuccin-macchiato .navbar-link:focus-within,html.theme--catppuccin-macchiato .navbar-link:hover,html.theme--catppuccin-macchiato .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}html.theme--catppuccin-macchiato .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .navbar-item img{max-height:1.75rem}html.theme--catppuccin-macchiato .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-macchiato .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-macchiato .navbar-item.is-tab:focus,html.theme--catppuccin-macchiato .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#8aadf4;border-bottom-style:solid;border-bottom-width:3px;color:#8aadf4;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-macchiato .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-macchiato .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-macchiato .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-macchiato .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-macchiato .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .navbar>.container{display:block}html.theme--catppuccin-macchiato .navbar-brand .navbar-item,html.theme--catppuccin-macchiato .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-macchiato .navbar-link::after{display:none}html.theme--catppuccin-macchiato .navbar-menu{background-color:#8aadf4;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-macchiato .navbar-menu.is-active{display:block}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch,html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-macchiato .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-macchiato .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-macchiato html.has-navbar-fixed-top-touch,html.theme--catppuccin-macchiato body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .navbar,html.theme--catppuccin-macchiato .navbar-menu,html.theme--catppuccin-macchiato .navbar-start,html.theme--catppuccin-macchiato .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-macchiato .navbar{min-height:4rem}html.theme--catppuccin-macchiato .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-start,html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-macchiato .navbar.is-spaced a.navbar-item,html.theme--catppuccin-macchiato .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-macchiato .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8087a2}html.theme--catppuccin-macchiato .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}html.theme--catppuccin-macchiato .navbar-burger{display:none}html.theme--catppuccin-macchiato .navbar-item,html.theme--catppuccin-macchiato .navbar-link{align-items:center;display:flex}html.theme--catppuccin-macchiato .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-macchiato .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-macchiato .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-macchiato .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-macchiato .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-macchiato .navbar-dropdown{background-color:#8aadf4;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-macchiato .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#8087a2}html.theme--catppuccin-macchiato .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#8aadf4}.navbar.is-spaced html.theme--catppuccin-macchiato .navbar-dropdown,html.theme--catppuccin-macchiato .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-macchiato .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-macchiato .navbar-divider{display:block}html.theme--catppuccin-macchiato .navbar>.container .navbar-brand,html.theme--catppuccin-macchiato .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-macchiato .navbar>.container .navbar-menu,html.theme--catppuccin-macchiato .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-macchiato .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-macchiato .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-macchiato html.has-navbar-fixed-top-desktop,html.theme--catppuccin-macchiato body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-macchiato html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-macchiato body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-macchiato html.has-spaced-navbar-fixed-top,html.theme--catppuccin-macchiato body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-macchiato html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-macchiato body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-macchiato a.navbar-item.is-active,html.theme--catppuccin-macchiato .navbar-link.is-active{color:#8aadf4}html.theme--catppuccin-macchiato a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-macchiato .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-macchiato .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-macchiato .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-macchiato .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-macchiato .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-macchiato .pagination.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-macchiato .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-previous,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-next,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-macchiato .pagination.is-rounded .pagination-link,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-macchiato .pagination,html.theme--catppuccin-macchiato .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link{border-color:#5b6078;color:#8aadf4;min-width:2.5em}html.theme--catppuccin-macchiato .pagination-previous:hover,html.theme--catppuccin-macchiato .pagination-next:hover,html.theme--catppuccin-macchiato .pagination-link:hover{border-color:#6e738d;color:#91d7e3}html.theme--catppuccin-macchiato .pagination-previous:focus,html.theme--catppuccin-macchiato .pagination-next:focus,html.theme--catppuccin-macchiato .pagination-link:focus{border-color:#6e738d}html.theme--catppuccin-macchiato .pagination-previous:active,html.theme--catppuccin-macchiato .pagination-next:active,html.theme--catppuccin-macchiato .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-macchiato .pagination-previous[disabled],html.theme--catppuccin-macchiato .pagination-previous.is-disabled,html.theme--catppuccin-macchiato .pagination-next[disabled],html.theme--catppuccin-macchiato .pagination-next.is-disabled,html.theme--catppuccin-macchiato .pagination-link[disabled],html.theme--catppuccin-macchiato .pagination-link.is-disabled{background-color:#5b6078;border-color:#5b6078;box-shadow:none;color:#f5f7fd;opacity:0.5}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-macchiato .pagination-link.is-current{background-color:#8aadf4;border-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .pagination-ellipsis{color:#6e738d;pointer-events:none}html.theme--catppuccin-macchiato .pagination-list{flex-wrap:wrap}html.theme--catppuccin-macchiato .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .pagination{flex-wrap:wrap}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-macchiato .pagination-previous{order:2}html.theme--catppuccin-macchiato .pagination-next{order:3}html.theme--catppuccin-macchiato .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-macchiato .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-macchiato .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-macchiato .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-macchiato .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-macchiato .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-macchiato .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-macchiato .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-macchiato .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-macchiato .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-macchiato .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-macchiato .panel.is-dark .panel-heading,html.theme--catppuccin-macchiato .content kbd.panel .panel-heading{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-macchiato .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363a4f}html.theme--catppuccin-macchiato .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-macchiato .content kbd.panel .panel-block.is-active .panel-icon{color:#363a4f}html.theme--catppuccin-macchiato .panel.is-primary .panel-heading,html.theme--catppuccin-macchiato .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-macchiato .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-macchiato .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-link .panel-heading{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .panel.is-link .panel-tabs a.is-active{border-bottom-color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-link .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel.is-info .panel-heading{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-info .panel-tabs a.is-active{border-bottom-color:#8bd5ca}html.theme--catppuccin-macchiato .panel.is-info .panel-block.is-active .panel-icon{color:#8bd5ca}html.theme--catppuccin-macchiato .panel.is-success .panel-heading{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6da95}html.theme--catppuccin-macchiato .panel.is-success .panel-block.is-active .panel-icon{color:#a6da95}html.theme--catppuccin-macchiato .panel.is-warning .panel-heading{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#eed49f}html.theme--catppuccin-macchiato .panel.is-warning .panel-block.is-active .panel-icon{color:#eed49f}html.theme--catppuccin-macchiato .panel.is-danger .panel-heading{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#ed8796}html.theme--catppuccin-macchiato .panel.is-danger .panel-block.is-active .panel-icon{color:#ed8796}html.theme--catppuccin-macchiato .panel-tabs:not(:last-child),html.theme--catppuccin-macchiato .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-macchiato .panel-heading{background-color:#494d64;border-radius:8px 8px 0 0;color:#b5c1f1;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-macchiato .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-macchiato .panel-tabs a{border-bottom:1px solid #5b6078;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-macchiato .panel-tabs a.is-active{border-bottom-color:#494d64;color:#739df2}html.theme--catppuccin-macchiato .panel-list a{color:#cad3f5}html.theme--catppuccin-macchiato .panel-list a:hover{color:#8aadf4}html.theme--catppuccin-macchiato .panel-block{align-items:center;color:#b5c1f1;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-macchiato .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-macchiato .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-macchiato .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-macchiato .panel-block.is-active{border-left-color:#8aadf4;color:#739df2}html.theme--catppuccin-macchiato .panel-block.is-active .panel-icon{color:#8aadf4}html.theme--catppuccin-macchiato .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-macchiato a.panel-block,html.theme--catppuccin-macchiato label.panel-block{cursor:pointer}html.theme--catppuccin-macchiato a.panel-block:hover,html.theme--catppuccin-macchiato label.panel-block:hover{background-color:#1e2030}html.theme--catppuccin-macchiato .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f5f7fd;margin-right:.75em}html.theme--catppuccin-macchiato .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-macchiato .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-macchiato .tabs a{align-items:center;border-bottom-color:#5b6078;border-bottom-style:solid;border-bottom-width:1px;color:#cad3f5;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-macchiato .tabs a:hover{border-bottom-color:#b5c1f1;color:#b5c1f1}html.theme--catppuccin-macchiato .tabs li{display:block}html.theme--catppuccin-macchiato .tabs li.is-active a{border-bottom-color:#8aadf4;color:#8aadf4}html.theme--catppuccin-macchiato .tabs ul{align-items:center;border-bottom-color:#5b6078;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-macchiato .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-macchiato .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-macchiato .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-macchiato .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-macchiato .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-macchiato .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-macchiato .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-macchiato .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-macchiato .tabs.is-boxed a:hover{background-color:#1e2030;border-bottom-color:#5b6078}html.theme--catppuccin-macchiato .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5b6078;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-macchiato .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-macchiato .tabs.is-toggle a{border-color:#5b6078;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-macchiato .tabs.is-toggle a:hover{background-color:#1e2030;border-color:#6e738d;z-index:2}html.theme--catppuccin-macchiato .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-macchiato .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-macchiato .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-macchiato .tabs.is-toggle li.is-active a{background-color:#8aadf4;border-color:#8aadf4;color:#fff;z-index:1}html.theme--catppuccin-macchiato .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-macchiato .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-macchiato .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-macchiato .tabs.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-macchiato .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-macchiato .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .column.is-narrow,html.theme--catppuccin-macchiato .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full,html.theme--catppuccin-macchiato .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters,html.theme--catppuccin-macchiato .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds,html.theme--catppuccin-macchiato .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half,html.theme--catppuccin-macchiato .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third,html.theme--catppuccin-macchiato .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter,html.theme--catppuccin-macchiato .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth,html.theme--catppuccin-macchiato .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths,html.theme--catppuccin-macchiato .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths,html.theme--catppuccin-macchiato .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths,html.theme--catppuccin-macchiato .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters,html.theme--catppuccin-macchiato .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds,html.theme--catppuccin-macchiato .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half,html.theme--catppuccin-macchiato .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third,html.theme--catppuccin-macchiato .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter,html.theme--catppuccin-macchiato .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth,html.theme--catppuccin-macchiato .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths,html.theme--catppuccin-macchiato .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths,html.theme--catppuccin-macchiato .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths,html.theme--catppuccin-macchiato .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0,html.theme--catppuccin-macchiato .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0,html.theme--catppuccin-macchiato .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1,html.theme--catppuccin-macchiato .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1,html.theme--catppuccin-macchiato .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2,html.theme--catppuccin-macchiato .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2,html.theme--catppuccin-macchiato .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3,html.theme--catppuccin-macchiato .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3,html.theme--catppuccin-macchiato .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4,html.theme--catppuccin-macchiato .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4,html.theme--catppuccin-macchiato .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5,html.theme--catppuccin-macchiato .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5,html.theme--catppuccin-macchiato .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6,html.theme--catppuccin-macchiato .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6,html.theme--catppuccin-macchiato .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7,html.theme--catppuccin-macchiato .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7,html.theme--catppuccin-macchiato .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8,html.theme--catppuccin-macchiato .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8,html.theme--catppuccin-macchiato .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9,html.theme--catppuccin-macchiato .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9,html.theme--catppuccin-macchiato .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10,html.theme--catppuccin-macchiato .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10,html.theme--catppuccin-macchiato .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11,html.theme--catppuccin-macchiato .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11,html.theme--catppuccin-macchiato .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12,html.theme--catppuccin-macchiato .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12,html.theme--catppuccin-macchiato .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-macchiato .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-macchiato .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-macchiato .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-macchiato .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-macchiato .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-macchiato .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-macchiato .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-macchiato .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-macchiato .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-macchiato .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-macchiato .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-macchiato .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-macchiato .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-macchiato .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-macchiato .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-macchiato .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-macchiato .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-macchiato .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-macchiato .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-macchiato .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-macchiato .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-macchiato .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-macchiato .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-macchiato .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-macchiato .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-macchiato .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-macchiato .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-macchiato .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-macchiato .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-macchiato .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-macchiato .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-macchiato .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-macchiato .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-macchiato .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-macchiato .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-macchiato .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-macchiato .columns.is-centered{justify-content:center}html.theme--catppuccin-macchiato .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-macchiato .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-macchiato .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-macchiato .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-macchiato .columns.is-mobile{display:flex}html.theme--catppuccin-macchiato .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-macchiato .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-desktop{display:flex}}html.theme--catppuccin-macchiato .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-macchiato .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-macchiato .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-macchiato .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-macchiato .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-macchiato .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-macchiato .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-macchiato .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-macchiato .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-macchiato .tile.is-child{margin:0 !important}html.theme--catppuccin-macchiato .tile.is-parent{padding:.75rem}html.theme--catppuccin-macchiato .tile.is-vertical{flex-direction:column}html.theme--catppuccin-macchiato .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .tile:not(.is-child){display:flex}html.theme--catppuccin-macchiato .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-macchiato .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-macchiato .tile.is-3{flex:none;width:25%}html.theme--catppuccin-macchiato .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-macchiato .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-macchiato .tile.is-6{flex:none;width:50%}html.theme--catppuccin-macchiato .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-macchiato .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-macchiato .tile.is-9{flex:none;width:75%}html.theme--catppuccin-macchiato .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-macchiato .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-macchiato .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-macchiato .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-macchiato .hero .navbar{background:none}html.theme--catppuccin-macchiato .hero .tabs ul{border-bottom:none}html.theme--catppuccin-macchiato .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-white strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-macchiato .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-macchiato .hero.is-white .navbar-item,html.theme--catppuccin-macchiato .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-macchiato .hero.is-white a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-white .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-macchiato .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-macchiato .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-black strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-black .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-macchiato .hero.is-black .navbar-item,html.theme--catppuccin-macchiato .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-black a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-black .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-macchiato .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-macchiato .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-macchiato .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-light strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-macchiato .hero.is-light .navbar-item,html.theme--catppuccin-macchiato .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-light .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-macchiato .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-macchiato .hero.is-dark,html.theme--catppuccin-macchiato .content kbd.hero{background-color:#363a4f;color:#fff}html.theme--catppuccin-macchiato .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-dark strong,html.theme--catppuccin-macchiato .content kbd.hero strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-dark .title,html.theme--catppuccin-macchiato .content kbd.hero .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .subtitle,html.theme--catppuccin-macchiato .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-macchiato .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-dark .subtitle strong,html.theme--catppuccin-macchiato .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-dark .navbar-menu,html.theme--catppuccin-macchiato .content kbd.hero .navbar-menu{background-color:#363a4f}}html.theme--catppuccin-macchiato .hero.is-dark .navbar-item,html.theme--catppuccin-macchiato .content kbd.hero .navbar-item,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-macchiato .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-macchiato .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link:hover,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-macchiato .content kbd.hero .navbar-link.is-active{background-color:#2c2f40;color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .tabs a,html.theme--catppuccin-macchiato .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-dark .tabs a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs li.is-active a{color:#363a4f !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363a4f}html.theme--catppuccin-macchiato .hero.is-dark.is-bold,html.theme--catppuccin-macchiato .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1d2535 0%, #363a4f 71%, #3d3c62 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-macchiato .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1d2535 0%, #363a4f 71%, #3d3c62 100%)}}html.theme--catppuccin-macchiato .hero.is-primary,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-primary strong,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-primary .title,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .subtitle,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-primary .subtitle strong,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-primary .navbar-menu,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#8aadf4}}html.theme--catppuccin-macchiato .hero.is-primary .navbar-item,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .tabs a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-primary .tabs a:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#8aadf4 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .hero.is-primary.is-bold,html.theme--catppuccin-macchiato .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-macchiato .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}}html.theme--catppuccin-macchiato .hero.is-link{background-color:#8aadf4;color:#fff}html.theme--catppuccin-macchiato .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-link strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-link .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-link .navbar-menu{background-color:#8aadf4}}html.theme--catppuccin-macchiato .hero.is-link .navbar-item,html.theme--catppuccin-macchiato .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-link a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-link .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-link .navbar-link.is-active{background-color:#739df2;color:#fff}html.theme--catppuccin-macchiato .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-link .tabs li.is-active a{color:#8aadf4 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#8aadf4}html.theme--catppuccin-macchiato .hero.is-link.is-bold{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #52a5f9 0%, #8aadf4 71%, #9fadf9 100%)}}html.theme--catppuccin-macchiato .hero.is-info{background-color:#8bd5ca;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-info strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-info .navbar-menu{background-color:#8bd5ca}}html.theme--catppuccin-macchiato .hero.is-info .navbar-item,html.theme--catppuccin-macchiato .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-info .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-info .navbar-link.is-active{background-color:#78cec1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-info .tabs li.is-active a{color:#8bd5ca !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#8bd5ca}html.theme--catppuccin-macchiato .hero.is-info.is-bold{background-image:linear-gradient(141deg, #5bd2ac 0%, #8bd5ca 71%, #9adedf 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #5bd2ac 0%, #8bd5ca 71%, #9adedf 100%)}}html.theme--catppuccin-macchiato .hero.is-success{background-color:#a6da95;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-success strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-success .navbar-menu{background-color:#a6da95}}html.theme--catppuccin-macchiato .hero.is-success .navbar-item,html.theme--catppuccin-macchiato .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-success .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-success .navbar-link.is-active{background-color:#96d382;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-success .tabs li.is-active a{color:#a6da95 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6da95}html.theme--catppuccin-macchiato .hero.is-success.is-bold{background-image:linear-gradient(141deg, #94d765 0%, #a6da95 71%, #aae4a5 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #94d765 0%, #a6da95 71%, #aae4a5 100%)}}html.theme--catppuccin-macchiato .hero.is-warning{background-color:#eed49f;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-warning strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-macchiato .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-warning .navbar-menu{background-color:#eed49f}}html.theme--catppuccin-macchiato .hero.is-warning .navbar-item,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-warning .navbar-link.is-active{background-color:#eaca89;color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-macchiato .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-warning .tabs li.is-active a{color:#eed49f !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#eed49f}html.theme--catppuccin-macchiato .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #efae6b 0%, #eed49f 71%, #f4e9b2 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #efae6b 0%, #eed49f 71%, #f4e9b2 100%)}}html.theme--catppuccin-macchiato .hero.is-danger{background-color:#ed8796;color:#fff}html.theme--catppuccin-macchiato .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-macchiato .hero.is-danger strong{color:inherit}html.theme--catppuccin-macchiato .hero.is-danger .title{color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-macchiato .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-macchiato .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .hero.is-danger .navbar-menu{background-color:#ed8796}}html.theme--catppuccin-macchiato .hero.is-danger .navbar-item,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-macchiato .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-macchiato .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link:hover,html.theme--catppuccin-macchiato .hero.is-danger .navbar-link.is-active{background-color:#ea7183;color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-macchiato .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-macchiato .hero.is-danger .tabs li.is-active a{color:#ed8796 !important;opacity:1}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-macchiato .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ed8796}html.theme--catppuccin-macchiato .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #f05183 0%, #ed8796 71%, #f39c9a 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f05183 0%, #ed8796 71%, #f39c9a 100%)}}html.theme--catppuccin-macchiato .hero.is-small .hero-body,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-macchiato .hero.is-halfheight .hero-body,html.theme--catppuccin-macchiato .hero.is-fullheight .hero-body,html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-macchiato .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-macchiato .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-macchiato .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-macchiato .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-macchiato .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-macchiato .hero-video{overflow:hidden}html.theme--catppuccin-macchiato .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-macchiato .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero-video{display:none}}html.theme--catppuccin-macchiato .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-macchiato .hero-buttons .button{display:flex}html.theme--catppuccin-macchiato .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-macchiato .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-macchiato .hero-head,html.theme--catppuccin-macchiato .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-macchiato .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-macchiato .hero-body{padding:3rem 3rem}}html.theme--catppuccin-macchiato .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato .section{padding:3rem 3rem}html.theme--catppuccin-macchiato .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-macchiato .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-macchiato .footer{background-color:#1e2030;padding:3rem 1.5rem 6rem}html.theme--catppuccin-macchiato h1 .docs-heading-anchor,html.theme--catppuccin-macchiato h1 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h1 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h2 .docs-heading-anchor,html.theme--catppuccin-macchiato h2 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h2 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h3 .docs-heading-anchor,html.theme--catppuccin-macchiato h3 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h3 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h4 .docs-heading-anchor,html.theme--catppuccin-macchiato h4 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h4 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h5 .docs-heading-anchor,html.theme--catppuccin-macchiato h5 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h5 .docs-heading-anchor:visited,html.theme--catppuccin-macchiato h6 .docs-heading-anchor,html.theme--catppuccin-macchiato h6 .docs-heading-anchor:hover,html.theme--catppuccin-macchiato h6 .docs-heading-anchor:visited{color:#cad3f5}html.theme--catppuccin-macchiato h1 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h2 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h3 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h4 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h5 .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-macchiato h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-macchiato h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-macchiato h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-macchiato h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-macchiato .docs-light-only{display:none !important}html.theme--catppuccin-macchiato pre{position:relative;overflow:hidden}html.theme--catppuccin-macchiato pre code,html.theme--catppuccin-macchiato pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-macchiato pre code:first-of-type,html.theme--catppuccin-macchiato pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-macchiato pre code:last-of-type,html.theme--catppuccin-macchiato pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-macchiato pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#cad3f5;cursor:pointer;text-align:center}html.theme--catppuccin-macchiato pre .copy-button:focus,html.theme--catppuccin-macchiato pre .copy-button:hover{opacity:1;background:rgba(202,211,245,0.1);color:#8aadf4}html.theme--catppuccin-macchiato pre .copy-button.success{color:#a6da95;opacity:1}html.theme--catppuccin-macchiato pre .copy-button.error{color:#ed8796;opacity:1}html.theme--catppuccin-macchiato pre:hover .copy-button{opacity:1}html.theme--catppuccin-macchiato .admonition{background-color:#1e2030;border-style:solid;border-width:2px;border-color:#b8c0e0;border-radius:4px;font-size:1rem}html.theme--catppuccin-macchiato .admonition strong{color:currentColor}html.theme--catppuccin-macchiato .admonition.is-small,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-macchiato .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-macchiato .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-macchiato .admonition.is-default{background-color:#1e2030;border-color:#b8c0e0}html.theme--catppuccin-macchiato .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#b8c0e0}html.theme--catppuccin-macchiato .admonition.is-default>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-info{background-color:#1e2030;border-color:#8bd5ca}html.theme--catppuccin-macchiato .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#8bd5ca}html.theme--catppuccin-macchiato .admonition.is-info>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-success{background-color:#1e2030;border-color:#a6da95}html.theme--catppuccin-macchiato .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6da95}html.theme--catppuccin-macchiato .admonition.is-success>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-warning{background-color:#1e2030;border-color:#eed49f}html.theme--catppuccin-macchiato .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#eed49f}html.theme--catppuccin-macchiato .admonition.is-warning>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-danger{background-color:#1e2030;border-color:#ed8796}html.theme--catppuccin-macchiato .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#ed8796}html.theme--catppuccin-macchiato .admonition.is-danger>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-compat{background-color:#1e2030;border-color:#91d7e3}html.theme--catppuccin-macchiato .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#91d7e3}html.theme--catppuccin-macchiato .admonition.is-compat>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition.is-todo{background-color:#1e2030;border-color:#c6a0f6}html.theme--catppuccin-macchiato .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#c6a0f6}html.theme--catppuccin-macchiato .admonition.is-todo>.admonition-body{color:#cad3f5}html.theme--catppuccin-macchiato .admonition-header{color:#b8c0e0;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-macchiato .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-macchiato details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-macchiato details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-macchiato details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-macchiato .admonition-body{color:#cad3f5;padding:0.5rem .75rem}html.theme--catppuccin-macchiato .admonition-body pre{background-color:#1e2030}html.theme--catppuccin-macchiato .admonition-body code{background-color:#1e2030}html.theme--catppuccin-macchiato .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #5b6078;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-macchiato .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#1e2030;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5b6078;overflow:auto}html.theme--catppuccin-macchiato .docstring>header code{background-color:transparent}html.theme--catppuccin-macchiato .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-macchiato .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-macchiato .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-macchiato .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5b6078}html.theme--catppuccin-macchiato .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-macchiato .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-macchiato .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-macchiato .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-macchiato .documenter-example-output{background-color:#24273a}html.theme--catppuccin-macchiato .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#1e2030;color:#cad3f5;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-macchiato .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-macchiato .outdated-warning-overlay a{color:#8aadf4}html.theme--catppuccin-macchiato .outdated-warning-overlay a:hover{color:#91d7e3}html.theme--catppuccin-macchiato .content pre{border:2px solid #5b6078;border-radius:4px}html.theme--catppuccin-macchiato .content code{font-weight:inherit}html.theme--catppuccin-macchiato .content a code{color:#8aadf4}html.theme--catppuccin-macchiato .content a:hover code{color:#91d7e3}html.theme--catppuccin-macchiato .content h1 code,html.theme--catppuccin-macchiato .content h2 code,html.theme--catppuccin-macchiato .content h3 code,html.theme--catppuccin-macchiato .content h4 code,html.theme--catppuccin-macchiato .content h5 code,html.theme--catppuccin-macchiato .content h6 code{color:#cad3f5}html.theme--catppuccin-macchiato .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-macchiato .content blockquote>ul:first-child,html.theme--catppuccin-macchiato .content blockquote>ol:first-child,html.theme--catppuccin-macchiato .content .admonition-body>ul:first-child,html.theme--catppuccin-macchiato .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-macchiato pre,html.theme--catppuccin-macchiato code{font-variant-ligatures:no-contextual}html.theme--catppuccin-macchiato .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-macchiato .breadcrumb a.is-disabled,html.theme--catppuccin-macchiato .breadcrumb a.is-disabled:hover{color:#b5c1f1}html.theme--catppuccin-macchiato .hljs{background:initial !important}html.theme--catppuccin-macchiato .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-macchiato .katex-display,html.theme--catppuccin-macchiato mjx-container,html.theme--catppuccin-macchiato .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-macchiato html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-macchiato li.no-marker{list-style:none}html.theme--catppuccin-macchiato #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-macchiato #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main{width:100%}html.theme--catppuccin-macchiato #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-macchiato #documenter .docs-main>header,html.theme--catppuccin-macchiato #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar{background-color:#24273a;border-bottom:1px solid #5b6078;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-macchiato #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes{border-top:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-macchiato #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-macchiato .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5b6078;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-macchiato #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-macchiato #documenter .docs-sidebar{display:flex;flex-direction:column;color:#cad3f5;background-color:#1e2030;border-right:1px solid #5b6078;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-macchiato #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-package-name a:hover{color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5b6078;display:none;padding:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5b6078;padding-bottom:1.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#cad3f5;background:#1e2030}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#cad3f5;background-color:#26283d}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5b6078;border-bottom:1px solid #5b6078;background-color:#181926}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#181926;color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#26283d;color:#cad3f5}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5b6078}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-macchiato #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#2e3149}html.theme--catppuccin-macchiato #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#3d4162}}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#2e3149}html.theme--catppuccin-macchiato #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#3d4162}}html.theme--catppuccin-macchiato kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-macchiato .search-min-width-50{min-width:50%}html.theme--catppuccin-macchiato .search-min-height-100{min-height:100%}html.theme--catppuccin-macchiato .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-macchiato .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover,html.theme--catppuccin-macchiato .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-macchiato .search-result-link .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-macchiato .property-search-result-badge,html.theme--catppuccin-macchiato .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-macchiato .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:hover .search-filter,html.theme--catppuccin-macchiato .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-macchiato .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-macchiato .search-filter:hover,html.theme--catppuccin-macchiato .search-filter:focus{color:#333}html.theme--catppuccin-macchiato .search-filter-selected{color:#363a4f;background-color:#b7bdf8}html.theme--catppuccin-macchiato .search-filter-selected:hover,html.theme--catppuccin-macchiato .search-filter-selected:focus{color:#363a4f}html.theme--catppuccin-macchiato .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-macchiato .search-divider{border-bottom:1px solid #5b6078}html.theme--catppuccin-macchiato .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-macchiato .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-macchiato #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-macchiato #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-macchiato .w-100{width:100%}html.theme--catppuccin-macchiato .gap-2{gap:0.5rem}html.theme--catppuccin-macchiato .gap-4{gap:1rem}html.theme--catppuccin-macchiato .gap-8{gap:2rem}html.theme--catppuccin-macchiato{background-color:#24273a;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-macchiato a{transition:all 200ms ease}html.theme--catppuccin-macchiato .label{color:#cad3f5}html.theme--catppuccin-macchiato .button,html.theme--catppuccin-macchiato .control.has-icons-left .icon,html.theme--catppuccin-macchiato .control.has-icons-right .icon,html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .pagination-ellipsis,html.theme--catppuccin-macchiato .pagination-link,html.theme--catppuccin-macchiato .pagination-next,html.theme--catppuccin-macchiato .pagination-previous,html.theme--catppuccin-macchiato .select,html.theme--catppuccin-macchiato .select select,html.theme--catppuccin-macchiato .textarea{height:2.5em;color:#cad3f5}html.theme--catppuccin-macchiato .input,html.theme--catppuccin-macchiato #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-macchiato .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#cad3f5}html.theme--catppuccin-macchiato .select:after,html.theme--catppuccin-macchiato .select select{border-width:1px}html.theme--catppuccin-macchiato .menu-list a{transition:all 300ms ease}html.theme--catppuccin-macchiato .modal-card-foot,html.theme--catppuccin-macchiato .modal-card-head{border-color:#5b6078}html.theme--catppuccin-macchiato .navbar{border-radius:.4em}html.theme--catppuccin-macchiato .navbar.is-transparent{background:none}html.theme--catppuccin-macchiato .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-macchiato .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#8aadf4}@media screen and (max-width: 1055px){html.theme--catppuccin-macchiato .navbar .navbar-menu{background-color:#8aadf4;border-radius:0 0 .4em .4em}}html.theme--catppuccin-macchiato .docstring>section>a.docs-sourcelink:not(body){color:#363a4f}html.theme--catppuccin-macchiato .tag.is-link:not(body),html.theme--catppuccin-macchiato .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-macchiato .content kbd.is-link:not(body){color:#363a4f}html.theme--catppuccin-macchiato .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-macchiato .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-macchiato .ansi span.sgr3{font-style:italic}html.theme--catppuccin-macchiato .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-macchiato .ansi span.sgr7{color:#24273a;background-color:#cad3f5}html.theme--catppuccin-macchiato .ansi span.sgr8{color:transparent}html.theme--catppuccin-macchiato .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-macchiato .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-macchiato .ansi span.sgr30{color:#494d64}html.theme--catppuccin-macchiato .ansi span.sgr31{color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr32{color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr33{color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr34{color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr35{color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr36{color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr37{color:#b8c0e0}html.theme--catppuccin-macchiato .ansi span.sgr40{background-color:#494d64}html.theme--catppuccin-macchiato .ansi span.sgr41{background-color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr42{background-color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr43{background-color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr44{background-color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr45{background-color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr46{background-color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr47{background-color:#b8c0e0}html.theme--catppuccin-macchiato .ansi span.sgr90{color:#5b6078}html.theme--catppuccin-macchiato .ansi span.sgr91{color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr92{color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr93{color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr94{color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr95{color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr96{color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr97{color:#a5adcb}html.theme--catppuccin-macchiato .ansi span.sgr100{background-color:#5b6078}html.theme--catppuccin-macchiato .ansi span.sgr101{background-color:#ed8796}html.theme--catppuccin-macchiato .ansi span.sgr102{background-color:#a6da95}html.theme--catppuccin-macchiato .ansi span.sgr103{background-color:#eed49f}html.theme--catppuccin-macchiato .ansi span.sgr104{background-color:#8aadf4}html.theme--catppuccin-macchiato .ansi span.sgr105{background-color:#f5bde6}html.theme--catppuccin-macchiato .ansi span.sgr106{background-color:#8bd5ca}html.theme--catppuccin-macchiato .ansi span.sgr107{background-color:#a5adcb}html.theme--catppuccin-macchiato code.language-julia-repl>span.hljs-meta{color:#a6da95;font-weight:bolder}html.theme--catppuccin-macchiato code .hljs{color:#cad3f5;background:#24273a}html.theme--catppuccin-macchiato code .hljs-keyword{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-built_in{color:#ed8796}html.theme--catppuccin-macchiato code .hljs-type{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-literal{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-number{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-operator{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-punctuation{color:#b8c0e0}html.theme--catppuccin-macchiato code .hljs-property{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-regexp{color:#f5bde6}html.theme--catppuccin-macchiato code .hljs-string{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-char.escape_{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-subst{color:#a5adcb}html.theme--catppuccin-macchiato code .hljs-symbol{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-variable{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-variable.language_{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-variable.constant_{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-title{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-title.class_{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-title.function_{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-params{color:#cad3f5}html.theme--catppuccin-macchiato code .hljs-comment{color:#5b6078}html.theme--catppuccin-macchiato code .hljs-doctag{color:#ed8796}html.theme--catppuccin-macchiato code .hljs-meta{color:#f5a97f}html.theme--catppuccin-macchiato code .hljs-section{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-tag{color:#a5adcb}html.theme--catppuccin-macchiato code .hljs-name{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-attr{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-attribute{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-bullet{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-code{color:#a6da95}html.theme--catppuccin-macchiato code .hljs-emphasis{color:#ed8796;font-style:italic}html.theme--catppuccin-macchiato code .hljs-strong{color:#ed8796;font-weight:bold}html.theme--catppuccin-macchiato code .hljs-formula{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-link{color:#7dc4e4;font-style:italic}html.theme--catppuccin-macchiato code .hljs-quote{color:#a6da95;font-style:italic}html.theme--catppuccin-macchiato code .hljs-selector-tag{color:#eed49f}html.theme--catppuccin-macchiato code .hljs-selector-id{color:#8aadf4}html.theme--catppuccin-macchiato code .hljs-selector-class{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-selector-attr{color:#c6a0f6}html.theme--catppuccin-macchiato code .hljs-selector-pseudo{color:#8bd5ca}html.theme--catppuccin-macchiato code .hljs-template-tag{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-template-variable{color:#f0c6c6}html.theme--catppuccin-macchiato code .hljs-addition{color:#a6da95;background:rgba(166,227,161,0.15)}html.theme--catppuccin-macchiato code .hljs-deletion{color:#ed8796;background:rgba(243,139,168,0.15)}html.theme--catppuccin-macchiato .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover,html.theme--catppuccin-macchiato .search-result-link:focus{background-color:#363a4f}html.theme--catppuccin-macchiato .search-result-link .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-macchiato .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:hover .search-filter,html.theme--catppuccin-macchiato .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-macchiato .search-result-link:focus .search-filter{color:#363a4f !important;background-color:#b7bdf8 !important}html.theme--catppuccin-macchiato .search-result-title{color:#cad3f5}html.theme--catppuccin-macchiato .search-result-highlight{background-color:#ed8796;color:#1e2030}html.theme--catppuccin-macchiato .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-macchiato .w-100{width:100%}html.theme--catppuccin-macchiato .gap-2{gap:0.5rem}html.theme--catppuccin-macchiato .gap-4{gap:1rem} diff --git a/previews/PR826/assets/themes/catppuccin-mocha.css b/previews/PR826/assets/themes/catppuccin-mocha.css new file mode 100644 index 0000000000..8b82652560 --- /dev/null +++ b/previews/PR826/assets/themes/catppuccin-mocha.css @@ -0,0 +1 @@ +html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha .file-name,html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--catppuccin-mocha .pagination-previous:focus,html.theme--catppuccin-mocha .pagination-next:focus,html.theme--catppuccin-mocha .pagination-link:focus,html.theme--catppuccin-mocha .pagination-ellipsis:focus,html.theme--catppuccin-mocha .file-cta:focus,html.theme--catppuccin-mocha .file-name:focus,html.theme--catppuccin-mocha .select select:focus,html.theme--catppuccin-mocha .textarea:focus,html.theme--catppuccin-mocha .input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-mocha .button:focus,html.theme--catppuccin-mocha .is-focused.pagination-previous,html.theme--catppuccin-mocha .is-focused.pagination-next,html.theme--catppuccin-mocha .is-focused.pagination-link,html.theme--catppuccin-mocha .is-focused.pagination-ellipsis,html.theme--catppuccin-mocha .is-focused.file-cta,html.theme--catppuccin-mocha .is-focused.file-name,html.theme--catppuccin-mocha .select select.is-focused,html.theme--catppuccin-mocha .is-focused.textarea,html.theme--catppuccin-mocha .is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-focused.button,html.theme--catppuccin-mocha .pagination-previous:active,html.theme--catppuccin-mocha .pagination-next:active,html.theme--catppuccin-mocha .pagination-link:active,html.theme--catppuccin-mocha .pagination-ellipsis:active,html.theme--catppuccin-mocha .file-cta:active,html.theme--catppuccin-mocha .file-name:active,html.theme--catppuccin-mocha .select select:active,html.theme--catppuccin-mocha .textarea:active,html.theme--catppuccin-mocha .input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-mocha .button:active,html.theme--catppuccin-mocha .is-active.pagination-previous,html.theme--catppuccin-mocha .is-active.pagination-next,html.theme--catppuccin-mocha .is-active.pagination-link,html.theme--catppuccin-mocha .is-active.pagination-ellipsis,html.theme--catppuccin-mocha .is-active.file-cta,html.theme--catppuccin-mocha .is-active.file-name,html.theme--catppuccin-mocha .select select.is-active,html.theme--catppuccin-mocha .is-active.textarea,html.theme--catppuccin-mocha .is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .is-active.button{outline:none}html.theme--catppuccin-mocha .pagination-previous[disabled],html.theme--catppuccin-mocha .pagination-next[disabled],html.theme--catppuccin-mocha .pagination-link[disabled],html.theme--catppuccin-mocha .pagination-ellipsis[disabled],html.theme--catppuccin-mocha .file-cta[disabled],html.theme--catppuccin-mocha .file-name[disabled],html.theme--catppuccin-mocha .select select[disabled],html.theme--catppuccin-mocha .textarea[disabled],html.theme--catppuccin-mocha .input[disabled],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--catppuccin-mocha .button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha fieldset[disabled] .file-cta,fieldset[disabled] html.theme--catppuccin-mocha .file-name,html.theme--catppuccin-mocha fieldset[disabled] .file-name,fieldset[disabled] html.theme--catppuccin-mocha .select select,fieldset[disabled] html.theme--catppuccin-mocha .textarea,fieldset[disabled] html.theme--catppuccin-mocha .input,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha fieldset[disabled] .select select,html.theme--catppuccin-mocha .select fieldset[disabled] select,html.theme--catppuccin-mocha fieldset[disabled] .textarea,html.theme--catppuccin-mocha fieldset[disabled] .input,html.theme--catppuccin-mocha fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--catppuccin-mocha .button,html.theme--catppuccin-mocha fieldset[disabled] .button{cursor:not-allowed}html.theme--catppuccin-mocha .tabs,html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .breadcrumb,html.theme--catppuccin-mocha .file,html.theme--catppuccin-mocha .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless)::after,html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--catppuccin-mocha .admonition:not(:last-child),html.theme--catppuccin-mocha .tabs:not(:last-child),html.theme--catppuccin-mocha .pagination:not(:last-child),html.theme--catppuccin-mocha .message:not(:last-child),html.theme--catppuccin-mocha .level:not(:last-child),html.theme--catppuccin-mocha .breadcrumb:not(:last-child),html.theme--catppuccin-mocha .block:not(:last-child),html.theme--catppuccin-mocha .title:not(:last-child),html.theme--catppuccin-mocha .subtitle:not(:last-child),html.theme--catppuccin-mocha .table-container:not(:last-child),html.theme--catppuccin-mocha .table:not(:last-child),html.theme--catppuccin-mocha .progress:not(:last-child),html.theme--catppuccin-mocha .notification:not(:last-child),html.theme--catppuccin-mocha .content:not(:last-child),html.theme--catppuccin-mocha .box:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .modal-close,html.theme--catppuccin-mocha .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--catppuccin-mocha .modal-close::before,html.theme--catppuccin-mocha .delete::before,html.theme--catppuccin-mocha .modal-close::after,html.theme--catppuccin-mocha .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-mocha .modal-close::before,html.theme--catppuccin-mocha .delete::before{height:2px;width:50%}html.theme--catppuccin-mocha .modal-close::after,html.theme--catppuccin-mocha .delete::after{height:50%;width:2px}html.theme--catppuccin-mocha .modal-close:hover,html.theme--catppuccin-mocha .delete:hover,html.theme--catppuccin-mocha .modal-close:focus,html.theme--catppuccin-mocha .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--catppuccin-mocha .modal-close:active,html.theme--catppuccin-mocha .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--catppuccin-mocha .is-small.modal-close,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--catppuccin-mocha .is-small.delete,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--catppuccin-mocha .is-medium.modal-close,html.theme--catppuccin-mocha .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--catppuccin-mocha .is-large.modal-close,html.theme--catppuccin-mocha .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--catppuccin-mocha .control.is-loading::after,html.theme--catppuccin-mocha .select.is-loading::after,html.theme--catppuccin-mocha .loader,html.theme--catppuccin-mocha .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #7f849c;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--catppuccin-mocha .hero-video,html.theme--catppuccin-mocha .modal-background,html.theme--catppuccin-mocha .modal,html.theme--catppuccin-mocha .image.is-square img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-mocha .image.is-square .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-mocha .image.is-1by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-mocha .image.is-1by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-mocha .image.is-5by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-mocha .image.is-5by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-mocha .image.is-4by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-mocha .image.is-4by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-mocha .image.is-3by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-mocha .image.is-5by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-mocha .image.is-5by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-mocha .image.is-16by9 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-mocha .image.is-16by9 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-mocha .image.is-2by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-mocha .image.is-2by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-mocha .image.is-3by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-mocha .image.is-3by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-mocha .image.is-4by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-mocha .image.is-4by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-mocha .image.is-3by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-mocha .image.is-3by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-mocha .image.is-2by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-mocha .image.is-2by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-mocha .image.is-3by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-mocha .image.is-9by16 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-mocha .image.is-9by16 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-mocha .image.is-1by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-mocha .image.is-1by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-mocha .image.is-1by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-mocha .image.is-1by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--catppuccin-mocha .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#313244 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c26 !important}.has-background-dark{background-color:#313244 !important}.has-text-primary{color:#89b4fa !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#5895f8 !important}.has-background-primary{background-color:#89b4fa !important}.has-text-primary-light{color:#ebf3fe !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#bbd3fc !important}.has-background-primary-light{background-color:#ebf3fe !important}.has-text-primary-dark{color:#063c93 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#0850c4 !important}.has-background-primary-dark{background-color:#063c93 !important}.has-text-link{color:#89b4fa !important}a.has-text-link:hover,a.has-text-link:focus{color:#5895f8 !important}.has-background-link{background-color:#89b4fa !important}.has-text-link-light{color:#ebf3fe !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#bbd3fc !important}.has-background-link-light{background-color:#ebf3fe !important}.has-text-link-dark{color:#063c93 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#0850c4 !important}.has-background-link-dark{background-color:#063c93 !important}.has-text-info{color:#94e2d5 !important}a.has-text-info:hover,a.has-text-info:focus{color:#6cd7c5 !important}.has-background-info{background-color:#94e2d5 !important}.has-text-info-light{color:#effbf9 !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c7f0e9 !important}.has-background-info-light{background-color:#effbf9 !important}.has-text-info-dark{color:#207466 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#2a9c89 !important}.has-background-info-dark{background-color:#207466 !important}.has-text-success{color:#a6e3a1 !important}a.has-text-success:hover,a.has-text-success:focus{color:#81d77a !important}.has-background-success{background-color:#a6e3a1 !important}.has-text-success-light{color:#f0faef !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#cbefc8 !important}.has-background-success-light{background-color:#f0faef !important}.has-text-success-dark{color:#287222 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#36992e !important}.has-background-success-dark{background-color:#287222 !important}.has-text-warning{color:#f9e2af !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#f5d180 !important}.has-background-warning{background-color:#f9e2af !important}.has-text-warning-light{color:#fef8ec !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fae7bd !important}.has-background-warning-light{background-color:#fef8ec !important}.has-text-warning-dark{color:#8a620a !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#b9840e !important}.has-background-warning-dark{background-color:#8a620a !important}.has-text-danger{color:#f38ba8 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ee5d85 !important}.has-background-danger{background-color:#f38ba8 !important}.has-text-danger-light{color:#fdedf1 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f8bece !important}.has-background-danger-light{background-color:#fdedf1 !important}.has-text-danger-dark{color:#991036 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#c71546 !important}.has-background-danger-dark{background-color:#991036 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#313244 !important}.has-background-grey-darker{background-color:#313244 !important}.has-text-grey-dark{color:#45475a !important}.has-background-grey-dark{background-color:#45475a !important}.has-text-grey{color:#585b70 !important}.has-background-grey{background-color:#585b70 !important}.has-text-grey-light{color:#6c7086 !important}.has-background-grey-light{background-color:#6c7086 !important}.has-text-grey-lighter{color:#7f849c !important}.has-background-grey-lighter{background-color:#7f849c !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--catppuccin-mocha html{background-color:#1e1e2e;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-mocha article,html.theme--catppuccin-mocha aside,html.theme--catppuccin-mocha figure,html.theme--catppuccin-mocha footer,html.theme--catppuccin-mocha header,html.theme--catppuccin-mocha hgroup,html.theme--catppuccin-mocha section{display:block}html.theme--catppuccin-mocha body,html.theme--catppuccin-mocha button,html.theme--catppuccin-mocha input,html.theme--catppuccin-mocha optgroup,html.theme--catppuccin-mocha select,html.theme--catppuccin-mocha textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--catppuccin-mocha code,html.theme--catppuccin-mocha pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-mocha body{color:#cdd6f4;font-size:1em;font-weight:400;line-height:1.5}html.theme--catppuccin-mocha a{color:#89b4fa;cursor:pointer;text-decoration:none}html.theme--catppuccin-mocha a strong{color:currentColor}html.theme--catppuccin-mocha a:hover{color:#89dceb}html.theme--catppuccin-mocha code{background-color:#181825;color:#cdd6f4;font-size:.875em;font-weight:normal;padding:.1em}html.theme--catppuccin-mocha hr{background-color:#181825;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--catppuccin-mocha img{height:auto;max-width:100%}html.theme--catppuccin-mocha input[type="checkbox"],html.theme--catppuccin-mocha input[type="radio"]{vertical-align:baseline}html.theme--catppuccin-mocha small{font-size:.875em}html.theme--catppuccin-mocha span{font-style:inherit;font-weight:inherit}html.theme--catppuccin-mocha strong{color:#b8c5ef;font-weight:700}html.theme--catppuccin-mocha fieldset{border:none}html.theme--catppuccin-mocha pre{-webkit-overflow-scrolling:touch;background-color:#181825;color:#cdd6f4;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--catppuccin-mocha pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--catppuccin-mocha table td,html.theme--catppuccin-mocha table th{vertical-align:top}html.theme--catppuccin-mocha table td:not([align]),html.theme--catppuccin-mocha table th:not([align]){text-align:inherit}html.theme--catppuccin-mocha table th{color:#b8c5ef}html.theme--catppuccin-mocha .box{background-color:#45475a;border-radius:8px;box-shadow:none;color:#cdd6f4;display:block;padding:1.25rem}html.theme--catppuccin-mocha a.box:hover,html.theme--catppuccin-mocha a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #89b4fa}html.theme--catppuccin-mocha a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #89b4fa}html.theme--catppuccin-mocha .button{background-color:#181825;border-color:#363653;border-width:1px;color:#89b4fa;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--catppuccin-mocha .button strong{color:inherit}html.theme--catppuccin-mocha .button .icon,html.theme--catppuccin-mocha .button .icon.is-small,html.theme--catppuccin-mocha .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--catppuccin-mocha .button .icon.is-medium,html.theme--catppuccin-mocha .button .icon.is-large{height:1.5em;width:1.5em}html.theme--catppuccin-mocha .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--catppuccin-mocha .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-mocha .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--catppuccin-mocha .button:hover,html.theme--catppuccin-mocha .button.is-hovered{border-color:#6c7086;color:#b8c5ef}html.theme--catppuccin-mocha .button:focus,html.theme--catppuccin-mocha .button.is-focused{border-color:#6c7086;color:#71a4f9}html.theme--catppuccin-mocha .button:focus:not(:active),html.theme--catppuccin-mocha .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button:active,html.theme--catppuccin-mocha .button.is-active{border-color:#45475a;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text{background-color:transparent;border-color:transparent;color:#cdd6f4;text-decoration:underline}html.theme--catppuccin-mocha .button.is-text:hover,html.theme--catppuccin-mocha .button.is-text.is-hovered,html.theme--catppuccin-mocha .button.is-text:focus,html.theme--catppuccin-mocha .button.is-text.is-focused{background-color:#181825;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text:active,html.theme--catppuccin-mocha .button.is-text.is-active{background-color:#0e0e16;color:#b8c5ef}html.theme--catppuccin-mocha .button.is-text[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--catppuccin-mocha .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#89b4fa;text-decoration:none}html.theme--catppuccin-mocha .button.is-ghost:hover,html.theme--catppuccin-mocha .button.is-ghost.is-hovered{color:#89b4fa;text-decoration:underline}html.theme--catppuccin-mocha .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:hover,html.theme--catppuccin-mocha .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:focus,html.theme--catppuccin-mocha .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white:focus:not(:active),html.theme--catppuccin-mocha .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .button.is-white:active,html.theme--catppuccin-mocha .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--catppuccin-mocha .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted:hover,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--catppuccin-mocha .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-outlined:hover,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-white.is-outlined:focus,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:hover,html.theme--catppuccin-mocha .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:focus,html.theme--catppuccin-mocha .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black:focus:not(:active),html.theme--catppuccin-mocha .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .button.is-black:active,html.theme--catppuccin-mocha .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-black[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--catppuccin-mocha .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted:hover,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-outlined:hover,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-black.is-outlined:focus,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:hover,html.theme--catppuccin-mocha .button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:focus,html.theme--catppuccin-mocha .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light:focus:not(:active),html.theme--catppuccin-mocha .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .button.is-light:active,html.theme--catppuccin-mocha .button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}html.theme--catppuccin-mocha .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted:hover,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-outlined:hover,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-light.is-outlined:focus,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-dark,html.theme--catppuccin-mocha .content kbd.button{background-color:#313244;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:hover,html.theme--catppuccin-mocha .content kbd.button:hover,html.theme--catppuccin-mocha .button.is-dark.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-hovered{background-color:#2c2d3d;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:focus,html.theme--catppuccin-mocha .content kbd.button:focus,html.theme--catppuccin-mocha .button.is-dark.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark:focus:not(:active),html.theme--catppuccin-mocha .content kbd.button:focus:not(:active),html.theme--catppuccin-mocha .button.is-dark.is-focused:not(:active),html.theme--catppuccin-mocha .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .button.is-dark:active,html.theme--catppuccin-mocha .content kbd.button:active,html.theme--catppuccin-mocha .button.is-dark.is-active,html.theme--catppuccin-mocha .content kbd.button.is-active{background-color:#262735;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-dark[disabled],html.theme--catppuccin-mocha .content kbd.button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button{background-color:#313244;border-color:#313244;box-shadow:none}html.theme--catppuccin-mocha .button.is-dark.is-inverted,html.theme--catppuccin-mocha .content kbd.button.is-inverted{background-color:#fff;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted:hover,html.theme--catppuccin-mocha .content kbd.button.is-inverted:hover,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-dark.is-inverted[disabled],html.theme--catppuccin-mocha .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-inverted,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-loading::after,html.theme--catppuccin-mocha .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined,html.theme--catppuccin-mocha .content kbd.button.is-outlined{background-color:transparent;border-color:#313244;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-outlined:hover,html.theme--catppuccin-mocha .content kbd.button.is-outlined:hover,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-dark.is-outlined:focus,html.theme--catppuccin-mocha .content kbd.button.is-outlined:focus,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-focused{background-color:#313244;border-color:#313244;color:#fff}html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #313244 #313244 !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-dark.is-outlined[disabled],html.theme--catppuccin-mocha .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-outlined{background-color:transparent;border-color:#313244;box-shadow:none;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#313244}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #313244 #313244 !important}html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined[disabled],html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-primary,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:hover,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:focus,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-focused,html.theme--catppuccin-mocha .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary:focus:not(:active),html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--catppuccin-mocha .button.is-primary.is-focused:not(:active),html.theme--catppuccin-mocha .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button.is-primary:active,html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink:active,html.theme--catppuccin-mocha .button.is-primary.is-active,html.theme--catppuccin-mocha .docstring>section>a.button.is-active.docs-sourcelink{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-primary[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.docs-sourcelink{background-color:#89b4fa;border-color:#89b4fa;box-shadow:none}html.theme--catppuccin-mocha .button.is-primary.is-inverted,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-primary.is-inverted[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-inverted,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-loading::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-outlined:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-mocha .button.is-primary.is-outlined:focus,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-focused,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-mocha .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-primary.is-outlined[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#89b4fa;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined[disabled],html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--catppuccin-mocha .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-primary.is-light,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.docs-sourcelink{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .button.is-primary.is-light:hover,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--catppuccin-mocha .button.is-primary.is-light.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#dfebfe;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-primary.is-light:active,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--catppuccin-mocha .button.is-primary.is-light.is-active,html.theme--catppuccin-mocha .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d3e3fd;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-link{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:hover,html.theme--catppuccin-mocha .button.is-link.is-hovered{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:focus,html.theme--catppuccin-mocha .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link:focus:not(:active),html.theme--catppuccin-mocha .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .button.is-link:active,html.theme--catppuccin-mocha .button.is-link.is-active{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-link[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link{background-color:#89b4fa;border-color:#89b4fa;box-shadow:none}html.theme--catppuccin-mocha .button.is-link.is-inverted{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted:hover,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-link.is-outlined{background-color:transparent;border-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-outlined:hover,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-link.is-outlined:focus,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-focused{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-outlined{background-color:transparent;border-color:#89b4fa;box-shadow:none;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #89b4fa #89b4fa !important}html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-link.is-light{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .button.is-link.is-light:hover,html.theme--catppuccin-mocha .button.is-link.is-light.is-hovered{background-color:#dfebfe;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-link.is-light:active,html.theme--catppuccin-mocha .button.is-link.is-light.is-active{background-color:#d3e3fd;border-color:transparent;color:#063c93}html.theme--catppuccin-mocha .button.is-info{background-color:#94e2d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:hover,html.theme--catppuccin-mocha .button.is-info.is-hovered{background-color:#8adfd1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:focus,html.theme--catppuccin-mocha .button.is-info.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info:focus:not(:active),html.theme--catppuccin-mocha .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .button.is-info:active,html.theme--catppuccin-mocha .button.is-info.is-active{background-color:#80ddcd;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info{background-color:#94e2d5;border-color:#94e2d5;box-shadow:none}html.theme--catppuccin-mocha .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted:hover,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-info.is-outlined{background-color:transparent;border-color:#94e2d5;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-outlined:hover,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-info.is-outlined:focus,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-focused{background-color:#94e2d5;border-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #94e2d5 #94e2d5 !important}html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-outlined{background-color:transparent;border-color:#94e2d5;box-shadow:none;color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #94e2d5 #94e2d5 !important}html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-info.is-light{background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .button.is-info.is-light:hover,html.theme--catppuccin-mocha .button.is-info.is-light.is-hovered{background-color:#e5f8f5;border-color:transparent;color:#207466}html.theme--catppuccin-mocha .button.is-info.is-light:active,html.theme--catppuccin-mocha .button.is-info.is-light.is-active{background-color:#dbf5f1;border-color:transparent;color:#207466}html.theme--catppuccin-mocha .button.is-success{background-color:#a6e3a1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:hover,html.theme--catppuccin-mocha .button.is-success.is-hovered{background-color:#9de097;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:focus,html.theme--catppuccin-mocha .button.is-success.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success:focus:not(:active),html.theme--catppuccin-mocha .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .button.is-success:active,html.theme--catppuccin-mocha .button.is-success.is-active{background-color:#93dd8d;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success{background-color:#a6e3a1;border-color:#a6e3a1;box-shadow:none}html.theme--catppuccin-mocha .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted:hover,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-success.is-outlined{background-color:transparent;border-color:#a6e3a1;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-outlined:hover,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-success.is-outlined:focus,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-focused{background-color:#a6e3a1;border-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #a6e3a1 #a6e3a1 !important}html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-outlined{background-color:transparent;border-color:#a6e3a1;box-shadow:none;color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a6e3a1 #a6e3a1 !important}html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-success.is-light{background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .button.is-success.is-light:hover,html.theme--catppuccin-mocha .button.is-success.is-light.is-hovered{background-color:#e7f7e5;border-color:transparent;color:#287222}html.theme--catppuccin-mocha .button.is-success.is-light:active,html.theme--catppuccin-mocha .button.is-success.is-light.is-active{background-color:#def4dc;border-color:transparent;color:#287222}html.theme--catppuccin-mocha .button.is-warning{background-color:#f9e2af;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:hover,html.theme--catppuccin-mocha .button.is-warning.is-hovered{background-color:#f8dea3;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:focus,html.theme--catppuccin-mocha .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning:focus:not(:active),html.theme--catppuccin-mocha .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .button.is-warning:active,html.theme--catppuccin-mocha .button.is-warning.is-active{background-color:#f7d997;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning{background-color:#f9e2af;border-color:#f9e2af;box-shadow:none}html.theme--catppuccin-mocha .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted:hover,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined{background-color:transparent;border-color:#f9e2af;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-outlined:hover,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-warning.is-outlined:focus,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-focused{background-color:#f9e2af;border-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #f9e2af #f9e2af !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--catppuccin-mocha .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-outlined{background-color:transparent;border-color:#f9e2af;box-shadow:none;color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f9e2af #f9e2af !important}html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .button.is-warning.is-light{background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .button.is-warning.is-light:hover,html.theme--catppuccin-mocha .button.is-warning.is-light.is-hovered{background-color:#fdf4e0;border-color:transparent;color:#8a620a}html.theme--catppuccin-mocha .button.is-warning.is-light:active,html.theme--catppuccin-mocha .button.is-warning.is-light.is-active{background-color:#fcf0d4;border-color:transparent;color:#8a620a}html.theme--catppuccin-mocha .button.is-danger{background-color:#f38ba8;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:hover,html.theme--catppuccin-mocha .button.is-danger.is-hovered{background-color:#f27f9f;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:focus,html.theme--catppuccin-mocha .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger:focus:not(:active),html.theme--catppuccin-mocha .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .button.is-danger:active,html.theme--catppuccin-mocha .button.is-danger.is-active{background-color:#f17497;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .button.is-danger[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger{background-color:#f38ba8;border-color:#f38ba8;box-shadow:none}html.theme--catppuccin-mocha .button.is-danger.is-inverted{background-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted:hover,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--catppuccin-mocha .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined{background-color:transparent;border-color:#f38ba8;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-outlined:hover,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-danger.is-outlined:focus,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-focused{background-color:#f38ba8;border-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f38ba8 #f38ba8 !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--catppuccin-mocha .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-outlined{background-color:transparent;border-color:#f38ba8;box-shadow:none;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined:hover,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined:focus,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f38ba8 #f38ba8 !important}html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--catppuccin-mocha .button.is-danger.is-light{background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .button.is-danger.is-light:hover,html.theme--catppuccin-mocha .button.is-danger.is-light.is-hovered{background-color:#fce1e8;border-color:transparent;color:#991036}html.theme--catppuccin-mocha .button.is-danger.is-light:active,html.theme--catppuccin-mocha .button.is-danger.is-light.is-active{background-color:#fbd5e0;border-color:transparent;color:#991036}html.theme--catppuccin-mocha .button.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--catppuccin-mocha .button.is-small:not(.is-rounded),html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--catppuccin-mocha .button.is-normal{font-size:1rem}html.theme--catppuccin-mocha .button.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .button.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .button[disabled],fieldset[disabled] html.theme--catppuccin-mocha .button{background-color:#6c7086;border-color:#585b70;box-shadow:none;opacity:.5}html.theme--catppuccin-mocha .button.is-fullwidth{display:flex;width:100%}html.theme--catppuccin-mocha .button.is-loading{color:transparent !important;pointer-events:none}html.theme--catppuccin-mocha .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--catppuccin-mocha .button.is-static{background-color:#181825;border-color:#585b70;color:#7f849c;box-shadow:none;pointer-events:none}html.theme--catppuccin-mocha .button.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--catppuccin-mocha .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .buttons .button{margin-bottom:0.5rem}html.theme--catppuccin-mocha .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--catppuccin-mocha .buttons:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-mocha .buttons:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-mocha .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--catppuccin-mocha .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--catppuccin-mocha .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--catppuccin-mocha .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--catppuccin-mocha .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--catppuccin-mocha .buttons.has-addons .button:last-child{margin-right:0}html.theme--catppuccin-mocha .buttons.has-addons .button:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-hovered{z-index:2}html.theme--catppuccin-mocha .buttons.has-addons .button:focus,html.theme--catppuccin-mocha .buttons.has-addons .button.is-focused,html.theme--catppuccin-mocha .buttons.has-addons .button:active,html.theme--catppuccin-mocha .buttons.has-addons .button.is-active,html.theme--catppuccin-mocha .buttons.has-addons .button.is-selected{z-index:3}html.theme--catppuccin-mocha .buttons.has-addons .button:focus:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-focused:hover,html.theme--catppuccin-mocha .buttons.has-addons .button:active:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-active:hover,html.theme--catppuccin-mocha .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--catppuccin-mocha .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .buttons.is-centered{justify-content:center}html.theme--catppuccin-mocha .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--catppuccin-mocha .buttons.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .button.is-responsive.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--catppuccin-mocha .button.is-responsive,html.theme--catppuccin-mocha .button.is-responsive.is-normal{font-size:.65625rem}html.theme--catppuccin-mocha .button.is-responsive.is-medium{font-size:.75rem}html.theme--catppuccin-mocha .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .button.is-responsive.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--catppuccin-mocha .button.is-responsive,html.theme--catppuccin-mocha .button.is-responsive.is-normal{font-size:.75rem}html.theme--catppuccin-mocha .button.is-responsive.is-medium{font-size:1rem}html.theme--catppuccin-mocha .button.is-responsive.is-large{font-size:1.25rem}}html.theme--catppuccin-mocha .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--catppuccin-mocha .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--catppuccin-mocha .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--catppuccin-mocha .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--catppuccin-mocha .content li+li{margin-top:0.25em}html.theme--catppuccin-mocha .content p:not(:last-child),html.theme--catppuccin-mocha .content dl:not(:last-child),html.theme--catppuccin-mocha .content ol:not(:last-child),html.theme--catppuccin-mocha .content ul:not(:last-child),html.theme--catppuccin-mocha .content blockquote:not(:last-child),html.theme--catppuccin-mocha .content pre:not(:last-child),html.theme--catppuccin-mocha .content table:not(:last-child){margin-bottom:1em}html.theme--catppuccin-mocha .content h1,html.theme--catppuccin-mocha .content h2,html.theme--catppuccin-mocha .content h3,html.theme--catppuccin-mocha .content h4,html.theme--catppuccin-mocha .content h5,html.theme--catppuccin-mocha .content h6{color:#cdd6f4;font-weight:600;line-height:1.125}html.theme--catppuccin-mocha .content h1{font-size:2em;margin-bottom:0.5em}html.theme--catppuccin-mocha .content h1:not(:first-child){margin-top:1em}html.theme--catppuccin-mocha .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--catppuccin-mocha .content h2:not(:first-child){margin-top:1.1428em}html.theme--catppuccin-mocha .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--catppuccin-mocha .content h3:not(:first-child){margin-top:1.3333em}html.theme--catppuccin-mocha .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--catppuccin-mocha .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--catppuccin-mocha .content h6{font-size:1em;margin-bottom:1em}html.theme--catppuccin-mocha .content blockquote{background-color:#181825;border-left:5px solid #585b70;padding:1.25em 1.5em}html.theme--catppuccin-mocha .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-mocha .content ol:not([type]){list-style-type:decimal}html.theme--catppuccin-mocha .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--catppuccin-mocha .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--catppuccin-mocha .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--catppuccin-mocha .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--catppuccin-mocha .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--catppuccin-mocha .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--catppuccin-mocha .content ul ul ul{list-style-type:square}html.theme--catppuccin-mocha .content dd{margin-left:2em}html.theme--catppuccin-mocha .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--catppuccin-mocha .content figure:not(:first-child){margin-top:2em}html.theme--catppuccin-mocha .content figure:not(:last-child){margin-bottom:2em}html.theme--catppuccin-mocha .content figure img{display:inline-block}html.theme--catppuccin-mocha .content figure figcaption{font-style:italic}html.theme--catppuccin-mocha .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--catppuccin-mocha .content sup,html.theme--catppuccin-mocha .content sub{font-size:75%}html.theme--catppuccin-mocha .content table{width:100%}html.theme--catppuccin-mocha .content table td,html.theme--catppuccin-mocha .content table th{border:1px solid #585b70;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-mocha .content table th{color:#b8c5ef}html.theme--catppuccin-mocha .content table th:not([align]){text-align:inherit}html.theme--catppuccin-mocha .content table thead td,html.theme--catppuccin-mocha .content table thead th{border-width:0 0 2px;color:#b8c5ef}html.theme--catppuccin-mocha .content table tfoot td,html.theme--catppuccin-mocha .content table tfoot th{border-width:2px 0 0;color:#b8c5ef}html.theme--catppuccin-mocha .content table tbody tr:last-child td,html.theme--catppuccin-mocha .content table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-mocha .content .tabs li+li{margin-top:0}html.theme--catppuccin-mocha .content.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--catppuccin-mocha .content.is-normal{font-size:1rem}html.theme--catppuccin-mocha .content.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .content.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--catppuccin-mocha .icon.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--catppuccin-mocha .icon.is-medium{height:2rem;width:2rem}html.theme--catppuccin-mocha .icon.is-large{height:3rem;width:3rem}html.theme--catppuccin-mocha .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--catppuccin-mocha .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--catppuccin-mocha .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--catppuccin-mocha div.icon-text{display:flex}html.theme--catppuccin-mocha .image,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--catppuccin-mocha .image img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--catppuccin-mocha .image img.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--catppuccin-mocha .image.is-fullwidth,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--catppuccin-mocha .image.is-square img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--catppuccin-mocha .image.is-square .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--catppuccin-mocha .image.is-1by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--catppuccin-mocha .image.is-1by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--catppuccin-mocha .image.is-5by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--catppuccin-mocha .image.is-5by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--catppuccin-mocha .image.is-4by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--catppuccin-mocha .image.is-4by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--catppuccin-mocha .image.is-3by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--catppuccin-mocha .image.is-5by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--catppuccin-mocha .image.is-5by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--catppuccin-mocha .image.is-16by9 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--catppuccin-mocha .image.is-16by9 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--catppuccin-mocha .image.is-2by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--catppuccin-mocha .image.is-2by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--catppuccin-mocha .image.is-3by1 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--catppuccin-mocha .image.is-3by1 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--catppuccin-mocha .image.is-4by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--catppuccin-mocha .image.is-4by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--catppuccin-mocha .image.is-3by4 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--catppuccin-mocha .image.is-3by4 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--catppuccin-mocha .image.is-2by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--catppuccin-mocha .image.is-2by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--catppuccin-mocha .image.is-3by5 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--catppuccin-mocha .image.is-3by5 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--catppuccin-mocha .image.is-9by16 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--catppuccin-mocha .image.is-9by16 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--catppuccin-mocha .image.is-1by2 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--catppuccin-mocha .image.is-1by2 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--catppuccin-mocha .image.is-1by3 img,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--catppuccin-mocha .image.is-1by3 .has-ratio,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--catppuccin-mocha .image.is-square,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--catppuccin-mocha .image.is-1by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--catppuccin-mocha .image.is-5by4,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--catppuccin-mocha .image.is-4by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--catppuccin-mocha .image.is-3by2,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--catppuccin-mocha .image.is-5by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--catppuccin-mocha .image.is-16by9,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--catppuccin-mocha .image.is-2by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--catppuccin-mocha .image.is-3by1,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--catppuccin-mocha .image.is-4by5,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--catppuccin-mocha .image.is-3by4,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--catppuccin-mocha .image.is-2by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--catppuccin-mocha .image.is-3by5,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--catppuccin-mocha .image.is-9by16,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--catppuccin-mocha .image.is-1by2,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--catppuccin-mocha .image.is-1by3,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--catppuccin-mocha .image.is-16x16,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--catppuccin-mocha .image.is-24x24,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--catppuccin-mocha .image.is-32x32,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--catppuccin-mocha .image.is-48x48,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--catppuccin-mocha .image.is-64x64,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--catppuccin-mocha .image.is-96x96,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--catppuccin-mocha .image.is-128x128,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--catppuccin-mocha .notification{background-color:#181825;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--catppuccin-mocha .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-mocha .notification strong{color:currentColor}html.theme--catppuccin-mocha .notification code,html.theme--catppuccin-mocha .notification pre{background:#fff}html.theme--catppuccin-mocha .notification pre code{background:transparent}html.theme--catppuccin-mocha .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--catppuccin-mocha .notification .title,html.theme--catppuccin-mocha .notification .subtitle,html.theme--catppuccin-mocha .notification .content{color:currentColor}html.theme--catppuccin-mocha .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-dark,html.theme--catppuccin-mocha .content kbd.notification{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .notification.is-primary,html.theme--catppuccin-mocha .docstring>section>a.notification.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .notification.is-primary.is-light,html.theme--catppuccin-mocha .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .notification.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .notification.is-link.is-light{background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .notification.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-info.is-light{background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .notification.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-success.is-light{background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .notification.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .notification.is-warning.is-light{background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .notification.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .notification.is-danger.is-light{background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--catppuccin-mocha .progress::-webkit-progress-bar{background-color:#45475a}html.theme--catppuccin-mocha .progress::-webkit-progress-value{background-color:#7f849c}html.theme--catppuccin-mocha .progress::-moz-progress-bar{background-color:#7f849c}html.theme--catppuccin-mocha .progress::-ms-fill{background-color:#7f849c;border:none}html.theme--catppuccin-mocha .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white::-ms-fill{background-color:#fff}html.theme--catppuccin-mocha .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--catppuccin-mocha .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-light::-webkit-progress-value{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light::-moz-progress-bar{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light::-ms-fill{background-color:#f5f5f5}html.theme--catppuccin-mocha .progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-dark::-webkit-progress-value,html.theme--catppuccin-mocha .content kbd.progress::-webkit-progress-value{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark::-moz-progress-bar,html.theme--catppuccin-mocha .content kbd.progress::-moz-progress-bar{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark::-ms-fill,html.theme--catppuccin-mocha .content kbd.progress::-ms-fill{background-color:#313244}html.theme--catppuccin-mocha .progress.is-dark:indeterminate,html.theme--catppuccin-mocha .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #313244 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-primary::-webkit-progress-value,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary::-moz-progress-bar,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary::-ms-fill,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-primary:indeterminate,html.theme--catppuccin-mocha .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #89b4fa 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-link::-webkit-progress-value{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link::-moz-progress-bar{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link::-ms-fill{background-color:#89b4fa}html.theme--catppuccin-mocha .progress.is-link:indeterminate{background-image:linear-gradient(to right, #89b4fa 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-info::-webkit-progress-value{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info::-moz-progress-bar{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info::-ms-fill{background-color:#94e2d5}html.theme--catppuccin-mocha .progress.is-info:indeterminate{background-image:linear-gradient(to right, #94e2d5 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-success::-webkit-progress-value{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success::-moz-progress-bar{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success::-ms-fill{background-color:#a6e3a1}html.theme--catppuccin-mocha .progress.is-success:indeterminate{background-image:linear-gradient(to right, #a6e3a1 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-warning::-webkit-progress-value{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning::-moz-progress-bar{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning::-ms-fill{background-color:#f9e2af}html.theme--catppuccin-mocha .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #f9e2af 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress.is-danger::-webkit-progress-value{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger::-moz-progress-bar{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger::-ms-fill{background-color:#f38ba8}html.theme--catppuccin-mocha .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #f38ba8 30%, #45475a 30%)}html.theme--catppuccin-mocha .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#45475a;background-image:linear-gradient(to right, #cdd6f4 30%, #45475a 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--catppuccin-mocha .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--catppuccin-mocha .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--catppuccin-mocha .progress:indeterminate::-ms-fill{animation-name:none}html.theme--catppuccin-mocha .progress.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--catppuccin-mocha .progress.is-medium{height:1.25rem}html.theme--catppuccin-mocha .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--catppuccin-mocha .table{background-color:#45475a;color:#cdd6f4}html.theme--catppuccin-mocha .table td,html.theme--catppuccin-mocha .table th{border:1px solid #585b70;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--catppuccin-mocha .table td.is-white,html.theme--catppuccin-mocha .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .table td.is-black,html.theme--catppuccin-mocha .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .table td.is-light,html.theme--catppuccin-mocha .table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-dark,html.theme--catppuccin-mocha .table th.is-dark{background-color:#313244;border-color:#313244;color:#fff}html.theme--catppuccin-mocha .table td.is-primary,html.theme--catppuccin-mocha .table th.is-primary{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-link,html.theme--catppuccin-mocha .table th.is-link{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-info,html.theme--catppuccin-mocha .table th.is-info{background-color:#94e2d5;border-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-success,html.theme--catppuccin-mocha .table th.is-success{background-color:#a6e3a1;border-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-warning,html.theme--catppuccin-mocha .table th.is-warning{background-color:#f9e2af;border-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .table td.is-danger,html.theme--catppuccin-mocha .table th.is-danger{background-color:#f38ba8;border-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .table td.is-narrow,html.theme--catppuccin-mocha .table th.is-narrow{white-space:nowrap;width:1%}html.theme--catppuccin-mocha .table td.is-selected,html.theme--catppuccin-mocha .table th.is-selected{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table td.is-selected a,html.theme--catppuccin-mocha .table td.is-selected strong,html.theme--catppuccin-mocha .table th.is-selected a,html.theme--catppuccin-mocha .table th.is-selected strong{color:currentColor}html.theme--catppuccin-mocha .table td.is-vcentered,html.theme--catppuccin-mocha .table th.is-vcentered{vertical-align:middle}html.theme--catppuccin-mocha .table th{color:#b8c5ef}html.theme--catppuccin-mocha .table th:not([align]){text-align:left}html.theme--catppuccin-mocha .table tr.is-selected{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .table tr.is-selected a,html.theme--catppuccin-mocha .table tr.is-selected strong{color:currentColor}html.theme--catppuccin-mocha .table tr.is-selected td,html.theme--catppuccin-mocha .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--catppuccin-mocha .table thead{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table thead td,html.theme--catppuccin-mocha .table thead th{border-width:0 0 2px;color:#b8c5ef}html.theme--catppuccin-mocha .table tfoot{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table tfoot td,html.theme--catppuccin-mocha .table tfoot th{border-width:2px 0 0;color:#b8c5ef}html.theme--catppuccin-mocha .table tbody{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .table tbody tr:last-child td,html.theme--catppuccin-mocha .table tbody tr:last-child th{border-bottom-width:0}html.theme--catppuccin-mocha .table.is-bordered td,html.theme--catppuccin-mocha .table.is-bordered th{border-width:1px}html.theme--catppuccin-mocha .table.is-bordered tr:last-child td,html.theme--catppuccin-mocha .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--catppuccin-mocha .table.is-fullwidth{width:100%}html.theme--catppuccin-mocha .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#313244}html.theme--catppuccin-mocha .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#313244}html.theme--catppuccin-mocha .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#35364a}html.theme--catppuccin-mocha .table.is-narrow td,html.theme--catppuccin-mocha .table.is-narrow th{padding:0.25em 0.5em}html.theme--catppuccin-mocha .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#313244}html.theme--catppuccin-mocha .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--catppuccin-mocha .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .tags .tag,html.theme--catppuccin-mocha .tags .content kbd,html.theme--catppuccin-mocha .content .tags kbd,html.theme--catppuccin-mocha .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--catppuccin-mocha .tags .tag:not(:last-child),html.theme--catppuccin-mocha .tags .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags kbd:not(:last-child),html.theme--catppuccin-mocha .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--catppuccin-mocha .tags:last-child{margin-bottom:-0.5rem}html.theme--catppuccin-mocha .tags:not(:last-child){margin-bottom:1rem}html.theme--catppuccin-mocha .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--catppuccin-mocha .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--catppuccin-mocha .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--catppuccin-mocha .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--catppuccin-mocha .tags.is-centered{justify-content:center}html.theme--catppuccin-mocha .tags.is-centered .tag,html.theme--catppuccin-mocha .tags.is-centered .content kbd,html.theme--catppuccin-mocha .content .tags.is-centered kbd,html.theme--catppuccin-mocha .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--catppuccin-mocha .tags.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .tags.is-right .tag:not(:first-child),html.theme--catppuccin-mocha .tags.is-right .content kbd:not(:first-child),html.theme--catppuccin-mocha .content .tags.is-right kbd:not(:first-child),html.theme--catppuccin-mocha .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--catppuccin-mocha .tags.is-right .tag:not(:last-child),html.theme--catppuccin-mocha .tags.is-right .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags.is-right kbd:not(:last-child),html.theme--catppuccin-mocha .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--catppuccin-mocha .tags.has-addons .tag,html.theme--catppuccin-mocha .tags.has-addons .content kbd,html.theme--catppuccin-mocha .content .tags.has-addons kbd,html.theme--catppuccin-mocha .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--catppuccin-mocha .tags.has-addons .tag:not(:first-child),html.theme--catppuccin-mocha .tags.has-addons .content kbd:not(:first-child),html.theme--catppuccin-mocha .content .tags.has-addons kbd:not(:first-child),html.theme--catppuccin-mocha .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--catppuccin-mocha .tags.has-addons .tag:not(:last-child),html.theme--catppuccin-mocha .tags.has-addons .content kbd:not(:last-child),html.theme--catppuccin-mocha .content .tags.has-addons kbd:not(:last-child),html.theme--catppuccin-mocha .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--catppuccin-mocha .tag:not(body),html.theme--catppuccin-mocha .content kbd:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#181825;border-radius:.4em;color:#cdd6f4;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--catppuccin-mocha .tag:not(body) .delete,html.theme--catppuccin-mocha .content kbd:not(body) .delete,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--catppuccin-mocha .tag.is-white:not(body),html.theme--catppuccin-mocha .content kbd.is-white:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .tag.is-black:not(body),html.theme--catppuccin-mocha .content kbd.is-black:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .tag.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-dark:not(body),html.theme--catppuccin-mocha .content kbd:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--catppuccin-mocha .content .docstring>section>kbd:not(body){background-color:#313244;color:#fff}html.theme--catppuccin-mocha .tag.is-primary:not(body),html.theme--catppuccin-mocha .content kbd.is-primary:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .tag.is-primary.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-primary.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .tag.is-link:not(body),html.theme--catppuccin-mocha .content kbd.is-link:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .tag.is-link.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-link.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#ebf3fe;color:#063c93}html.theme--catppuccin-mocha .tag.is-info:not(body),html.theme--catppuccin-mocha .content kbd.is-info:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-info.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-info.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#effbf9;color:#207466}html.theme--catppuccin-mocha .tag.is-success:not(body),html.theme--catppuccin-mocha .content kbd.is-success:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-success.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-success.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#f0faef;color:#287222}html.theme--catppuccin-mocha .tag.is-warning:not(body),html.theme--catppuccin-mocha .content kbd.is-warning:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .tag.is-warning.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-warning.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fef8ec;color:#8a620a}html.theme--catppuccin-mocha .tag.is-danger:not(body),html.theme--catppuccin-mocha .content kbd.is-danger:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .tag.is-danger.is-light:not(body),html.theme--catppuccin-mocha .content kbd.is-danger.is-light:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fdedf1;color:#991036}html.theme--catppuccin-mocha .tag.is-normal:not(body),html.theme--catppuccin-mocha .content kbd.is-normal:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--catppuccin-mocha .tag.is-medium:not(body),html.theme--catppuccin-mocha .content kbd.is-medium:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--catppuccin-mocha .tag.is-large:not(body),html.theme--catppuccin-mocha .content kbd.is-large:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--catppuccin-mocha .tag:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-mocha .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--catppuccin-mocha .tag:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-mocha .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--catppuccin-mocha .tag:not(body) .icon:first-child:last-child,html.theme--catppuccin-mocha .content kbd:not(body) .icon:first-child:last-child,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--catppuccin-mocha .tag.is-delete:not(body),html.theme--catppuccin-mocha .content kbd.is-delete:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--catppuccin-mocha .tag.is-delete:not(body)::before,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::before,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--catppuccin-mocha .tag.is-delete:not(body)::after,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::after,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--catppuccin-mocha .tag.is-delete:not(body)::before,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::before,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--catppuccin-mocha .tag.is-delete:not(body)::after,html.theme--catppuccin-mocha .content kbd.is-delete:not(body)::after,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--catppuccin-mocha .tag.is-delete:not(body):hover,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):hover,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--catppuccin-mocha .tag.is-delete:not(body):focus,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):focus,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#0e0e16}html.theme--catppuccin-mocha .tag.is-delete:not(body):active,html.theme--catppuccin-mocha .content kbd.is-delete:not(body):active,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#040406}html.theme--catppuccin-mocha .tag.is-rounded:not(body),html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--catppuccin-mocha .content kbd.is-rounded:not(body),html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--catppuccin-mocha a.tag:hover,html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--catppuccin-mocha .title,html.theme--catppuccin-mocha .subtitle{word-break:break-word}html.theme--catppuccin-mocha .title em,html.theme--catppuccin-mocha .title span,html.theme--catppuccin-mocha .subtitle em,html.theme--catppuccin-mocha .subtitle span{font-weight:inherit}html.theme--catppuccin-mocha .title sub,html.theme--catppuccin-mocha .subtitle sub{font-size:.75em}html.theme--catppuccin-mocha .title sup,html.theme--catppuccin-mocha .subtitle sup{font-size:.75em}html.theme--catppuccin-mocha .title .tag,html.theme--catppuccin-mocha .title .content kbd,html.theme--catppuccin-mocha .content .title kbd,html.theme--catppuccin-mocha .title .docstring>section>a.docs-sourcelink,html.theme--catppuccin-mocha .subtitle .tag,html.theme--catppuccin-mocha .subtitle .content kbd,html.theme--catppuccin-mocha .content .subtitle kbd,html.theme--catppuccin-mocha .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--catppuccin-mocha .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--catppuccin-mocha .title strong{color:inherit;font-weight:inherit}html.theme--catppuccin-mocha .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--catppuccin-mocha .title.is-1{font-size:3rem}html.theme--catppuccin-mocha .title.is-2{font-size:2.5rem}html.theme--catppuccin-mocha .title.is-3{font-size:2rem}html.theme--catppuccin-mocha .title.is-4{font-size:1.5rem}html.theme--catppuccin-mocha .title.is-5{font-size:1.25rem}html.theme--catppuccin-mocha .title.is-6{font-size:1rem}html.theme--catppuccin-mocha .title.is-7{font-size:.75rem}html.theme--catppuccin-mocha .subtitle{color:#6c7086;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--catppuccin-mocha .subtitle strong{color:#6c7086;font-weight:600}html.theme--catppuccin-mocha .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--catppuccin-mocha .subtitle.is-1{font-size:3rem}html.theme--catppuccin-mocha .subtitle.is-2{font-size:2.5rem}html.theme--catppuccin-mocha .subtitle.is-3{font-size:2rem}html.theme--catppuccin-mocha .subtitle.is-4{font-size:1.5rem}html.theme--catppuccin-mocha .subtitle.is-5{font-size:1.25rem}html.theme--catppuccin-mocha .subtitle.is-6{font-size:1rem}html.theme--catppuccin-mocha .subtitle.is-7{font-size:.75rem}html.theme--catppuccin-mocha .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--catppuccin-mocha .number{align-items:center;background-color:#181825;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{background-color:#1e1e2e;border-color:#585b70;border-radius:.4em;color:#7f849c}html.theme--catppuccin-mocha .select select::-moz-placeholder,html.theme--catppuccin-mocha .textarea::-moz-placeholder,html.theme--catppuccin-mocha .input::-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select::-webkit-input-placeholder,html.theme--catppuccin-mocha .textarea::-webkit-input-placeholder,html.theme--catppuccin-mocha .input::-webkit-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:-moz-placeholder,html.theme--catppuccin-mocha .textarea:-moz-placeholder,html.theme--catppuccin-mocha .input:-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:-ms-input-placeholder,html.theme--catppuccin-mocha .textarea:-ms-input-placeholder,html.theme--catppuccin-mocha .input:-ms-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--catppuccin-mocha .select select:hover,html.theme--catppuccin-mocha .textarea:hover,html.theme--catppuccin-mocha .input:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:hover,html.theme--catppuccin-mocha .select select.is-hovered,html.theme--catppuccin-mocha .is-hovered.textarea,html.theme--catppuccin-mocha .is-hovered.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#6c7086}html.theme--catppuccin-mocha .select select:focus,html.theme--catppuccin-mocha .textarea:focus,html.theme--catppuccin-mocha .input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:focus,html.theme--catppuccin-mocha .select select.is-focused,html.theme--catppuccin-mocha .is-focused.textarea,html.theme--catppuccin-mocha .is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .select select:active,html.theme--catppuccin-mocha .textarea:active,html.theme--catppuccin-mocha .input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:active,html.theme--catppuccin-mocha .select select.is-active,html.theme--catppuccin-mocha .is-active.textarea,html.theme--catppuccin-mocha .is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#89b4fa;box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select select[disabled],html.theme--catppuccin-mocha .textarea[disabled],html.theme--catppuccin-mocha .input[disabled],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--catppuccin-mocha .select select,fieldset[disabled] html.theme--catppuccin-mocha .textarea,fieldset[disabled] html.theme--catppuccin-mocha .input,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{background-color:#6c7086;border-color:#181825;box-shadow:none;color:#f7f8fd}html.theme--catppuccin-mocha .select select[disabled]::-moz-placeholder,html.theme--catppuccin-mocha .textarea[disabled]::-moz-placeholder,html.theme--catppuccin-mocha .input[disabled]::-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input::-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha .textarea[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha .input[disabled]::-webkit-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input::-webkit-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]:-moz-placeholder,html.theme--catppuccin-mocha .textarea[disabled]:-moz-placeholder,html.theme--catppuccin-mocha .input[disabled]:-moz-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input:-moz-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .select select[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha .textarea[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha .input[disabled]:-ms-input-placeholder,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .select select:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha .input:-ms-input-placeholder,fieldset[disabled] html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(247,248,253,0.3)}html.theme--catppuccin-mocha .textarea,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--catppuccin-mocha .textarea[readonly],html.theme--catppuccin-mocha .input[readonly],html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--catppuccin-mocha .is-white.textarea,html.theme--catppuccin-mocha .is-white.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--catppuccin-mocha .is-white.textarea:focus,html.theme--catppuccin-mocha .is-white.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--catppuccin-mocha .is-white.is-focused.textarea,html.theme--catppuccin-mocha .is-white.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-white.textarea:active,html.theme--catppuccin-mocha .is-white.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--catppuccin-mocha .is-white.is-active.textarea,html.theme--catppuccin-mocha .is-white.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .is-black.textarea,html.theme--catppuccin-mocha .is-black.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--catppuccin-mocha .is-black.textarea:focus,html.theme--catppuccin-mocha .is-black.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--catppuccin-mocha .is-black.is-focused.textarea,html.theme--catppuccin-mocha .is-black.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-black.textarea:active,html.theme--catppuccin-mocha .is-black.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--catppuccin-mocha .is-black.is-active.textarea,html.theme--catppuccin-mocha .is-black.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .is-light.textarea,html.theme--catppuccin-mocha .is-light.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}html.theme--catppuccin-mocha .is-light.textarea:focus,html.theme--catppuccin-mocha .is-light.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--catppuccin-mocha .is-light.is-focused.textarea,html.theme--catppuccin-mocha .is-light.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-light.textarea:active,html.theme--catppuccin-mocha .is-light.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--catppuccin-mocha .is-light.is-active.textarea,html.theme--catppuccin-mocha .is-light.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .is-dark.textarea,html.theme--catppuccin-mocha .content kbd.textarea,html.theme--catppuccin-mocha .is-dark.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--catppuccin-mocha .content kbd.input{border-color:#313244}html.theme--catppuccin-mocha .is-dark.textarea:focus,html.theme--catppuccin-mocha .content kbd.textarea:focus,html.theme--catppuccin-mocha .is-dark.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--catppuccin-mocha .content kbd.input:focus,html.theme--catppuccin-mocha .is-dark.is-focused.textarea,html.theme--catppuccin-mocha .content kbd.is-focused.textarea,html.theme--catppuccin-mocha .is-dark.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .content kbd.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-dark.textarea:active,html.theme--catppuccin-mocha .content kbd.textarea:active,html.theme--catppuccin-mocha .is-dark.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--catppuccin-mocha .content kbd.input:active,html.theme--catppuccin-mocha .is-dark.is-active.textarea,html.theme--catppuccin-mocha .content kbd.is-active.textarea,html.theme--catppuccin-mocha .is-dark.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .content kbd.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .is-primary.textarea,html.theme--catppuccin-mocha .docstring>section>a.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--catppuccin-mocha .docstring>section>a.input.docs-sourcelink{border-color:#89b4fa}html.theme--catppuccin-mocha .is-primary.textarea:focus,html.theme--catppuccin-mocha .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--catppuccin-mocha .is-primary.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--catppuccin-mocha .docstring>section>a.input.docs-sourcelink:focus,html.theme--catppuccin-mocha .is-primary.is-focused.textarea,html.theme--catppuccin-mocha .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.textarea:active,html.theme--catppuccin-mocha .docstring>section>a.textarea.docs-sourcelink:active,html.theme--catppuccin-mocha .is-primary.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--catppuccin-mocha .docstring>section>a.input.docs-sourcelink:active,html.theme--catppuccin-mocha .is-primary.is-active.textarea,html.theme--catppuccin-mocha .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--catppuccin-mocha .is-primary.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--catppuccin-mocha .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .is-link.textarea,html.theme--catppuccin-mocha .is-link.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#89b4fa}html.theme--catppuccin-mocha .is-link.textarea:focus,html.theme--catppuccin-mocha .is-link.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--catppuccin-mocha .is-link.is-focused.textarea,html.theme--catppuccin-mocha .is-link.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-link.textarea:active,html.theme--catppuccin-mocha .is-link.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--catppuccin-mocha .is-link.is-active.textarea,html.theme--catppuccin-mocha .is-link.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .is-info.textarea,html.theme--catppuccin-mocha .is-info.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#94e2d5}html.theme--catppuccin-mocha .is-info.textarea:focus,html.theme--catppuccin-mocha .is-info.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--catppuccin-mocha .is-info.is-focused.textarea,html.theme--catppuccin-mocha .is-info.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-info.textarea:active,html.theme--catppuccin-mocha .is-info.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--catppuccin-mocha .is-info.is-active.textarea,html.theme--catppuccin-mocha .is-info.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .is-success.textarea,html.theme--catppuccin-mocha .is-success.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#a6e3a1}html.theme--catppuccin-mocha .is-success.textarea:focus,html.theme--catppuccin-mocha .is-success.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--catppuccin-mocha .is-success.is-focused.textarea,html.theme--catppuccin-mocha .is-success.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-success.textarea:active,html.theme--catppuccin-mocha .is-success.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--catppuccin-mocha .is-success.is-active.textarea,html.theme--catppuccin-mocha .is-success.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .is-warning.textarea,html.theme--catppuccin-mocha .is-warning.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#f9e2af}html.theme--catppuccin-mocha .is-warning.textarea:focus,html.theme--catppuccin-mocha .is-warning.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--catppuccin-mocha .is-warning.is-focused.textarea,html.theme--catppuccin-mocha .is-warning.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-warning.textarea:active,html.theme--catppuccin-mocha .is-warning.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--catppuccin-mocha .is-warning.is-active.textarea,html.theme--catppuccin-mocha .is-warning.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .is-danger.textarea,html.theme--catppuccin-mocha .is-danger.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#f38ba8}html.theme--catppuccin-mocha .is-danger.textarea:focus,html.theme--catppuccin-mocha .is-danger.input:focus,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--catppuccin-mocha .is-danger.is-focused.textarea,html.theme--catppuccin-mocha .is-danger.is-focused.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--catppuccin-mocha .is-danger.textarea:active,html.theme--catppuccin-mocha .is-danger.input:active,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--catppuccin-mocha .is-danger.is-active.textarea,html.theme--catppuccin-mocha .is-danger.is-active.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .is-small.textarea,html.theme--catppuccin-mocha .is-small.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--catppuccin-mocha .is-medium.textarea,html.theme--catppuccin-mocha .is-medium.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .is-large.textarea,html.theme--catppuccin-mocha .is-large.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .is-fullwidth.textarea,html.theme--catppuccin-mocha .is-fullwidth.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--catppuccin-mocha .is-inline.textarea,html.theme--catppuccin-mocha .is-inline.input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--catppuccin-mocha .input.is-rounded,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--catppuccin-mocha .input.is-static,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--catppuccin-mocha .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--catppuccin-mocha .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--catppuccin-mocha .textarea[rows]{height:initial}html.theme--catppuccin-mocha .textarea.has-fixed-size{resize:none}html.theme--catppuccin-mocha .radio,html.theme--catppuccin-mocha .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--catppuccin-mocha .radio input,html.theme--catppuccin-mocha .checkbox input{cursor:pointer}html.theme--catppuccin-mocha .radio:hover,html.theme--catppuccin-mocha .checkbox:hover{color:#89dceb}html.theme--catppuccin-mocha .radio[disabled],html.theme--catppuccin-mocha .checkbox[disabled],fieldset[disabled] html.theme--catppuccin-mocha .radio,fieldset[disabled] html.theme--catppuccin-mocha .checkbox,html.theme--catppuccin-mocha .radio input[disabled],html.theme--catppuccin-mocha .checkbox input[disabled]{color:#f7f8fd;cursor:not-allowed}html.theme--catppuccin-mocha .radio+.radio{margin-left:.5em}html.theme--catppuccin-mocha .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--catppuccin-mocha .select:not(.is-multiple){height:2.5em}html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading)::after{border-color:#89b4fa;right:1.125em;z-index:4}html.theme--catppuccin-mocha .select.is-rounded select,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--catppuccin-mocha .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--catppuccin-mocha .select select::-ms-expand{display:none}html.theme--catppuccin-mocha .select select[disabled]:hover,fieldset[disabled] html.theme--catppuccin-mocha .select select:hover{border-color:#181825}html.theme--catppuccin-mocha .select select:not([multiple]){padding-right:2.5em}html.theme--catppuccin-mocha .select select[multiple]{height:auto;padding:0}html.theme--catppuccin-mocha .select select[multiple] option{padding:0.5em 1em}html.theme--catppuccin-mocha .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#89dceb}html.theme--catppuccin-mocha .select.is-white:not(:hover)::after{border-color:#fff}html.theme--catppuccin-mocha .select.is-white select{border-color:#fff}html.theme--catppuccin-mocha .select.is-white select:hover,html.theme--catppuccin-mocha .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--catppuccin-mocha .select.is-white select:focus,html.theme--catppuccin-mocha .select.is-white select.is-focused,html.theme--catppuccin-mocha .select.is-white select:active,html.theme--catppuccin-mocha .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--catppuccin-mocha .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .select.is-black select{border-color:#0a0a0a}html.theme--catppuccin-mocha .select.is-black select:hover,html.theme--catppuccin-mocha .select.is-black select.is-hovered{border-color:#000}html.theme--catppuccin-mocha .select.is-black select:focus,html.theme--catppuccin-mocha .select.is-black select.is-focused,html.theme--catppuccin-mocha .select.is-black select:active,html.theme--catppuccin-mocha .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--catppuccin-mocha .select.is-light:not(:hover)::after{border-color:#f5f5f5}html.theme--catppuccin-mocha .select.is-light select{border-color:#f5f5f5}html.theme--catppuccin-mocha .select.is-light select:hover,html.theme--catppuccin-mocha .select.is-light select.is-hovered{border-color:#e8e8e8}html.theme--catppuccin-mocha .select.is-light select:focus,html.theme--catppuccin-mocha .select.is-light select.is-focused,html.theme--catppuccin-mocha .select.is-light select:active,html.theme--catppuccin-mocha .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}html.theme--catppuccin-mocha .select.is-dark:not(:hover)::after,html.theme--catppuccin-mocha .content kbd.select:not(:hover)::after{border-color:#313244}html.theme--catppuccin-mocha .select.is-dark select,html.theme--catppuccin-mocha .content kbd.select select{border-color:#313244}html.theme--catppuccin-mocha .select.is-dark select:hover,html.theme--catppuccin-mocha .content kbd.select select:hover,html.theme--catppuccin-mocha .select.is-dark select.is-hovered,html.theme--catppuccin-mocha .content kbd.select select.is-hovered{border-color:#262735}html.theme--catppuccin-mocha .select.is-dark select:focus,html.theme--catppuccin-mocha .content kbd.select select:focus,html.theme--catppuccin-mocha .select.is-dark select.is-focused,html.theme--catppuccin-mocha .content kbd.select select.is-focused,html.theme--catppuccin-mocha .select.is-dark select:active,html.theme--catppuccin-mocha .content kbd.select select:active,html.theme--catppuccin-mocha .select.is-dark select.is-active,html.theme--catppuccin-mocha .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(49,50,68,0.25)}html.theme--catppuccin-mocha .select.is-primary:not(:hover)::after,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-primary select,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-primary select:hover,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select:hover,html.theme--catppuccin-mocha .select.is-primary select.is-hovered,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#71a4f9}html.theme--catppuccin-mocha .select.is-primary select:focus,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select:focus,html.theme--catppuccin-mocha .select.is-primary select.is-focused,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--catppuccin-mocha .select.is-primary select:active,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select:active,html.theme--catppuccin-mocha .select.is-primary select.is-active,html.theme--catppuccin-mocha .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select.is-link:not(:hover)::after{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-link select{border-color:#89b4fa}html.theme--catppuccin-mocha .select.is-link select:hover,html.theme--catppuccin-mocha .select.is-link select.is-hovered{border-color:#71a4f9}html.theme--catppuccin-mocha .select.is-link select:focus,html.theme--catppuccin-mocha .select.is-link select.is-focused,html.theme--catppuccin-mocha .select.is-link select:active,html.theme--catppuccin-mocha .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(137,180,250,0.25)}html.theme--catppuccin-mocha .select.is-info:not(:hover)::after{border-color:#94e2d5}html.theme--catppuccin-mocha .select.is-info select{border-color:#94e2d5}html.theme--catppuccin-mocha .select.is-info select:hover,html.theme--catppuccin-mocha .select.is-info select.is-hovered{border-color:#80ddcd}html.theme--catppuccin-mocha .select.is-info select:focus,html.theme--catppuccin-mocha .select.is-info select.is-focused,html.theme--catppuccin-mocha .select.is-info select:active,html.theme--catppuccin-mocha .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(148,226,213,0.25)}html.theme--catppuccin-mocha .select.is-success:not(:hover)::after{border-color:#a6e3a1}html.theme--catppuccin-mocha .select.is-success select{border-color:#a6e3a1}html.theme--catppuccin-mocha .select.is-success select:hover,html.theme--catppuccin-mocha .select.is-success select.is-hovered{border-color:#93dd8d}html.theme--catppuccin-mocha .select.is-success select:focus,html.theme--catppuccin-mocha .select.is-success select.is-focused,html.theme--catppuccin-mocha .select.is-success select:active,html.theme--catppuccin-mocha .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(166,227,161,0.25)}html.theme--catppuccin-mocha .select.is-warning:not(:hover)::after{border-color:#f9e2af}html.theme--catppuccin-mocha .select.is-warning select{border-color:#f9e2af}html.theme--catppuccin-mocha .select.is-warning select:hover,html.theme--catppuccin-mocha .select.is-warning select.is-hovered{border-color:#f7d997}html.theme--catppuccin-mocha .select.is-warning select:focus,html.theme--catppuccin-mocha .select.is-warning select.is-focused,html.theme--catppuccin-mocha .select.is-warning select:active,html.theme--catppuccin-mocha .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(249,226,175,0.25)}html.theme--catppuccin-mocha .select.is-danger:not(:hover)::after{border-color:#f38ba8}html.theme--catppuccin-mocha .select.is-danger select{border-color:#f38ba8}html.theme--catppuccin-mocha .select.is-danger select:hover,html.theme--catppuccin-mocha .select.is-danger select.is-hovered{border-color:#f17497}html.theme--catppuccin-mocha .select.is-danger select:focus,html.theme--catppuccin-mocha .select.is-danger select.is-focused,html.theme--catppuccin-mocha .select.is-danger select:active,html.theme--catppuccin-mocha .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(243,139,168,0.25)}html.theme--catppuccin-mocha .select.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--catppuccin-mocha .select.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .select.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .select.is-disabled::after{border-color:#f7f8fd !important;opacity:0.5}html.theme--catppuccin-mocha .select.is-fullwidth{width:100%}html.theme--catppuccin-mocha .select.is-fullwidth select{width:100%}html.theme--catppuccin-mocha .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--catppuccin-mocha .select.is-loading.is-small:after,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-mocha .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-mocha .select.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-mocha .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--catppuccin-mocha .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:hover .file-cta,html.theme--catppuccin-mocha .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:focus .file-cta,html.theme--catppuccin-mocha .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--catppuccin-mocha .file.is-white:active .file-cta,html.theme--catppuccin-mocha .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--catppuccin-mocha .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-black:hover .file-cta,html.theme--catppuccin-mocha .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-black:focus .file-cta,html.theme--catppuccin-mocha .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-black:active .file-cta,html.theme--catppuccin-mocha .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:hover .file-cta,html.theme--catppuccin-mocha .file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:focus .file-cta,html.theme--catppuccin-mocha .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-light:active .file-cta,html.theme--catppuccin-mocha .file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-dark .file-cta,html.theme--catppuccin-mocha .content kbd.file .file-cta{background-color:#313244;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-dark:hover .file-cta,html.theme--catppuccin-mocha .content kbd.file:hover .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-hovered .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-hovered .file-cta{background-color:#2c2d3d;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-dark:focus .file-cta,html.theme--catppuccin-mocha .content kbd.file:focus .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-focused .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(49,50,68,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-dark:active .file-cta,html.theme--catppuccin-mocha .content kbd.file:active .file-cta,html.theme--catppuccin-mocha .file.is-dark.is-active .file-cta,html.theme--catppuccin-mocha .content kbd.file.is-active .file-cta{background-color:#262735;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary:hover .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-hovered .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-primary:focus .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-focused .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(137,180,250,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-primary:active .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--catppuccin-mocha .file.is-primary.is-active .file-cta,html.theme--catppuccin-mocha .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link .file-cta{background-color:#89b4fa;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link:hover .file-cta,html.theme--catppuccin-mocha .file.is-link.is-hovered .file-cta{background-color:#7dacf9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-link:focus .file-cta,html.theme--catppuccin-mocha .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(137,180,250,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-link:active .file-cta,html.theme--catppuccin-mocha .file.is-link.is-active .file-cta{background-color:#71a4f9;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-info .file-cta{background-color:#94e2d5;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:hover .file-cta,html.theme--catppuccin-mocha .file.is-info.is-hovered .file-cta{background-color:#8adfd1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:focus .file-cta,html.theme--catppuccin-mocha .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(148,226,213,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-info:active .file-cta,html.theme--catppuccin-mocha .file.is-info.is-active .file-cta{background-color:#80ddcd;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success .file-cta{background-color:#a6e3a1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:hover .file-cta,html.theme--catppuccin-mocha .file.is-success.is-hovered .file-cta{background-color:#9de097;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:focus .file-cta,html.theme--catppuccin-mocha .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(166,227,161,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-success:active .file-cta,html.theme--catppuccin-mocha .file.is-success.is-active .file-cta{background-color:#93dd8d;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning .file-cta{background-color:#f9e2af;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:hover .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-hovered .file-cta{background-color:#f8dea3;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:focus .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(249,226,175,0.25);color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-warning:active .file-cta,html.theme--catppuccin-mocha .file.is-warning.is-active .file-cta{background-color:#f7d997;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .file.is-danger .file-cta{background-color:#f38ba8;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-danger:hover .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-hovered .file-cta{background-color:#f27f9f;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-danger:focus .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(243,139,168,0.25);color:#fff}html.theme--catppuccin-mocha .file.is-danger:active .file-cta,html.theme--catppuccin-mocha .file.is-danger.is-active .file-cta{background-color:#f17497;border-color:transparent;color:#fff}html.theme--catppuccin-mocha .file.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--catppuccin-mocha .file.is-normal{font-size:1rem}html.theme--catppuccin-mocha .file.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .file.is-medium .file-icon .fa{font-size:21px}html.theme--catppuccin-mocha .file.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .file.is-large .file-icon .fa{font-size:28px}html.theme--catppuccin-mocha .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--catppuccin-mocha .file.has-name.is-empty .file-name{display:none}html.theme--catppuccin-mocha .file.is-boxed .file-label{flex-direction:column}html.theme--catppuccin-mocha .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--catppuccin-mocha .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--catppuccin-mocha .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--catppuccin-mocha .file.is-boxed .file-icon .fa{font-size:21px}html.theme--catppuccin-mocha .file.is-boxed.is-small .file-icon .fa,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--catppuccin-mocha .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--catppuccin-mocha .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--catppuccin-mocha .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--catppuccin-mocha .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--catppuccin-mocha .file.is-centered{justify-content:center}html.theme--catppuccin-mocha .file.is-fullwidth .file-label{width:100%}html.theme--catppuccin-mocha .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--catppuccin-mocha .file.is-right{justify-content:flex-end}html.theme--catppuccin-mocha .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--catppuccin-mocha .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--catppuccin-mocha .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--catppuccin-mocha .file-label:hover .file-cta{background-color:#2c2d3d;color:#b8c5ef}html.theme--catppuccin-mocha .file-label:hover .file-name{border-color:#525569}html.theme--catppuccin-mocha .file-label:active .file-cta{background-color:#262735;color:#b8c5ef}html.theme--catppuccin-mocha .file-label:active .file-name{border-color:#4d4f62}html.theme--catppuccin-mocha .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--catppuccin-mocha .file-cta,html.theme--catppuccin-mocha .file-name{border-color:#585b70;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--catppuccin-mocha .file-cta{background-color:#313244;color:#cdd6f4}html.theme--catppuccin-mocha .file-name{border-color:#585b70;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--catppuccin-mocha .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--catppuccin-mocha .file-icon .fa{font-size:14px}html.theme--catppuccin-mocha .label{color:#b8c5ef;display:block;font-size:1rem;font-weight:700}html.theme--catppuccin-mocha .label:not(:last-child){margin-bottom:0.5em}html.theme--catppuccin-mocha .label.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--catppuccin-mocha .label.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .label.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--catppuccin-mocha .help.is-white{color:#fff}html.theme--catppuccin-mocha .help.is-black{color:#0a0a0a}html.theme--catppuccin-mocha .help.is-light{color:#f5f5f5}html.theme--catppuccin-mocha .help.is-dark,html.theme--catppuccin-mocha .content kbd.help{color:#313244}html.theme--catppuccin-mocha .help.is-primary,html.theme--catppuccin-mocha .docstring>section>a.help.docs-sourcelink{color:#89b4fa}html.theme--catppuccin-mocha .help.is-link{color:#89b4fa}html.theme--catppuccin-mocha .help.is-info{color:#94e2d5}html.theme--catppuccin-mocha .help.is-success{color:#a6e3a1}html.theme--catppuccin-mocha .help.is-warning{color:#f9e2af}html.theme--catppuccin-mocha .help.is-danger{color:#f38ba8}html.theme--catppuccin-mocha .field:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-mocha .field.has-addons{display:flex;justify-content:flex-start}html.theme--catppuccin-mocha .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .button,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .input,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--catppuccin-mocha .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .button.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .button.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .input.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .input.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):focus,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):active,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .button:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .input:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--catppuccin-mocha .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--catppuccin-mocha .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--catppuccin-mocha .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .field.has-addons.has-addons-centered{justify-content:center}html.theme--catppuccin-mocha .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--catppuccin-mocha .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .field.is-grouped{display:flex;justify-content:flex-start}html.theme--catppuccin-mocha .field.is-grouped>.control{flex-shrink:0}html.theme--catppuccin-mocha .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-mocha .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--catppuccin-mocha .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field.is-horizontal{display:flex}}html.theme--catppuccin-mocha .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--catppuccin-mocha .field-label.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-normal{padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--catppuccin-mocha .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--catppuccin-mocha .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--catppuccin-mocha .field-body .field{margin-bottom:0}html.theme--catppuccin-mocha .field-body>.field{flex-shrink:1}html.theme--catppuccin-mocha .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--catppuccin-mocha .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-mocha .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--catppuccin-mocha .control.has-icons-left .input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select:focus~.icon{color:#313244}html.theme--catppuccin-mocha .control.has-icons-left .input.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-small~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--catppuccin-mocha .control.has-icons-left .input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--catppuccin-mocha .control.has-icons-left .input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-left .select.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right .input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--catppuccin-mocha .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--catppuccin-mocha .control.has-icons-left .icon,html.theme--catppuccin-mocha .control.has-icons-right .icon{color:#585b70;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--catppuccin-mocha .control.has-icons-left .input,html.theme--catppuccin-mocha .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--catppuccin-mocha .control.has-icons-left .select select{padding-left:2.5em}html.theme--catppuccin-mocha .control.has-icons-left .icon.is-left{left:0}html.theme--catppuccin-mocha .control.has-icons-right .input,html.theme--catppuccin-mocha .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--catppuccin-mocha .control.has-icons-right .select select{padding-right:2.5em}html.theme--catppuccin-mocha .control.has-icons-right .icon.is-right{right:0}html.theme--catppuccin-mocha .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--catppuccin-mocha .control.is-loading.is-small:after,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--catppuccin-mocha .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--catppuccin-mocha .control.is-loading.is-large:after{font-size:1.5rem}html.theme--catppuccin-mocha .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--catppuccin-mocha .breadcrumb a{align-items:center;color:#89b4fa;display:flex;justify-content:center;padding:0 .75em}html.theme--catppuccin-mocha .breadcrumb a:hover{color:#89dceb}html.theme--catppuccin-mocha .breadcrumb li{align-items:center;display:flex}html.theme--catppuccin-mocha .breadcrumb li:first-child a{padding-left:0}html.theme--catppuccin-mocha .breadcrumb li.is-active a{color:#b8c5ef;cursor:default;pointer-events:none}html.theme--catppuccin-mocha .breadcrumb li+li::before{color:#6c7086;content:"\0002f"}html.theme--catppuccin-mocha .breadcrumb ul,html.theme--catppuccin-mocha .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--catppuccin-mocha .breadcrumb .icon:first-child{margin-right:.5em}html.theme--catppuccin-mocha .breadcrumb .icon:last-child{margin-left:.5em}html.theme--catppuccin-mocha .breadcrumb.is-centered ol,html.theme--catppuccin-mocha .breadcrumb.is-centered ul{justify-content:center}html.theme--catppuccin-mocha .breadcrumb.is-right ol,html.theme--catppuccin-mocha .breadcrumb.is-right ul{justify-content:flex-end}html.theme--catppuccin-mocha .breadcrumb.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--catppuccin-mocha .breadcrumb.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .breadcrumb.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--catppuccin-mocha .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--catppuccin-mocha .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--catppuccin-mocha .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--catppuccin-mocha .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#cdd6f4;max-width:100%;position:relative}html.theme--catppuccin-mocha .card-footer:first-child,html.theme--catppuccin-mocha .card-content:first-child,html.theme--catppuccin-mocha .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-mocha .card-footer:last-child,html.theme--catppuccin-mocha .card-content:last-child,html.theme--catppuccin-mocha .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-mocha .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--catppuccin-mocha .card-header-title{align-items:center;color:#b8c5ef;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--catppuccin-mocha .card-header-title.is-centered{justify-content:center}html.theme--catppuccin-mocha .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--catppuccin-mocha .card-image{display:block;position:relative}html.theme--catppuccin-mocha .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--catppuccin-mocha .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--catppuccin-mocha .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--catppuccin-mocha .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--catppuccin-mocha .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--catppuccin-mocha .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--catppuccin-mocha .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--catppuccin-mocha .dropdown.is-active .dropdown-menu,html.theme--catppuccin-mocha .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--catppuccin-mocha .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--catppuccin-mocha .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--catppuccin-mocha .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--catppuccin-mocha .dropdown-content{background-color:#181825;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--catppuccin-mocha .dropdown-item{color:#cdd6f4;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--catppuccin-mocha a.dropdown-item,html.theme--catppuccin-mocha button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--catppuccin-mocha a.dropdown-item:hover,html.theme--catppuccin-mocha button.dropdown-item:hover{background-color:#181825;color:#0a0a0a}html.theme--catppuccin-mocha a.dropdown-item.is-active,html.theme--catppuccin-mocha button.dropdown-item.is-active{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--catppuccin-mocha .level{align-items:center;justify-content:space-between}html.theme--catppuccin-mocha .level code{border-radius:.4em}html.theme--catppuccin-mocha .level img{display:inline-block;vertical-align:top}html.theme--catppuccin-mocha .level.is-mobile{display:flex}html.theme--catppuccin-mocha .level.is-mobile .level-left,html.theme--catppuccin-mocha .level.is-mobile .level-right{display:flex}html.theme--catppuccin-mocha .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--catppuccin-mocha .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--catppuccin-mocha .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level{display:flex}html.theme--catppuccin-mocha .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--catppuccin-mocha .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--catppuccin-mocha .level-item .title,html.theme--catppuccin-mocha .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--catppuccin-mocha .level-left,html.theme--catppuccin-mocha .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .level-left .level-item.is-flexible,html.theme--catppuccin-mocha .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-left .level-item:not(:last-child),html.theme--catppuccin-mocha .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--catppuccin-mocha .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-left{display:flex}}html.theme--catppuccin-mocha .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .level-right{display:flex}}html.theme--catppuccin-mocha .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--catppuccin-mocha .media .content:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-mocha .media .media{border-top:1px solid rgba(88,91,112,0.5);display:flex;padding-top:.75rem}html.theme--catppuccin-mocha .media .media .content:not(:last-child),html.theme--catppuccin-mocha .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--catppuccin-mocha .media .media .media{padding-top:.5rem}html.theme--catppuccin-mocha .media .media .media+.media{margin-top:.5rem}html.theme--catppuccin-mocha .media+.media{border-top:1px solid rgba(88,91,112,0.5);margin-top:1rem;padding-top:1rem}html.theme--catppuccin-mocha .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--catppuccin-mocha .media-left,html.theme--catppuccin-mocha .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .media-left{margin-right:1rem}html.theme--catppuccin-mocha .media-right{margin-left:1rem}html.theme--catppuccin-mocha .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .media-content{overflow-x:auto}}html.theme--catppuccin-mocha .menu{font-size:1rem}html.theme--catppuccin-mocha .menu.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--catppuccin-mocha .menu.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .menu.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .menu-list{line-height:1.25}html.theme--catppuccin-mocha .menu-list a{border-radius:3px;color:#cdd6f4;display:block;padding:0.5em 0.75em}html.theme--catppuccin-mocha .menu-list a:hover{background-color:#181825;color:#b8c5ef}html.theme--catppuccin-mocha .menu-list a.is-active{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .menu-list li ul{border-left:1px solid #585b70;margin:.75em;padding-left:.75em}html.theme--catppuccin-mocha .menu-label{color:#f7f8fd;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--catppuccin-mocha .menu-label:not(:first-child){margin-top:1em}html.theme--catppuccin-mocha .menu-label:not(:last-child){margin-bottom:1em}html.theme--catppuccin-mocha .message{background-color:#181825;border-radius:.4em;font-size:1rem}html.theme--catppuccin-mocha .message strong{color:currentColor}html.theme--catppuccin-mocha .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--catppuccin-mocha .message.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--catppuccin-mocha .message.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .message.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .message.is-white{background-color:#fff}html.theme--catppuccin-mocha .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .message.is-white .message-body{border-color:#fff}html.theme--catppuccin-mocha .message.is-black{background-color:#fafafa}html.theme--catppuccin-mocha .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .message.is-black .message-body{border-color:#0a0a0a}html.theme--catppuccin-mocha .message.is-light{background-color:#fafafa}html.theme--catppuccin-mocha .message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-light .message-body{border-color:#f5f5f5}html.theme--catppuccin-mocha .message.is-dark,html.theme--catppuccin-mocha .content kbd.message{background-color:#f9f9fb}html.theme--catppuccin-mocha .message.is-dark .message-header,html.theme--catppuccin-mocha .content kbd.message .message-header{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .message.is-dark .message-body,html.theme--catppuccin-mocha .content kbd.message .message-body{border-color:#313244}html.theme--catppuccin-mocha .message.is-primary,html.theme--catppuccin-mocha .docstring>section>a.message.docs-sourcelink{background-color:#ebf3fe}html.theme--catppuccin-mocha .message.is-primary .message-header,html.theme--catppuccin-mocha .docstring>section>a.message.docs-sourcelink .message-header{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .message.is-primary .message-body,html.theme--catppuccin-mocha .docstring>section>a.message.docs-sourcelink .message-body{border-color:#89b4fa;color:#063c93}html.theme--catppuccin-mocha .message.is-link{background-color:#ebf3fe}html.theme--catppuccin-mocha .message.is-link .message-header{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .message.is-link .message-body{border-color:#89b4fa;color:#063c93}html.theme--catppuccin-mocha .message.is-info{background-color:#effbf9}html.theme--catppuccin-mocha .message.is-info .message-header{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-info .message-body{border-color:#94e2d5;color:#207466}html.theme--catppuccin-mocha .message.is-success{background-color:#f0faef}html.theme--catppuccin-mocha .message.is-success .message-header{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-success .message-body{border-color:#a6e3a1;color:#287222}html.theme--catppuccin-mocha .message.is-warning{background-color:#fef8ec}html.theme--catppuccin-mocha .message.is-warning .message-header{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .message.is-warning .message-body{border-color:#f9e2af;color:#8a620a}html.theme--catppuccin-mocha .message.is-danger{background-color:#fdedf1}html.theme--catppuccin-mocha .message.is-danger .message-header{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .message.is-danger .message-body{border-color:#f38ba8;color:#991036}html.theme--catppuccin-mocha .message-header{align-items:center;background-color:#cdd6f4;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--catppuccin-mocha .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--catppuccin-mocha .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--catppuccin-mocha .message-body{border-color:#585b70;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#cdd6f4;padding:1.25em 1.5em}html.theme--catppuccin-mocha .message-body code,html.theme--catppuccin-mocha .message-body pre{background-color:#fff}html.theme--catppuccin-mocha .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--catppuccin-mocha .modal.is-active{display:flex}html.theme--catppuccin-mocha .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--catppuccin-mocha .modal-content,html.theme--catppuccin-mocha .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--catppuccin-mocha .modal-content,html.theme--catppuccin-mocha .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--catppuccin-mocha .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--catppuccin-mocha .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--catppuccin-mocha .modal-card-head,html.theme--catppuccin-mocha .modal-card-foot{align-items:center;background-color:#181825;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--catppuccin-mocha .modal-card-head{border-bottom:1px solid #585b70;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--catppuccin-mocha .modal-card-title{color:#cdd6f4;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--catppuccin-mocha .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #585b70}html.theme--catppuccin-mocha .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--catppuccin-mocha .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#1e1e2e;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--catppuccin-mocha .navbar{background-color:#89b4fa;min-height:4rem;position:relative;z-index:30}html.theme--catppuccin-mocha .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-white .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--catppuccin-mocha .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-black .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--catppuccin-mocha .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--catppuccin-mocha .navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-light .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-dark,html.theme--catppuccin-mocha .content kbd.navbar{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-burger,html.theme--catppuccin-mocha .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>.navbar-item,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-dark .navbar-end .navbar-link::after,html.theme--catppuccin-mocha .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#313244;color:#fff}}html.theme--catppuccin-mocha .navbar.is-primary,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-burger,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>.navbar-item,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>.navbar-item,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-primary .navbar-end .navbar-link::after,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa;color:#fff}}html.theme--catppuccin-mocha .navbar.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-link .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa;color:#fff}}html.theme--catppuccin-mocha .navbar.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-info .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-info .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#94e2d5;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-success .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-success .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#f9e2af;color:rgba(0,0,0,0.7)}}html.theme--catppuccin-mocha .navbar.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>.navbar-item,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-start .navbar-link::after,html.theme--catppuccin-mocha .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f38ba8;color:#fff}}html.theme--catppuccin-mocha .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--catppuccin-mocha .navbar.has-shadow{box-shadow:0 2px 0 0 #181825}html.theme--catppuccin-mocha .navbar.is-fixed-bottom,html.theme--catppuccin-mocha .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #181825}html.theme--catppuccin-mocha .navbar.is-fixed-top{top:0}html.theme--catppuccin-mocha html.has-navbar-fixed-top,html.theme--catppuccin-mocha body.has-navbar-fixed-top{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--catppuccin-mocha .navbar-brand,html.theme--catppuccin-mocha .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--catppuccin-mocha .navbar-brand a.navbar-item:focus,html.theme--catppuccin-mocha .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--catppuccin-mocha .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--catppuccin-mocha .navbar-burger{color:#cdd6f4;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--catppuccin-mocha .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--catppuccin-mocha .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--catppuccin-mocha .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--catppuccin-mocha .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--catppuccin-mocha .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--catppuccin-mocha .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--catppuccin-mocha .navbar-menu{display:none}html.theme--catppuccin-mocha .navbar-item,html.theme--catppuccin-mocha .navbar-link{color:#cdd6f4;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--catppuccin-mocha .navbar-item .icon:only-child,html.theme--catppuccin-mocha .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--catppuccin-mocha a.navbar-item,html.theme--catppuccin-mocha .navbar-link{cursor:pointer}html.theme--catppuccin-mocha a.navbar-item:focus,html.theme--catppuccin-mocha a.navbar-item:focus-within,html.theme--catppuccin-mocha a.navbar-item:hover,html.theme--catppuccin-mocha a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar-link:focus,html.theme--catppuccin-mocha .navbar-link:focus-within,html.theme--catppuccin-mocha .navbar-link:hover,html.theme--catppuccin-mocha .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}html.theme--catppuccin-mocha .navbar-item{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .navbar-item img{max-height:1.75rem}html.theme--catppuccin-mocha .navbar-item.has-dropdown{padding:0}html.theme--catppuccin-mocha .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--catppuccin-mocha .navbar-item.is-tab:focus,html.theme--catppuccin-mocha .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#89b4fa;border-bottom-style:solid;border-bottom-width:3px;color:#89b4fa;padding-bottom:calc(0.5rem - 3px)}html.theme--catppuccin-mocha .navbar-content{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--catppuccin-mocha .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--catppuccin-mocha .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--catppuccin-mocha .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--catppuccin-mocha .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .navbar>.container{display:block}html.theme--catppuccin-mocha .navbar-brand .navbar-item,html.theme--catppuccin-mocha .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--catppuccin-mocha .navbar-link::after{display:none}html.theme--catppuccin-mocha .navbar-menu{background-color:#89b4fa;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--catppuccin-mocha .navbar-menu.is-active{display:block}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch,html.theme--catppuccin-mocha .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .navbar.is-fixed-top-touch{top:0}html.theme--catppuccin-mocha .navbar.is-fixed-top .navbar-menu,html.theme--catppuccin-mocha .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--catppuccin-mocha html.has-navbar-fixed-top-touch,html.theme--catppuccin-mocha body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom-touch,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .navbar,html.theme--catppuccin-mocha .navbar-menu,html.theme--catppuccin-mocha .navbar-start,html.theme--catppuccin-mocha .navbar-end{align-items:stretch;display:flex}html.theme--catppuccin-mocha .navbar{min-height:4rem}html.theme--catppuccin-mocha .navbar.is-spaced{padding:1rem 2rem}html.theme--catppuccin-mocha .navbar.is-spaced .navbar-start,html.theme--catppuccin-mocha .navbar.is-spaced .navbar-end{align-items:center}html.theme--catppuccin-mocha .navbar.is-spaced a.navbar-item,html.theme--catppuccin-mocha .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item:hover,html.theme--catppuccin-mocha .navbar.is-transparent a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link:focus,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link:hover,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#7f849c}html.theme--catppuccin-mocha .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}html.theme--catppuccin-mocha .navbar-burger{display:none}html.theme--catppuccin-mocha .navbar-item,html.theme--catppuccin-mocha .navbar-link{align-items:center;display:flex}html.theme--catppuccin-mocha .navbar-item.has-dropdown{align-items:stretch}html.theme--catppuccin-mocha .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--catppuccin-mocha .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--catppuccin-mocha .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--catppuccin-mocha .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--catppuccin-mocha .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--catppuccin-mocha .navbar-dropdown{background-color:#89b4fa;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--catppuccin-mocha .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item:focus,html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#7f849c}html.theme--catppuccin-mocha .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#89b4fa}.navbar.is-spaced html.theme--catppuccin-mocha .navbar-dropdown,html.theme--catppuccin-mocha .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--catppuccin-mocha .navbar-dropdown.is-right{left:auto;right:0}html.theme--catppuccin-mocha .navbar-divider{display:block}html.theme--catppuccin-mocha .navbar>.container .navbar-brand,html.theme--catppuccin-mocha .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--catppuccin-mocha .navbar>.container .navbar-menu,html.theme--catppuccin-mocha .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop,html.theme--catppuccin-mocha .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--catppuccin-mocha .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .navbar.is-fixed-top-desktop{top:0}html.theme--catppuccin-mocha html.has-navbar-fixed-top-desktop,html.theme--catppuccin-mocha body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--catppuccin-mocha html.has-navbar-fixed-bottom-desktop,html.theme--catppuccin-mocha body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--catppuccin-mocha html.has-spaced-navbar-fixed-top,html.theme--catppuccin-mocha body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--catppuccin-mocha html.has-spaced-navbar-fixed-bottom,html.theme--catppuccin-mocha body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--catppuccin-mocha a.navbar-item.is-active,html.theme--catppuccin-mocha .navbar-link.is-active{color:#89b4fa}html.theme--catppuccin-mocha a.navbar-item.is-active:not(:focus):not(:hover),html.theme--catppuccin-mocha .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--catppuccin-mocha .navbar-item.has-dropdown:focus .navbar-link,html.theme--catppuccin-mocha .navbar-item.has-dropdown:hover .navbar-link,html.theme--catppuccin-mocha .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--catppuccin-mocha .pagination{font-size:1rem;margin:-.25rem}html.theme--catppuccin-mocha .pagination.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--catppuccin-mocha .pagination.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .pagination.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .pagination.is-rounded .pagination-previous,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--catppuccin-mocha .pagination.is-rounded .pagination-next,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--catppuccin-mocha .pagination.is-rounded .pagination-link,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--catppuccin-mocha .pagination,html.theme--catppuccin-mocha .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link{border-color:#585b70;color:#89b4fa;min-width:2.5em}html.theme--catppuccin-mocha .pagination-previous:hover,html.theme--catppuccin-mocha .pagination-next:hover,html.theme--catppuccin-mocha .pagination-link:hover{border-color:#6c7086;color:#89dceb}html.theme--catppuccin-mocha .pagination-previous:focus,html.theme--catppuccin-mocha .pagination-next:focus,html.theme--catppuccin-mocha .pagination-link:focus{border-color:#6c7086}html.theme--catppuccin-mocha .pagination-previous:active,html.theme--catppuccin-mocha .pagination-next:active,html.theme--catppuccin-mocha .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--catppuccin-mocha .pagination-previous[disabled],html.theme--catppuccin-mocha .pagination-previous.is-disabled,html.theme--catppuccin-mocha .pagination-next[disabled],html.theme--catppuccin-mocha .pagination-next.is-disabled,html.theme--catppuccin-mocha .pagination-link[disabled],html.theme--catppuccin-mocha .pagination-link.is-disabled{background-color:#585b70;border-color:#585b70;box-shadow:none;color:#f7f8fd;opacity:0.5}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--catppuccin-mocha .pagination-link.is-current{background-color:#89b4fa;border-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .pagination-ellipsis{color:#6c7086;pointer-events:none}html.theme--catppuccin-mocha .pagination-list{flex-wrap:wrap}html.theme--catppuccin-mocha .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .pagination{flex-wrap:wrap}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--catppuccin-mocha .pagination-previous{order:2}html.theme--catppuccin-mocha .pagination-next{order:3}html.theme--catppuccin-mocha .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--catppuccin-mocha .pagination.is-centered .pagination-previous{order:1}html.theme--catppuccin-mocha .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--catppuccin-mocha .pagination.is-centered .pagination-next{order:3}html.theme--catppuccin-mocha .pagination.is-right .pagination-previous{order:1}html.theme--catppuccin-mocha .pagination.is-right .pagination-next{order:2}html.theme--catppuccin-mocha .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--catppuccin-mocha .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--catppuccin-mocha .panel:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--catppuccin-mocha .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--catppuccin-mocha .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--catppuccin-mocha .panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}html.theme--catppuccin-mocha .panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}html.theme--catppuccin-mocha .panel.is-dark .panel-heading,html.theme--catppuccin-mocha .content kbd.panel .panel-heading{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .panel.is-dark .panel-tabs a.is-active,html.theme--catppuccin-mocha .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#313244}html.theme--catppuccin-mocha .panel.is-dark .panel-block.is-active .panel-icon,html.theme--catppuccin-mocha .content kbd.panel .panel-block.is-active .panel-icon{color:#313244}html.theme--catppuccin-mocha .panel.is-primary .panel-heading,html.theme--catppuccin-mocha .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .panel.is-primary .panel-tabs a.is-active,html.theme--catppuccin-mocha .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .panel.is-primary .panel-block.is-active .panel-icon,html.theme--catppuccin-mocha .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel.is-link .panel-heading{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .panel.is-link .panel-tabs a.is-active{border-bottom-color:#89b4fa}html.theme--catppuccin-mocha .panel.is-link .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel.is-info .panel-heading{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-info .panel-tabs a.is-active{border-bottom-color:#94e2d5}html.theme--catppuccin-mocha .panel.is-info .panel-block.is-active .panel-icon{color:#94e2d5}html.theme--catppuccin-mocha .panel.is-success .panel-heading{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-success .panel-tabs a.is-active{border-bottom-color:#a6e3a1}html.theme--catppuccin-mocha .panel.is-success .panel-block.is-active .panel-icon{color:#a6e3a1}html.theme--catppuccin-mocha .panel.is-warning .panel-heading{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#f9e2af}html.theme--catppuccin-mocha .panel.is-warning .panel-block.is-active .panel-icon{color:#f9e2af}html.theme--catppuccin-mocha .panel.is-danger .panel-heading{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f38ba8}html.theme--catppuccin-mocha .panel.is-danger .panel-block.is-active .panel-icon{color:#f38ba8}html.theme--catppuccin-mocha .panel-tabs:not(:last-child),html.theme--catppuccin-mocha .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--catppuccin-mocha .panel-heading{background-color:#45475a;border-radius:8px 8px 0 0;color:#b8c5ef;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--catppuccin-mocha .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--catppuccin-mocha .panel-tabs a{border-bottom:1px solid #585b70;margin-bottom:-1px;padding:0.5em}html.theme--catppuccin-mocha .panel-tabs a.is-active{border-bottom-color:#45475a;color:#71a4f9}html.theme--catppuccin-mocha .panel-list a{color:#cdd6f4}html.theme--catppuccin-mocha .panel-list a:hover{color:#89b4fa}html.theme--catppuccin-mocha .panel-block{align-items:center;color:#b8c5ef;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--catppuccin-mocha .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--catppuccin-mocha .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--catppuccin-mocha .panel-block.is-wrapped{flex-wrap:wrap}html.theme--catppuccin-mocha .panel-block.is-active{border-left-color:#89b4fa;color:#71a4f9}html.theme--catppuccin-mocha .panel-block.is-active .panel-icon{color:#89b4fa}html.theme--catppuccin-mocha .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--catppuccin-mocha a.panel-block,html.theme--catppuccin-mocha label.panel-block{cursor:pointer}html.theme--catppuccin-mocha a.panel-block:hover,html.theme--catppuccin-mocha label.panel-block:hover{background-color:#181825}html.theme--catppuccin-mocha .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#f7f8fd;margin-right:.75em}html.theme--catppuccin-mocha .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--catppuccin-mocha .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--catppuccin-mocha .tabs a{align-items:center;border-bottom-color:#585b70;border-bottom-style:solid;border-bottom-width:1px;color:#cdd6f4;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--catppuccin-mocha .tabs a:hover{border-bottom-color:#b8c5ef;color:#b8c5ef}html.theme--catppuccin-mocha .tabs li{display:block}html.theme--catppuccin-mocha .tabs li.is-active a{border-bottom-color:#89b4fa;color:#89b4fa}html.theme--catppuccin-mocha .tabs ul{align-items:center;border-bottom-color:#585b70;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--catppuccin-mocha .tabs ul.is-left{padding-right:0.75em}html.theme--catppuccin-mocha .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--catppuccin-mocha .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--catppuccin-mocha .tabs .icon:first-child{margin-right:.5em}html.theme--catppuccin-mocha .tabs .icon:last-child{margin-left:.5em}html.theme--catppuccin-mocha .tabs.is-centered ul{justify-content:center}html.theme--catppuccin-mocha .tabs.is-right ul{justify-content:flex-end}html.theme--catppuccin-mocha .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--catppuccin-mocha .tabs.is-boxed a:hover{background-color:#181825;border-bottom-color:#585b70}html.theme--catppuccin-mocha .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#585b70;border-bottom-color:rgba(0,0,0,0) !important}html.theme--catppuccin-mocha .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--catppuccin-mocha .tabs.is-toggle a{border-color:#585b70;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--catppuccin-mocha .tabs.is-toggle a:hover{background-color:#181825;border-color:#6c7086;z-index:2}html.theme--catppuccin-mocha .tabs.is-toggle li+li{margin-left:-1px}html.theme--catppuccin-mocha .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--catppuccin-mocha .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--catppuccin-mocha .tabs.is-toggle li.is-active a{background-color:#89b4fa;border-color:#89b4fa;color:#fff;z-index:1}html.theme--catppuccin-mocha .tabs.is-toggle ul{border-bottom:none}html.theme--catppuccin-mocha .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--catppuccin-mocha .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--catppuccin-mocha .tabs.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--catppuccin-mocha .tabs.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .tabs.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--catppuccin-mocha .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .column.is-narrow-mobile{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-mobile{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-mobile{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-mobile{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-mobile{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-mobile{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-mobile{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-mobile{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-mobile{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-mobile{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-mobile{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-mobile{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-mobile{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .column.is-narrow,html.theme--catppuccin-mocha .column.is-narrow-tablet{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full,html.theme--catppuccin-mocha .column.is-full-tablet{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters,html.theme--catppuccin-mocha .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds,html.theme--catppuccin-mocha .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half,html.theme--catppuccin-mocha .column.is-half-tablet{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third,html.theme--catppuccin-mocha .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter,html.theme--catppuccin-mocha .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth,html.theme--catppuccin-mocha .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths,html.theme--catppuccin-mocha .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths,html.theme--catppuccin-mocha .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths,html.theme--catppuccin-mocha .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters,html.theme--catppuccin-mocha .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds,html.theme--catppuccin-mocha .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half,html.theme--catppuccin-mocha .column.is-offset-half-tablet{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third,html.theme--catppuccin-mocha .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter,html.theme--catppuccin-mocha .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth,html.theme--catppuccin-mocha .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths,html.theme--catppuccin-mocha .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths,html.theme--catppuccin-mocha .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths,html.theme--catppuccin-mocha .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--catppuccin-mocha .column.is-0,html.theme--catppuccin-mocha .column.is-0-tablet{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0,html.theme--catppuccin-mocha .column.is-offset-0-tablet{margin-left:0%}html.theme--catppuccin-mocha .column.is-1,html.theme--catppuccin-mocha .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1,html.theme--catppuccin-mocha .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2,html.theme--catppuccin-mocha .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2,html.theme--catppuccin-mocha .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3,html.theme--catppuccin-mocha .column.is-3-tablet{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3,html.theme--catppuccin-mocha .column.is-offset-3-tablet{margin-left:25%}html.theme--catppuccin-mocha .column.is-4,html.theme--catppuccin-mocha .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4,html.theme--catppuccin-mocha .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5,html.theme--catppuccin-mocha .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5,html.theme--catppuccin-mocha .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6,html.theme--catppuccin-mocha .column.is-6-tablet{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6,html.theme--catppuccin-mocha .column.is-offset-6-tablet{margin-left:50%}html.theme--catppuccin-mocha .column.is-7,html.theme--catppuccin-mocha .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7,html.theme--catppuccin-mocha .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8,html.theme--catppuccin-mocha .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8,html.theme--catppuccin-mocha .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9,html.theme--catppuccin-mocha .column.is-9-tablet{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9,html.theme--catppuccin-mocha .column.is-offset-9-tablet{margin-left:75%}html.theme--catppuccin-mocha .column.is-10,html.theme--catppuccin-mocha .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10,html.theme--catppuccin-mocha .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11,html.theme--catppuccin-mocha .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11,html.theme--catppuccin-mocha .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12,html.theme--catppuccin-mocha .column.is-12-tablet{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12,html.theme--catppuccin-mocha .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .column.is-narrow-touch{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-touch{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-touch{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-touch{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-touch{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-touch{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-touch{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-touch{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-touch{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-touch{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-touch{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-touch{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-touch{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-touch{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-touch{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-touch{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-touch{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-touch{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-touch{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-touch{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-touch{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-touch{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-touch{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-touch{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-touch{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-touch{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-touch{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .column.is-narrow-desktop{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-desktop{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-desktop{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-desktop{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-desktop{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-desktop{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-desktop{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-desktop{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-desktop{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-desktop{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-desktop{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-desktop{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-desktop{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .column.is-narrow-widescreen{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-widescreen{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-widescreen{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-widescreen{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-widescreen{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-widescreen{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-widescreen{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-widescreen{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-widescreen{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-widescreen{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-widescreen{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-widescreen{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-widescreen{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .column.is-narrow-fullhd{flex:none;width:unset}html.theme--catppuccin-mocha .column.is-full-fullhd{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--catppuccin-mocha .column.is-half-fullhd{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--catppuccin-mocha .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--catppuccin-mocha .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--catppuccin-mocha .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--catppuccin-mocha .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--catppuccin-mocha .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--catppuccin-mocha .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--catppuccin-mocha .column.is-offset-half-fullhd{margin-left:50%}html.theme--catppuccin-mocha .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--catppuccin-mocha .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--catppuccin-mocha .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--catppuccin-mocha .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--catppuccin-mocha .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--catppuccin-mocha .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--catppuccin-mocha .column.is-0-fullhd{flex:none;width:0%}html.theme--catppuccin-mocha .column.is-offset-0-fullhd{margin-left:0%}html.theme--catppuccin-mocha .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--catppuccin-mocha .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--catppuccin-mocha .column.is-3-fullhd{flex:none;width:25%}html.theme--catppuccin-mocha .column.is-offset-3-fullhd{margin-left:25%}html.theme--catppuccin-mocha .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--catppuccin-mocha .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--catppuccin-mocha .column.is-6-fullhd{flex:none;width:50%}html.theme--catppuccin-mocha .column.is-offset-6-fullhd{margin-left:50%}html.theme--catppuccin-mocha .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--catppuccin-mocha .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--catppuccin-mocha .column.is-9-fullhd{flex:none;width:75%}html.theme--catppuccin-mocha .column.is-offset-9-fullhd{margin-left:75%}html.theme--catppuccin-mocha .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--catppuccin-mocha .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--catppuccin-mocha .column.is-12-fullhd{flex:none;width:100%}html.theme--catppuccin-mocha .column.is-offset-12-fullhd{margin-left:100%}}html.theme--catppuccin-mocha .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-mocha .columns:last-child{margin-bottom:-.75rem}html.theme--catppuccin-mocha .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--catppuccin-mocha .columns.is-centered{justify-content:center}html.theme--catppuccin-mocha .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--catppuccin-mocha .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--catppuccin-mocha .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--catppuccin-mocha .columns.is-gapless:last-child{margin-bottom:0}html.theme--catppuccin-mocha .columns.is-mobile{display:flex}html.theme--catppuccin-mocha .columns.is-multiline{flex-wrap:wrap}html.theme--catppuccin-mocha .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-desktop{display:flex}}html.theme--catppuccin-mocha .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--catppuccin-mocha .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--catppuccin-mocha .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--catppuccin-mocha .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--catppuccin-mocha .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--catppuccin-mocha .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--catppuccin-mocha .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--catppuccin-mocha .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--catppuccin-mocha .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--catppuccin-mocha .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--catppuccin-mocha .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--catppuccin-mocha .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--catppuccin-mocha .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--catppuccin-mocha .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--catppuccin-mocha .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--catppuccin-mocha .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--catppuccin-mocha .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--catppuccin-mocha .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--catppuccin-mocha .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--catppuccin-mocha .tile.is-child{margin:0 !important}html.theme--catppuccin-mocha .tile.is-parent{padding:.75rem}html.theme--catppuccin-mocha .tile.is-vertical{flex-direction:column}html.theme--catppuccin-mocha .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .tile:not(.is-child){display:flex}html.theme--catppuccin-mocha .tile.is-1{flex:none;width:8.33333337%}html.theme--catppuccin-mocha .tile.is-2{flex:none;width:16.66666674%}html.theme--catppuccin-mocha .tile.is-3{flex:none;width:25%}html.theme--catppuccin-mocha .tile.is-4{flex:none;width:33.33333337%}html.theme--catppuccin-mocha .tile.is-5{flex:none;width:41.66666674%}html.theme--catppuccin-mocha .tile.is-6{flex:none;width:50%}html.theme--catppuccin-mocha .tile.is-7{flex:none;width:58.33333337%}html.theme--catppuccin-mocha .tile.is-8{flex:none;width:66.66666674%}html.theme--catppuccin-mocha .tile.is-9{flex:none;width:75%}html.theme--catppuccin-mocha .tile.is-10{flex:none;width:83.33333337%}html.theme--catppuccin-mocha .tile.is-11{flex:none;width:91.66666674%}html.theme--catppuccin-mocha .tile.is-12{flex:none;width:100%}}html.theme--catppuccin-mocha .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--catppuccin-mocha .hero .navbar{background:none}html.theme--catppuccin-mocha .hero .tabs ul{border-bottom:none}html.theme--catppuccin-mocha .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-white strong{color:inherit}html.theme--catppuccin-mocha .hero.is-white .title{color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--catppuccin-mocha .hero.is-white .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-white .navbar-menu{background-color:#fff}}html.theme--catppuccin-mocha .hero.is-white .navbar-item,html.theme--catppuccin-mocha .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--catppuccin-mocha .hero.is-white a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-white a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-white .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--catppuccin-mocha .hero.is-white .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--catppuccin-mocha .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--catppuccin-mocha .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-black strong{color:inherit}html.theme--catppuccin-mocha .hero.is-black .title{color:#fff}html.theme--catppuccin-mocha .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-black .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--catppuccin-mocha .hero.is-black .navbar-item,html.theme--catppuccin-mocha .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-black a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-black a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-black .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--catppuccin-mocha .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-black .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--catppuccin-mocha .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--catppuccin-mocha .hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-light strong{color:inherit}html.theme--catppuccin-mocha .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-light .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-light .navbar-menu{background-color:#f5f5f5}}html.theme--catppuccin-mocha .hero.is-light .navbar-item,html.theme--catppuccin-mocha .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-light a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-light .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-light .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}html.theme--catppuccin-mocha .hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}html.theme--catppuccin-mocha .hero.is-dark,html.theme--catppuccin-mocha .content kbd.hero{background-color:#313244;color:#fff}html.theme--catppuccin-mocha .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-dark strong,html.theme--catppuccin-mocha .content kbd.hero strong{color:inherit}html.theme--catppuccin-mocha .hero.is-dark .title,html.theme--catppuccin-mocha .content kbd.hero .title{color:#fff}html.theme--catppuccin-mocha .hero.is-dark .subtitle,html.theme--catppuccin-mocha .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-dark .subtitle a:not(.button),html.theme--catppuccin-mocha .content kbd.hero .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-dark .subtitle strong,html.theme--catppuccin-mocha .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-dark .navbar-menu,html.theme--catppuccin-mocha .content kbd.hero .navbar-menu{background-color:#313244}}html.theme--catppuccin-mocha .hero.is-dark .navbar-item,html.theme--catppuccin-mocha .content kbd.hero .navbar-item,html.theme--catppuccin-mocha .hero.is-dark .navbar-link,html.theme--catppuccin-mocha .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-dark a.navbar-item:hover,html.theme--catppuccin-mocha .content kbd.hero a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-dark a.navbar-item.is-active,html.theme--catppuccin-mocha .content kbd.hero a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-dark .navbar-link:hover,html.theme--catppuccin-mocha .content kbd.hero .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-dark .navbar-link.is-active,html.theme--catppuccin-mocha .content kbd.hero .navbar-link.is-active{background-color:#262735;color:#fff}html.theme--catppuccin-mocha .hero.is-dark .tabs a,html.theme--catppuccin-mocha .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-dark .tabs a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-dark .tabs li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs li.is-active a{color:#313244 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle a:hover,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#313244}html.theme--catppuccin-mocha .hero.is-dark.is-bold,html.theme--catppuccin-mocha .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #181c2a 0%, #313244 71%, #3c3856 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-dark.is-bold .navbar-menu,html.theme--catppuccin-mocha .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #181c2a 0%, #313244 71%, #3c3856 100%)}}html.theme--catppuccin-mocha .hero.is-primary,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-primary strong,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--catppuccin-mocha .hero.is-primary .title,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--catppuccin-mocha .hero.is-primary .subtitle,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-primary .subtitle a:not(.button),html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-primary .subtitle strong,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-primary .navbar-menu,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#89b4fa}}html.theme--catppuccin-mocha .hero.is-primary .navbar-item,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--catppuccin-mocha .hero.is-primary .navbar-link,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-primary a.navbar-item:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-primary a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-primary .navbar-link:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-primary .navbar-link.is-active,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .hero.is-primary .tabs a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-primary .tabs a:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-primary .tabs li.is-active a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#89b4fa !important;opacity:1}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle a:hover,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .hero.is-primary.is-bold,html.theme--catppuccin-mocha .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-primary.is-bold .navbar-menu,html.theme--catppuccin-mocha .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}}html.theme--catppuccin-mocha .hero.is-link{background-color:#89b4fa;color:#fff}html.theme--catppuccin-mocha .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-link strong{color:inherit}html.theme--catppuccin-mocha .hero.is-link .title{color:#fff}html.theme--catppuccin-mocha .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-link .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-link .navbar-menu{background-color:#89b4fa}}html.theme--catppuccin-mocha .hero.is-link .navbar-item,html.theme--catppuccin-mocha .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-link a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-link a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-link .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-link .navbar-link.is-active{background-color:#71a4f9;color:#fff}html.theme--catppuccin-mocha .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-link .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-link .tabs li.is-active a{color:#89b4fa !important;opacity:1}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#89b4fa}html.theme--catppuccin-mocha .hero.is-link.is-bold{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #51b0ff 0%, #89b4fa 71%, #9fb3fd 100%)}}html.theme--catppuccin-mocha .hero.is-info{background-color:#94e2d5;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-info strong{color:inherit}html.theme--catppuccin-mocha .hero.is-info .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-info .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-info .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-info .navbar-menu{background-color:#94e2d5}}html.theme--catppuccin-mocha .hero.is-info .navbar-item,html.theme--catppuccin-mocha .hero.is-info .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-info a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-info .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-info .navbar-link.is-active{background-color:#80ddcd;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-info .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-info .tabs li.is-active a{color:#94e2d5 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#94e2d5}html.theme--catppuccin-mocha .hero.is-info.is-bold{background-image:linear-gradient(141deg, #63e0b6 0%, #94e2d5 71%, #a5eaea 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #63e0b6 0%, #94e2d5 71%, #a5eaea 100%)}}html.theme--catppuccin-mocha .hero.is-success{background-color:#a6e3a1;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-success strong{color:inherit}html.theme--catppuccin-mocha .hero.is-success .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-success .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-success .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-success .navbar-menu{background-color:#a6e3a1}}html.theme--catppuccin-mocha .hero.is-success .navbar-item,html.theme--catppuccin-mocha .hero.is-success .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-success a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-success .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-success .navbar-link.is-active{background-color:#93dd8d;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-success .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-success .tabs li.is-active a{color:#a6e3a1 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#a6e3a1}html.theme--catppuccin-mocha .hero.is-success.is-bold{background-image:linear-gradient(141deg, #8ce071 0%, #a6e3a1 71%, #b2ebb7 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #8ce071 0%, #a6e3a1 71%, #b2ebb7 100%)}}html.theme--catppuccin-mocha .hero.is-warning{background-color:#f9e2af;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-warning strong{color:inherit}html.theme--catppuccin-mocha .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--catppuccin-mocha .hero.is-warning .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-warning .navbar-menu{background-color:#f9e2af}}html.theme--catppuccin-mocha .hero.is-warning .navbar-item,html.theme--catppuccin-mocha .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-warning a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-warning .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-warning .navbar-link.is-active{background-color:#f7d997;color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--catppuccin-mocha .hero.is-warning .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-warning .tabs li.is-active a{color:#f9e2af !important;opacity:1}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f9e2af}html.theme--catppuccin-mocha .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #fcbd79 0%, #f9e2af 71%, #fcf4c5 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #fcbd79 0%, #f9e2af 71%, #fcf4c5 100%)}}html.theme--catppuccin-mocha .hero.is-danger{background-color:#f38ba8;color:#fff}html.theme--catppuccin-mocha .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--catppuccin-mocha .hero.is-danger strong{color:inherit}html.theme--catppuccin-mocha .hero.is-danger .title{color:#fff}html.theme--catppuccin-mocha .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--catppuccin-mocha .hero.is-danger .subtitle a:not(.button),html.theme--catppuccin-mocha .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .hero.is-danger .navbar-menu{background-color:#f38ba8}}html.theme--catppuccin-mocha .hero.is-danger .navbar-item,html.theme--catppuccin-mocha .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--catppuccin-mocha .hero.is-danger a.navbar-item:hover,html.theme--catppuccin-mocha .hero.is-danger a.navbar-item.is-active,html.theme--catppuccin-mocha .hero.is-danger .navbar-link:hover,html.theme--catppuccin-mocha .hero.is-danger .navbar-link.is-active{background-color:#f17497;color:#fff}html.theme--catppuccin-mocha .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--catppuccin-mocha .hero.is-danger .tabs a:hover{opacity:1}html.theme--catppuccin-mocha .hero.is-danger .tabs li.is-active a{color:#f38ba8 !important;opacity:1}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed a:hover,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--catppuccin-mocha .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f38ba8}html.theme--catppuccin-mocha .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #f7549d 0%, #f38ba8 71%, #f8a0a9 100%)}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f7549d 0%, #f38ba8 71%, #f8a0a9 100%)}}html.theme--catppuccin-mocha .hero.is-small .hero-body,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--catppuccin-mocha .hero.is-halfheight .hero-body,html.theme--catppuccin-mocha .hero.is-fullheight .hero-body,html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--catppuccin-mocha .hero.is-halfheight .hero-body>.container,html.theme--catppuccin-mocha .hero.is-fullheight .hero-body>.container,html.theme--catppuccin-mocha .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--catppuccin-mocha .hero.is-halfheight{min-height:50vh}html.theme--catppuccin-mocha .hero.is-fullheight{min-height:100vh}html.theme--catppuccin-mocha .hero-video{overflow:hidden}html.theme--catppuccin-mocha .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--catppuccin-mocha .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero-video{display:none}}html.theme--catppuccin-mocha .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--catppuccin-mocha .hero-buttons .button{display:flex}html.theme--catppuccin-mocha .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero-buttons{display:flex;justify-content:center}html.theme--catppuccin-mocha .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--catppuccin-mocha .hero-head,html.theme--catppuccin-mocha .hero-foot{flex-grow:0;flex-shrink:0}html.theme--catppuccin-mocha .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--catppuccin-mocha .hero-body{padding:3rem 3rem}}html.theme--catppuccin-mocha .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha .section{padding:3rem 3rem}html.theme--catppuccin-mocha .section.is-medium{padding:9rem 4.5rem}html.theme--catppuccin-mocha .section.is-large{padding:18rem 6rem}}html.theme--catppuccin-mocha .footer{background-color:#181825;padding:3rem 1.5rem 6rem}html.theme--catppuccin-mocha h1 .docs-heading-anchor,html.theme--catppuccin-mocha h1 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h1 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h2 .docs-heading-anchor,html.theme--catppuccin-mocha h2 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h2 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h3 .docs-heading-anchor,html.theme--catppuccin-mocha h3 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h3 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h4 .docs-heading-anchor,html.theme--catppuccin-mocha h4 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h4 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h5 .docs-heading-anchor,html.theme--catppuccin-mocha h5 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h5 .docs-heading-anchor:visited,html.theme--catppuccin-mocha h6 .docs-heading-anchor,html.theme--catppuccin-mocha h6 .docs-heading-anchor:hover,html.theme--catppuccin-mocha h6 .docs-heading-anchor:visited{color:#cdd6f4}html.theme--catppuccin-mocha h1 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h2 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h3 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h4 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h5 .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--catppuccin-mocha h1 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h2 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h3 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h4 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h5 .docs-heading-anchor-permalink::before,html.theme--catppuccin-mocha h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--catppuccin-mocha h1:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h2:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h3:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h4:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h5:hover .docs-heading-anchor-permalink,html.theme--catppuccin-mocha h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--catppuccin-mocha .docs-light-only{display:none !important}html.theme--catppuccin-mocha pre{position:relative;overflow:hidden}html.theme--catppuccin-mocha pre code,html.theme--catppuccin-mocha pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--catppuccin-mocha pre code:first-of-type,html.theme--catppuccin-mocha pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--catppuccin-mocha pre code:last-of-type,html.theme--catppuccin-mocha pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--catppuccin-mocha pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#cdd6f4;cursor:pointer;text-align:center}html.theme--catppuccin-mocha pre .copy-button:focus,html.theme--catppuccin-mocha pre .copy-button:hover{opacity:1;background:rgba(205,214,244,0.1);color:#89b4fa}html.theme--catppuccin-mocha pre .copy-button.success{color:#a6e3a1;opacity:1}html.theme--catppuccin-mocha pre .copy-button.error{color:#f38ba8;opacity:1}html.theme--catppuccin-mocha pre:hover .copy-button{opacity:1}html.theme--catppuccin-mocha .admonition{background-color:#181825;border-style:solid;border-width:2px;border-color:#bac2de;border-radius:4px;font-size:1rem}html.theme--catppuccin-mocha .admonition strong{color:currentColor}html.theme--catppuccin-mocha .admonition.is-small,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--catppuccin-mocha .admonition.is-medium{font-size:1.25rem}html.theme--catppuccin-mocha .admonition.is-large{font-size:1.5rem}html.theme--catppuccin-mocha .admonition.is-default{background-color:#181825;border-color:#bac2de}html.theme--catppuccin-mocha .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#bac2de}html.theme--catppuccin-mocha .admonition.is-default>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-info{background-color:#181825;border-color:#94e2d5}html.theme--catppuccin-mocha .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#94e2d5}html.theme--catppuccin-mocha .admonition.is-info>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-success{background-color:#181825;border-color:#a6e3a1}html.theme--catppuccin-mocha .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#a6e3a1}html.theme--catppuccin-mocha .admonition.is-success>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-warning{background-color:#181825;border-color:#f9e2af}html.theme--catppuccin-mocha .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#f9e2af}html.theme--catppuccin-mocha .admonition.is-warning>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-danger{background-color:#181825;border-color:#f38ba8}html.theme--catppuccin-mocha .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#f38ba8}html.theme--catppuccin-mocha .admonition.is-danger>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-compat{background-color:#181825;border-color:#89dceb}html.theme--catppuccin-mocha .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#89dceb}html.theme--catppuccin-mocha .admonition.is-compat>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition.is-todo{background-color:#181825;border-color:#cba6f7}html.theme--catppuccin-mocha .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#cba6f7}html.theme--catppuccin-mocha .admonition.is-todo>.admonition-body{color:#cdd6f4}html.theme--catppuccin-mocha .admonition-header{color:#bac2de;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--catppuccin-mocha .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--catppuccin-mocha details.admonition.is-details>.admonition-header{list-style:none}html.theme--catppuccin-mocha details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--catppuccin-mocha details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--catppuccin-mocha .admonition-body{color:#cdd6f4;padding:0.5rem .75rem}html.theme--catppuccin-mocha .admonition-body pre{background-color:#181825}html.theme--catppuccin-mocha .admonition-body code{background-color:#181825}html.theme--catppuccin-mocha .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #585b70;border-radius:4px;box-shadow:none;max-width:100%}html.theme--catppuccin-mocha .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#181825;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #585b70;overflow:auto}html.theme--catppuccin-mocha .docstring>header code{background-color:transparent}html.theme--catppuccin-mocha .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--catppuccin-mocha .docstring>header .docstring-binding{margin-right:0.3em}html.theme--catppuccin-mocha .docstring>header .docstring-category{margin-left:0.3em}html.theme--catppuccin-mocha .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #585b70}html.theme--catppuccin-mocha .docstring>section:last-child{border-bottom:none}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--catppuccin-mocha .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-mocha .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--catppuccin-mocha .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--catppuccin-mocha .documenter-example-output{background-color:#1e1e2e}html.theme--catppuccin-mocha .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#181825;color:#cdd6f4;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--catppuccin-mocha .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--catppuccin-mocha .outdated-warning-overlay a{color:#89b4fa}html.theme--catppuccin-mocha .outdated-warning-overlay a:hover{color:#89dceb}html.theme--catppuccin-mocha .content pre{border:2px solid #585b70;border-radius:4px}html.theme--catppuccin-mocha .content code{font-weight:inherit}html.theme--catppuccin-mocha .content a code{color:#89b4fa}html.theme--catppuccin-mocha .content a:hover code{color:#89dceb}html.theme--catppuccin-mocha .content h1 code,html.theme--catppuccin-mocha .content h2 code,html.theme--catppuccin-mocha .content h3 code,html.theme--catppuccin-mocha .content h4 code,html.theme--catppuccin-mocha .content h5 code,html.theme--catppuccin-mocha .content h6 code{color:#cdd6f4}html.theme--catppuccin-mocha .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--catppuccin-mocha .content blockquote>ul:first-child,html.theme--catppuccin-mocha .content blockquote>ol:first-child,html.theme--catppuccin-mocha .content .admonition-body>ul:first-child,html.theme--catppuccin-mocha .content .admonition-body>ol:first-child{margin-top:0}html.theme--catppuccin-mocha pre,html.theme--catppuccin-mocha code{font-variant-ligatures:no-contextual}html.theme--catppuccin-mocha .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--catppuccin-mocha .breadcrumb a.is-disabled,html.theme--catppuccin-mocha .breadcrumb a.is-disabled:hover{color:#b8c5ef}html.theme--catppuccin-mocha .hljs{background:initial !important}html.theme--catppuccin-mocha .katex .katex-mathml{top:0;right:0}html.theme--catppuccin-mocha .katex-display,html.theme--catppuccin-mocha mjx-container,html.theme--catppuccin-mocha .MathJax_Display{margin:0.5em 0 !important}html.theme--catppuccin-mocha html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--catppuccin-mocha li.no-marker{list-style:none}html.theme--catppuccin-mocha #documenter .docs-main>article{overflow-wrap:break-word}html.theme--catppuccin-mocha #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main{width:100%}html.theme--catppuccin-mocha #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--catppuccin-mocha #documenter .docs-main>header,html.theme--catppuccin-mocha #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar{background-color:#1e1e2e;border-bottom:1px solid #585b70;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--catppuccin-mocha #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--catppuccin-mocha #documenter .docs-main section.footnotes{border-top:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .tag:first-child,html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--catppuccin-mocha #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--catppuccin-mocha .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #585b70;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--catppuccin-mocha #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--catppuccin-mocha #documenter .docs-sidebar{display:flex;flex-direction:column;color:#cdd6f4;background-color:#181825;border-right:1px solid #585b70;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--catppuccin-mocha #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar{left:0;top:0}}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name a,html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-package-name a:hover{color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #585b70;display:none;padding:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #585b70;padding-bottom:1.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#cdd6f4;background:#181825}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#cdd6f4;background-color:#202031}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #585b70;border-bottom:1px solid #585b70;background-color:#11111b}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#11111b;color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#202031;color:#cdd6f4}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #585b70}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--catppuccin-mocha #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#28283e}html.theme--catppuccin-mocha #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#383856}}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#28283e}html.theme--catppuccin-mocha #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#383856}}html.theme--catppuccin-mocha kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--catppuccin-mocha .search-min-width-50{min-width:50%}html.theme--catppuccin-mocha .search-min-height-100{min-height:100%}html.theme--catppuccin-mocha .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--catppuccin-mocha .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover,html.theme--catppuccin-mocha .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--catppuccin-mocha .search-result-link .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-mocha .property-search-result-badge,html.theme--catppuccin-mocha .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--catppuccin-mocha .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:hover .search-filter,html.theme--catppuccin-mocha .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--catppuccin-mocha .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--catppuccin-mocha .search-filter:hover,html.theme--catppuccin-mocha .search-filter:focus{color:#333}html.theme--catppuccin-mocha .search-filter-selected{color:#313244;background-color:#b4befe}html.theme--catppuccin-mocha .search-filter-selected:hover,html.theme--catppuccin-mocha .search-filter-selected:focus{color:#313244}html.theme--catppuccin-mocha .search-result-highlight{background-color:#ffdd57;color:black}html.theme--catppuccin-mocha .search-divider{border-bottom:1px solid #585b70}html.theme--catppuccin-mocha .search-result-title{width:85%;color:#f5f5f5}html.theme--catppuccin-mocha .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--catppuccin-mocha #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--catppuccin-mocha #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--catppuccin-mocha .w-100{width:100%}html.theme--catppuccin-mocha .gap-2{gap:0.5rem}html.theme--catppuccin-mocha .gap-4{gap:1rem}html.theme--catppuccin-mocha .gap-8{gap:2rem}html.theme--catppuccin-mocha{background-color:#1e1e2e;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--catppuccin-mocha a{transition:all 200ms ease}html.theme--catppuccin-mocha .label{color:#cdd6f4}html.theme--catppuccin-mocha .button,html.theme--catppuccin-mocha .control.has-icons-left .icon,html.theme--catppuccin-mocha .control.has-icons-right .icon,html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .pagination-ellipsis,html.theme--catppuccin-mocha .pagination-link,html.theme--catppuccin-mocha .pagination-next,html.theme--catppuccin-mocha .pagination-previous,html.theme--catppuccin-mocha .select,html.theme--catppuccin-mocha .select select,html.theme--catppuccin-mocha .textarea{height:2.5em;color:#cdd6f4}html.theme--catppuccin-mocha .input,html.theme--catppuccin-mocha #documenter .docs-sidebar form.docs-search>input,html.theme--catppuccin-mocha .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em;color:#cdd6f4}html.theme--catppuccin-mocha .select:after,html.theme--catppuccin-mocha .select select{border-width:1px}html.theme--catppuccin-mocha .menu-list a{transition:all 300ms ease}html.theme--catppuccin-mocha .modal-card-foot,html.theme--catppuccin-mocha .modal-card-head{border-color:#585b70}html.theme--catppuccin-mocha .navbar{border-radius:.4em}html.theme--catppuccin-mocha .navbar.is-transparent{background:none}html.theme--catppuccin-mocha .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--catppuccin-mocha .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#89b4fa}@media screen and (max-width: 1055px){html.theme--catppuccin-mocha .navbar .navbar-menu{background-color:#89b4fa;border-radius:0 0 .4em .4em}}html.theme--catppuccin-mocha .docstring>section>a.docs-sourcelink:not(body){color:#313244}html.theme--catppuccin-mocha .tag.is-link:not(body),html.theme--catppuccin-mocha .docstring>section>a.is-link.docs-sourcelink:not(body),html.theme--catppuccin-mocha .content kbd.is-link:not(body){color:#313244}html.theme--catppuccin-mocha .ansi span.sgr1{font-weight:bolder}html.theme--catppuccin-mocha .ansi span.sgr2{font-weight:lighter}html.theme--catppuccin-mocha .ansi span.sgr3{font-style:italic}html.theme--catppuccin-mocha .ansi span.sgr4{text-decoration:underline}html.theme--catppuccin-mocha .ansi span.sgr7{color:#1e1e2e;background-color:#cdd6f4}html.theme--catppuccin-mocha .ansi span.sgr8{color:transparent}html.theme--catppuccin-mocha .ansi span.sgr8 span{color:transparent}html.theme--catppuccin-mocha .ansi span.sgr9{text-decoration:line-through}html.theme--catppuccin-mocha .ansi span.sgr30{color:#45475a}html.theme--catppuccin-mocha .ansi span.sgr31{color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr32{color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr33{color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr34{color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr35{color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr36{color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr37{color:#bac2de}html.theme--catppuccin-mocha .ansi span.sgr40{background-color:#45475a}html.theme--catppuccin-mocha .ansi span.sgr41{background-color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr42{background-color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr43{background-color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr44{background-color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr45{background-color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr46{background-color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr47{background-color:#bac2de}html.theme--catppuccin-mocha .ansi span.sgr90{color:#585b70}html.theme--catppuccin-mocha .ansi span.sgr91{color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr92{color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr93{color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr94{color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr95{color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr96{color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr97{color:#a6adc8}html.theme--catppuccin-mocha .ansi span.sgr100{background-color:#585b70}html.theme--catppuccin-mocha .ansi span.sgr101{background-color:#f38ba8}html.theme--catppuccin-mocha .ansi span.sgr102{background-color:#a6e3a1}html.theme--catppuccin-mocha .ansi span.sgr103{background-color:#f9e2af}html.theme--catppuccin-mocha .ansi span.sgr104{background-color:#89b4fa}html.theme--catppuccin-mocha .ansi span.sgr105{background-color:#f5c2e7}html.theme--catppuccin-mocha .ansi span.sgr106{background-color:#94e2d5}html.theme--catppuccin-mocha .ansi span.sgr107{background-color:#a6adc8}html.theme--catppuccin-mocha code.language-julia-repl>span.hljs-meta{color:#a6e3a1;font-weight:bolder}html.theme--catppuccin-mocha code .hljs{color:#cdd6f4;background:#1e1e2e}html.theme--catppuccin-mocha code .hljs-keyword{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-built_in{color:#f38ba8}html.theme--catppuccin-mocha code .hljs-type{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-literal{color:#fab387}html.theme--catppuccin-mocha code .hljs-number{color:#fab387}html.theme--catppuccin-mocha code .hljs-operator{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-punctuation{color:#bac2de}html.theme--catppuccin-mocha code .hljs-property{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-regexp{color:#f5c2e7}html.theme--catppuccin-mocha code .hljs-string{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-char.escape_{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-subst{color:#a6adc8}html.theme--catppuccin-mocha code .hljs-symbol{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-variable{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-variable.language_{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-variable.constant_{color:#fab387}html.theme--catppuccin-mocha code .hljs-title{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-title.class_{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-title.function_{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-params{color:#cdd6f4}html.theme--catppuccin-mocha code .hljs-comment{color:#585b70}html.theme--catppuccin-mocha code .hljs-doctag{color:#f38ba8}html.theme--catppuccin-mocha code .hljs-meta{color:#fab387}html.theme--catppuccin-mocha code .hljs-section{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-tag{color:#a6adc8}html.theme--catppuccin-mocha code .hljs-name{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-attr{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-attribute{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-bullet{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-code{color:#a6e3a1}html.theme--catppuccin-mocha code .hljs-emphasis{color:#f38ba8;font-style:italic}html.theme--catppuccin-mocha code .hljs-strong{color:#f38ba8;font-weight:bold}html.theme--catppuccin-mocha code .hljs-formula{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-link{color:#74c7ec;font-style:italic}html.theme--catppuccin-mocha code .hljs-quote{color:#a6e3a1;font-style:italic}html.theme--catppuccin-mocha code .hljs-selector-tag{color:#f9e2af}html.theme--catppuccin-mocha code .hljs-selector-id{color:#89b4fa}html.theme--catppuccin-mocha code .hljs-selector-class{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-selector-attr{color:#cba6f7}html.theme--catppuccin-mocha code .hljs-selector-pseudo{color:#94e2d5}html.theme--catppuccin-mocha code .hljs-template-tag{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-template-variable{color:#f2cdcd}html.theme--catppuccin-mocha code .hljs-addition{color:#a6e3a1;background:rgba(166,227,161,0.15)}html.theme--catppuccin-mocha code .hljs-deletion{color:#f38ba8;background:rgba(243,139,168,0.15)}html.theme--catppuccin-mocha .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover,html.theme--catppuccin-mocha .search-result-link:focus{background-color:#313244}html.theme--catppuccin-mocha .search-result-link .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link .search-filter{transition:all 300ms}html.theme--catppuccin-mocha .search-result-link:hover .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:hover .search-filter,html.theme--catppuccin-mocha .search-result-link:focus .property-search-result-badge,html.theme--catppuccin-mocha .search-result-link:focus .search-filter{color:#313244 !important;background-color:#b4befe !important}html.theme--catppuccin-mocha .search-result-title{color:#cdd6f4}html.theme--catppuccin-mocha .search-result-highlight{background-color:#f38ba8;color:#181825}html.theme--catppuccin-mocha .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--catppuccin-mocha .w-100{width:100%}html.theme--catppuccin-mocha .gap-2{gap:0.5rem}html.theme--catppuccin-mocha .gap-4{gap:1rem} diff --git a/previews/PR826/assets/themes/documenter-dark.css b/previews/PR826/assets/themes/documenter-dark.css new file mode 100644 index 0000000000..c41c82f25a --- /dev/null +++ b/previews/PR826/assets/themes/documenter-dark.css @@ -0,0 +1,7 @@ +html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:.4em;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus,html.theme--documenter-dark .pagination-ellipsis:focus,html.theme--documenter-dark .file-cta:focus,html.theme--documenter-dark .file-name:focus,html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .button:focus,html.theme--documenter-dark .is-focused.pagination-previous,html.theme--documenter-dark .is-focused.pagination-next,html.theme--documenter-dark .is-focused.pagination-link,html.theme--documenter-dark .is-focused.pagination-ellipsis,html.theme--documenter-dark .is-focused.file-cta,html.theme--documenter-dark .is-focused.file-name,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-focused.button,html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active,html.theme--documenter-dark .pagination-ellipsis:active,html.theme--documenter-dark .file-cta:active,html.theme--documenter-dark .file-name:active,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .button:active,html.theme--documenter-dark .is-active.pagination-previous,html.theme--documenter-dark .is-active.pagination-next,html.theme--documenter-dark .is-active.pagination-link,html.theme--documenter-dark .is-active.pagination-ellipsis,html.theme--documenter-dark .is-active.file-cta,html.theme--documenter-dark .is-active.file-name,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .is-active.button{outline:none}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-ellipsis[disabled],html.theme--documenter-dark .file-cta[disabled],html.theme--documenter-dark .file-name[disabled],html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark fieldset[disabled] .pagination-previous,fieldset[disabled] html.theme--documenter-dark .pagination-next,html.theme--documenter-dark fieldset[disabled] .pagination-next,fieldset[disabled] html.theme--documenter-dark .pagination-link,html.theme--documenter-dark fieldset[disabled] .pagination-link,fieldset[disabled] html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark fieldset[disabled] .pagination-ellipsis,fieldset[disabled] html.theme--documenter-dark .file-cta,html.theme--documenter-dark fieldset[disabled] .file-cta,fieldset[disabled] html.theme--documenter-dark .file-name,html.theme--documenter-dark fieldset[disabled] .file-name,fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark fieldset[disabled] .select select,html.theme--documenter-dark .select fieldset[disabled] select,html.theme--documenter-dark fieldset[disabled] .textarea,html.theme--documenter-dark fieldset[disabled] .input,html.theme--documenter-dark fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] html.theme--documenter-dark .button,html.theme--documenter-dark fieldset[disabled] .button{cursor:not-allowed}html.theme--documenter-dark .tabs,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .breadcrumb,html.theme--documenter-dark .file,html.theme--documenter-dark .button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after,html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}html.theme--documenter-dark .admonition:not(:last-child),html.theme--documenter-dark .tabs:not(:last-child),html.theme--documenter-dark .pagination:not(:last-child),html.theme--documenter-dark .message:not(:last-child),html.theme--documenter-dark .level:not(:last-child),html.theme--documenter-dark .breadcrumb:not(:last-child),html.theme--documenter-dark .block:not(:last-child),html.theme--documenter-dark .title:not(:last-child),html.theme--documenter-dark .subtitle:not(:last-child),html.theme--documenter-dark .table-container:not(:last-child),html.theme--documenter-dark .table:not(:last-child),html.theme--documenter-dark .progress:not(:last-child),html.theme--documenter-dark .notification:not(:last-child),html.theme--documenter-dark .content:not(:last-child),html.theme--documenter-dark .box:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .modal-close,html.theme--documenter-dark .delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before,html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .modal-close::before,html.theme--documenter-dark .delete::before{height:2px;width:50%}html.theme--documenter-dark .modal-close::after,html.theme--documenter-dark .delete::after{height:50%;width:2px}html.theme--documenter-dark .modal-close:hover,html.theme--documenter-dark .delete:hover,html.theme--documenter-dark .modal-close:focus,html.theme--documenter-dark .delete:focus{background-color:rgba(10,10,10,0.3)}html.theme--documenter-dark .modal-close:active,html.theme--documenter-dark .delete:active{background-color:rgba(10,10,10,0.4)}html.theme--documenter-dark .is-small.modal-close,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.modal-close,html.theme--documenter-dark .is-small.delete,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}html.theme--documenter-dark .is-medium.modal-close,html.theme--documenter-dark .is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}html.theme--documenter-dark .is-large.modal-close,html.theme--documenter-dark .is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}html.theme--documenter-dark .control.is-loading::after,html.theme--documenter-dark .select.is-loading::after,html.theme--documenter-dark .loader,html.theme--documenter-dark .button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdee0;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}html.theme--documenter-dark .hero-video,html.theme--documenter-dark .modal-background,html.theme--documenter-dark .modal,html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}html.theme--documenter-dark .navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#ecf0f1 !important}a.has-text-light:hover,a.has-text-light:focus{color:#cfd9db !important}.has-background-light{background-color:#ecf0f1 !important}.has-text-dark{color:#282f2f !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#111414 !important}.has-background-dark{background-color:#282f2f !important}.has-text-primary{color:#375a7f !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#28415b !important}.has-background-primary{background-color:#375a7f !important}.has-text-primary-light{color:#f1f5f9 !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#cddbe9 !important}.has-background-primary-light{background-color:#f1f5f9 !important}.has-text-primary-dark{color:#4d7eb2 !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#7198c1 !important}.has-background-primary-dark{background-color:#4d7eb2 !important}.has-text-link{color:#1abc9c !important}a.has-text-link:hover,a.has-text-link:focus{color:#148f77 !important}.has-background-link{background-color:#1abc9c !important}.has-text-link-light{color:#edfdf9 !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c0f6ec !important}.has-background-link-light{background-color:#edfdf9 !important}.has-text-link-dark{color:#15987e !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#1bc5a4 !important}.has-background-link-dark{background-color:#15987e !important}.has-text-info{color:#3c5dcd !important}a.has-text-info:hover,a.has-text-info:focus{color:#2c48aa !important}.has-background-info{background-color:#3c5dcd !important}.has-text-info-light{color:#eff2fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6d0f0 !important}.has-background-info-light{background-color:#eff2fb !important}.has-text-info-dark{color:#3253c3 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#5571d3 !important}.has-background-info-dark{background-color:#3253c3 !important}.has-text-success{color:#259a12 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a6c0d !important}.has-background-success{background-color:#259a12 !important}.has-text-success-light{color:#effded !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c7f8bf !important}.has-background-success-light{background-color:#effded !important}.has-text-success-dark{color:#2ec016 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#3fe524 !important}.has-background-success-dark{background-color:#2ec016 !important}.has-text-warning{color:#f4c72f !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#e4b30c !important}.has-background-warning{background-color:#f4c72f !important}.has-text-warning-light{color:#fefaec !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fbedbb !important}.has-background-warning-light{background-color:#fefaec !important}.has-text-warning-dark{color:#8c6e07 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#bd940a !important}.has-background-warning-dark{background-color:#8c6e07 !important}.has-text-danger{color:#cb3c33 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a23029 !important}.has-background-danger{background-color:#cb3c33 !important}.has-text-danger-light{color:#fbefef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f1c8c6 !important}.has-background-danger-light{background-color:#fbefef !important}.has-text-danger-dark{color:#c03930 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#d35850 !important}.has-background-danger-dark{background-color:#c03930 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#282f2f !important}.has-background-grey-darker{background-color:#282f2f !important}.has-text-grey-dark{color:#343c3d !important}.has-background-grey-dark{background-color:#343c3d !important}.has-text-grey{color:#5e6d6f !important}.has-background-grey{background-color:#5e6d6f !important}.has-text-grey-light{color:#8c9b9d !important}.has-background-grey-light{background-color:#8c9b9d !important}.has-text-grey-lighter{color:#dbdee0 !important}.has-background-grey-lighter{background-color:#dbdee0 !important}.has-text-white-ter{color:#ecf0f1 !important}.has-background-white-ter{background-color:#ecf0f1 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}html.theme--documenter-dark{/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/}html.theme--documenter-dark html{background-color:#1f2424;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark article,html.theme--documenter-dark aside,html.theme--documenter-dark figure,html.theme--documenter-dark footer,html.theme--documenter-dark header,html.theme--documenter-dark hgroup,html.theme--documenter-dark section{display:block}html.theme--documenter-dark body,html.theme--documenter-dark button,html.theme--documenter-dark input,html.theme--documenter-dark optgroup,html.theme--documenter-dark select,html.theme--documenter-dark textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}html.theme--documenter-dark code,html.theme--documenter-dark pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark body{color:#fff;font-size:1em;font-weight:400;line-height:1.5}html.theme--documenter-dark a{color:#1abc9c;cursor:pointer;text-decoration:none}html.theme--documenter-dark a strong{color:currentColor}html.theme--documenter-dark a:hover{color:#1dd2af}html.theme--documenter-dark code{background-color:rgba(255,255,255,0.05);color:#ececec;font-size:.875em;font-weight:normal;padding:.1em}html.theme--documenter-dark hr{background-color:#282f2f;border:none;display:block;height:2px;margin:1.5rem 0}html.theme--documenter-dark img{height:auto;max-width:100%}html.theme--documenter-dark input[type="checkbox"],html.theme--documenter-dark input[type="radio"]{vertical-align:baseline}html.theme--documenter-dark small{font-size:.875em}html.theme--documenter-dark span{font-style:inherit;font-weight:inherit}html.theme--documenter-dark strong{color:#f2f2f2;font-weight:700}html.theme--documenter-dark fieldset{border:none}html.theme--documenter-dark pre{-webkit-overflow-scrolling:touch;background-color:#282f2f;color:#fff;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}html.theme--documenter-dark pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}html.theme--documenter-dark table td,html.theme--documenter-dark table th{vertical-align:top}html.theme--documenter-dark table td:not([align]),html.theme--documenter-dark table th:not([align]){text-align:inherit}html.theme--documenter-dark table th{color:#f2f2f2}html.theme--documenter-dark .box{background-color:#343c3d;border-radius:8px;box-shadow:none;color:#fff;display:block;padding:1.25rem}html.theme--documenter-dark a.box:hover,html.theme--documenter-dark a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #1abc9c}html.theme--documenter-dark a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #1abc9c}html.theme--documenter-dark .button{background-color:#282f2f;border-color:#4c5759;border-width:1px;color:#375a7f;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}html.theme--documenter-dark .button strong{color:inherit}html.theme--documenter-dark .button .icon,html.theme--documenter-dark .button .icon.is-small,html.theme--documenter-dark .button #documenter .docs-sidebar form.docs-search>input.icon,html.theme--documenter-dark #documenter .docs-sidebar .button form.docs-search>input.icon,html.theme--documenter-dark .button .icon.is-medium,html.theme--documenter-dark .button .icon.is-large{height:1.5em;width:1.5em}html.theme--documenter-dark .button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}html.theme--documenter-dark .button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}html.theme--documenter-dark .button:hover,html.theme--documenter-dark .button.is-hovered{border-color:#8c9b9d;color:#f2f2f2}html.theme--documenter-dark .button:focus,html.theme--documenter-dark .button.is-focused{border-color:#8c9b9d;color:#17a689}html.theme--documenter-dark .button:focus:not(:active),html.theme--documenter-dark .button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button:active,html.theme--documenter-dark .button.is-active{border-color:#343c3d;color:#f2f2f2}html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;color:#fff;text-decoration:underline}html.theme--documenter-dark .button.is-text:hover,html.theme--documenter-dark .button.is-text.is-hovered,html.theme--documenter-dark .button.is-text:focus,html.theme--documenter-dark .button.is-text.is-focused{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .button.is-text:active,html.theme--documenter-dark .button.is-text.is-active{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .button.is-text[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}html.theme--documenter-dark .button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#1abc9c;text-decoration:none}html.theme--documenter-dark .button.is-ghost:hover,html.theme--documenter-dark .button.is-ghost.is-hovered{color:#1abc9c;text-decoration:underline}html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:hover,html.theme--documenter-dark .button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus,html.theme--documenter-dark .button.is-white.is-focused{border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white:focus:not(:active),html.theme--documenter-dark .button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .button.is-white[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-hovered{background-color:#000}html.theme--documenter-dark .button.is-white.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-white.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-white.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:hover,html.theme--documenter-dark .button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus,html.theme--documenter-dark .button.is-black.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black:focus:not(:active),html.theme--documenter-dark .button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-black[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-black.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-black.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}html.theme--documenter-dark .button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:hover,html.theme--documenter-dark .button.is-light.is-hovered{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus,html.theme--documenter-dark .button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light:focus:not(:active),html.theme--documenter-dark .button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light.is-active{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light{background-color:#ecf0f1;border-color:#ecf0f1;box-shadow:none}html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-outlined.is-focused{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-light.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-outlined{background-color:transparent;border-color:#ecf0f1;box-shadow:none;color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ecf0f1 #ecf0f1 !important}html.theme--documenter-dark .button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-dark,html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover,html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark:focus:not(:active),html.theme--documenter-dark .content kbd.button:focus:not(:active),html.theme--documenter-dark .button.is-dark.is-focused:not(:active),html.theme--documenter-dark .content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-dark[disabled],html.theme--documenter-dark .content kbd.button[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark,fieldset[disabled] html.theme--documenter-dark .content kbd.button{background-color:#282f2f;border-color:#282f2f;box-shadow:none}html.theme--documenter-dark .button.is-dark.is-inverted,html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted:hover,html.theme--documenter-dark .content kbd.button.is-inverted:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-dark.is-inverted[disabled],html.theme--documenter-dark .content kbd.button.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-loading::after,html.theme--documenter-dark .content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined,html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-outlined.is-focused{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-dark.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-outlined{background-color:transparent;border-color:#282f2f;box-shadow:none;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:hover,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined:focus,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#282f2f}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #282f2f #282f2f !important}html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined[disabled],html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-dark.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary:focus:not(:active),html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus:not(:active),html.theme--documenter-dark .button.is-primary.is-focused:not(:active),html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-primary[disabled],html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;box-shadow:none}html.theme--documenter-dark .button.is-primary.is-inverted,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}html.theme--documenter-dark .button.is-primary.is-inverted[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-primary.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#375a7f;box-shadow:none;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:hover,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined:focus,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#375a7f}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #375a7f #375a7f !important}html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined[disabled],html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-primary.is-inverted.is-outlined,fieldset[disabled] html.theme--documenter-dark .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:hover,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:hover,html.theme--documenter-dark .button.is-primary.is-light.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e8eef5;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-primary.is-light:active,html.theme--documenter-dark .docstring>section>a.button.is-light.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary.is-light.is-active,html.theme--documenter-dark .docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#dfe8f1;border-color:transparent;color:#4d7eb2}html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:hover,html.theme--documenter-dark .button.is-link.is-hovered{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus,html.theme--documenter-dark .button.is-link.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link:focus:not(:active),html.theme--documenter-dark .button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link.is-active{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-link[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link{background-color:#1abc9c;border-color:#1abc9c;box-shadow:none}html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-link.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-outlined.is-focused{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-link.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-outlined{background-color:transparent;border-color:#1abc9c;box-shadow:none;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#1abc9c}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #1abc9c #1abc9c !important}html.theme--documenter-dark .button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:hover,html.theme--documenter-dark .button.is-link.is-light.is-hovered{background-color:#e2fbf6;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-link.is-light:active,html.theme--documenter-dark .button.is-link.is-light.is-active{background-color:#d7f9f3;border-color:transparent;color:#15987e}html.theme--documenter-dark .button.is-info{background-color:#3c5dcd;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:hover,html.theme--documenter-dark .button.is-info.is-hovered{background-color:#3355c9;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus,html.theme--documenter-dark .button.is-info.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info:focus:not(:active),html.theme--documenter-dark .button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info.is-active{background-color:#3151bf;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-info[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info{background-color:#3c5dcd;border-color:#3c5dcd;box-shadow:none}html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-info.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-outlined.is-focused{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}html.theme--documenter-dark .button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}html.theme--documenter-dark .button.is-info.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-info.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;box-shadow:none;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3c5dcd}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}html.theme--documenter-dark .button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-info.is-light{background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .button.is-info.is-light:hover,html.theme--documenter-dark .button.is-info.is-light.is-hovered{background-color:#e5e9f8;border-color:transparent;color:#3253c3}html.theme--documenter-dark .button.is-info.is-light:active,html.theme--documenter-dark .button.is-info.is-light.is-active{background-color:#dae1f6;border-color:transparent;color:#3253c3}html.theme--documenter-dark .button.is-success{background-color:#259a12;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:hover,html.theme--documenter-dark .button.is-success.is-hovered{background-color:#228f11;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus,html.theme--documenter-dark .button.is-success.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success:focus:not(:active),html.theme--documenter-dark .button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success.is-active{background-color:#20830f;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-success[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success{background-color:#259a12;border-color:#259a12;box-shadow:none}html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-success.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#259a12}html.theme--documenter-dark .button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;color:#259a12}html.theme--documenter-dark .button.is-success.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-outlined.is-focused{background-color:#259a12;border-color:#259a12;color:#fff}html.theme--documenter-dark .button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #259a12 #259a12 !important}html.theme--documenter-dark .button.is-success.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-success.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;box-shadow:none;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#259a12}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #259a12 #259a12 !important}html.theme--documenter-dark .button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-success.is-light{background-color:#effded;color:#2ec016}html.theme--documenter-dark .button.is-success.is-light:hover,html.theme--documenter-dark .button.is-success.is-light.is-hovered{background-color:#e5fce1;border-color:transparent;color:#2ec016}html.theme--documenter-dark .button.is-success.is-light:active,html.theme--documenter-dark .button.is-success.is-light.is-active{background-color:#dbfad6;border-color:transparent;color:#2ec016}html.theme--documenter-dark .button.is-warning{background-color:#f4c72f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:hover,html.theme--documenter-dark .button.is-warning.is-hovered{background-color:#f3c423;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:focus,html.theme--documenter-dark .button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning:focus:not(:active),html.theme--documenter-dark .button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning.is-active{background-color:#f3c017;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning{background-color:#f4c72f;border-color:#f4c72f;box-shadow:none}html.theme--documenter-dark .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#f4c72f;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-outlined.is-focused{background-color:#f4c72f;border-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #f4c72f #f4c72f !important}html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}html.theme--documenter-dark .button.is-warning.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-outlined{background-color:transparent;border-color:#f4c72f;box-shadow:none;color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f4c72f #f4c72f !important}html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .button.is-warning.is-light{background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .button.is-warning.is-light:hover,html.theme--documenter-dark .button.is-warning.is-light.is-hovered{background-color:#fdf7e0;border-color:transparent;color:#8c6e07}html.theme--documenter-dark .button.is-warning.is-light:active,html.theme--documenter-dark .button.is-warning.is-light.is-active{background-color:#fdf3d3;border-color:transparent;color:#8c6e07}html.theme--documenter-dark .button.is-danger{background-color:#cb3c33;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:hover,html.theme--documenter-dark .button.is-danger.is-hovered{background-color:#c13930;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus,html.theme--documenter-dark .button.is-danger.is-focused{border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger:focus:not(:active),html.theme--documenter-dark .button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger.is-active{background-color:#b7362e;border-color:transparent;color:#fff}html.theme--documenter-dark .button.is-danger[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger{background-color:#cb3c33;border-color:#cb3c33;box-shadow:none}html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}html.theme--documenter-dark .button.is-danger.is-inverted[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-outlined.is-focused{background-color:#cb3c33;border-color:#cb3c33;color:#fff}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}html.theme--documenter-dark .button.is-danger.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;box-shadow:none;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:hover,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-hovered,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined:focus,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#cb3c33}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:hover::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading:focus::after,html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] html.theme--documenter-dark .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}html.theme--documenter-dark .button.is-danger.is-light{background-color:#fbefef;color:#c03930}html.theme--documenter-dark .button.is-danger.is-light:hover,html.theme--documenter-dark .button.is-danger.is-light.is-hovered{background-color:#f8e6e5;border-color:transparent;color:#c03930}html.theme--documenter-dark .button.is-danger.is-light:active,html.theme--documenter-dark .button.is-danger.is-light.is-active{background-color:#f6dcda;border-color:transparent;color:#c03930}html.theme--documenter-dark .button.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}html.theme--documenter-dark .button.is-small:not(.is-rounded),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:3px}html.theme--documenter-dark .button.is-normal{font-size:1rem}html.theme--documenter-dark .button.is-medium{font-size:1.25rem}html.theme--documenter-dark .button.is-large{font-size:1.5rem}html.theme--documenter-dark .button[disabled],fieldset[disabled] html.theme--documenter-dark .button{background-color:#8c9b9d;border-color:#5e6d6f;box-shadow:none;opacity:.5}html.theme--documenter-dark .button.is-fullwidth{display:flex;width:100%}html.theme--documenter-dark .button.is-loading{color:transparent !important;pointer-events:none}html.theme--documenter-dark .button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}html.theme--documenter-dark .button.is-static{background-color:#282f2f;border-color:#5e6d6f;color:#dbdee0;box-shadow:none;pointer-events:none}html.theme--documenter-dark .button.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}html.theme--documenter-dark .buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .buttons .button{margin-bottom:0.5rem}html.theme--documenter-dark .buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}html.theme--documenter-dark .buttons:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .buttons:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}html.theme--documenter-dark .buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:3px}html.theme--documenter-dark .buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}html.theme--documenter-dark .buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}html.theme--documenter-dark .buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}html.theme--documenter-dark .buttons.has-addons .button:last-child{margin-right:0}html.theme--documenter-dark .buttons.has-addons .button:hover,html.theme--documenter-dark .buttons.has-addons .button.is-hovered{z-index:2}html.theme--documenter-dark .buttons.has-addons .button:focus,html.theme--documenter-dark .buttons.has-addons .button.is-focused,html.theme--documenter-dark .buttons.has-addons .button:active,html.theme--documenter-dark .buttons.has-addons .button.is-active,html.theme--documenter-dark .buttons.has-addons .button.is-selected{z-index:3}html.theme--documenter-dark .buttons.has-addons .button:focus:hover,html.theme--documenter-dark .buttons.has-addons .button.is-focused:hover,html.theme--documenter-dark .buttons.has-addons .button:active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-active:hover,html.theme--documenter-dark .buttons.has-addons .button.is-selected:hover{z-index:4}html.theme--documenter-dark .buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .buttons.is-centered{justify-content:center}html.theme--documenter-dark .buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}html.theme--documenter-dark .buttons.is-right{justify-content:flex-end}html.theme--documenter-dark .buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .button.is-responsive.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}html.theme--documenter-dark .button.is-responsive,html.theme--documenter-dark .button.is-responsive.is-normal{font-size:.75rem}html.theme--documenter-dark .button.is-responsive.is-medium{font-size:1rem}html.theme--documenter-dark .button.is-responsive.is-large{font-size:1.25rem}}html.theme--documenter-dark .container{flex-grow:1;margin:0 auto;position:relative;width:auto}html.theme--documenter-dark .container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){html.theme--documenter-dark .container{max-width:992px}}@media screen and (max-width: 1215px){html.theme--documenter-dark .container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){html.theme--documenter-dark .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){html.theme--documenter-dark .container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){html.theme--documenter-dark .container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}html.theme--documenter-dark .content li+li{margin-top:0.25em}html.theme--documenter-dark .content p:not(:last-child),html.theme--documenter-dark .content dl:not(:last-child),html.theme--documenter-dark .content ol:not(:last-child),html.theme--documenter-dark .content ul:not(:last-child),html.theme--documenter-dark .content blockquote:not(:last-child),html.theme--documenter-dark .content pre:not(:last-child),html.theme--documenter-dark .content table:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .content h1,html.theme--documenter-dark .content h2,html.theme--documenter-dark .content h3,html.theme--documenter-dark .content h4,html.theme--documenter-dark .content h5,html.theme--documenter-dark .content h6{color:#f2f2f2;font-weight:600;line-height:1.125}html.theme--documenter-dark .content h1{font-size:2em;margin-bottom:0.5em}html.theme--documenter-dark .content h1:not(:first-child){margin-top:1em}html.theme--documenter-dark .content h2{font-size:1.75em;margin-bottom:0.5714em}html.theme--documenter-dark .content h2:not(:first-child){margin-top:1.1428em}html.theme--documenter-dark .content h3{font-size:1.5em;margin-bottom:0.6666em}html.theme--documenter-dark .content h3:not(:first-child){margin-top:1.3333em}html.theme--documenter-dark .content h4{font-size:1.25em;margin-bottom:0.8em}html.theme--documenter-dark .content h5{font-size:1.125em;margin-bottom:0.8888em}html.theme--documenter-dark .content h6{font-size:1em;margin-bottom:1em}html.theme--documenter-dark .content blockquote{background-color:#282f2f;border-left:5px solid #5e6d6f;padding:1.25em 1.5em}html.theme--documenter-dark .content ol{list-style-position:outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ol:not([type]){list-style-type:decimal}html.theme--documenter-dark .content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}html.theme--documenter-dark .content ol.is-lower-roman:not([type]){list-style-type:lower-roman}html.theme--documenter-dark .content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}html.theme--documenter-dark .content ol.is-upper-roman:not([type]){list-style-type:upper-roman}html.theme--documenter-dark .content ul{list-style:disc outside;margin-left:2em;margin-top:1em}html.theme--documenter-dark .content ul ul{list-style-type:circle;margin-top:0.5em}html.theme--documenter-dark .content ul ul ul{list-style-type:square}html.theme--documenter-dark .content dd{margin-left:2em}html.theme--documenter-dark .content figure{margin-left:2em;margin-right:2em;text-align:center}html.theme--documenter-dark .content figure:not(:first-child){margin-top:2em}html.theme--documenter-dark .content figure:not(:last-child){margin-bottom:2em}html.theme--documenter-dark .content figure img{display:inline-block}html.theme--documenter-dark .content figure figcaption{font-style:italic}html.theme--documenter-dark .content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}html.theme--documenter-dark .content sup,html.theme--documenter-dark .content sub{font-size:75%}html.theme--documenter-dark .content table{width:100%}html.theme--documenter-dark .content table td,html.theme--documenter-dark .content table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .content table th{color:#f2f2f2}html.theme--documenter-dark .content table th:not([align]){text-align:inherit}html.theme--documenter-dark .content table thead td,html.theme--documenter-dark .content table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .content table tfoot td,html.theme--documenter-dark .content table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .content table tbody tr:last-child td,html.theme--documenter-dark .content table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .content .tabs li+li{margin-top:0}html.theme--documenter-dark .content.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}html.theme--documenter-dark .content.is-normal{font-size:1rem}html.theme--documenter-dark .content.is-medium{font-size:1.25rem}html.theme--documenter-dark .content.is-large{font-size:1.5rem}html.theme--documenter-dark .icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}html.theme--documenter-dark .icon.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}html.theme--documenter-dark .icon.is-medium{height:2rem;width:2rem}html.theme--documenter-dark .icon.is-large{height:3rem;width:3rem}html.theme--documenter-dark .icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}html.theme--documenter-dark .icon-text .icon{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .icon-text .icon:not(:last-child){margin-right:.25em}html.theme--documenter-dark .icon-text .icon:not(:first-child){margin-left:.25em}html.theme--documenter-dark div.icon-text{display:flex}html.theme--documenter-dark .image,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{display:block;position:relative}html.theme--documenter-dark .image img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}html.theme--documenter-dark .image img.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}html.theme--documenter-dark .image.is-fullwidth,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}html.theme--documenter-dark .image.is-square img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square img,html.theme--documenter-dark .image.is-square .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,html.theme--documenter-dark .image.is-1by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 img,html.theme--documenter-dark .image.is-1by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,html.theme--documenter-dark .image.is-5by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 img,html.theme--documenter-dark .image.is-5by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,html.theme--documenter-dark .image.is-4by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 img,html.theme--documenter-dark .image.is-4by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,html.theme--documenter-dark .image.is-3by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 img,html.theme--documenter-dark .image.is-3by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,html.theme--documenter-dark .image.is-5by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 img,html.theme--documenter-dark .image.is-5by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,html.theme--documenter-dark .image.is-16by9 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 img,html.theme--documenter-dark .image.is-16by9 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,html.theme--documenter-dark .image.is-2by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 img,html.theme--documenter-dark .image.is-2by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,html.theme--documenter-dark .image.is-3by1 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 img,html.theme--documenter-dark .image.is-3by1 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,html.theme--documenter-dark .image.is-4by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 img,html.theme--documenter-dark .image.is-4by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,html.theme--documenter-dark .image.is-3by4 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 img,html.theme--documenter-dark .image.is-3by4 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,html.theme--documenter-dark .image.is-2by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 img,html.theme--documenter-dark .image.is-2by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,html.theme--documenter-dark .image.is-3by5 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 img,html.theme--documenter-dark .image.is-3by5 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,html.theme--documenter-dark .image.is-9by16 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 img,html.theme--documenter-dark .image.is-9by16 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,html.theme--documenter-dark .image.is-1by2 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 img,html.theme--documenter-dark .image.is-1by2 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,html.theme--documenter-dark .image.is-1by3 img,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 img,html.theme--documenter-dark .image.is-1by3 .has-ratio,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}html.theme--documenter-dark .image.is-square,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-square,html.theme--documenter-dark .image.is-1by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}html.theme--documenter-dark .image.is-5by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}html.theme--documenter-dark .image.is-4by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}html.theme--documenter-dark .image.is-3by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}html.theme--documenter-dark .image.is-5by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}html.theme--documenter-dark .image.is-16by9,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}html.theme--documenter-dark .image.is-2by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}html.theme--documenter-dark .image.is-3by1,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}html.theme--documenter-dark .image.is-4by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}html.theme--documenter-dark .image.is-3by4,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}html.theme--documenter-dark .image.is-2by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}html.theme--documenter-dark .image.is-3by5,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}html.theme--documenter-dark .image.is-9by16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}html.theme--documenter-dark .image.is-1by2,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}html.theme--documenter-dark .image.is-1by3,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}html.theme--documenter-dark .image.is-16x16,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}html.theme--documenter-dark .image.is-24x24,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}html.theme--documenter-dark .image.is-32x32,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}html.theme--documenter-dark .image.is-48x48,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}html.theme--documenter-dark .image.is-64x64,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}html.theme--documenter-dark .image.is-96x96,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}html.theme--documenter-dark .image.is-128x128,html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}html.theme--documenter-dark .notification{background-color:#282f2f;border-radius:.4em;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}html.theme--documenter-dark .notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .notification strong{color:currentColor}html.theme--documenter-dark .notification code,html.theme--documenter-dark .notification pre{background:#fff}html.theme--documenter-dark .notification pre code{background:transparent}html.theme--documenter-dark .notification>.delete{right:.5rem;position:absolute;top:0.5rem}html.theme--documenter-dark .notification .title,html.theme--documenter-dark .notification .subtitle,html.theme--documenter-dark .notification .content{color:currentColor}html.theme--documenter-dark .notification.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .notification.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .notification.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-dark,html.theme--documenter-dark .content kbd.notification{background-color:#282f2f;color:#fff}html.theme--documenter-dark .notification.is-primary,html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .notification.is-primary.is-light,html.theme--documenter-dark .docstring>section>a.notification.is-light.docs-sourcelink{background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .notification.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .notification.is-link.is-light{background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .notification.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .notification.is-info.is-light{background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .notification.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .notification.is-success.is-light{background-color:#effded;color:#2ec016}html.theme--documenter-dark .notification.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .notification.is-warning.is-light{background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .notification.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .notification.is-danger.is-light{background-color:#fbefef;color:#c03930}html.theme--documenter-dark .progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}html.theme--documenter-dark .progress::-webkit-progress-bar{background-color:#343c3d}html.theme--documenter-dark .progress::-webkit-progress-value{background-color:#dbdee0}html.theme--documenter-dark .progress::-moz-progress-bar{background-color:#dbdee0}html.theme--documenter-dark .progress::-ms-fill{background-color:#dbdee0;border:none}html.theme--documenter-dark .progress.is-white::-webkit-progress-value{background-color:#fff}html.theme--documenter-dark .progress.is-white::-moz-progress-bar{background-color:#fff}html.theme--documenter-dark .progress.is-white::-ms-fill{background-color:#fff}html.theme--documenter-dark .progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-black::-webkit-progress-value{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-moz-progress-bar{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black::-ms-fill{background-color:#0a0a0a}html.theme--documenter-dark .progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-light::-webkit-progress-value{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-moz-progress-bar{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light::-ms-fill{background-color:#ecf0f1}html.theme--documenter-dark .progress.is-light:indeterminate{background-image:linear-gradient(to right, #ecf0f1 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-dark::-webkit-progress-value,html.theme--documenter-dark .content kbd.progress::-webkit-progress-value{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-moz-progress-bar,html.theme--documenter-dark .content kbd.progress::-moz-progress-bar{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark::-ms-fill,html.theme--documenter-dark .content kbd.progress::-ms-fill{background-color:#282f2f}html.theme--documenter-dark .progress.is-dark:indeterminate,html.theme--documenter-dark .content kbd.progress:indeterminate{background-image:linear-gradient(to right, #282f2f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-primary::-webkit-progress-value,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-moz-progress-bar,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary::-ms-fill,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#375a7f}html.theme--documenter-dark .progress.is-primary:indeterminate,html.theme--documenter-dark .docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #375a7f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-link::-webkit-progress-value{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-moz-progress-bar{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link::-ms-fill{background-color:#1abc9c}html.theme--documenter-dark .progress.is-link:indeterminate{background-image:linear-gradient(to right, #1abc9c 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-info::-webkit-progress-value{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info::-moz-progress-bar{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info::-ms-fill{background-color:#3c5dcd}html.theme--documenter-dark .progress.is-info:indeterminate{background-image:linear-gradient(to right, #3c5dcd 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-success::-webkit-progress-value{background-color:#259a12}html.theme--documenter-dark .progress.is-success::-moz-progress-bar{background-color:#259a12}html.theme--documenter-dark .progress.is-success::-ms-fill{background-color:#259a12}html.theme--documenter-dark .progress.is-success:indeterminate{background-image:linear-gradient(to right, #259a12 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-warning::-webkit-progress-value{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning::-moz-progress-bar{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning::-ms-fill{background-color:#f4c72f}html.theme--documenter-dark .progress.is-warning:indeterminate{background-image:linear-gradient(to right, #f4c72f 30%, #343c3d 30%)}html.theme--documenter-dark .progress.is-danger::-webkit-progress-value{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger::-moz-progress-bar{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger::-ms-fill{background-color:#cb3c33}html.theme--documenter-dark .progress.is-danger:indeterminate{background-image:linear-gradient(to right, #cb3c33 30%, #343c3d 30%)}html.theme--documenter-dark .progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#343c3d;background-image:linear-gradient(to right, #fff 30%, #343c3d 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}html.theme--documenter-dark .progress:indeterminate::-webkit-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-moz-progress-bar{background-color:transparent}html.theme--documenter-dark .progress:indeterminate::-ms-fill{animation-name:none}html.theme--documenter-dark .progress.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}html.theme--documenter-dark .progress.is-medium{height:1.25rem}html.theme--documenter-dark .progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}html.theme--documenter-dark .table{background-color:#343c3d;color:#fff}html.theme--documenter-dark .table td,html.theme--documenter-dark .table th{border:1px solid #5e6d6f;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}html.theme--documenter-dark .table td.is-white,html.theme--documenter-dark .table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .table td.is-black,html.theme--documenter-dark .table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .table td.is-light,html.theme--documenter-dark .table th.is-light{background-color:#ecf0f1;border-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-dark,html.theme--documenter-dark .table th.is-dark{background-color:#282f2f;border-color:#282f2f;color:#fff}html.theme--documenter-dark .table td.is-primary,html.theme--documenter-dark .table th.is-primary{background-color:#375a7f;border-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-link,html.theme--documenter-dark .table th.is-link{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .table td.is-info,html.theme--documenter-dark .table th.is-info{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}html.theme--documenter-dark .table td.is-success,html.theme--documenter-dark .table th.is-success{background-color:#259a12;border-color:#259a12;color:#fff}html.theme--documenter-dark .table td.is-warning,html.theme--documenter-dark .table th.is-warning{background-color:#f4c72f;border-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .table td.is-danger,html.theme--documenter-dark .table th.is-danger{background-color:#cb3c33;border-color:#cb3c33;color:#fff}html.theme--documenter-dark .table td.is-narrow,html.theme--documenter-dark .table th.is-narrow{white-space:nowrap;width:1%}html.theme--documenter-dark .table td.is-selected,html.theme--documenter-dark .table th.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table td.is-selected a,html.theme--documenter-dark .table td.is-selected strong,html.theme--documenter-dark .table th.is-selected a,html.theme--documenter-dark .table th.is-selected strong{color:currentColor}html.theme--documenter-dark .table td.is-vcentered,html.theme--documenter-dark .table th.is-vcentered{vertical-align:middle}html.theme--documenter-dark .table th{color:#f2f2f2}html.theme--documenter-dark .table th:not([align]){text-align:left}html.theme--documenter-dark .table tr.is-selected{background-color:#375a7f;color:#fff}html.theme--documenter-dark .table tr.is-selected a,html.theme--documenter-dark .table tr.is-selected strong{color:currentColor}html.theme--documenter-dark .table tr.is-selected td,html.theme--documenter-dark .table tr.is-selected th{border-color:#fff;color:currentColor}html.theme--documenter-dark .table thead{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table thead td,html.theme--documenter-dark .table thead th{border-width:0 0 2px;color:#f2f2f2}html.theme--documenter-dark .table tfoot{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tfoot td,html.theme--documenter-dark .table tfoot th{border-width:2px 0 0;color:#f2f2f2}html.theme--documenter-dark .table tbody{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .table tbody tr:last-child td,html.theme--documenter-dark .table tbody tr:last-child th{border-bottom-width:0}html.theme--documenter-dark .table.is-bordered td,html.theme--documenter-dark .table.is-bordered th{border-width:1px}html.theme--documenter-dark .table.is-bordered tr:last-child td,html.theme--documenter-dark .table.is-bordered tr:last-child th{border-bottom-width:1px}html.theme--documenter-dark .table.is-fullwidth{width:100%}html.theme--documenter-dark .table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#282f2f}html.theme--documenter-dark .table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#2d3435}html.theme--documenter-dark .table.is-narrow td,html.theme--documenter-dark .table.is-narrow th{padding:0.25em 0.5em}html.theme--documenter-dark .table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#282f2f}html.theme--documenter-dark .table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}html.theme--documenter-dark .tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .tags .tag,html.theme--documenter-dark .tags .content kbd,html.theme--documenter-dark .content .tags kbd,html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}html.theme--documenter-dark .tags .tag:not(:last-child),html.theme--documenter-dark .tags .content kbd:not(:last-child),html.theme--documenter-dark .content .tags kbd:not(:last-child),html.theme--documenter-dark .tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}html.theme--documenter-dark .tags:last-child{margin-bottom:-0.5rem}html.theme--documenter-dark .tags:not(:last-child){margin-bottom:1rem}html.theme--documenter-dark .tags.are-medium .tag:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .content kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .content .tags.are-medium kbd:not(.is-normal):not(.is-large),html.theme--documenter-dark .tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}html.theme--documenter-dark .tags.are-large .tag:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .content kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .content .tags.are-large kbd:not(.is-normal):not(.is-medium),html.theme--documenter-dark .tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}html.theme--documenter-dark .tags.is-centered{justify-content:center}html.theme--documenter-dark .tags.is-centered .tag,html.theme--documenter-dark .tags.is-centered .content kbd,html.theme--documenter-dark .content .tags.is-centered kbd,html.theme--documenter-dark .tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}html.theme--documenter-dark .tags.is-right{justify-content:flex-end}html.theme--documenter-dark .tags.is-right .tag:not(:first-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:first-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}html.theme--documenter-dark .tags.is-right .tag:not(:last-child),html.theme--documenter-dark .tags.is-right .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.is-right kbd:not(:last-child),html.theme--documenter-dark .tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}html.theme--documenter-dark .tags.has-addons .tag,html.theme--documenter-dark .tags.has-addons .content kbd,html.theme--documenter-dark .content .tags.has-addons kbd,html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}html.theme--documenter-dark .tags.has-addons .tag:not(:first-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:first-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:first-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}html.theme--documenter-dark .tags.has-addons .tag:not(:last-child),html.theme--documenter-dark .tags.has-addons .content kbd:not(:last-child),html.theme--documenter-dark .content .tags.has-addons kbd:not(:last-child),html.theme--documenter-dark .tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}html.theme--documenter-dark .tag:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#282f2f;border-radius:.4em;color:#fff;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}html.theme--documenter-dark .tag:not(body) .delete,html.theme--documenter-dark .content kbd:not(body) .delete,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}html.theme--documenter-dark .tag.is-white:not(body),html.theme--documenter-dark .content kbd.is-white:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .tag.is-black:not(body),html.theme--documenter-dark .content kbd.is-black:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .tag.is-light:not(body),html.theme--documenter-dark .content kbd.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-dark:not(body),html.theme--documenter-dark .content kbd:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-dark:not(body),html.theme--documenter-dark .content .docstring>section>kbd:not(body){background-color:#282f2f;color:#fff}html.theme--documenter-dark .tag.is-primary:not(body),html.theme--documenter-dark .content kbd.is-primary:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body){background-color:#375a7f;color:#fff}html.theme--documenter-dark .tag.is-primary.is-light:not(body),html.theme--documenter-dark .content kbd.is-primary.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f1f5f9;color:#4d7eb2}html.theme--documenter-dark .tag.is-link:not(body),html.theme--documenter-dark .content kbd.is-link:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#1abc9c;color:#fff}html.theme--documenter-dark .tag.is-link.is-light:not(body),html.theme--documenter-dark .content kbd.is-link.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#edfdf9;color:#15987e}html.theme--documenter-dark .tag.is-info:not(body),html.theme--documenter-dark .content kbd.is-info:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .tag.is-info.is-light:not(body),html.theme--documenter-dark .content kbd.is-info.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#eff2fb;color:#3253c3}html.theme--documenter-dark .tag.is-success:not(body),html.theme--documenter-dark .content kbd.is-success:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#259a12;color:#fff}html.theme--documenter-dark .tag.is-success.is-light:not(body),html.theme--documenter-dark .content kbd.is-success.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#effded;color:#2ec016}html.theme--documenter-dark .tag.is-warning:not(body),html.theme--documenter-dark .content kbd.is-warning:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .tag.is-warning.is-light:not(body),html.theme--documenter-dark .content kbd.is-warning.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fefaec;color:#8c6e07}html.theme--documenter-dark .tag.is-danger:not(body),html.theme--documenter-dark .content kbd.is-danger:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#cb3c33;color:#fff}html.theme--documenter-dark .tag.is-danger.is-light:not(body),html.theme--documenter-dark .content kbd.is-danger.is-light:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fbefef;color:#c03930}html.theme--documenter-dark .tag.is-normal:not(body),html.theme--documenter-dark .content kbd.is-normal:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}html.theme--documenter-dark .tag.is-medium:not(body),html.theme--documenter-dark .content kbd.is-medium:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}html.theme--documenter-dark .tag.is-large:not(body),html.theme--documenter-dark .content kbd.is-large:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}html.theme--documenter-dark .tag:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .content kbd:not(body) .icon:first-child:not(:last-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}html.theme--documenter-dark .tag:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .content kbd:not(body) .icon:last-child:not(:first-child),html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}html.theme--documenter-dark .tag:not(body) .icon:first-child:last-child,html.theme--documenter-dark .content kbd:not(body) .icon:first-child:last-child,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}html.theme--documenter-dark .tag.is-delete:not(body),html.theme--documenter-dark .content kbd.is-delete:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before,html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}html.theme--documenter-dark .tag.is-delete:not(body)::before,html.theme--documenter-dark .content kbd.is-delete:not(body)::before,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}html.theme--documenter-dark .tag.is-delete:not(body)::after,html.theme--documenter-dark .content kbd.is-delete:not(body)::after,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}html.theme--documenter-dark .tag.is-delete:not(body):hover,html.theme--documenter-dark .content kbd.is-delete:not(body):hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):hover,html.theme--documenter-dark .tag.is-delete:not(body):focus,html.theme--documenter-dark .content kbd.is-delete:not(body):focus,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#1d2122}html.theme--documenter-dark .tag.is-delete:not(body):active,html.theme--documenter-dark .content kbd.is-delete:not(body):active,html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#111414}html.theme--documenter-dark .tag.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:not(body),html.theme--documenter-dark .content kbd.is-rounded:not(body),html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input:not(body),html.theme--documenter-dark .docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}html.theme--documenter-dark a.tag:hover,html.theme--documenter-dark .docstring>section>a.docs-sourcelink:hover{text-decoration:underline}html.theme--documenter-dark .title,html.theme--documenter-dark .subtitle{word-break:break-word}html.theme--documenter-dark .title em,html.theme--documenter-dark .title span,html.theme--documenter-dark .subtitle em,html.theme--documenter-dark .subtitle span{font-weight:inherit}html.theme--documenter-dark .title sub,html.theme--documenter-dark .subtitle sub{font-size:.75em}html.theme--documenter-dark .title sup,html.theme--documenter-dark .subtitle sup{font-size:.75em}html.theme--documenter-dark .title .tag,html.theme--documenter-dark .title .content kbd,html.theme--documenter-dark .content .title kbd,html.theme--documenter-dark .title .docstring>section>a.docs-sourcelink,html.theme--documenter-dark .subtitle .tag,html.theme--documenter-dark .subtitle .content kbd,html.theme--documenter-dark .content .subtitle kbd,html.theme--documenter-dark .subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}html.theme--documenter-dark .title{color:#fff;font-size:2rem;font-weight:500;line-height:1.125}html.theme--documenter-dark .title strong{color:inherit;font-weight:inherit}html.theme--documenter-dark .title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}html.theme--documenter-dark .title.is-1{font-size:3rem}html.theme--documenter-dark .title.is-2{font-size:2.5rem}html.theme--documenter-dark .title.is-3{font-size:2rem}html.theme--documenter-dark .title.is-4{font-size:1.5rem}html.theme--documenter-dark .title.is-5{font-size:1.25rem}html.theme--documenter-dark .title.is-6{font-size:1rem}html.theme--documenter-dark .title.is-7{font-size:.75rem}html.theme--documenter-dark .subtitle{color:#8c9b9d;font-size:1.25rem;font-weight:400;line-height:1.25}html.theme--documenter-dark .subtitle strong{color:#8c9b9d;font-weight:600}html.theme--documenter-dark .subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}html.theme--documenter-dark .subtitle.is-1{font-size:3rem}html.theme--documenter-dark .subtitle.is-2{font-size:2.5rem}html.theme--documenter-dark .subtitle.is-3{font-size:2rem}html.theme--documenter-dark .subtitle.is-4{font-size:1.5rem}html.theme--documenter-dark .subtitle.is-5{font-size:1.25rem}html.theme--documenter-dark .subtitle.is-6{font-size:1rem}html.theme--documenter-dark .subtitle.is-7{font-size:.75rem}html.theme--documenter-dark .heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}html.theme--documenter-dark .number{align-items:center;background-color:#282f2f;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#1f2424;border-color:#5e6d6f;border-radius:.4em;color:#dbdee0}html.theme--documenter-dark .select select::-moz-placeholder,html.theme--documenter-dark .textarea::-moz-placeholder,html.theme--documenter-dark .input::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select::-webkit-input-placeholder,html.theme--documenter-dark .textarea::-webkit-input-placeholder,html.theme--documenter-dark .input::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:-moz-placeholder,html.theme--documenter-dark .textarea:-moz-placeholder,html.theme--documenter-dark .input:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#868c98}html.theme--documenter-dark .select select:-ms-input-placeholder,html.theme--documenter-dark .textarea:-ms-input-placeholder,html.theme--documenter-dark .input:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#868c98}html.theme--documenter-dark .select select:hover,html.theme--documenter-dark .textarea:hover,html.theme--documenter-dark .input:hover,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:hover,html.theme--documenter-dark .select select.is-hovered,html.theme--documenter-dark .is-hovered.textarea,html.theme--documenter-dark .is-hovered.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#8c9b9d}html.theme--documenter-dark .select select:focus,html.theme--documenter-dark .textarea:focus,html.theme--documenter-dark .input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:focus,html.theme--documenter-dark .select select.is-focused,html.theme--documenter-dark .is-focused.textarea,html.theme--documenter-dark .is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .select select:active,html.theme--documenter-dark .textarea:active,html.theme--documenter-dark .input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:active,html.theme--documenter-dark .select select.is-active,html.theme--documenter-dark .is-active.textarea,html.theme--documenter-dark .is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{border-color:#1abc9c;box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select select[disabled],html.theme--documenter-dark .textarea[disabled],html.theme--documenter-dark .input[disabled],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] html.theme--documenter-dark .select select,fieldset[disabled] html.theme--documenter-dark .textarea,fieldset[disabled] html.theme--documenter-dark .input,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{background-color:#8c9b9d;border-color:#282f2f;box-shadow:none;color:#fff}html.theme--documenter-dark .select select[disabled]::-moz-placeholder,html.theme--documenter-dark .textarea[disabled]::-moz-placeholder,html.theme--documenter-dark .input[disabled]::-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .textarea[disabled]::-webkit-input-placeholder,html.theme--documenter-dark .input[disabled]::-webkit-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input::-webkit-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-moz-placeholder,html.theme--documenter-dark .textarea[disabled]:-moz-placeholder,html.theme--documenter-dark .input[disabled]:-moz-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-moz-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .select select[disabled]:-ms-input-placeholder,html.theme--documenter-dark .textarea[disabled]:-ms-input-placeholder,html.theme--documenter-dark .input[disabled]:-ms-input-placeholder,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .select select:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .textarea:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark .input:-ms-input-placeholder,fieldset[disabled] html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:rgba(255,255,255,0.3)}html.theme--documenter-dark .textarea,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}html.theme--documenter-dark .textarea[readonly],html.theme--documenter-dark .input[readonly],html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}html.theme--documenter-dark .is-white.textarea,html.theme--documenter-dark .is-white.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}html.theme--documenter-dark .is-white.textarea:focus,html.theme--documenter-dark .is-white.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:focus,html.theme--documenter-dark .is-white.is-focused.textarea,html.theme--documenter-dark .is-white.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-white.textarea:active,html.theme--documenter-dark .is-white.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-white:active,html.theme--documenter-dark .is-white.is-active.textarea,html.theme--documenter-dark .is-white.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .is-black.textarea,html.theme--documenter-dark .is-black.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}html.theme--documenter-dark .is-black.textarea:focus,html.theme--documenter-dark .is-black.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:focus,html.theme--documenter-dark .is-black.is-focused.textarea,html.theme--documenter-dark .is-black.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-black.textarea:active,html.theme--documenter-dark .is-black.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-black:active,html.theme--documenter-dark .is-black.is-active.textarea,html.theme--documenter-dark .is-black.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .is-light.textarea,html.theme--documenter-dark .is-light.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light{border-color:#ecf0f1}html.theme--documenter-dark .is-light.textarea:focus,html.theme--documenter-dark .is-light.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:focus,html.theme--documenter-dark .is-light.is-focused.textarea,html.theme--documenter-dark .is-light.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-light.textarea:active,html.theme--documenter-dark .is-light.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-light:active,html.theme--documenter-dark .is-light.is-active.textarea,html.theme--documenter-dark .is-light.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .is-dark.textarea,html.theme--documenter-dark .content kbd.textarea,html.theme--documenter-dark .is-dark.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark,html.theme--documenter-dark .content kbd.input{border-color:#282f2f}html.theme--documenter-dark .is-dark.textarea:focus,html.theme--documenter-dark .content kbd.textarea:focus,html.theme--documenter-dark .is-dark.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:focus,html.theme--documenter-dark .content kbd.input:focus,html.theme--documenter-dark .is-dark.is-focused.textarea,html.theme--documenter-dark .content kbd.is-focused.textarea,html.theme--documenter-dark .is-dark.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .content kbd.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-focused,html.theme--documenter-dark .is-dark.textarea:active,html.theme--documenter-dark .content kbd.textarea:active,html.theme--documenter-dark .is-dark.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-dark:active,html.theme--documenter-dark .content kbd.input:active,html.theme--documenter-dark .is-dark.is-active.textarea,html.theme--documenter-dark .content kbd.is-active.textarea,html.theme--documenter-dark .is-dark.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .content kbd.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .is-primary.textarea,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink{border-color:#375a7f}html.theme--documenter-dark .is-primary.textarea:focus,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:focus,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:focus,html.theme--documenter-dark .is-primary.is-focused.textarea,html.theme--documenter-dark .docstring>section>a.is-focused.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .docstring>section>a.is-focused.input.docs-sourcelink,html.theme--documenter-dark .is-primary.textarea:active,html.theme--documenter-dark .docstring>section>a.textarea.docs-sourcelink:active,html.theme--documenter-dark .is-primary.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-primary:active,html.theme--documenter-dark .docstring>section>a.input.docs-sourcelink:active,html.theme--documenter-dark .is-primary.is-active.textarea,html.theme--documenter-dark .docstring>section>a.is-active.textarea.docs-sourcelink,html.theme--documenter-dark .is-primary.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active,html.theme--documenter-dark .docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .is-link.textarea,html.theme--documenter-dark .is-link.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link{border-color:#1abc9c}html.theme--documenter-dark .is-link.textarea:focus,html.theme--documenter-dark .is-link.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:focus,html.theme--documenter-dark .is-link.is-focused.textarea,html.theme--documenter-dark .is-link.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-link.textarea:active,html.theme--documenter-dark .is-link.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-link:active,html.theme--documenter-dark .is-link.is-active.textarea,html.theme--documenter-dark .is-link.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .is-info.textarea,html.theme--documenter-dark .is-info.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info{border-color:#3c5dcd}html.theme--documenter-dark .is-info.textarea:focus,html.theme--documenter-dark .is-info.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:focus,html.theme--documenter-dark .is-info.is-focused.textarea,html.theme--documenter-dark .is-info.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-info.textarea:active,html.theme--documenter-dark .is-info.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-info:active,html.theme--documenter-dark .is-info.is-active.textarea,html.theme--documenter-dark .is-info.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .is-success.textarea,html.theme--documenter-dark .is-success.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success{border-color:#259a12}html.theme--documenter-dark .is-success.textarea:focus,html.theme--documenter-dark .is-success.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:focus,html.theme--documenter-dark .is-success.is-focused.textarea,html.theme--documenter-dark .is-success.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-success.textarea:active,html.theme--documenter-dark .is-success.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-success:active,html.theme--documenter-dark .is-success.is-active.textarea,html.theme--documenter-dark .is-success.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .is-warning.textarea,html.theme--documenter-dark .is-warning.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#f4c72f}html.theme--documenter-dark .is-warning.textarea:focus,html.theme--documenter-dark .is-warning.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:focus,html.theme--documenter-dark .is-warning.is-focused.textarea,html.theme--documenter-dark .is-warning.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-warning.textarea:active,html.theme--documenter-dark .is-warning.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-warning:active,html.theme--documenter-dark .is-warning.is-active.textarea,html.theme--documenter-dark .is-warning.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .is-danger.textarea,html.theme--documenter-dark .is-danger.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#cb3c33}html.theme--documenter-dark .is-danger.textarea:focus,html.theme--documenter-dark .is-danger.input:focus,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:focus,html.theme--documenter-dark .is-danger.is-focused.textarea,html.theme--documenter-dark .is-danger.is-focused.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-focused,html.theme--documenter-dark .is-danger.textarea:active,html.theme--documenter-dark .is-danger.input:active,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-danger:active,html.theme--documenter-dark .is-danger.is-active.textarea,html.theme--documenter-dark .is-danger.is-active.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .is-small.textarea,html.theme--documenter-dark .is-small.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .is-medium.textarea,html.theme--documenter-dark .is-medium.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}html.theme--documenter-dark .is-large.textarea,html.theme--documenter-dark .is-large.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}html.theme--documenter-dark .is-fullwidth.textarea,html.theme--documenter-dark .is-fullwidth.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}html.theme--documenter-dark .is-inline.textarea,html.theme--documenter-dark .is-inline.input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}html.theme--documenter-dark .input.is-rounded,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}html.theme--documenter-dark .input.is-static,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}html.theme--documenter-dark .textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}html.theme--documenter-dark .textarea:not([rows]){max-height:40em;min-height:8em}html.theme--documenter-dark .textarea[rows]{height:initial}html.theme--documenter-dark .textarea.has-fixed-size{resize:none}html.theme--documenter-dark .radio,html.theme--documenter-dark .checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}html.theme--documenter-dark .radio input,html.theme--documenter-dark .checkbox input{cursor:pointer}html.theme--documenter-dark .radio:hover,html.theme--documenter-dark .checkbox:hover{color:#8c9b9d}html.theme--documenter-dark .radio[disabled],html.theme--documenter-dark .checkbox[disabled],fieldset[disabled] html.theme--documenter-dark .radio,fieldset[disabled] html.theme--documenter-dark .checkbox,html.theme--documenter-dark .radio input[disabled],html.theme--documenter-dark .checkbox input[disabled]{color:#fff;cursor:not-allowed}html.theme--documenter-dark .radio+.radio{margin-left:.5em}html.theme--documenter-dark .select{display:inline-block;max-width:100%;position:relative;vertical-align:top}html.theme--documenter-dark .select:not(.is-multiple){height:2.5em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading)::after{border-color:#1abc9c;right:1.125em;z-index:4}html.theme--documenter-dark .select.is-rounded select,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}html.theme--documenter-dark .select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}html.theme--documenter-dark .select select::-ms-expand{display:none}html.theme--documenter-dark .select select[disabled]:hover,fieldset[disabled] html.theme--documenter-dark .select select:hover{border-color:#282f2f}html.theme--documenter-dark .select select:not([multiple]){padding-right:2.5em}html.theme--documenter-dark .select select[multiple]{height:auto;padding:0}html.theme--documenter-dark .select select[multiple] option{padding:0.5em 1em}html.theme--documenter-dark .select:not(.is-multiple):not(.is-loading):hover::after{border-color:#8c9b9d}html.theme--documenter-dark .select.is-white:not(:hover)::after{border-color:#fff}html.theme--documenter-dark .select.is-white select{border-color:#fff}html.theme--documenter-dark .select.is-white select:hover,html.theme--documenter-dark .select.is-white select.is-hovered{border-color:#f2f2f2}html.theme--documenter-dark .select.is-white select:focus,html.theme--documenter-dark .select.is-white select.is-focused,html.theme--documenter-dark .select.is-white select:active,html.theme--documenter-dark .select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}html.theme--documenter-dark .select.is-black:not(:hover)::after{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select{border-color:#0a0a0a}html.theme--documenter-dark .select.is-black select:hover,html.theme--documenter-dark .select.is-black select.is-hovered{border-color:#000}html.theme--documenter-dark .select.is-black select:focus,html.theme--documenter-dark .select.is-black select.is-focused,html.theme--documenter-dark .select.is-black select:active,html.theme--documenter-dark .select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}html.theme--documenter-dark .select.is-light:not(:hover)::after{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select{border-color:#ecf0f1}html.theme--documenter-dark .select.is-light select:hover,html.theme--documenter-dark .select.is-light select.is-hovered{border-color:#dde4e6}html.theme--documenter-dark .select.is-light select:focus,html.theme--documenter-dark .select.is-light select.is-focused,html.theme--documenter-dark .select.is-light select:active,html.theme--documenter-dark .select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(236,240,241,0.25)}html.theme--documenter-dark .select.is-dark:not(:hover)::after,html.theme--documenter-dark .content kbd.select:not(:hover)::after{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select,html.theme--documenter-dark .content kbd.select select{border-color:#282f2f}html.theme--documenter-dark .select.is-dark select:hover,html.theme--documenter-dark .content kbd.select select:hover,html.theme--documenter-dark .select.is-dark select.is-hovered,html.theme--documenter-dark .content kbd.select select.is-hovered{border-color:#1d2122}html.theme--documenter-dark .select.is-dark select:focus,html.theme--documenter-dark .content kbd.select select:focus,html.theme--documenter-dark .select.is-dark select.is-focused,html.theme--documenter-dark .content kbd.select select.is-focused,html.theme--documenter-dark .select.is-dark select:active,html.theme--documenter-dark .content kbd.select select:active,html.theme--documenter-dark .select.is-dark select.is-active,html.theme--documenter-dark .content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(40,47,47,0.25)}html.theme--documenter-dark .select.is-primary:not(:hover)::after,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select{border-color:#375a7f}html.theme--documenter-dark .select.is-primary select:hover,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:hover,html.theme--documenter-dark .select.is-primary select.is-hovered,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#2f4d6d}html.theme--documenter-dark .select.is-primary select:focus,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:focus,html.theme--documenter-dark .select.is-primary select.is-focused,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-focused,html.theme--documenter-dark .select.is-primary select:active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select:active,html.theme--documenter-dark .select.is-primary select.is-active,html.theme--documenter-dark .docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(55,90,127,0.25)}html.theme--documenter-dark .select.is-link:not(:hover)::after{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select{border-color:#1abc9c}html.theme--documenter-dark .select.is-link select:hover,html.theme--documenter-dark .select.is-link select.is-hovered{border-color:#17a689}html.theme--documenter-dark .select.is-link select:focus,html.theme--documenter-dark .select.is-link select.is-focused,html.theme--documenter-dark .select.is-link select:active,html.theme--documenter-dark .select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(26,188,156,0.25)}html.theme--documenter-dark .select.is-info:not(:hover)::after{border-color:#3c5dcd}html.theme--documenter-dark .select.is-info select{border-color:#3c5dcd}html.theme--documenter-dark .select.is-info select:hover,html.theme--documenter-dark .select.is-info select.is-hovered{border-color:#3151bf}html.theme--documenter-dark .select.is-info select:focus,html.theme--documenter-dark .select.is-info select.is-focused,html.theme--documenter-dark .select.is-info select:active,html.theme--documenter-dark .select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}html.theme--documenter-dark .select.is-success:not(:hover)::after{border-color:#259a12}html.theme--documenter-dark .select.is-success select{border-color:#259a12}html.theme--documenter-dark .select.is-success select:hover,html.theme--documenter-dark .select.is-success select.is-hovered{border-color:#20830f}html.theme--documenter-dark .select.is-success select:focus,html.theme--documenter-dark .select.is-success select.is-focused,html.theme--documenter-dark .select.is-success select:active,html.theme--documenter-dark .select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}html.theme--documenter-dark .select.is-warning:not(:hover)::after{border-color:#f4c72f}html.theme--documenter-dark .select.is-warning select{border-color:#f4c72f}html.theme--documenter-dark .select.is-warning select:hover,html.theme--documenter-dark .select.is-warning select.is-hovered{border-color:#f3c017}html.theme--documenter-dark .select.is-warning select:focus,html.theme--documenter-dark .select.is-warning select.is-focused,html.theme--documenter-dark .select.is-warning select:active,html.theme--documenter-dark .select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(244,199,47,0.25)}html.theme--documenter-dark .select.is-danger:not(:hover)::after{border-color:#cb3c33}html.theme--documenter-dark .select.is-danger select{border-color:#cb3c33}html.theme--documenter-dark .select.is-danger select:hover,html.theme--documenter-dark .select.is-danger select.is-hovered{border-color:#b7362e}html.theme--documenter-dark .select.is-danger select:focus,html.theme--documenter-dark .select.is-danger select.is-focused,html.theme--documenter-dark .select.is-danger select:active,html.theme--documenter-dark .select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}html.theme--documenter-dark .select.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.select{border-radius:3px;font-size:.75rem}html.theme--documenter-dark .select.is-medium{font-size:1.25rem}html.theme--documenter-dark .select.is-large{font-size:1.5rem}html.theme--documenter-dark .select.is-disabled::after{border-color:#fff !important;opacity:0.5}html.theme--documenter-dark .select.is-fullwidth{width:100%}html.theme--documenter-dark .select.is-fullwidth select{width:100%}html.theme--documenter-dark .select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}html.theme--documenter-dark .select.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .select.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .select.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}html.theme--documenter-dark .file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:hover .file-cta,html.theme--documenter-dark .file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-white:focus .file-cta,html.theme--documenter-dark .file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}html.theme--documenter-dark .file.is-white:active .file-cta,html.theme--documenter-dark .file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}html.theme--documenter-dark .file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:hover .file-cta,html.theme--documenter-dark .file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-black:focus .file-cta,html.theme--documenter-dark .file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}html.theme--documenter-dark .file.is-black:active .file-cta,html.theme--documenter-dark .file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-light .file-cta{background-color:#ecf0f1;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:hover .file-cta,html.theme--documenter-dark .file.is-light.is-hovered .file-cta{background-color:#e5eaec;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:focus .file-cta,html.theme--documenter-dark .file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(236,240,241,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-light:active .file-cta,html.theme--documenter-dark .file.is-light.is-active .file-cta{background-color:#dde4e6;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-dark .file-cta,html.theme--documenter-dark .content kbd.file .file-cta{background-color:#282f2f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:hover .file-cta,html.theme--documenter-dark .content kbd.file:hover .file-cta,html.theme--documenter-dark .file.is-dark.is-hovered .file-cta,html.theme--documenter-dark .content kbd.file.is-hovered .file-cta{background-color:#232829;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-dark:focus .file-cta,html.theme--documenter-dark .content kbd.file:focus .file-cta,html.theme--documenter-dark .file.is-dark.is-focused .file-cta,html.theme--documenter-dark .content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(40,47,47,0.25);color:#fff}html.theme--documenter-dark .file.is-dark:active .file-cta,html.theme--documenter-dark .content kbd.file:active .file-cta,html.theme--documenter-dark .file.is-dark.is-active .file-cta,html.theme--documenter-dark .content kbd.file.is-active .file-cta{background-color:#1d2122;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink .file-cta{background-color:#375a7f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:hover .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:hover .file-cta,html.theme--documenter-dark .file.is-primary.is-hovered .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#335476;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-primary:focus .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:focus .file-cta,html.theme--documenter-dark .file.is-primary.is-focused .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(55,90,127,0.25);color:#fff}html.theme--documenter-dark .file.is-primary:active .file-cta,html.theme--documenter-dark .docstring>section>a.file.docs-sourcelink:active .file-cta,html.theme--documenter-dark .file.is-primary.is-active .file-cta,html.theme--documenter-dark .docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#2f4d6d;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link .file-cta{background-color:#1abc9c;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:hover .file-cta,html.theme--documenter-dark .file.is-link.is-hovered .file-cta{background-color:#18b193;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-link:focus .file-cta,html.theme--documenter-dark .file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(26,188,156,0.25);color:#fff}html.theme--documenter-dark .file.is-link:active .file-cta,html.theme--documenter-dark .file.is-link.is-active .file-cta{background-color:#17a689;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info .file-cta{background-color:#3c5dcd;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:hover .file-cta,html.theme--documenter-dark .file.is-info.is-hovered .file-cta{background-color:#3355c9;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-info:focus .file-cta,html.theme--documenter-dark .file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(60,93,205,0.25);color:#fff}html.theme--documenter-dark .file.is-info:active .file-cta,html.theme--documenter-dark .file.is-info.is-active .file-cta{background-color:#3151bf;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success .file-cta{background-color:#259a12;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:hover .file-cta,html.theme--documenter-dark .file.is-success.is-hovered .file-cta{background-color:#228f11;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-success:focus .file-cta,html.theme--documenter-dark .file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(37,154,18,0.25);color:#fff}html.theme--documenter-dark .file.is-success:active .file-cta,html.theme--documenter-dark .file.is-success.is-active .file-cta{background-color:#20830f;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-warning .file-cta{background-color:#f4c72f;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:hover .file-cta,html.theme--documenter-dark .file.is-warning.is-hovered .file-cta{background-color:#f3c423;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:focus .file-cta,html.theme--documenter-dark .file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(244,199,47,0.25);color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-warning:active .file-cta,html.theme--documenter-dark .file.is-warning.is-active .file-cta{background-color:#f3c017;border-color:transparent;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .file.is-danger .file-cta{background-color:#cb3c33;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:hover .file-cta,html.theme--documenter-dark .file.is-danger.is-hovered .file-cta{background-color:#c13930;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-danger:focus .file-cta,html.theme--documenter-dark .file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(203,60,51,0.25);color:#fff}html.theme--documenter-dark .file.is-danger:active .file-cta,html.theme--documenter-dark .file.is-danger.is-active .file-cta{background-color:#b7362e;border-color:transparent;color:#fff}html.theme--documenter-dark .file.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}html.theme--documenter-dark .file.is-normal{font-size:1rem}html.theme--documenter-dark .file.is-medium{font-size:1.25rem}html.theme--documenter-dark .file.is-medium .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-large{font-size:1.5rem}html.theme--documenter-dark .file.is-large .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .file.has-name.is-empty .file-cta{border-radius:.4em}html.theme--documenter-dark .file.has-name.is-empty .file-name{display:none}html.theme--documenter-dark .file.is-boxed .file-label{flex-direction:column}html.theme--documenter-dark .file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}html.theme--documenter-dark .file.is-boxed .file-name{border-width:0 1px 1px}html.theme--documenter-dark .file.is-boxed .file-icon{height:1.5em;width:1.5em}html.theme--documenter-dark .file.is-boxed .file-icon .fa{font-size:21px}html.theme--documenter-dark .file.is-boxed.is-small .file-icon .fa,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}html.theme--documenter-dark .file.is-boxed.is-medium .file-icon .fa{font-size:28px}html.theme--documenter-dark .file.is-boxed.is-large .file-icon .fa{font-size:35px}html.theme--documenter-dark .file.is-boxed.has-name .file-cta{border-radius:.4em .4em 0 0}html.theme--documenter-dark .file.is-boxed.has-name .file-name{border-radius:0 0 .4em .4em;border-width:0 1px 1px}html.theme--documenter-dark .file.is-centered{justify-content:center}html.theme--documenter-dark .file.is-fullwidth .file-label{width:100%}html.theme--documenter-dark .file.is-fullwidth .file-name{flex-grow:1;max-width:none}html.theme--documenter-dark .file.is-right{justify-content:flex-end}html.theme--documenter-dark .file.is-right .file-cta{border-radius:0 .4em .4em 0}html.theme--documenter-dark .file.is-right .file-name{border-radius:.4em 0 0 .4em;border-width:1px 0 1px 1px;order:-1}html.theme--documenter-dark .file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}html.theme--documenter-dark .file-label:hover .file-cta{background-color:#232829;color:#f2f2f2}html.theme--documenter-dark .file-label:hover .file-name{border-color:#596668}html.theme--documenter-dark .file-label:active .file-cta{background-color:#1d2122;color:#f2f2f2}html.theme--documenter-dark .file-label:active .file-name{border-color:#535f61}html.theme--documenter-dark .file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}html.theme--documenter-dark .file-cta,html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-radius:.4em;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}html.theme--documenter-dark .file-cta{background-color:#282f2f;color:#fff}html.theme--documenter-dark .file-name{border-color:#5e6d6f;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}html.theme--documenter-dark .file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}html.theme--documenter-dark .file-icon .fa{font-size:14px}html.theme--documenter-dark .label{color:#f2f2f2;display:block;font-size:1rem;font-weight:700}html.theme--documenter-dark .label:not(:last-child){margin-bottom:0.5em}html.theme--documenter-dark .label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}html.theme--documenter-dark .label.is-medium{font-size:1.25rem}html.theme--documenter-dark .label.is-large{font-size:1.5rem}html.theme--documenter-dark .help{display:block;font-size:.75rem;margin-top:0.25rem}html.theme--documenter-dark .help.is-white{color:#fff}html.theme--documenter-dark .help.is-black{color:#0a0a0a}html.theme--documenter-dark .help.is-light{color:#ecf0f1}html.theme--documenter-dark .help.is-dark,html.theme--documenter-dark .content kbd.help{color:#282f2f}html.theme--documenter-dark .help.is-primary,html.theme--documenter-dark .docstring>section>a.help.docs-sourcelink{color:#375a7f}html.theme--documenter-dark .help.is-link{color:#1abc9c}html.theme--documenter-dark .help.is-info{color:#3c5dcd}html.theme--documenter-dark .help.is-success{color:#259a12}html.theme--documenter-dark .help.is-warning{color:#f4c72f}html.theme--documenter-dark .help.is-danger{color:#cb3c33}html.theme--documenter-dark .field:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.has-addons{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.has-addons .control:not(:last-child){margin-right:-1px}html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .button,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .button,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,html.theme--documenter-dark .field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]),html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]){z-index:3}html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .button.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .button:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .button.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark #documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):focus:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-focused:not([disabled]):hover,html.theme--documenter-dark .field.has-addons .control .select select:not([disabled]):active:hover,html.theme--documenter-dark .field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}html.theme--documenter-dark .field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.has-addons.has-addons-centered{justify-content:center}html.theme--documenter-dark .field.has-addons.has-addons-right{justify-content:flex-end}html.theme--documenter-dark .field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .field.is-grouped{display:flex;justify-content:flex-start}html.theme--documenter-dark .field.is-grouped>.control{flex-shrink:0}html.theme--documenter-dark .field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .field.is-grouped.is-grouped-centered{justify-content:center}html.theme--documenter-dark .field.is-grouped.is-grouped-right{justify-content:flex-end}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline{flex-wrap:wrap}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:last-child,html.theme--documenter-dark .field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}html.theme--documenter-dark .field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field.is-horizontal{display:flex}}html.theme--documenter-dark .field-label .label{font-size:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}html.theme--documenter-dark .field-label.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-normal{padding-top:0.375em}html.theme--documenter-dark .field-label.is-medium{font-size:1.25rem;padding-top:0.375em}html.theme--documenter-dark .field-label.is-large{font-size:1.5rem;padding-top:0.375em}}html.theme--documenter-dark .field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{html.theme--documenter-dark .field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}html.theme--documenter-dark .field-body .field{margin-bottom:0}html.theme--documenter-dark .field-body>.field{flex-shrink:1}html.theme--documenter-dark .field-body>.field:not(.is-narrow){flex-grow:1}html.theme--documenter-dark .field-body>.field:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}html.theme--documenter-dark .control.has-icons-left .input:focus~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-left .select:focus~.icon,html.theme--documenter-dark .control.has-icons-right .input:focus~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,html.theme--documenter-dark .control.has-icons-right .select:focus~.icon{color:#282f2f}html.theme--documenter-dark .control.has-icons-left .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-small~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-small~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-small~.icon{font-size:.75rem}html.theme--documenter-dark .control.has-icons-left .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}html.theme--documenter-dark .control.has-icons-left .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-left .select.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,html.theme--documenter-dark .control.has-icons-right .select.is-large~.icon{font-size:1.5rem}html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon{color:#5e6d6f;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}html.theme--documenter-dark .control.has-icons-left .input,html.theme--documenter-dark .control.has-icons-left #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-left form.docs-search>input,html.theme--documenter-dark .control.has-icons-left .select select{padding-left:2.5em}html.theme--documenter-dark .control.has-icons-left .icon.is-left{left:0}html.theme--documenter-dark .control.has-icons-right .input,html.theme--documenter-dark .control.has-icons-right #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-icons-right form.docs-search>input,html.theme--documenter-dark .control.has-icons-right .select select{padding-right:2.5em}html.theme--documenter-dark .control.has-icons-right .icon.is-right{right:0}html.theme--documenter-dark .control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}html.theme--documenter-dark .control.is-loading.is-small:after,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}html.theme--documenter-dark .control.is-loading.is-medium:after{font-size:1.25rem}html.theme--documenter-dark .control.is-loading.is-large:after{font-size:1.5rem}html.theme--documenter-dark .breadcrumb{font-size:1rem;white-space:nowrap}html.theme--documenter-dark .breadcrumb a{align-items:center;color:#1abc9c;display:flex;justify-content:center;padding:0 .75em}html.theme--documenter-dark .breadcrumb a:hover{color:#1dd2af}html.theme--documenter-dark .breadcrumb li{align-items:center;display:flex}html.theme--documenter-dark .breadcrumb li:first-child a{padding-left:0}html.theme--documenter-dark .breadcrumb li.is-active a{color:#f2f2f2;cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb li+li::before{color:#8c9b9d;content:"\0002f"}html.theme--documenter-dark .breadcrumb ul,html.theme--documenter-dark .breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}html.theme--documenter-dark .breadcrumb .icon:first-child{margin-right:.5em}html.theme--documenter-dark .breadcrumb .icon:last-child{margin-left:.5em}html.theme--documenter-dark .breadcrumb.is-centered ol,html.theme--documenter-dark .breadcrumb.is-centered ul{justify-content:center}html.theme--documenter-dark .breadcrumb.is-right ol,html.theme--documenter-dark .breadcrumb.is-right ul{justify-content:flex-end}html.theme--documenter-dark .breadcrumb.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}html.theme--documenter-dark .breadcrumb.is-medium{font-size:1.25rem}html.theme--documenter-dark .breadcrumb.is-large{font-size:1.5rem}html.theme--documenter-dark .breadcrumb.has-arrow-separator li+li::before{content:"\02192"}html.theme--documenter-dark .breadcrumb.has-bullet-separator li+li::before{content:"\02022"}html.theme--documenter-dark .breadcrumb.has-dot-separator li+li::before{content:"\000b7"}html.theme--documenter-dark .breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}html.theme--documenter-dark .card{background-color:#fff;border-radius:.25rem;box-shadow:#171717;color:#fff;max-width:100%;position:relative}html.theme--documenter-dark .card-footer:first-child,html.theme--documenter-dark .card-content:first-child,html.theme--documenter-dark .card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-footer:last-child,html.theme--documenter-dark .card-content:last-child,html.theme--documenter-dark .card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}html.theme--documenter-dark .card-header-title{align-items:center;color:#f2f2f2;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}html.theme--documenter-dark .card-header-title.is-centered{justify-content:center}html.theme--documenter-dark .card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}html.theme--documenter-dark .card-image{display:block;position:relative}html.theme--documenter-dark .card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}html.theme--documenter-dark .card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}html.theme--documenter-dark .card-content{background-color:rgba(0,0,0,0);padding:1.5rem}html.theme--documenter-dark .card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}html.theme--documenter-dark .card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}html.theme--documenter-dark .card-footer-item:not(:last-child){border-right:1px solid #ededed}html.theme--documenter-dark .card .media:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .dropdown{display:inline-flex;position:relative;vertical-align:top}html.theme--documenter-dark .dropdown.is-active .dropdown-menu,html.theme--documenter-dark .dropdown.is-hoverable:hover .dropdown-menu{display:block}html.theme--documenter-dark .dropdown.is-right .dropdown-menu{left:auto;right:0}html.theme--documenter-dark .dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}html.theme--documenter-dark .dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .dropdown-content{background-color:#282f2f;border-radius:.4em;box-shadow:#171717;padding-bottom:.5rem;padding-top:.5rem}html.theme--documenter-dark .dropdown-item{color:#fff;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}html.theme--documenter-dark a.dropdown-item,html.theme--documenter-dark button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}html.theme--documenter-dark a.dropdown-item:hover,html.theme--documenter-dark button.dropdown-item:hover{background-color:#282f2f;color:#0a0a0a}html.theme--documenter-dark a.dropdown-item.is-active,html.theme--documenter-dark button.dropdown-item.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}html.theme--documenter-dark .level{align-items:center;justify-content:space-between}html.theme--documenter-dark .level code{border-radius:.4em}html.theme--documenter-dark .level img{display:inline-block;vertical-align:top}html.theme--documenter-dark .level.is-mobile{display:flex}html.theme--documenter-dark .level.is-mobile .level-left,html.theme--documenter-dark .level.is-mobile .level-right{display:flex}html.theme--documenter-dark .level.is-mobile .level-left+.level-right{margin-top:0}html.theme--documenter-dark .level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}html.theme--documenter-dark .level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level{display:flex}html.theme--documenter-dark .level>.level-item:not(.is-narrow){flex-grow:1}}html.theme--documenter-dark .level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}html.theme--documenter-dark .level-item .title,html.theme--documenter-dark .level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){html.theme--documenter-dark .level-item:not(:last-child){margin-bottom:.75rem}}html.theme--documenter-dark .level-left,html.theme--documenter-dark .level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .level-left .level-item.is-flexible,html.theme--documenter-dark .level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left .level-item:not(:last-child),html.theme--documenter-dark .level-right .level-item:not(:last-child){margin-right:.75rem}}html.theme--documenter-dark .level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){html.theme--documenter-dark .level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-left{display:flex}}html.theme--documenter-dark .level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{html.theme--documenter-dark .level-right{display:flex}}html.theme--documenter-dark .media{align-items:flex-start;display:flex;text-align:inherit}html.theme--documenter-dark .media .content:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .media .media{border-top:1px solid rgba(94,109,111,0.5);display:flex;padding-top:.75rem}html.theme--documenter-dark .media .media .content:not(:last-child),html.theme--documenter-dark .media .media .control:not(:last-child){margin-bottom:.5rem}html.theme--documenter-dark .media .media .media{padding-top:.5rem}html.theme--documenter-dark .media .media .media+.media{margin-top:.5rem}html.theme--documenter-dark .media+.media{border-top:1px solid rgba(94,109,111,0.5);margin-top:1rem;padding-top:1rem}html.theme--documenter-dark .media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}html.theme--documenter-dark .media-left,html.theme--documenter-dark .media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}html.theme--documenter-dark .media-left{margin-right:1rem}html.theme--documenter-dark .media-right{margin-left:1rem}html.theme--documenter-dark .media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){html.theme--documenter-dark .media-content{overflow-x:auto}}html.theme--documenter-dark .menu{font-size:1rem}html.theme--documenter-dark .menu.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}html.theme--documenter-dark .menu.is-medium{font-size:1.25rem}html.theme--documenter-dark .menu.is-large{font-size:1.5rem}html.theme--documenter-dark .menu-list{line-height:1.25}html.theme--documenter-dark .menu-list a{border-radius:3px;color:#fff;display:block;padding:0.5em 0.75em}html.theme--documenter-dark .menu-list a:hover{background-color:#282f2f;color:#f2f2f2}html.theme--documenter-dark .menu-list a.is-active{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .menu-list li ul{border-left:1px solid #5e6d6f;margin:.75em;padding-left:.75em}html.theme--documenter-dark .menu-label{color:#fff;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}html.theme--documenter-dark .menu-label:not(:first-child){margin-top:1em}html.theme--documenter-dark .menu-label:not(:last-child){margin-bottom:1em}html.theme--documenter-dark .message{background-color:#282f2f;border-radius:.4em;font-size:1rem}html.theme--documenter-dark .message strong{color:currentColor}html.theme--documenter-dark .message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}html.theme--documenter-dark .message.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}html.theme--documenter-dark .message.is-medium{font-size:1.25rem}html.theme--documenter-dark .message.is-large{font-size:1.5rem}html.theme--documenter-dark .message.is-white{background-color:#fff}html.theme--documenter-dark .message.is-white .message-header{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .message.is-white .message-body{border-color:#fff}html.theme--documenter-dark .message.is-black{background-color:#fafafa}html.theme--documenter-dark .message.is-black .message-header{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .message.is-black .message-body{border-color:#0a0a0a}html.theme--documenter-dark .message.is-light{background-color:#f9fafb}html.theme--documenter-dark .message.is-light .message-header{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-light .message-body{border-color:#ecf0f1}html.theme--documenter-dark .message.is-dark,html.theme--documenter-dark .content kbd.message{background-color:#f9fafa}html.theme--documenter-dark .message.is-dark .message-header,html.theme--documenter-dark .content kbd.message .message-header{background-color:#282f2f;color:#fff}html.theme--documenter-dark .message.is-dark .message-body,html.theme--documenter-dark .content kbd.message .message-body{border-color:#282f2f}html.theme--documenter-dark .message.is-primary,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink{background-color:#f1f5f9}html.theme--documenter-dark .message.is-primary .message-header,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-header{background-color:#375a7f;color:#fff}html.theme--documenter-dark .message.is-primary .message-body,html.theme--documenter-dark .docstring>section>a.message.docs-sourcelink .message-body{border-color:#375a7f;color:#4d7eb2}html.theme--documenter-dark .message.is-link{background-color:#edfdf9}html.theme--documenter-dark .message.is-link .message-header{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .message.is-link .message-body{border-color:#1abc9c;color:#15987e}html.theme--documenter-dark .message.is-info{background-color:#eff2fb}html.theme--documenter-dark .message.is-info .message-header{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .message.is-info .message-body{border-color:#3c5dcd;color:#3253c3}html.theme--documenter-dark .message.is-success{background-color:#effded}html.theme--documenter-dark .message.is-success .message-header{background-color:#259a12;color:#fff}html.theme--documenter-dark .message.is-success .message-body{border-color:#259a12;color:#2ec016}html.theme--documenter-dark .message.is-warning{background-color:#fefaec}html.theme--documenter-dark .message.is-warning .message-header{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .message.is-warning .message-body{border-color:#f4c72f;color:#8c6e07}html.theme--documenter-dark .message.is-danger{background-color:#fbefef}html.theme--documenter-dark .message.is-danger .message-header{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .message.is-danger .message-body{border-color:#cb3c33;color:#c03930}html.theme--documenter-dark .message-header{align-items:center;background-color:#fff;border-radius:.4em .4em 0 0;color:rgba(0,0,0,0.7);display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}html.theme--documenter-dark .message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}html.theme--documenter-dark .message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}html.theme--documenter-dark .message-body{border-color:#5e6d6f;border-radius:.4em;border-style:solid;border-width:0 0 0 4px;color:#fff;padding:1.25em 1.5em}html.theme--documenter-dark .message-body code,html.theme--documenter-dark .message-body pre{background-color:#fff}html.theme--documenter-dark .message-body pre code{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}html.theme--documenter-dark .modal.is-active{display:flex}html.theme--documenter-dark .modal-background{background-color:rgba(10,10,10,0.86)}html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){html.theme--documenter-dark .modal-content,html.theme--documenter-dark .modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}html.theme--documenter-dark .modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}html.theme--documenter-dark .modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}html.theme--documenter-dark .modal-card-head,html.theme--documenter-dark .modal-card-foot{align-items:center;background-color:#282f2f;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}html.theme--documenter-dark .modal-card-head{border-bottom:1px solid #5e6d6f;border-top-left-radius:8px;border-top-right-radius:8px}html.theme--documenter-dark .modal-card-title{color:#f2f2f2;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}html.theme--documenter-dark .modal-card-foot{border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid #5e6d6f}html.theme--documenter-dark .modal-card-foot .button:not(:last-child){margin-right:.5em}html.theme--documenter-dark .modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}html.theme--documenter-dark .navbar{background-color:#375a7f;min-height:4rem;position:relative;z-index:30}html.theme--documenter-dark .navbar.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-white .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-white .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}html.theme--documenter-dark .navbar.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-black .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-black .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}html.theme--documenter-dark .navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}html.theme--documenter-dark .navbar.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-light .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-light .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-dark,html.theme--documenter-dark .content kbd.navbar{background-color:#282f2f;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-brand .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-burger,html.theme--documenter-dark .content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-dark .navbar-start>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-end>.navbar-item,html.theme--documenter-dark .content kbd.navbar .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:focus,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link:hover,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-start .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-dark .navbar-end .navbar-link::after,html.theme--documenter-dark .content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#1d2122;color:#fff}html.theme--documenter-dark .navbar.is-dark .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#282f2f;color:#fff}}html.theme--documenter-dark .navbar.is-primary,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-brand .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-burger,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-primary .navbar-start>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-end>.navbar-item,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:focus,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-start .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-primary .navbar-end .navbar-link::after,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#375a7f;color:#fff}}html.theme--documenter-dark .navbar.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-link .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-link .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#17a689;color:#fff}html.theme--documenter-dark .navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c;color:#fff}}html.theme--documenter-dark .navbar.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-info .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-info .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3151bf;color:#fff}html.theme--documenter-dark .navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3c5dcd;color:#fff}}html.theme--documenter-dark .navbar.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-success .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-success .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20830f;color:#fff}html.theme--documenter-dark .navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#259a12;color:#fff}}html.theme--documenter-dark .navbar.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-warning .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#f4c72f;color:rgba(0,0,0,0.7)}}html.theme--documenter-dark .navbar.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar.is-danger .navbar-start>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-end>.navbar-item,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link{color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end>a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:focus,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link:hover,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-start .navbar-link::after,html.theme--documenter-dark .navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#b7362e;color:#fff}html.theme--documenter-dark .navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#cb3c33;color:#fff}}html.theme--documenter-dark .navbar>.container{align-items:stretch;display:flex;min-height:4rem;width:100%}html.theme--documenter-dark .navbar.has-shadow{box-shadow:0 2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-bottom,html.theme--documenter-dark .navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #282f2f}html.theme--documenter-dark .navbar.is-fixed-top{top:0}html.theme--documenter-dark html.has-navbar-fixed-top,html.theme--documenter-dark body.has-navbar-fixed-top{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom,html.theme--documenter-dark body.has-navbar-fixed-bottom{padding-bottom:4rem}html.theme--documenter-dark .navbar-brand,html.theme--documenter-dark .navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:4rem}html.theme--documenter-dark .navbar-brand a.navbar-item:focus,html.theme--documenter-dark .navbar-brand a.navbar-item:hover{background-color:transparent}html.theme--documenter-dark .navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}html.theme--documenter-dark .navbar-burger{color:#fff;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:4rem;position:relative;width:4rem;margin-left:auto}html.theme--documenter-dark .navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}html.theme--documenter-dark .navbar-burger span:nth-child(1){top:calc(50% - 6px)}html.theme--documenter-dark .navbar-burger span:nth-child(2){top:calc(50% - 1px)}html.theme--documenter-dark .navbar-burger span:nth-child(3){top:calc(50% + 4px)}html.theme--documenter-dark .navbar-burger:hover{background-color:rgba(0,0,0,0.05)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(2){opacity:0}html.theme--documenter-dark .navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}html.theme--documenter-dark .navbar-menu{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{color:#fff;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}html.theme--documenter-dark .navbar-item .icon:only-child,html.theme--documenter-dark .navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}html.theme--documenter-dark a.navbar-item,html.theme--documenter-dark .navbar-link{cursor:pointer}html.theme--documenter-dark a.navbar-item:focus,html.theme--documenter-dark a.navbar-item:focus-within,html.theme--documenter-dark a.navbar-item:hover,html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link:focus,html.theme--documenter-dark .navbar-link:focus-within,html.theme--documenter-dark .navbar-link:hover,html.theme--documenter-dark .navbar-link.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-item{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .navbar-item img{max-height:1.75rem}html.theme--documenter-dark .navbar-item.has-dropdown{padding:0}html.theme--documenter-dark .navbar-item.is-expanded{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-item.is-tab{border-bottom:1px solid transparent;min-height:4rem;padding-bottom:calc(0.5rem - 1px)}html.theme--documenter-dark .navbar-item.is-tab:focus,html.theme--documenter-dark .navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c}html.theme--documenter-dark .navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#1abc9c;border-bottom-style:solid;border-bottom-width:3px;color:#1abc9c;padding-bottom:calc(0.5rem - 3px)}html.theme--documenter-dark .navbar-content{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .navbar-link:not(.is-arrowless){padding-right:2.5em}html.theme--documenter-dark .navbar-link:not(.is-arrowless)::after{border-color:#fff;margin-top:-0.375em;right:1.125em}html.theme--documenter-dark .navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}html.theme--documenter-dark .navbar-divider{background-color:rgba(0,0,0,0.2);border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar>.container{display:block}html.theme--documenter-dark .navbar-brand .navbar-item,html.theme--documenter-dark .navbar-tabs .navbar-item{align-items:center;display:flex}html.theme--documenter-dark .navbar-link::after{display:none}html.theme--documenter-dark .navbar-menu{background-color:#375a7f;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}html.theme--documenter-dark .navbar-menu.is-active{display:block}html.theme--documenter-dark .navbar.is-fixed-bottom-touch,html.theme--documenter-dark .navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-touch{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-touch{top:0}html.theme--documenter-dark .navbar.is-fixed-top .navbar-menu,html.theme--documenter-dark .navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 4rem);overflow:auto}html.theme--documenter-dark html.has-navbar-fixed-top-touch,html.theme--documenter-dark body.has-navbar-fixed-top-touch{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-touch,html.theme--documenter-dark body.has-navbar-fixed-bottom-touch{padding-bottom:4rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .navbar,html.theme--documenter-dark .navbar-menu,html.theme--documenter-dark .navbar-start,html.theme--documenter-dark .navbar-end{align-items:stretch;display:flex}html.theme--documenter-dark .navbar{min-height:4rem}html.theme--documenter-dark .navbar.is-spaced{padding:1rem 2rem}html.theme--documenter-dark .navbar.is-spaced .navbar-start,html.theme--documenter-dark .navbar.is-spaced .navbar-end{align-items:center}html.theme--documenter-dark .navbar.is-spaced a.navbar-item,html.theme--documenter-dark .navbar.is-spaced .navbar-link{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent a.navbar-item:hover,html.theme--documenter-dark .navbar.is-transparent a.navbar-item.is-active,html.theme--documenter-dark .navbar.is-transparent .navbar-link:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-link:hover,html.theme--documenter-dark .navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,html.theme--documenter-dark .navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}html.theme--documenter-dark .navbar-burger{display:none}html.theme--documenter-dark .navbar-item,html.theme--documenter-dark .navbar-link{align-items:center;display:flex}html.theme--documenter-dark .navbar-item.has-dropdown{align-items:stretch}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}html.theme--documenter-dark .navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:1px solid rgba(0,0,0,0.2);border-radius:8px 8px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown,html.theme--documenter-dark .navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}html.theme--documenter-dark .navbar-menu{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .navbar-start{justify-content:flex-start;margin-right:auto}html.theme--documenter-dark .navbar-end{justify-content:flex-end;margin-left:auto}html.theme--documenter-dark .navbar-dropdown{background-color:#375a7f;border-bottom-left-radius:8px;border-bottom-right-radius:8px;border-top:1px solid rgba(0,0,0,0.2);box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}html.theme--documenter-dark .navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}html.theme--documenter-dark .navbar-dropdown a.navbar-item{padding-right:3rem}html.theme--documenter-dark .navbar-dropdown a.navbar-item:focus,html.theme--documenter-dark .navbar-dropdown a.navbar-item:hover{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .navbar-dropdown a.navbar-item.is-active{background-color:rgba(0,0,0,0);color:#1abc9c}.navbar.is-spaced html.theme--documenter-dark .navbar-dropdown,html.theme--documenter-dark .navbar-dropdown.is-boxed{border-radius:8px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}html.theme--documenter-dark .navbar-dropdown.is-right{left:auto;right:0}html.theme--documenter-dark .navbar-divider{display:block}html.theme--documenter-dark .navbar>.container .navbar-brand,html.theme--documenter-dark .container>.navbar .navbar-brand{margin-left:-.75rem}html.theme--documenter-dark .navbar>.container .navbar-menu,html.theme--documenter-dark .container>.navbar .navbar-menu{margin-right:-.75rem}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop,html.theme--documenter-dark .navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop{bottom:0}html.theme--documenter-dark .navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}html.theme--documenter-dark .navbar.is-fixed-top-desktop{top:0}html.theme--documenter-dark html.has-navbar-fixed-top-desktop,html.theme--documenter-dark body.has-navbar-fixed-top-desktop{padding-top:4rem}html.theme--documenter-dark html.has-navbar-fixed-bottom-desktop,html.theme--documenter-dark body.has-navbar-fixed-bottom-desktop{padding-bottom:4rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-top,html.theme--documenter-dark body.has-spaced-navbar-fixed-top{padding-top:6rem}html.theme--documenter-dark html.has-spaced-navbar-fixed-bottom,html.theme--documenter-dark body.has-spaced-navbar-fixed-bottom{padding-bottom:6rem}html.theme--documenter-dark a.navbar-item.is-active,html.theme--documenter-dark .navbar-link.is-active{color:#1abc9c}html.theme--documenter-dark a.navbar-item.is-active:not(:focus):not(:hover),html.theme--documenter-dark .navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}html.theme--documenter-dark .navbar-item.has-dropdown:focus .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown:hover .navbar-link,html.theme--documenter-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:rgba(0,0,0,0)}}html.theme--documenter-dark .hero.is-fullheight-with-navbar{min-height:calc(100vh - 4rem)}html.theme--documenter-dark .pagination{font-size:1rem;margin:-.25rem}html.theme--documenter-dark .pagination.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}html.theme--documenter-dark .pagination.is-medium{font-size:1.25rem}html.theme--documenter-dark .pagination.is-large{font-size:1.5rem}html.theme--documenter-dark .pagination.is-rounded .pagination-previous,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,html.theme--documenter-dark .pagination.is-rounded .pagination-next,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}html.theme--documenter-dark .pagination.is-rounded .pagination-link,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}html.theme--documenter-dark .pagination,html.theme--documenter-dark .pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link{border-color:#5e6d6f;color:#1abc9c;min-width:2.5em}html.theme--documenter-dark .pagination-previous:hover,html.theme--documenter-dark .pagination-next:hover,html.theme--documenter-dark .pagination-link:hover{border-color:#8c9b9d;color:#1dd2af}html.theme--documenter-dark .pagination-previous:focus,html.theme--documenter-dark .pagination-next:focus,html.theme--documenter-dark .pagination-link:focus{border-color:#8c9b9d}html.theme--documenter-dark .pagination-previous:active,html.theme--documenter-dark .pagination-next:active,html.theme--documenter-dark .pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}html.theme--documenter-dark .pagination-previous[disabled],html.theme--documenter-dark .pagination-previous.is-disabled,html.theme--documenter-dark .pagination-next[disabled],html.theme--documenter-dark .pagination-next.is-disabled,html.theme--documenter-dark .pagination-link[disabled],html.theme--documenter-dark .pagination-link.is-disabled{background-color:#5e6d6f;border-color:#5e6d6f;box-shadow:none;color:#fff;opacity:0.5}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}html.theme--documenter-dark .pagination-link.is-current{background-color:#1abc9c;border-color:#1abc9c;color:#fff}html.theme--documenter-dark .pagination-ellipsis{color:#8c9b9d;pointer-events:none}html.theme--documenter-dark .pagination-list{flex-wrap:wrap}html.theme--documenter-dark .pagination-list li{list-style:none}@media screen and (max-width: 768px){html.theme--documenter-dark .pagination{flex-wrap:wrap}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-ellipsis{margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination-previous{order:2}html.theme--documenter-dark .pagination-next{order:3}html.theme--documenter-dark .pagination{justify-content:space-between;margin-bottom:0;margin-top:0}html.theme--documenter-dark .pagination.is-centered .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-centered .pagination-list{justify-content:center;order:2}html.theme--documenter-dark .pagination.is-centered .pagination-next{order:3}html.theme--documenter-dark .pagination.is-right .pagination-previous{order:1}html.theme--documenter-dark .pagination.is-right .pagination-next{order:2}html.theme--documenter-dark .pagination.is-right .pagination-list{justify-content:flex-end;order:3}}html.theme--documenter-dark .panel{border-radius:8px;box-shadow:#171717;font-size:1rem}html.theme--documenter-dark .panel:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}html.theme--documenter-dark .panel.is-white .panel-block.is-active .panel-icon{color:#fff}html.theme--documenter-dark .panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}html.theme--documenter-dark .panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}html.theme--documenter-dark .panel.is-light .panel-heading{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-light .panel-tabs a.is-active{border-bottom-color:#ecf0f1}html.theme--documenter-dark .panel.is-light .panel-block.is-active .panel-icon{color:#ecf0f1}html.theme--documenter-dark .panel.is-dark .panel-heading,html.theme--documenter-dark .content kbd.panel .panel-heading{background-color:#282f2f;color:#fff}html.theme--documenter-dark .panel.is-dark .panel-tabs a.is-active,html.theme--documenter-dark .content kbd.panel .panel-tabs a.is-active{border-bottom-color:#282f2f}html.theme--documenter-dark .panel.is-dark .panel-block.is-active .panel-icon,html.theme--documenter-dark .content kbd.panel .panel-block.is-active .panel-icon{color:#282f2f}html.theme--documenter-dark .panel.is-primary .panel-heading,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#375a7f;color:#fff}html.theme--documenter-dark .panel.is-primary .panel-tabs a.is-active,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#375a7f}html.theme--documenter-dark .panel.is-primary .panel-block.is-active .panel-icon,html.theme--documenter-dark .docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#375a7f}html.theme--documenter-dark .panel.is-link .panel-heading{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .panel.is-link .panel-tabs a.is-active{border-bottom-color:#1abc9c}html.theme--documenter-dark .panel.is-link .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel.is-info .panel-heading{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .panel.is-info .panel-tabs a.is-active{border-bottom-color:#3c5dcd}html.theme--documenter-dark .panel.is-info .panel-block.is-active .panel-icon{color:#3c5dcd}html.theme--documenter-dark .panel.is-success .panel-heading{background-color:#259a12;color:#fff}html.theme--documenter-dark .panel.is-success .panel-tabs a.is-active{border-bottom-color:#259a12}html.theme--documenter-dark .panel.is-success .panel-block.is-active .panel-icon{color:#259a12}html.theme--documenter-dark .panel.is-warning .panel-heading{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .panel.is-warning .panel-tabs a.is-active{border-bottom-color:#f4c72f}html.theme--documenter-dark .panel.is-warning .panel-block.is-active .panel-icon{color:#f4c72f}html.theme--documenter-dark .panel.is-danger .panel-heading{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .panel.is-danger .panel-tabs a.is-active{border-bottom-color:#cb3c33}html.theme--documenter-dark .panel.is-danger .panel-block.is-active .panel-icon{color:#cb3c33}html.theme--documenter-dark .panel-tabs:not(:last-child),html.theme--documenter-dark .panel-block:not(:last-child){border-bottom:1px solid #ededed}html.theme--documenter-dark .panel-heading{background-color:#343c3d;border-radius:8px 8px 0 0;color:#f2f2f2;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}html.theme--documenter-dark .panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}html.theme--documenter-dark .panel-tabs a{border-bottom:1px solid #5e6d6f;margin-bottom:-1px;padding:0.5em}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#343c3d;color:#17a689}html.theme--documenter-dark .panel-list a{color:#fff}html.theme--documenter-dark .panel-list a:hover{color:#1abc9c}html.theme--documenter-dark .panel-block{align-items:center;color:#f2f2f2;display:flex;justify-content:flex-start;padding:0.5em 0.75em}html.theme--documenter-dark .panel-block input[type="checkbox"]{margin-right:.75em}html.theme--documenter-dark .panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}html.theme--documenter-dark .panel-block.is-wrapped{flex-wrap:wrap}html.theme--documenter-dark .panel-block.is-active{border-left-color:#1abc9c;color:#17a689}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#1abc9c}html.theme--documenter-dark .panel-block:last-child{border-bottom-left-radius:8px;border-bottom-right-radius:8px}html.theme--documenter-dark a.panel-block,html.theme--documenter-dark label.panel-block{cursor:pointer}html.theme--documenter-dark a.panel-block:hover,html.theme--documenter-dark label.panel-block:hover{background-color:#282f2f}html.theme--documenter-dark .panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#fff;margin-right:.75em}html.theme--documenter-dark .panel-icon .fa{font-size:inherit;line-height:inherit}html.theme--documenter-dark .tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}html.theme--documenter-dark .tabs a{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;color:#fff;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}html.theme--documenter-dark .tabs a:hover{border-bottom-color:#f2f2f2;color:#f2f2f2}html.theme--documenter-dark .tabs li{display:block}html.theme--documenter-dark .tabs li.is-active a{border-bottom-color:#1abc9c;color:#1abc9c}html.theme--documenter-dark .tabs ul{align-items:center;border-bottom-color:#5e6d6f;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}html.theme--documenter-dark .tabs ul.is-left{padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}html.theme--documenter-dark .tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}html.theme--documenter-dark .tabs .icon:first-child{margin-right:.5em}html.theme--documenter-dark .tabs .icon:last-child{margin-left:.5em}html.theme--documenter-dark .tabs.is-centered ul{justify-content:center}html.theme--documenter-dark .tabs.is-right ul{justify-content:flex-end}html.theme--documenter-dark .tabs.is-boxed a{border:1px solid transparent;border-radius:.4em .4em 0 0}html.theme--documenter-dark .tabs.is-boxed a:hover{background-color:#282f2f;border-bottom-color:#5e6d6f}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#fff;border-color:#5e6d6f;border-bottom-color:rgba(0,0,0,0) !important}html.theme--documenter-dark .tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}html.theme--documenter-dark .tabs.is-toggle a{border-color:#5e6d6f;border-style:solid;border-width:1px;margin-bottom:0;position:relative}html.theme--documenter-dark .tabs.is-toggle a:hover{background-color:#282f2f;border-color:#8c9b9d;z-index:2}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .tabs.is-toggle li:first-child a{border-top-left-radius:.4em;border-bottom-left-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li:last-child a{border-top-right-radius:.4em;border-bottom-right-radius:.4em}html.theme--documenter-dark .tabs.is-toggle li.is-active a{background-color:#1abc9c;border-color:#1abc9c;color:#fff;z-index:1}html.theme--documenter-dark .tabs.is-toggle ul{border-bottom:none}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}html.theme--documenter-dark .tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}html.theme--documenter-dark .tabs.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}html.theme--documenter-dark .tabs.is-medium{font-size:1.25rem}html.theme--documenter-dark .tabs.is-large{font-size:1.5rem}html.theme--documenter-dark .column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>html.theme--documenter-dark .column.is-narrow{flex:none;width:unset}.columns.is-mobile>html.theme--documenter-dark .column.is-full{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-half{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-half{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>html.theme--documenter-dark .column.is-0{flex:none;width:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-0{margin-left:0%}.columns.is-mobile>html.theme--documenter-dark .column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-3{flex:none;width:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-3{margin-left:25%}.columns.is-mobile>html.theme--documenter-dark .column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-6{flex:none;width:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-6{margin-left:50%}.columns.is-mobile>html.theme--documenter-dark .column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-9{flex:none;width:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-9{margin-left:75%}.columns.is-mobile>html.theme--documenter-dark .column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>html.theme--documenter-dark .column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>html.theme--documenter-dark .column.is-12{flex:none;width:100%}.columns.is-mobile>html.theme--documenter-dark .column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){html.theme--documenter-dark .column.is-narrow-mobile{flex:none;width:unset}html.theme--documenter-dark .column.is-full-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-mobile{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-mobile{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-mobile{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-mobile{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-mobile{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-mobile{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-mobile{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-mobile{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-mobile{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-mobile{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-mobile{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-mobile{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-mobile{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-mobile{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-mobile{margin-left:80%}html.theme--documenter-dark .column.is-0-mobile{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-mobile{margin-left:0%}html.theme--documenter-dark .column.is-1-mobile{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-mobile{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-mobile{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-mobile{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-mobile{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-mobile{margin-left:25%}html.theme--documenter-dark .column.is-4-mobile{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-mobile{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-mobile{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-mobile{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-mobile{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-mobile{margin-left:50%}html.theme--documenter-dark .column.is-7-mobile{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-mobile{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-mobile{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-mobile{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-mobile{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-mobile{margin-left:75%}html.theme--documenter-dark .column.is-10-mobile{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-mobile{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-mobile{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-mobile{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-mobile{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .column.is-narrow,html.theme--documenter-dark .column.is-narrow-tablet{flex:none;width:unset}html.theme--documenter-dark .column.is-full,html.theme--documenter-dark .column.is-full-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters,html.theme--documenter-dark .column.is-three-quarters-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds,html.theme--documenter-dark .column.is-two-thirds-tablet{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half,html.theme--documenter-dark .column.is-half-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third,html.theme--documenter-dark .column.is-one-third-tablet{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter,html.theme--documenter-dark .column.is-one-quarter-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth,html.theme--documenter-dark .column.is-one-fifth-tablet{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths,html.theme--documenter-dark .column.is-two-fifths-tablet{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths,html.theme--documenter-dark .column.is-three-fifths-tablet{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths,html.theme--documenter-dark .column.is-four-fifths-tablet{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters,html.theme--documenter-dark .column.is-offset-three-quarters-tablet{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds,html.theme--documenter-dark .column.is-offset-two-thirds-tablet{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half,html.theme--documenter-dark .column.is-offset-half-tablet{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third,html.theme--documenter-dark .column.is-offset-one-third-tablet{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter,html.theme--documenter-dark .column.is-offset-one-quarter-tablet{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth,html.theme--documenter-dark .column.is-offset-one-fifth-tablet{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths,html.theme--documenter-dark .column.is-offset-two-fifths-tablet{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths,html.theme--documenter-dark .column.is-offset-three-fifths-tablet{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths,html.theme--documenter-dark .column.is-offset-four-fifths-tablet{margin-left:80%}html.theme--documenter-dark .column.is-0,html.theme--documenter-dark .column.is-0-tablet{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0,html.theme--documenter-dark .column.is-offset-0-tablet{margin-left:0%}html.theme--documenter-dark .column.is-1,html.theme--documenter-dark .column.is-1-tablet{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1,html.theme--documenter-dark .column.is-offset-1-tablet{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2,html.theme--documenter-dark .column.is-2-tablet{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2,html.theme--documenter-dark .column.is-offset-2-tablet{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3,html.theme--documenter-dark .column.is-3-tablet{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3,html.theme--documenter-dark .column.is-offset-3-tablet{margin-left:25%}html.theme--documenter-dark .column.is-4,html.theme--documenter-dark .column.is-4-tablet{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4,html.theme--documenter-dark .column.is-offset-4-tablet{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5,html.theme--documenter-dark .column.is-5-tablet{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5,html.theme--documenter-dark .column.is-offset-5-tablet{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6,html.theme--documenter-dark .column.is-6-tablet{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6,html.theme--documenter-dark .column.is-offset-6-tablet{margin-left:50%}html.theme--documenter-dark .column.is-7,html.theme--documenter-dark .column.is-7-tablet{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7,html.theme--documenter-dark .column.is-offset-7-tablet{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8,html.theme--documenter-dark .column.is-8-tablet{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8,html.theme--documenter-dark .column.is-offset-8-tablet{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9,html.theme--documenter-dark .column.is-9-tablet{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9,html.theme--documenter-dark .column.is-offset-9-tablet{margin-left:75%}html.theme--documenter-dark .column.is-10,html.theme--documenter-dark .column.is-10-tablet{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10,html.theme--documenter-dark .column.is-offset-10-tablet{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11,html.theme--documenter-dark .column.is-11-tablet{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11,html.theme--documenter-dark .column.is-offset-11-tablet{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12,html.theme--documenter-dark .column.is-12-tablet{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12,html.theme--documenter-dark .column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){html.theme--documenter-dark .column.is-narrow-touch{flex:none;width:unset}html.theme--documenter-dark .column.is-full-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-touch{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-touch{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-touch{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-touch{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-touch{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-touch{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-touch{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-touch{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-touch{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-touch{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-touch{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-touch{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-touch{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-touch{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-touch{margin-left:80%}html.theme--documenter-dark .column.is-0-touch{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-touch{margin-left:0%}html.theme--documenter-dark .column.is-1-touch{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-touch{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-touch{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-touch{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-touch{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-touch{margin-left:25%}html.theme--documenter-dark .column.is-4-touch{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-touch{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-touch{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-touch{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-touch{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-touch{margin-left:50%}html.theme--documenter-dark .column.is-7-touch{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-touch{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-touch{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-touch{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-touch{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-touch{margin-left:75%}html.theme--documenter-dark .column.is-10-touch{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-touch{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-touch{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-touch{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-touch{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){html.theme--documenter-dark .column.is-narrow-desktop{flex:none;width:unset}html.theme--documenter-dark .column.is-full-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-desktop{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-desktop{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-desktop{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-desktop{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-desktop{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-desktop{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-desktop{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-desktop{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-desktop{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-desktop{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-desktop{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-desktop{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-desktop{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-desktop{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-desktop{margin-left:80%}html.theme--documenter-dark .column.is-0-desktop{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-desktop{margin-left:0%}html.theme--documenter-dark .column.is-1-desktop{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-desktop{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-desktop{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-desktop{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-desktop{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-desktop{margin-left:25%}html.theme--documenter-dark .column.is-4-desktop{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-desktop{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-desktop{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-desktop{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-desktop{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-desktop{margin-left:50%}html.theme--documenter-dark .column.is-7-desktop{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-desktop{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-desktop{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-desktop{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-desktop{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-desktop{margin-left:75%}html.theme--documenter-dark .column.is-10-desktop{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-desktop{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-desktop{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-desktop{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-desktop{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){html.theme--documenter-dark .column.is-narrow-widescreen{flex:none;width:unset}html.theme--documenter-dark .column.is-full-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-widescreen{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-widescreen{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-widescreen{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-widescreen{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-widescreen{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-widescreen{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-widescreen{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-widescreen{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-widescreen{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-widescreen{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-widescreen{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-widescreen{margin-left:80%}html.theme--documenter-dark .column.is-0-widescreen{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-widescreen{margin-left:0%}html.theme--documenter-dark .column.is-1-widescreen{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-widescreen{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-widescreen{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-widescreen{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-widescreen{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-widescreen{margin-left:25%}html.theme--documenter-dark .column.is-4-widescreen{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-widescreen{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-widescreen{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-widescreen{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-widescreen{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-widescreen{margin-left:50%}html.theme--documenter-dark .column.is-7-widescreen{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-widescreen{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-widescreen{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-widescreen{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-widescreen{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-widescreen{margin-left:75%}html.theme--documenter-dark .column.is-10-widescreen{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-widescreen{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-widescreen{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-widescreen{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-widescreen{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){html.theme--documenter-dark .column.is-narrow-fullhd{flex:none;width:unset}html.theme--documenter-dark .column.is-full-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-three-quarters-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-two-thirds-fullhd{flex:none;width:66.6666%}html.theme--documenter-dark .column.is-half-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-one-third-fullhd{flex:none;width:33.3333%}html.theme--documenter-dark .column.is-one-quarter-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-one-fifth-fullhd{flex:none;width:20%}html.theme--documenter-dark .column.is-two-fifths-fullhd{flex:none;width:40%}html.theme--documenter-dark .column.is-three-fifths-fullhd{flex:none;width:60%}html.theme--documenter-dark .column.is-four-fifths-fullhd{flex:none;width:80%}html.theme--documenter-dark .column.is-offset-three-quarters-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-offset-two-thirds-fullhd{margin-left:66.6666%}html.theme--documenter-dark .column.is-offset-half-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-offset-one-third-fullhd{margin-left:33.3333%}html.theme--documenter-dark .column.is-offset-one-quarter-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-offset-one-fifth-fullhd{margin-left:20%}html.theme--documenter-dark .column.is-offset-two-fifths-fullhd{margin-left:40%}html.theme--documenter-dark .column.is-offset-three-fifths-fullhd{margin-left:60%}html.theme--documenter-dark .column.is-offset-four-fifths-fullhd{margin-left:80%}html.theme--documenter-dark .column.is-0-fullhd{flex:none;width:0%}html.theme--documenter-dark .column.is-offset-0-fullhd{margin-left:0%}html.theme--documenter-dark .column.is-1-fullhd{flex:none;width:8.33333337%}html.theme--documenter-dark .column.is-offset-1-fullhd{margin-left:8.33333337%}html.theme--documenter-dark .column.is-2-fullhd{flex:none;width:16.66666674%}html.theme--documenter-dark .column.is-offset-2-fullhd{margin-left:16.66666674%}html.theme--documenter-dark .column.is-3-fullhd{flex:none;width:25%}html.theme--documenter-dark .column.is-offset-3-fullhd{margin-left:25%}html.theme--documenter-dark .column.is-4-fullhd{flex:none;width:33.33333337%}html.theme--documenter-dark .column.is-offset-4-fullhd{margin-left:33.33333337%}html.theme--documenter-dark .column.is-5-fullhd{flex:none;width:41.66666674%}html.theme--documenter-dark .column.is-offset-5-fullhd{margin-left:41.66666674%}html.theme--documenter-dark .column.is-6-fullhd{flex:none;width:50%}html.theme--documenter-dark .column.is-offset-6-fullhd{margin-left:50%}html.theme--documenter-dark .column.is-7-fullhd{flex:none;width:58.33333337%}html.theme--documenter-dark .column.is-offset-7-fullhd{margin-left:58.33333337%}html.theme--documenter-dark .column.is-8-fullhd{flex:none;width:66.66666674%}html.theme--documenter-dark .column.is-offset-8-fullhd{margin-left:66.66666674%}html.theme--documenter-dark .column.is-9-fullhd{flex:none;width:75%}html.theme--documenter-dark .column.is-offset-9-fullhd{margin-left:75%}html.theme--documenter-dark .column.is-10-fullhd{flex:none;width:83.33333337%}html.theme--documenter-dark .column.is-offset-10-fullhd{margin-left:83.33333337%}html.theme--documenter-dark .column.is-11-fullhd{flex:none;width:91.66666674%}html.theme--documenter-dark .column.is-offset-11-fullhd{margin-left:91.66666674%}html.theme--documenter-dark .column.is-12-fullhd{flex:none;width:100%}html.theme--documenter-dark .column.is-offset-12-fullhd{margin-left:100%}}html.theme--documenter-dark .columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .columns:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}html.theme--documenter-dark .columns.is-centered{justify-content:center}html.theme--documenter-dark .columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}html.theme--documenter-dark .columns.is-gapless>.column{margin:0;padding:0 !important}html.theme--documenter-dark .columns.is-gapless:not(:last-child){margin-bottom:1.5rem}html.theme--documenter-dark .columns.is-gapless:last-child{margin-bottom:0}html.theme--documenter-dark .columns.is-mobile{display:flex}html.theme--documenter-dark .columns.is-multiline{flex-wrap:wrap}html.theme--documenter-dark .columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-desktop{display:flex}}html.theme--documenter-dark .columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}html.theme--documenter-dark .columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}html.theme--documenter-dark .columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-0-fullhd{--columnGap: 0rem}}html.theme--documenter-dark .columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-1-fullhd{--columnGap: .25rem}}html.theme--documenter-dark .columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-2-fullhd{--columnGap: .5rem}}html.theme--documenter-dark .columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-3-fullhd{--columnGap: .75rem}}html.theme--documenter-dark .columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-4-fullhd{--columnGap: 1rem}}html.theme--documenter-dark .columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}html.theme--documenter-dark .columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}html.theme--documenter-dark .columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}html.theme--documenter-dark .columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){html.theme--documenter-dark .columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark .columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){html.theme--documenter-dark .columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){html.theme--documenter-dark .columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){html.theme--documenter-dark .columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){html.theme--documenter-dark .columns.is-variable.is-8-fullhd{--columnGap: 2rem}}html.theme--documenter-dark .tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}html.theme--documenter-dark .tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}html.theme--documenter-dark .tile.is-ancestor:last-child{margin-bottom:-.75rem}html.theme--documenter-dark .tile.is-ancestor:not(:last-child){margin-bottom:.75rem}html.theme--documenter-dark .tile.is-child{margin:0 !important}html.theme--documenter-dark .tile.is-parent{padding:.75rem}html.theme--documenter-dark .tile.is-vertical{flex-direction:column}html.theme--documenter-dark .tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{html.theme--documenter-dark .tile:not(.is-child){display:flex}html.theme--documenter-dark .tile.is-1{flex:none;width:8.33333337%}html.theme--documenter-dark .tile.is-2{flex:none;width:16.66666674%}html.theme--documenter-dark .tile.is-3{flex:none;width:25%}html.theme--documenter-dark .tile.is-4{flex:none;width:33.33333337%}html.theme--documenter-dark .tile.is-5{flex:none;width:41.66666674%}html.theme--documenter-dark .tile.is-6{flex:none;width:50%}html.theme--documenter-dark .tile.is-7{flex:none;width:58.33333337%}html.theme--documenter-dark .tile.is-8{flex:none;width:66.66666674%}html.theme--documenter-dark .tile.is-9{flex:none;width:75%}html.theme--documenter-dark .tile.is-10{flex:none;width:83.33333337%}html.theme--documenter-dark .tile.is-11{flex:none;width:91.66666674%}html.theme--documenter-dark .tile.is-12{flex:none;width:100%}}html.theme--documenter-dark .hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}html.theme--documenter-dark .hero .navbar{background:none}html.theme--documenter-dark .hero .tabs ul{border-bottom:none}html.theme--documenter-dark .hero.is-white{background-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-white strong{color:inherit}html.theme--documenter-dark .hero.is-white .title{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .subtitle{color:rgba(10,10,10,0.9)}html.theme--documenter-dark .hero.is-white .subtitle a:not(.button),html.theme--documenter-dark .hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-white .navbar-menu{background-color:#fff}}html.theme--documenter-dark .hero.is-white .navbar-item,html.theme--documenter-dark .hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}html.theme--documenter-dark .hero.is-white a.navbar-item:hover,html.theme--documenter-dark .hero.is-white a.navbar-item.is-active,html.theme--documenter-dark .hero.is-white .navbar-link:hover,html.theme--documenter-dark .hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}html.theme--documenter-dark .hero.is-white .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a{color:#0a0a0a}html.theme--documenter-dark .hero.is-white .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}html.theme--documenter-dark .hero.is-black{background-color:#0a0a0a;color:#fff}html.theme--documenter-dark .hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-black strong{color:inherit}html.theme--documenter-dark .hero.is-black .title{color:#fff}html.theme--documenter-dark .hero.is-black .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-black .subtitle a:not(.button),html.theme--documenter-dark .hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-black .navbar-menu{background-color:#0a0a0a}}html.theme--documenter-dark .hero.is-black .navbar-item,html.theme--documenter-dark .hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-black a.navbar-item:hover,html.theme--documenter-dark .hero.is-black a.navbar-item.is-active,html.theme--documenter-dark .hero.is-black .navbar-link:hover,html.theme--documenter-dark .hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}html.theme--documenter-dark .hero.is-black .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-black .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-black .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}html.theme--documenter-dark .hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}html.theme--documenter-dark .hero.is-light{background-color:#ecf0f1;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-light strong{color:inherit}html.theme--documenter-dark .hero.is-light .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-light .subtitle a:not(.button),html.theme--documenter-dark .hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-light .navbar-menu{background-color:#ecf0f1}}html.theme--documenter-dark .hero.is-light .navbar-item,html.theme--documenter-dark .hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light a.navbar-item:hover,html.theme--documenter-dark .hero.is-light a.navbar-item.is-active,html.theme--documenter-dark .hero.is-light .navbar-link:hover,html.theme--documenter-dark .hero.is-light .navbar-link.is-active{background-color:#dde4e6;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-light .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-light .tabs li.is-active a{color:#ecf0f1 !important;opacity:1}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ecf0f1}html.theme--documenter-dark .hero.is-light.is-bold{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #cadfe0 0%, #ecf0f1 71%, #fafbfc 100%)}}html.theme--documenter-dark .hero.is-dark,html.theme--documenter-dark .content kbd.hero{background-color:#282f2f;color:#fff}html.theme--documenter-dark .hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-dark strong,html.theme--documenter-dark .content kbd.hero strong{color:inherit}html.theme--documenter-dark .hero.is-dark .title,html.theme--documenter-dark .content kbd.hero .title{color:#fff}html.theme--documenter-dark .hero.is-dark .subtitle,html.theme--documenter-dark .content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-dark .subtitle a:not(.button),html.theme--documenter-dark .content kbd.hero .subtitle a:not(.button),html.theme--documenter-dark .hero.is-dark .subtitle strong,html.theme--documenter-dark .content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-dark .navbar-menu,html.theme--documenter-dark .content kbd.hero .navbar-menu{background-color:#282f2f}}html.theme--documenter-dark .hero.is-dark .navbar-item,html.theme--documenter-dark .content kbd.hero .navbar-item,html.theme--documenter-dark .hero.is-dark .navbar-link,html.theme--documenter-dark .content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-dark a.navbar-item:hover,html.theme--documenter-dark .content kbd.hero a.navbar-item:hover,html.theme--documenter-dark .hero.is-dark a.navbar-item.is-active,html.theme--documenter-dark .content kbd.hero a.navbar-item.is-active,html.theme--documenter-dark .hero.is-dark .navbar-link:hover,html.theme--documenter-dark .content kbd.hero .navbar-link:hover,html.theme--documenter-dark .hero.is-dark .navbar-link.is-active,html.theme--documenter-dark .content kbd.hero .navbar-link.is-active{background-color:#1d2122;color:#fff}html.theme--documenter-dark .hero.is-dark .tabs a,html.theme--documenter-dark .content kbd.hero .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-dark .tabs a:hover,html.theme--documenter-dark .content kbd.hero .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-dark .tabs li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs li.is-active a{color:#282f2f !important;opacity:1}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle a:hover,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a,html.theme--documenter-dark .content kbd.hero .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#282f2f}html.theme--documenter-dark .hero.is-dark.is-bold,html.theme--documenter-dark .content kbd.hero.is-bold{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-dark.is-bold .navbar-menu,html.theme--documenter-dark .content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0f1615 0%, #282f2f 71%, #313c40 100%)}}html.theme--documenter-dark .hero.is-primary,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink{background-color:#375a7f;color:#fff}html.theme--documenter-dark .hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-primary strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink strong{color:inherit}html.theme--documenter-dark .hero.is-primary .title,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .title{color:#fff}html.theme--documenter-dark .hero.is-primary .subtitle,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-primary .subtitle a:not(.button),html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),html.theme--documenter-dark .hero.is-primary .subtitle strong,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-primary .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#375a7f}}html.theme--documenter-dark .hero.is-primary .navbar-item,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-item,html.theme--documenter-dark .hero.is-primary .navbar-link,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-primary a.navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,html.theme--documenter-dark .hero.is-primary a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,html.theme--documenter-dark .hero.is-primary .navbar-link:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link:hover,html.theme--documenter-dark .hero.is-primary .navbar-link.is-active,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#2f4d6d;color:#fff}html.theme--documenter-dark .hero.is-primary .tabs a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-primary .tabs a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-primary .tabs li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#375a7f !important;opacity:1}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle a:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#375a7f}html.theme--documenter-dark .hero.is-primary.is-bold,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-primary.is-bold .navbar-menu,html.theme--documenter-dark .docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #214b62 0%, #375a7f 71%, #3a5796 100%)}}html.theme--documenter-dark .hero.is-link{background-color:#1abc9c;color:#fff}html.theme--documenter-dark .hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-link strong{color:inherit}html.theme--documenter-dark .hero.is-link .title{color:#fff}html.theme--documenter-dark .hero.is-link .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-link .subtitle a:not(.button),html.theme--documenter-dark .hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-link .navbar-menu{background-color:#1abc9c}}html.theme--documenter-dark .hero.is-link .navbar-item,html.theme--documenter-dark .hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-link a.navbar-item:hover,html.theme--documenter-dark .hero.is-link a.navbar-item.is-active,html.theme--documenter-dark .hero.is-link .navbar-link:hover,html.theme--documenter-dark .hero.is-link .navbar-link.is-active{background-color:#17a689;color:#fff}html.theme--documenter-dark .hero.is-link .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-link .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-link .tabs li.is-active a{color:#1abc9c !important;opacity:1}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-link .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#1abc9c}html.theme--documenter-dark .hero.is-link.is-bold{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #0c9764 0%, #1abc9c 71%, #17d8d2 100%)}}html.theme--documenter-dark .hero.is-info{background-color:#3c5dcd;color:#fff}html.theme--documenter-dark .hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-info strong{color:inherit}html.theme--documenter-dark .hero.is-info .title{color:#fff}html.theme--documenter-dark .hero.is-info .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-info .subtitle a:not(.button),html.theme--documenter-dark .hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-info .navbar-menu{background-color:#3c5dcd}}html.theme--documenter-dark .hero.is-info .navbar-item,html.theme--documenter-dark .hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-info a.navbar-item:hover,html.theme--documenter-dark .hero.is-info a.navbar-item.is-active,html.theme--documenter-dark .hero.is-info .navbar-link:hover,html.theme--documenter-dark .hero.is-info .navbar-link.is-active{background-color:#3151bf;color:#fff}html.theme--documenter-dark .hero.is-info .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-info .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-info .tabs li.is-active a{color:#3c5dcd !important;opacity:1}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-info .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3c5dcd}html.theme--documenter-dark .hero.is-info.is-bold{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}}html.theme--documenter-dark .hero.is-success{background-color:#259a12;color:#fff}html.theme--documenter-dark .hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-success strong{color:inherit}html.theme--documenter-dark .hero.is-success .title{color:#fff}html.theme--documenter-dark .hero.is-success .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-success .subtitle a:not(.button),html.theme--documenter-dark .hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-success .navbar-menu{background-color:#259a12}}html.theme--documenter-dark .hero.is-success .navbar-item,html.theme--documenter-dark .hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-success a.navbar-item:hover,html.theme--documenter-dark .hero.is-success a.navbar-item.is-active,html.theme--documenter-dark .hero.is-success .navbar-link:hover,html.theme--documenter-dark .hero.is-success .navbar-link.is-active{background-color:#20830f;color:#fff}html.theme--documenter-dark .hero.is-success .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-success .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-success .tabs li.is-active a{color:#259a12 !important;opacity:1}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-success .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#259a12}html.theme--documenter-dark .hero.is-success.is-bold{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}}html.theme--documenter-dark .hero.is-warning{background-color:#f4c72f;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-warning strong{color:inherit}html.theme--documenter-dark .hero.is-warning .title{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}html.theme--documenter-dark .hero.is-warning .subtitle a:not(.button),html.theme--documenter-dark .hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-warning .navbar-menu{background-color:#f4c72f}}html.theme--documenter-dark .hero.is-warning .navbar-item,html.theme--documenter-dark .hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning a.navbar-item:hover,html.theme--documenter-dark .hero.is-warning a.navbar-item.is-active,html.theme--documenter-dark .hero.is-warning .navbar-link:hover,html.theme--documenter-dark .hero.is-warning .navbar-link.is-active{background-color:#f3c017;color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}html.theme--documenter-dark .hero.is-warning .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-warning .tabs li.is-active a{color:#f4c72f !important;opacity:1}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f4c72f}html.theme--documenter-dark .hero.is-warning.is-bold{background-image:linear-gradient(141deg, #f09100 0%, #f4c72f 71%, #faef42 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #f09100 0%, #f4c72f 71%, #faef42 100%)}}html.theme--documenter-dark .hero.is-danger{background-color:#cb3c33;color:#fff}html.theme--documenter-dark .hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),html.theme--documenter-dark .hero.is-danger strong{color:inherit}html.theme--documenter-dark .hero.is-danger .title{color:#fff}html.theme--documenter-dark .hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}html.theme--documenter-dark .hero.is-danger .subtitle a:not(.button),html.theme--documenter-dark .hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){html.theme--documenter-dark .hero.is-danger .navbar-menu{background-color:#cb3c33}}html.theme--documenter-dark .hero.is-danger .navbar-item,html.theme--documenter-dark .hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}html.theme--documenter-dark .hero.is-danger a.navbar-item:hover,html.theme--documenter-dark .hero.is-danger a.navbar-item.is-active,html.theme--documenter-dark .hero.is-danger .navbar-link:hover,html.theme--documenter-dark .hero.is-danger .navbar-link.is-active{background-color:#b7362e;color:#fff}html.theme--documenter-dark .hero.is-danger .tabs a{color:#fff;opacity:0.9}html.theme--documenter-dark .hero.is-danger .tabs a:hover{opacity:1}html.theme--documenter-dark .hero.is-danger .tabs li.is-active a{color:#cb3c33 !important;opacity:1}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a{color:#fff}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-boxed li.is-active a:hover,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a,html.theme--documenter-dark .hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#cb3c33}html.theme--documenter-dark .hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}@media screen and (max-width: 768px){html.theme--documenter-dark .hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}}html.theme--documenter-dark .hero.is-small .hero-body,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero.is-large .hero-body{padding:18rem 6rem}}html.theme--documenter-dark .hero.is-halfheight .hero-body,html.theme--documenter-dark .hero.is-fullheight .hero-body,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}html.theme--documenter-dark .hero.is-halfheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight .hero-body>.container,html.theme--documenter-dark .hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}html.theme--documenter-dark .hero.is-halfheight{min-height:50vh}html.theme--documenter-dark .hero.is-fullheight{min-height:100vh}html.theme--documenter-dark .hero-video{overflow:hidden}html.theme--documenter-dark .hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}html.theme--documenter-dark .hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-video{display:none}}html.theme--documenter-dark .hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){html.theme--documenter-dark .hero-buttons .button{display:flex}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-buttons{display:flex;justify-content:center}html.theme--documenter-dark .hero-buttons .button:not(:last-child){margin-right:1.5rem}}html.theme--documenter-dark .hero-head,html.theme--documenter-dark .hero-foot{flex-grow:0;flex-shrink:0}html.theme--documenter-dark .hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{html.theme--documenter-dark .hero-body{padding:3rem 3rem}}html.theme--documenter-dark .section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){html.theme--documenter-dark .section{padding:3rem 3rem}html.theme--documenter-dark .section.is-medium{padding:9rem 4.5rem}html.theme--documenter-dark .section.is-large{padding:18rem 6rem}}html.theme--documenter-dark .footer{background-color:#282f2f;padding:3rem 1.5rem 6rem}html.theme--documenter-dark hr{height:1px}html.theme--documenter-dark h6{text-transform:uppercase;letter-spacing:0.5px}html.theme--documenter-dark .hero{background-color:#343c3d}html.theme--documenter-dark a{transition:all 200ms ease}html.theme--documenter-dark .button{transition:all 200ms ease;border-width:1px;color:#fff}html.theme--documenter-dark .button.is-active,html.theme--documenter-dark .button.is-focused,html.theme--documenter-dark .button:active,html.theme--documenter-dark .button:focus{box-shadow:0 0 0 2px rgba(140,155,157,0.5)}html.theme--documenter-dark .button.is-white.is-hovered,html.theme--documenter-dark .button.is-white:hover{background-color:#fff}html.theme--documenter-dark .button.is-white.is-active,html.theme--documenter-dark .button.is-white.is-focused,html.theme--documenter-dark .button.is-white:active,html.theme--documenter-dark .button.is-white:focus{border-color:#fff;box-shadow:0 0 0 2px rgba(255,255,255,0.5)}html.theme--documenter-dark .button.is-black.is-hovered,html.theme--documenter-dark .button.is-black:hover{background-color:#1d1d1d}html.theme--documenter-dark .button.is-black.is-active,html.theme--documenter-dark .button.is-black.is-focused,html.theme--documenter-dark .button.is-black:active,html.theme--documenter-dark .button.is-black:focus{border-color:#0a0a0a;box-shadow:0 0 0 2px rgba(10,10,10,0.5)}html.theme--documenter-dark .button.is-light.is-hovered,html.theme--documenter-dark .button.is-light:hover{background-color:#fff}html.theme--documenter-dark .button.is-light.is-active,html.theme--documenter-dark .button.is-light.is-focused,html.theme--documenter-dark .button.is-light:active,html.theme--documenter-dark .button.is-light:focus{border-color:#ecf0f1;box-shadow:0 0 0 2px rgba(236,240,241,0.5)}html.theme--documenter-dark .button.is-dark.is-hovered,html.theme--documenter-dark .content kbd.button.is-hovered,html.theme--documenter-dark .button.is-dark:hover,html.theme--documenter-dark .content kbd.button:hover{background-color:#3a4344}html.theme--documenter-dark .button.is-dark.is-active,html.theme--documenter-dark .content kbd.button.is-active,html.theme--documenter-dark .button.is-dark.is-focused,html.theme--documenter-dark .content kbd.button.is-focused,html.theme--documenter-dark .button.is-dark:active,html.theme--documenter-dark .content kbd.button:active,html.theme--documenter-dark .button.is-dark:focus,html.theme--documenter-dark .content kbd.button:focus{border-color:#282f2f;box-shadow:0 0 0 2px rgba(40,47,47,0.5)}html.theme--documenter-dark .button.is-primary.is-hovered,html.theme--documenter-dark .docstring>section>a.button.is-hovered.docs-sourcelink,html.theme--documenter-dark .button.is-primary:hover,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:hover{background-color:#436d9a}html.theme--documenter-dark .button.is-primary.is-active,html.theme--documenter-dark .docstring>section>a.button.is-active.docs-sourcelink,html.theme--documenter-dark .button.is-primary.is-focused,html.theme--documenter-dark .docstring>section>a.button.is-focused.docs-sourcelink,html.theme--documenter-dark .button.is-primary:active,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:active,html.theme--documenter-dark .button.is-primary:focus,html.theme--documenter-dark .docstring>section>a.button.docs-sourcelink:focus{border-color:#375a7f;box-shadow:0 0 0 2px rgba(55,90,127,0.5)}html.theme--documenter-dark .button.is-link.is-hovered,html.theme--documenter-dark .button.is-link:hover{background-color:#1fdeb8}html.theme--documenter-dark .button.is-link.is-active,html.theme--documenter-dark .button.is-link.is-focused,html.theme--documenter-dark .button.is-link:active,html.theme--documenter-dark .button.is-link:focus{border-color:#1abc9c;box-shadow:0 0 0 2px rgba(26,188,156,0.5)}html.theme--documenter-dark .button.is-info.is-hovered,html.theme--documenter-dark .button.is-info:hover{background-color:#5a76d5}html.theme--documenter-dark .button.is-info.is-active,html.theme--documenter-dark .button.is-info.is-focused,html.theme--documenter-dark .button.is-info:active,html.theme--documenter-dark .button.is-info:focus{border-color:#3c5dcd;box-shadow:0 0 0 2px rgba(60,93,205,0.5)}html.theme--documenter-dark .button.is-success.is-hovered,html.theme--documenter-dark .button.is-success:hover{background-color:#2dbc16}html.theme--documenter-dark .button.is-success.is-active,html.theme--documenter-dark .button.is-success.is-focused,html.theme--documenter-dark .button.is-success:active,html.theme--documenter-dark .button.is-success:focus{border-color:#259a12;box-shadow:0 0 0 2px rgba(37,154,18,0.5)}html.theme--documenter-dark .button.is-warning.is-hovered,html.theme--documenter-dark .button.is-warning:hover{background-color:#f6d153}html.theme--documenter-dark .button.is-warning.is-active,html.theme--documenter-dark .button.is-warning.is-focused,html.theme--documenter-dark .button.is-warning:active,html.theme--documenter-dark .button.is-warning:focus{border-color:#f4c72f;box-shadow:0 0 0 2px rgba(244,199,47,0.5)}html.theme--documenter-dark .button.is-danger.is-hovered,html.theme--documenter-dark .button.is-danger:hover{background-color:#d35951}html.theme--documenter-dark .button.is-danger.is-active,html.theme--documenter-dark .button.is-danger.is-focused,html.theme--documenter-dark .button.is-danger:active,html.theme--documenter-dark .button.is-danger:focus{border-color:#cb3c33;box-shadow:0 0 0 2px rgba(203,60,51,0.5)}html.theme--documenter-dark .label{color:#dbdee0}html.theme--documenter-dark .button,html.theme--documenter-dark .control.has-icons-left .icon,html.theme--documenter-dark .control.has-icons-right .icon,html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .pagination-ellipsis,html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous,html.theme--documenter-dark .select,html.theme--documenter-dark .select select,html.theme--documenter-dark .textarea{height:2.5em}html.theme--documenter-dark .input,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark .textarea{transition:all 200ms ease;box-shadow:none;border-width:1px;padding-left:1em;padding-right:1em}html.theme--documenter-dark .select:after,html.theme--documenter-dark .select select{border-width:1px}html.theme--documenter-dark .control.has-addons .button,html.theme--documenter-dark .control.has-addons .input,html.theme--documenter-dark .control.has-addons #documenter .docs-sidebar form.docs-search>input,html.theme--documenter-dark #documenter .docs-sidebar .control.has-addons form.docs-search>input,html.theme--documenter-dark .control.has-addons .select{margin-right:-1px}html.theme--documenter-dark .notification{background-color:#343c3d}html.theme--documenter-dark .card{box-shadow:none;border:1px solid #343c3d;background-color:#282f2f;border-radius:.4em}html.theme--documenter-dark .card .card-image img{border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-header{box-shadow:none;background-color:rgba(18,18,18,0.2);border-radius:.4em .4em 0 0}html.theme--documenter-dark .card .card-footer{background-color:rgba(18,18,18,0.2)}html.theme--documenter-dark .card .card-footer,html.theme--documenter-dark .card .card-footer-item{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .notification.is-white a:not(.button){color:#0a0a0a;text-decoration:underline}html.theme--documenter-dark .notification.is-black a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-light a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-dark a:not(.button),html.theme--documenter-dark .content kbd.notification a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-primary a:not(.button),html.theme--documenter-dark .docstring>section>a.notification.docs-sourcelink a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-link a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-info a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-success a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .notification.is-warning a:not(.button){color:rgba(0,0,0,0.7);text-decoration:underline}html.theme--documenter-dark .notification.is-danger a:not(.button){color:#fff;text-decoration:underline}html.theme--documenter-dark .tag,html.theme--documenter-dark .content kbd,html.theme--documenter-dark .docstring>section>a.docs-sourcelink{border-radius:.4em}html.theme--documenter-dark .menu-list a{transition:all 300ms ease}html.theme--documenter-dark .modal-card-body{background-color:#282f2f}html.theme--documenter-dark .modal-card-foot,html.theme--documenter-dark .modal-card-head{border-color:#343c3d}html.theme--documenter-dark .message-header{font-weight:700;background-color:#343c3d;color:#fff}html.theme--documenter-dark .message-body{border-width:1px;border-color:#343c3d}html.theme--documenter-dark .navbar{border-radius:.4em}html.theme--documenter-dark .navbar.is-transparent{background:none}html.theme--documenter-dark .navbar.is-primary .navbar-dropdown a.navbar-item.is-active,html.theme--documenter-dark .docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#1abc9c}@media screen and (max-width: 1055px){html.theme--documenter-dark .navbar .navbar-menu{background-color:#375a7f;border-radius:0 0 .4em .4em}}html.theme--documenter-dark .hero .navbar,html.theme--documenter-dark body>.navbar{border-radius:0}html.theme--documenter-dark .pagination-link,html.theme--documenter-dark .pagination-next,html.theme--documenter-dark .pagination-previous{border-width:1px}html.theme--documenter-dark .panel-block,html.theme--documenter-dark .panel-heading,html.theme--documenter-dark .panel-tabs{border-width:1px}html.theme--documenter-dark .panel-block:first-child,html.theme--documenter-dark .panel-heading:first-child,html.theme--documenter-dark .panel-tabs:first-child{border-top-width:1px}html.theme--documenter-dark .panel-heading{font-weight:700}html.theme--documenter-dark .panel-tabs a{border-width:1px;margin-bottom:-1px}html.theme--documenter-dark .panel-tabs a.is-active{border-bottom-color:#17a689}html.theme--documenter-dark .panel-block:hover{color:#1dd2af}html.theme--documenter-dark .panel-block:hover .panel-icon{color:#1dd2af}html.theme--documenter-dark .panel-block.is-active .panel-icon{color:#17a689}html.theme--documenter-dark .tabs a{border-bottom-width:1px;margin-bottom:-1px}html.theme--documenter-dark .tabs ul{border-bottom-width:1px}html.theme--documenter-dark .tabs.is-boxed a{border-width:1px}html.theme--documenter-dark .tabs.is-boxed li.is-active a{background-color:#1f2424}html.theme--documenter-dark .tabs.is-toggle li a{border-width:1px;margin-bottom:0}html.theme--documenter-dark .tabs.is-toggle li+li{margin-left:-1px}html.theme--documenter-dark .hero.is-white .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-black .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-light .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-dark .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .content kbd.hero .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-primary .navbar .navbar-dropdown .navbar-item:hover,html.theme--documenter-dark .docstring>section>a.hero.docs-sourcelink .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-link .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-info .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-success .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-warning .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark .hero.is-danger .navbar .navbar-dropdown .navbar-item:hover{background-color:rgba(0,0,0,0)}html.theme--documenter-dark h1 .docs-heading-anchor,html.theme--documenter-dark h1 .docs-heading-anchor:hover,html.theme--documenter-dark h1 .docs-heading-anchor:visited,html.theme--documenter-dark h2 .docs-heading-anchor,html.theme--documenter-dark h2 .docs-heading-anchor:hover,html.theme--documenter-dark h2 .docs-heading-anchor:visited,html.theme--documenter-dark h3 .docs-heading-anchor,html.theme--documenter-dark h3 .docs-heading-anchor:hover,html.theme--documenter-dark h3 .docs-heading-anchor:visited,html.theme--documenter-dark h4 .docs-heading-anchor,html.theme--documenter-dark h4 .docs-heading-anchor:hover,html.theme--documenter-dark h4 .docs-heading-anchor:visited,html.theme--documenter-dark h5 .docs-heading-anchor,html.theme--documenter-dark h5 .docs-heading-anchor:hover,html.theme--documenter-dark h5 .docs-heading-anchor:visited,html.theme--documenter-dark h6 .docs-heading-anchor,html.theme--documenter-dark h6 .docs-heading-anchor:hover,html.theme--documenter-dark h6 .docs-heading-anchor:visited{color:#f2f2f2}html.theme--documenter-dark h1 .docs-heading-anchor-permalink,html.theme--documenter-dark h2 .docs-heading-anchor-permalink,html.theme--documenter-dark h3 .docs-heading-anchor-permalink,html.theme--documenter-dark h4 .docs-heading-anchor-permalink,html.theme--documenter-dark h5 .docs-heading-anchor-permalink,html.theme--documenter-dark h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}html.theme--documenter-dark h1 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h2 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h3 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h4 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h5 .docs-heading-anchor-permalink::before,html.theme--documenter-dark h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}html.theme--documenter-dark h1:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h2:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h3:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h4:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h5:hover .docs-heading-anchor-permalink,html.theme--documenter-dark h6:hover .docs-heading-anchor-permalink{visibility:visible}html.theme--documenter-dark .docs-light-only{display:none !important}html.theme--documenter-dark pre{position:relative;overflow:hidden}html.theme--documenter-dark pre code,html.theme--documenter-dark pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}html.theme--documenter-dark pre code:first-of-type,html.theme--documenter-dark pre code.hljs:first-of-type{padding-top:0.5rem !important}html.theme--documenter-dark pre code:last-of-type,html.theme--documenter-dark pre code.hljs:last-of-type{padding-bottom:0.5rem !important}html.theme--documenter-dark pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#fff;cursor:pointer;text-align:center}html.theme--documenter-dark pre .copy-button:focus,html.theme--documenter-dark pre .copy-button:hover{opacity:1;background:rgba(255,255,255,0.1);color:#1abc9c}html.theme--documenter-dark pre .copy-button.success{color:#259a12;opacity:1}html.theme--documenter-dark pre .copy-button.error{color:#cb3c33;opacity:1}html.theme--documenter-dark pre:hover .copy-button{opacity:1}html.theme--documenter-dark .admonition{background-color:#282f2f;border-style:solid;border-width:2px;border-color:#dbdee0;border-radius:4px;font-size:1rem}html.theme--documenter-dark .admonition strong{color:currentColor}html.theme--documenter-dark .admonition.is-small,html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}html.theme--documenter-dark .admonition.is-medium{font-size:1.25rem}html.theme--documenter-dark .admonition.is-large{font-size:1.5rem}html.theme--documenter-dark .admonition.is-default{background-color:#282f2f;border-color:#dbdee0}html.theme--documenter-dark .admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#dbdee0}html.theme--documenter-dark .admonition.is-default>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-info{background-color:#282f2f;border-color:#3c5dcd}html.theme--documenter-dark .admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#3c5dcd}html.theme--documenter-dark .admonition.is-info>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-success{background-color:#282f2f;border-color:#259a12}html.theme--documenter-dark .admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#259a12}html.theme--documenter-dark .admonition.is-success>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-warning{background-color:#282f2f;border-color:#f4c72f}html.theme--documenter-dark .admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#f4c72f}html.theme--documenter-dark .admonition.is-warning>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-danger{background-color:#282f2f;border-color:#cb3c33}html.theme--documenter-dark .admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#cb3c33}html.theme--documenter-dark .admonition.is-danger>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-compat{background-color:#282f2f;border-color:#3489da}html.theme--documenter-dark .admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#3489da}html.theme--documenter-dark .admonition.is-compat>.admonition-body{color:#fff}html.theme--documenter-dark .admonition.is-todo{background-color:#282f2f;border-color:#9558b2}html.theme--documenter-dark .admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#9558b2}html.theme--documenter-dark .admonition.is-todo>.admonition-body{color:#fff}html.theme--documenter-dark .admonition-header{color:#dbdee0;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}html.theme--documenter-dark .admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}html.theme--documenter-dark details.admonition.is-details>.admonition-header{list-style:none}html.theme--documenter-dark details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}html.theme--documenter-dark details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}html.theme--documenter-dark .admonition-body{color:#fff;padding:0.5rem .75rem}html.theme--documenter-dark .admonition-body pre{background-color:#282f2f}html.theme--documenter-dark .admonition-body code{background-color:rgba(255,255,255,0.05)}html.theme--documenter-dark .docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #5e6d6f;border-radius:4px;box-shadow:none;max-width:100%}html.theme--documenter-dark .docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#282f2f;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #5e6d6f;overflow:auto}html.theme--documenter-dark .docstring>header code{background-color:transparent}html.theme--documenter-dark .docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}html.theme--documenter-dark .docstring>header .docstring-binding{margin-right:0.3em}html.theme--documenter-dark .docstring>header .docstring-category{margin-left:0.3em}html.theme--documenter-dark .docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .docstring>section:last-child{border-bottom:none}html.theme--documenter-dark .docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}html.theme--documenter-dark .docstring>section>a.docs-sourcelink:focus{opacity:1 !important}html.theme--documenter-dark .docstring:hover>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}html.theme--documenter-dark .docstring>section:hover a.docs-sourcelink{opacity:1}html.theme--documenter-dark .documenter-example-output{background-color:#1f2424}html.theme--documenter-dark .outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#282f2f;color:#fff;border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}html.theme--documenter-dark .outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}html.theme--documenter-dark .outdated-warning-overlay a{color:#1abc9c}html.theme--documenter-dark .outdated-warning-overlay a:hover{color:#1dd2af}html.theme--documenter-dark .content pre{border:2px solid #5e6d6f;border-radius:4px}html.theme--documenter-dark .content code{font-weight:inherit}html.theme--documenter-dark .content a code{color:#1abc9c}html.theme--documenter-dark .content a:hover code{color:#1dd2af}html.theme--documenter-dark .content h1 code,html.theme--documenter-dark .content h2 code,html.theme--documenter-dark .content h3 code,html.theme--documenter-dark .content h4 code,html.theme--documenter-dark .content h5 code,html.theme--documenter-dark .content h6 code{color:#f2f2f2}html.theme--documenter-dark .content table{display:block;width:initial;max-width:100%;overflow-x:auto}html.theme--documenter-dark .content blockquote>ul:first-child,html.theme--documenter-dark .content blockquote>ol:first-child,html.theme--documenter-dark .content .admonition-body>ul:first-child,html.theme--documenter-dark .content .admonition-body>ol:first-child{margin-top:0}html.theme--documenter-dark pre,html.theme--documenter-dark code{font-variant-ligatures:no-contextual}html.theme--documenter-dark .breadcrumb a.is-disabled{cursor:default;pointer-events:none}html.theme--documenter-dark .breadcrumb a.is-disabled,html.theme--documenter-dark .breadcrumb a.is-disabled:hover{color:#f2f2f2}html.theme--documenter-dark .hljs{background:initial !important}html.theme--documenter-dark .katex .katex-mathml{top:0;right:0}html.theme--documenter-dark .katex-display,html.theme--documenter-dark mjx-container,html.theme--documenter-dark .MathJax_Display{margin:0.5em 0 !important}html.theme--documenter-dark html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}html.theme--documenter-dark li.no-marker{list-style:none}html.theme--documenter-dark #documenter .docs-main>article{overflow-wrap:break-word}html.theme--documenter-dark #documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main{width:100%}html.theme--documenter-dark #documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-main>header,html.theme--documenter-dark #documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar{background-color:#1f2424;border-bottom:1px solid #5e6d6f;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-icon,html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}html.theme--documenter-dark #documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #171717;transition-duration:0.7s;-webkit-transition-duration:0.7s}html.theme--documenter-dark #documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}html.theme--documenter-dark #documenter .docs-main section.footnotes{border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-main section.footnotes li .tag:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,html.theme--documenter-dark #documenter .docs-main section.footnotes li .content kbd:first-child,html.theme--documenter-dark .content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}html.theme--documenter-dark #documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #5e6d6f;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage,html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}html.theme--documenter-dark #documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}html.theme--documenter-dark #documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}html.theme--documenter-dark #documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}html.theme--documenter-dark #documenter .docs-sidebar{display:flex;flex-direction:column;color:#fff;background-color:#282f2f;border-right:1px solid #5e6d6f;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}html.theme--documenter-dark #documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #171717}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar{left:0;top:0}}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a,html.theme--documenter-dark #documenter .docs-sidebar .docs-package-name a:hover{color:#fff}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector{border-top:1px solid #5e6d6f;display:none;padding:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar .docs-version-selector.visible{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #5e6d6f;padding-bottom:1.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#fff;background:#282f2f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu a.tocitem:hover,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#fff;background-color:#32393a}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #5e6d6f;border-bottom:1px solid #5e6d6f;background-color:#1f2424}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#1f2424;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#32393a;color:#fff}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #5e6d6f}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}html.theme--documenter-dark #documenter .docs-sidebar form.docs-search>input{width:14.4rem}html.theme--documenter-dark #documenter .docs-sidebar #documenter-search-query{color:#868c98;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}@media screen and (max-width: 1055px){html.theme--documenter-dark #documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#3b4445}html.theme--documenter-dark #documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#4e5a5c}}html.theme--documenter-dark kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(245,245,245,0.6);box-shadow:0 2px 0 1px rgba(245,245,245,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}html.theme--documenter-dark .search-min-width-50{min-width:50%}html.theme--documenter-dark .search-min-height-100{min-height:100%}html.theme--documenter-dark .search-modal-card-body{max-height:calc(100vh - 15rem)}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .property-search-result-badge,html.theme--documenter-dark .search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333;background-color:#f1f5f9}html.theme--documenter-dark .search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}html.theme--documenter-dark .search-filter:hover,html.theme--documenter-dark .search-filter:focus{color:#333}html.theme--documenter-dark .search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}html.theme--documenter-dark .search-filter-selected:hover,html.theme--documenter-dark .search-filter-selected:focus{color:#f5f5f5}html.theme--documenter-dark .search-result-highlight{background-color:#ffdd57;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f}html.theme--documenter-dark .search-result-title{width:85%;color:#f5f5f5}html.theme--documenter-dark .search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-thumb,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}html.theme--documenter-dark #search-modal .modal-card-body::-webkit-scrollbar-track,html.theme--documenter-dark #search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem}html.theme--documenter-dark .gap-8{gap:2rem}html.theme--documenter-dark{background-color:#1f2424;font-size:16px;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}html.theme--documenter-dark .ansi span.sgr1{font-weight:bolder}html.theme--documenter-dark .ansi span.sgr2{font-weight:lighter}html.theme--documenter-dark .ansi span.sgr3{font-style:italic}html.theme--documenter-dark .ansi span.sgr4{text-decoration:underline}html.theme--documenter-dark .ansi span.sgr7{color:#1f2424;background-color:#fff}html.theme--documenter-dark .ansi span.sgr8{color:transparent}html.theme--documenter-dark .ansi span.sgr8 span{color:transparent}html.theme--documenter-dark .ansi span.sgr9{text-decoration:line-through}html.theme--documenter-dark .ansi span.sgr30{color:#242424}html.theme--documenter-dark .ansi span.sgr31{color:#f6705f}html.theme--documenter-dark .ansi span.sgr32{color:#4fb43a}html.theme--documenter-dark .ansi span.sgr33{color:#f4c72f}html.theme--documenter-dark .ansi span.sgr34{color:#7587f0}html.theme--documenter-dark .ansi span.sgr35{color:#bc89d3}html.theme--documenter-dark .ansi span.sgr36{color:#49b6ca}html.theme--documenter-dark .ansi span.sgr37{color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr40{background-color:#242424}html.theme--documenter-dark .ansi span.sgr41{background-color:#f6705f}html.theme--documenter-dark .ansi span.sgr42{background-color:#4fb43a}html.theme--documenter-dark .ansi span.sgr43{background-color:#f4c72f}html.theme--documenter-dark .ansi span.sgr44{background-color:#7587f0}html.theme--documenter-dark .ansi span.sgr45{background-color:#bc89d3}html.theme--documenter-dark .ansi span.sgr46{background-color:#49b6ca}html.theme--documenter-dark .ansi span.sgr47{background-color:#b3bdbe}html.theme--documenter-dark .ansi span.sgr90{color:#92a0a2}html.theme--documenter-dark .ansi span.sgr91{color:#ff8674}html.theme--documenter-dark .ansi span.sgr92{color:#79d462}html.theme--documenter-dark .ansi span.sgr93{color:#ffe76b}html.theme--documenter-dark .ansi span.sgr94{color:#8a98ff}html.theme--documenter-dark .ansi span.sgr95{color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr96{color:#6bc8db}html.theme--documenter-dark .ansi span.sgr97{color:#ecf0f1}html.theme--documenter-dark .ansi span.sgr100{background-color:#92a0a2}html.theme--documenter-dark .ansi span.sgr101{background-color:#ff8674}html.theme--documenter-dark .ansi span.sgr102{background-color:#79d462}html.theme--documenter-dark .ansi span.sgr103{background-color:#ffe76b}html.theme--documenter-dark .ansi span.sgr104{background-color:#8a98ff}html.theme--documenter-dark .ansi span.sgr105{background-color:#d2a4e6}html.theme--documenter-dark .ansi span.sgr106{background-color:#6bc8db}html.theme--documenter-dark .ansi span.sgr107{background-color:#ecf0f1}html.theme--documenter-dark code.language-julia-repl>span.hljs-meta{color:#4fb43a;font-weight:bolder}html.theme--documenter-dark .hljs{background:#2b2b2b;color:#f8f8f2}html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-quote{color:#d4d0ab}html.theme--documenter-dark .hljs-variable,html.theme--documenter-dark .hljs-template-variable,html.theme--documenter-dark .hljs-tag,html.theme--documenter-dark .hljs-name,html.theme--documenter-dark .hljs-selector-id,html.theme--documenter-dark .hljs-selector-class,html.theme--documenter-dark .hljs-regexp,html.theme--documenter-dark .hljs-deletion{color:#ffa07a}html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-link{color:#f5ab35}html.theme--documenter-dark .hljs-attribute{color:#ffd700}html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-addition{color:#abe338}html.theme--documenter-dark .hljs-title,html.theme--documenter-dark .hljs-section{color:#00e0e0}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{color:#dcc6e0}html.theme--documenter-dark .hljs-emphasis{font-style:italic}html.theme--documenter-dark .hljs-strong{font-weight:bold}@media screen and (-ms-high-contrast: active){html.theme--documenter-dark .hljs-addition,html.theme--documenter-dark .hljs-attribute,html.theme--documenter-dark .hljs-built_in,html.theme--documenter-dark .hljs-bullet,html.theme--documenter-dark .hljs-comment,html.theme--documenter-dark .hljs-link,html.theme--documenter-dark .hljs-literal,html.theme--documenter-dark .hljs-meta,html.theme--documenter-dark .hljs-number,html.theme--documenter-dark .hljs-params,html.theme--documenter-dark .hljs-string,html.theme--documenter-dark .hljs-symbol,html.theme--documenter-dark .hljs-type,html.theme--documenter-dark .hljs-quote{color:highlight}html.theme--documenter-dark .hljs-keyword,html.theme--documenter-dark .hljs-selector-tag{font-weight:bold}}html.theme--documenter-dark .hljs-subst{color:#f8f8f2}html.theme--documenter-dark .search-result-link{border-radius:0.7em;transition:all 300ms}html.theme--documenter-dark .search-result-link:hover,html.theme--documenter-dark .search-result-link:focus{background-color:rgba(0,128,128,0.1)}html.theme--documenter-dark .search-result-link .property-search-result-badge,html.theme--documenter-dark .search-result-link .search-filter{transition:all 300ms}html.theme--documenter-dark .search-result-link:hover .property-search-result-badge,html.theme--documenter-dark .search-result-link:hover .search-filter,html.theme--documenter-dark .search-result-link:focus .property-search-result-badge,html.theme--documenter-dark .search-result-link:focus .search-filter{color:#333 !important;background-color:#f1f5f9 !important}html.theme--documenter-dark .search-result-title{color:whitesmoke}html.theme--documenter-dark .search-result-highlight{background-color:greenyellow;color:black}html.theme--documenter-dark .search-divider{border-bottom:1px solid #5e6d6f50}html.theme--documenter-dark .w-100{width:100%}html.theme--documenter-dark .gap-2{gap:0.5rem}html.theme--documenter-dark .gap-4{gap:1rem} diff --git a/previews/PR826/assets/themes/documenter-light.css b/previews/PR826/assets/themes/documenter-light.css new file mode 100644 index 0000000000..e000447e60 --- /dev/null +++ b/previews/PR826/assets/themes/documenter-light.css @@ -0,0 +1,9 @@ +.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.file-cta,.file-name,.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input,.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.button:focus,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-focused.button,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.button:active,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.is-active.button{outline:none}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled],.file-cta[disabled],.file-name[disabled],.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],.button[disabled],fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input,fieldset[disabled] .button{cursor:not-allowed}.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb,.file,.button,.is-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:0.625em}.admonition:not(:last-child),.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child),.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child){margin-bottom:1.5rem}.modal-close,.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.modal-close::before,.delete::before,.modal-close::after,.delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close::before,.delete::before{height:2px;width:50%}.modal-close::after,.delete::after{height:50%;width:2px}.modal-close:hover,.delete:hover,.modal-close:focus,.delete:focus{background-color:rgba(10,10,10,0.3)}.modal-close:active,.delete:active{background-color:rgba(10,10,10,0.4)}.is-small.modal-close,#documenter .docs-sidebar form.docs-search>input.modal-close,.is-small.delete,#documenter .docs-sidebar form.docs-search>input.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.modal-close,.is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.modal-close,.is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.modal-background,.modal,.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio,.is-overlay{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#4eb5de !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#27a1d2 !important}.has-background-primary{background-color:#4eb5de !important}.has-text-primary-light{color:#eef8fc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#c3e6f4 !important}.has-background-primary-light{background-color:#eef8fc !important}.has-text-primary-dark{color:#1a6d8e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#228eb9 !important}.has-background-primary-dark{background-color:#1a6d8e !important}.has-text-link{color:#2e63b8 !important}a.has-text-link:hover,a.has-text-link:focus{color:#244d8f !important}.has-background-link{background-color:#2e63b8 !important}.has-text-link-light{color:#eff3fb !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c6d6f1 !important}.has-background-link-light{background-color:#eff3fb !important}.has-text-link-dark{color:#3169c4 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#5485d4 !important}.has-background-link-dark{background-color:#3169c4 !important}.has-text-info{color:#3c5dcd !important}a.has-text-info:hover,a.has-text-info:focus{color:#2c48aa !important}.has-background-info{background-color:#3c5dcd !important}.has-text-info-light{color:#eff2fb !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c6d0f0 !important}.has-background-info-light{background-color:#eff2fb !important}.has-text-info-dark{color:#3253c3 !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#5571d3 !important}.has-background-info-dark{background-color:#3253c3 !important}.has-text-success{color:#259a12 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1a6c0d !important}.has-background-success{background-color:#259a12 !important}.has-text-success-light{color:#effded !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c7f8bf !important}.has-background-success-light{background-color:#effded !important}.has-text-success-dark{color:#2ec016 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#3fe524 !important}.has-background-success-dark{background-color:#2ec016 !important}.has-text-warning{color:#a98800 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#765f00 !important}.has-background-warning{background-color:#a98800 !important}.has-text-warning-light{color:#fffbeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fff1b8 !important}.has-background-warning-light{background-color:#fffbeb !important}.has-text-warning-dark{color:#cca400 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#ffcd00 !important}.has-background-warning-dark{background-color:#cca400 !important}.has-text-danger{color:#cb3c33 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#a23029 !important}.has-background-danger{background-color:#cb3c33 !important}.has-text-danger-light{color:#fbefef !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#f1c8c6 !important}.has-background-danger-light{background-color:#fbefef !important}.has-text-danger-dark{color:#c03930 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#d35850 !important}.has-background-danger-dark{background-color:#c03930 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#6b6b6b !important}.has-background-grey{background-color:#6b6b6b !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-flex-direction-row{flex-direction:row !important}.is-flex-direction-row-reverse{flex-direction:row-reverse !important}.is-flex-direction-column{flex-direction:column !important}.is-flex-direction-column-reverse{flex-direction:column-reverse !important}.is-flex-wrap-nowrap{flex-wrap:nowrap !important}.is-flex-wrap-wrap{flex-wrap:wrap !important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse !important}.is-justify-content-flex-start{justify-content:flex-start !important}.is-justify-content-flex-end{justify-content:flex-end !important}.is-justify-content-center{justify-content:center !important}.is-justify-content-space-between{justify-content:space-between !important}.is-justify-content-space-around{justify-content:space-around !important}.is-justify-content-space-evenly{justify-content:space-evenly !important}.is-justify-content-start{justify-content:start !important}.is-justify-content-end{justify-content:end !important}.is-justify-content-left{justify-content:left !important}.is-justify-content-right{justify-content:right !important}.is-align-content-flex-start{align-content:flex-start !important}.is-align-content-flex-end{align-content:flex-end !important}.is-align-content-center{align-content:center !important}.is-align-content-space-between{align-content:space-between !important}.is-align-content-space-around{align-content:space-around !important}.is-align-content-space-evenly{align-content:space-evenly !important}.is-align-content-stretch{align-content:stretch !important}.is-align-content-start{align-content:start !important}.is-align-content-end{align-content:end !important}.is-align-content-baseline{align-content:baseline !important}.is-align-items-stretch{align-items:stretch !important}.is-align-items-flex-start{align-items:flex-start !important}.is-align-items-flex-end{align-items:flex-end !important}.is-align-items-center{align-items:center !important}.is-align-items-baseline{align-items:baseline !important}.is-align-items-start{align-items:start !important}.is-align-items-end{align-items:end !important}.is-align-items-self-start{align-items:self-start !important}.is-align-items-self-end{align-items:self-end !important}.is-align-self-auto{align-self:auto !important}.is-align-self-flex-start{align-self:flex-start !important}.is-align-self-flex-end{align-self:flex-end !important}.is-align-self-center{align-self:center !important}.is-align-self-baseline{align-self:baseline !important}.is-align-self-stretch{align-self:stretch !important}.is-flex-grow-0{flex-grow:0 !important}.is-flex-grow-1{flex-grow:1 !important}.is-flex-grow-2{flex-grow:2 !important}.is-flex-grow-3{flex-grow:3 !important}.is-flex-grow-4{flex-grow:4 !important}.is-flex-grow-5{flex-grow:5 !important}.is-flex-shrink-0{flex-shrink:0 !important}.is-flex-shrink-1{flex-shrink:1 !important}.is-flex-shrink-2{flex-shrink:2 !important}.is-flex-shrink-3{flex-shrink:3 !important}.is-flex-shrink-4{flex-shrink:4 !important}.is-flex-shrink-5{flex-shrink:5 !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clickable{cursor:pointer !important;pointer-events:all !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:.25rem !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.m-2{margin:.5rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.m-3{margin:.75rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.m-4{margin:1rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.m-5{margin:1.5rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.m-6{margin:3rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.m-auto{margin:auto !important}.mt-auto{margin-top:auto !important}.mr-auto{margin-right:auto !important}.mb-auto{margin-bottom:auto !important}.ml-auto{margin-left:auto !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:.25rem !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.p-2{padding:.5rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.p-3{padding:.75rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.p-4{padding:1rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.p-5{padding:1.5rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.p-6{padding:3rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.p-auto{padding:auto !important}.pt-auto{padding-top:auto !important}.pr-auto{padding-right:auto !important}.pb-auto{padding-bottom:auto !important}.pl-auto{padding-left:auto !important}.px-auto{padding-left:auto !important;padding-right:auto !important}.py-auto{padding-top:auto !important;padding-bottom:auto !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7,.docstring>section>a.docs-sourcelink{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1055px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1056px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1055px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1056px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1055px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1056px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1055px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1056px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1055px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1056px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.is-underlined{text-decoration:underline !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-family-code{font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1055px){.is-block-touch{display:block !important}}@media screen and (min-width: 1056px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1055px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1056px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1055px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1056px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1055px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1056px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1055px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1056px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1055px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1056px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1055px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1055px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1056px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1056px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:auto;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:"Lato Medium",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}body{color:#222;font-size:1em;font-weight:400;line-height:1.5}a{color:#2e63b8;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:rgba(0,0,0,0.05);color:#000;font-size:.875em;font-weight:normal;padding:.1em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#222;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#222;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#222}@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:#bbb;color:#222;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 0.5em 1em -0.125em rgba(10,10,10,0.1),0 0 0 1px #2e63b8}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #2e63b8}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#222;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button #documenter .docs-sidebar form.docs-search>input.icon,#documenter .docs-sidebar .button form.docs-search>input.icon,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3c5dcd;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#222;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#222}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#222}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:none;border-color:rgba(0,0,0,0);color:#2e63b8;text-decoration:none}.button.is-ghost:hover,.button.is-ghost.is-hovered{color:#2e63b8;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:#fff;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:#0a0a0a;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-dark,.content kbd.button{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.content kbd.button:hover,.button.is-dark.is-hovered,.content kbd.button.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.content kbd.button:focus,.button.is-dark.is-focused,.content kbd.button.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.content kbd.button:focus:not(:active),.button.is-dark.is-focused:not(:active),.content kbd.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.button.is-dark:active,.content kbd.button:active,.button.is-dark.is-active,.content kbd.button.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],.content kbd.button[disabled],fieldset[disabled] .button.is-dark,fieldset[disabled] .content kbd.button,.content fieldset[disabled] kbd.button{background-color:#363636;border-color:#363636;box-shadow:none}.button.is-dark.is-inverted,.content kbd.button.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.content kbd.button.is-inverted:hover,.button.is-dark.is-inverted.is-hovered,.content kbd.button.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],.content kbd.button.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted,fieldset[disabled] .content kbd.button.is-inverted,.content fieldset[disabled] kbd.button.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after,.content kbd.button.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined,.content kbd.button.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.content kbd.button.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.content kbd.button.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.content kbd.button.is-outlined:focus,.button.is-dark.is-outlined.is-focused,.content kbd.button.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after,.content kbd.button.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.content kbd.button.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.content kbd.button.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after,.content kbd.button.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],.content kbd.button.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined,fieldset[disabled] .content kbd.button.is-outlined,.content fieldset[disabled] kbd.button.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined,.content kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.content kbd.button.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.content kbd.button.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.content kbd.button.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused,.content kbd.button.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.content kbd.button.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.content kbd.button.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.content kbd.button.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],.content kbd.button.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined,fieldset[disabled] .content kbd.button.is-inverted.is-outlined,.content fieldset[disabled] kbd.button.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary,.docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:transparent;color:#fff}.button.is-primary:hover,.docstring>section>a.button.docs-sourcelink:hover,.button.is-primary.is-hovered,.docstring>section>a.button.is-hovered.docs-sourcelink{background-color:#43b1dc;border-color:transparent;color:#fff}.button.is-primary:focus,.docstring>section>a.button.docs-sourcelink:focus,.button.is-primary.is-focused,.docstring>section>a.button.is-focused.docs-sourcelink{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.docstring>section>a.button.docs-sourcelink:focus:not(:active),.button.is-primary.is-focused:not(:active),.docstring>section>a.button.is-focused.docs-sourcelink:not(:active){box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.button.is-primary:active,.docstring>section>a.button.docs-sourcelink:active,.button.is-primary.is-active,.docstring>section>a.button.is-active.docs-sourcelink{background-color:#39acda;border-color:transparent;color:#fff}.button.is-primary[disabled],.docstring>section>a.button.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary,fieldset[disabled] .docstring>section>a.button.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;box-shadow:none}.button.is-primary.is-inverted,.docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted:hover,.docstring>section>a.button.is-inverted.docs-sourcelink:hover,.button.is-primary.is-inverted.is-hovered,.docstring>section>a.button.is-inverted.is-hovered.docs-sourcelink{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],.docstring>section>a.button.is-inverted.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted,fieldset[disabled] .docstring>section>a.button.is-inverted.docs-sourcelink{background-color:#fff;border-color:transparent;box-shadow:none;color:#4eb5de}.button.is-primary.is-loading::after,.docstring>section>a.button.is-loading.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined,.docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;color:#4eb5de}.button.is-primary.is-outlined:hover,.docstring>section>a.button.is-outlined.docs-sourcelink:hover,.button.is-primary.is-outlined.is-hovered,.docstring>section>a.button.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-outlined:focus,.docstring>section>a.button.is-outlined.docs-sourcelink:focus,.button.is-primary.is-outlined.is-focused,.docstring>section>a.button.is-outlined.is-focused.docs-sourcelink{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.button.is-primary.is-outlined.is-loading::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],.docstring>section>a.button.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-outlined,fieldset[disabled] .docstring>section>a.button.is-outlined.docs-sourcelink{background-color:transparent;border-color:#4eb5de;box-shadow:none;color:#4eb5de}.button.is-primary.is-inverted.is-outlined,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.docstring>section>a.button.is-inverted.is-outlined.is-hovered.docs-sourcelink,.button.is-primary.is-inverted.is-outlined:focus,.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink:focus,.button.is-primary.is-inverted.is-outlined.is-focused,.docstring>section>a.button.is-inverted.is-outlined.is-focused.docs-sourcelink{background-color:#fff;color:#4eb5de}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-hovered.docs-sourcelink::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.docs-sourcelink:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.docstring>section>a.button.is-inverted.is-outlined.is-loading.is-focused.docs-sourcelink::after{border-color:transparent transparent #4eb5de #4eb5de !important}.button.is-primary.is-inverted.is-outlined[disabled],.docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined,fieldset[disabled] .docstring>section>a.button.is-inverted.is-outlined.docs-sourcelink{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light,.docstring>section>a.button.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.button.is-primary.is-light:hover,.docstring>section>a.button.is-light.docs-sourcelink:hover,.button.is-primary.is-light.is-hovered,.docstring>section>a.button.is-light.is-hovered.docs-sourcelink{background-color:#e3f3fa;border-color:transparent;color:#1a6d8e}.button.is-primary.is-light:active,.docstring>section>a.button.is-light.docs-sourcelink:active,.button.is-primary.is-light.is-active,.docstring>section>a.button.is-light.is-active.docs-sourcelink{background-color:#d8eff8;border-color:transparent;color:#1a6d8e}.button.is-link{background-color:#2e63b8;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#2b5eae;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2958a4;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#2e63b8;border-color:#2e63b8;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#2e63b8}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;color:#2e63b8}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#2e63b8;box-shadow:none;color:#2e63b8}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#2e63b8}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #2e63b8 #2e63b8 !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff3fb;color:#3169c4}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#e4ecf8;border-color:transparent;color:#3169c4}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#dae5f6;border-color:transparent;color:#3169c4}.button.is-info{background-color:#3c5dcd;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#3355c9;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.button.is-info:active,.button.is-info.is-active{background-color:#3151bf;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3c5dcd;border-color:#3c5dcd;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3c5dcd}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3c5dcd}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;color:#3c5dcd}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3c5dcd;box-shadow:none;color:#3c5dcd}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3c5dcd}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3c5dcd #3c5dcd !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff2fb;color:#3253c3}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e5e9f8;border-color:transparent;color:#3253c3}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#dae1f6;border-color:transparent;color:#3253c3}.button.is-success{background-color:#259a12;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#228f11;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.button.is-success:active,.button.is-success.is-active{background-color:#20830f;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#259a12;border-color:#259a12;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#259a12}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#259a12}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#259a12;color:#259a12}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#259a12;border-color:#259a12;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #259a12 #259a12 !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#259a12;box-shadow:none;color:#259a12}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#259a12}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #259a12 #259a12 !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effded;color:#2ec016}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e5fce1;border-color:transparent;color:#2ec016}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#dbfad6;border-color:transparent;color:#2ec016}.button.is-warning{background-color:#a98800;border-color:transparent;color:#fff}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#9c7d00;border-color:transparent;color:#fff}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:#fff}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#8f7300;border-color:transparent;color:#fff}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#a98800;border-color:#a98800;box-shadow:none}.button.is-warning.is-inverted{background-color:#fff;color:#a98800}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#a98800}.button.is-warning.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#a98800;color:#a98800}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#a98800;border-color:#a98800;color:#fff}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #a98800 #a98800 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#a98800;box-shadow:none;color:#a98800}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:#fff;color:#a98800}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #a98800 #a98800 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-warning.is-light{background-color:#fffbeb;color:#cca400}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff9de;border-color:transparent;color:#cca400}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff6d1;border-color:transparent;color:#cca400}.button.is-danger{background-color:#cb3c33;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#c13930;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#b7362e;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#cb3c33;border-color:#cb3c33;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#cb3c33}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#cb3c33}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;color:#cb3c33}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#cb3c33;border-color:#cb3c33;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#cb3c33;box-shadow:none;color:#cb3c33}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#cb3c33}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #cb3c33 #cb3c33 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#fbefef;color:#c03930}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#f8e6e5;border-color:transparent;color:#c03930}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#f6dcda;border-color:transparent;color:#c03930}.button.is-small,#documenter .docs-sidebar form.docs-search>input.button{font-size:.75rem}.button.is-small:not(.is-rounded),#documenter .docs-sidebar form.docs-search>input.button:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * 0.5));top:calc(50% - (1em * 0.5));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#6b6b6b;box-shadow:none;pointer-events:none}.button.is-rounded,#documenter .docs-sidebar form.docs-search>input.button{border-radius:9999px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:0.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}@media screen and (max-width: 768px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.5625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.65625rem}.button.is-responsive.is-medium{font-size:.75rem}.button.is-responsive.is-large{font-size:1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.button.is-responsive.is-small,#documenter .docs-sidebar form.docs-search>input.is-responsive{font-size:.65625rem}.button.is-responsive,.button.is-responsive.is-normal{font-size:.75rem}.button.is-responsive.is-medium{font-size:1rem}.button.is-responsive.is-large{font-size:1.25rem}}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none !important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1056px){.container{max-width:992px}}@media screen and (max-width: 1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width: 1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width: 1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:0.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#222;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:0.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:0.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:0.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:0.8em}.content h5{font-size:1.125em;margin-bottom:0.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol.is-lower-alpha:not([type]){list-style-type:lower-alpha}.content ol.is-lower-roman:not([type]){list-style-type:lower-roman}.content ol.is-upper-alpha:not([type]){list-style-type:upper-alpha}.content ol.is-upper-roman:not([type]){list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:0;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.content table th{color:#222}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#222}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#222}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small,#documenter .docs-sidebar form.docs-search>input.content{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small,#documenter .docs-sidebar form.docs-search>input.icon{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image,#documenter .docs-sidebar .docs-logo>img{display:block;position:relative}.image img,#documenter .docs-sidebar .docs-logo>img img{display:block;height:auto;width:100%}.image img.is-rounded,#documenter .docs-sidebar .docs-logo>img img.is-rounded{border-radius:9999px}.image.is-fullwidth,#documenter .docs-sidebar .docs-logo>img.is-fullwidth{width:100%}.image.is-square img,#documenter .docs-sidebar .docs-logo>img.is-square img,.image.is-square .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-square .has-ratio,.image.is-1by1 img,#documenter .docs-sidebar .docs-logo>img.is-1by1 img,.image.is-1by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by1 .has-ratio,.image.is-5by4 img,#documenter .docs-sidebar .docs-logo>img.is-5by4 img,.image.is-5by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by4 .has-ratio,.image.is-4by3 img,#documenter .docs-sidebar .docs-logo>img.is-4by3 img,.image.is-4by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by3 .has-ratio,.image.is-3by2 img,#documenter .docs-sidebar .docs-logo>img.is-3by2 img,.image.is-3by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by2 .has-ratio,.image.is-5by3 img,#documenter .docs-sidebar .docs-logo>img.is-5by3 img,.image.is-5by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-5by3 .has-ratio,.image.is-16by9 img,#documenter .docs-sidebar .docs-logo>img.is-16by9 img,.image.is-16by9 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-16by9 .has-ratio,.image.is-2by1 img,#documenter .docs-sidebar .docs-logo>img.is-2by1 img,.image.is-2by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by1 .has-ratio,.image.is-3by1 img,#documenter .docs-sidebar .docs-logo>img.is-3by1 img,.image.is-3by1 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by1 .has-ratio,.image.is-4by5 img,#documenter .docs-sidebar .docs-logo>img.is-4by5 img,.image.is-4by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-4by5 .has-ratio,.image.is-3by4 img,#documenter .docs-sidebar .docs-logo>img.is-3by4 img,.image.is-3by4 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by4 .has-ratio,.image.is-2by3 img,#documenter .docs-sidebar .docs-logo>img.is-2by3 img,.image.is-2by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-2by3 .has-ratio,.image.is-3by5 img,#documenter .docs-sidebar .docs-logo>img.is-3by5 img,.image.is-3by5 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-3by5 .has-ratio,.image.is-9by16 img,#documenter .docs-sidebar .docs-logo>img.is-9by16 img,.image.is-9by16 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-9by16 .has-ratio,.image.is-1by2 img,#documenter .docs-sidebar .docs-logo>img.is-1by2 img,.image.is-1by2 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by2 .has-ratio,.image.is-1by3 img,#documenter .docs-sidebar .docs-logo>img.is-1by3 img,.image.is-1by3 .has-ratio,#documenter .docs-sidebar .docs-logo>img.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,#documenter .docs-sidebar .docs-logo>img.is-square,.image.is-1by1,#documenter .docs-sidebar .docs-logo>img.is-1by1{padding-top:100%}.image.is-5by4,#documenter .docs-sidebar .docs-logo>img.is-5by4{padding-top:80%}.image.is-4by3,#documenter .docs-sidebar .docs-logo>img.is-4by3{padding-top:75%}.image.is-3by2,#documenter .docs-sidebar .docs-logo>img.is-3by2{padding-top:66.6666%}.image.is-5by3,#documenter .docs-sidebar .docs-logo>img.is-5by3{padding-top:60%}.image.is-16by9,#documenter .docs-sidebar .docs-logo>img.is-16by9{padding-top:56.25%}.image.is-2by1,#documenter .docs-sidebar .docs-logo>img.is-2by1{padding-top:50%}.image.is-3by1,#documenter .docs-sidebar .docs-logo>img.is-3by1{padding-top:33.3333%}.image.is-4by5,#documenter .docs-sidebar .docs-logo>img.is-4by5{padding-top:125%}.image.is-3by4,#documenter .docs-sidebar .docs-logo>img.is-3by4{padding-top:133.3333%}.image.is-2by3,#documenter .docs-sidebar .docs-logo>img.is-2by3{padding-top:150%}.image.is-3by5,#documenter .docs-sidebar .docs-logo>img.is-3by5{padding-top:166.6666%}.image.is-9by16,#documenter .docs-sidebar .docs-logo>img.is-9by16{padding-top:177.7777%}.image.is-1by2,#documenter .docs-sidebar .docs-logo>img.is-1by2{padding-top:200%}.image.is-1by3,#documenter .docs-sidebar .docs-logo>img.is-1by3{padding-top:300%}.image.is-16x16,#documenter .docs-sidebar .docs-logo>img.is-16x16{height:16px;width:16px}.image.is-24x24,#documenter .docs-sidebar .docs-logo>img.is-24x24{height:24px;width:24px}.image.is-32x32,#documenter .docs-sidebar .docs-logo>img.is-32x32{height:32px;width:32px}.image.is-48x48,#documenter .docs-sidebar .docs-logo>img.is-48x48{height:48px;width:48px}.image.is-64x64,#documenter .docs-sidebar .docs-logo>img.is-64x64{height:64px;width:64px}.image.is-96x96,#documenter .docs-sidebar .docs-logo>img.is-96x96{height:96px;width:96px}.image.is-128x128,#documenter .docs-sidebar .docs-logo>img.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:0.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.notification.is-dark,.content kbd.notification{background-color:#363636;color:#fff}.notification.is-primary,.docstring>section>a.notification.docs-sourcelink{background-color:#4eb5de;color:#fff}.notification.is-primary.is-light,.docstring>section>a.notification.is-light.docs-sourcelink{background-color:#eef8fc;color:#1a6d8e}.notification.is-link{background-color:#2e63b8;color:#fff}.notification.is-link.is-light{background-color:#eff3fb;color:#3169c4}.notification.is-info{background-color:#3c5dcd;color:#fff}.notification.is-info.is-light{background-color:#eff2fb;color:#3253c3}.notification.is-success{background-color:#259a12;color:#fff}.notification.is-success.is-light{background-color:#effded;color:#2ec016}.notification.is-warning{background-color:#a98800;color:#fff}.notification.is-warning.is-light{background-color:#fffbeb;color:#cca400}.notification.is-danger{background-color:#cb3c33;color:#fff}.notification.is-danger.is-light{background-color:#fbefef;color:#c03930}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#222}.progress::-moz-progress-bar{background-color:#222}.progress::-ms-fill{background-color:#222;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value,.content kbd.progress::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar,.content kbd.progress::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill,.content kbd.progress::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate,.content kbd.progress:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value,.docstring>section>a.progress.docs-sourcelink::-webkit-progress-value{background-color:#4eb5de}.progress.is-primary::-moz-progress-bar,.docstring>section>a.progress.docs-sourcelink::-moz-progress-bar{background-color:#4eb5de}.progress.is-primary::-ms-fill,.docstring>section>a.progress.docs-sourcelink::-ms-fill{background-color:#4eb5de}.progress.is-primary:indeterminate,.docstring>section>a.progress.docs-sourcelink:indeterminate{background-image:linear-gradient(to right, #4eb5de 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#2e63b8}.progress.is-link::-moz-progress-bar{background-color:#2e63b8}.progress.is-link::-ms-fill{background-color:#2e63b8}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #2e63b8 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3c5dcd}.progress.is-info::-moz-progress-bar{background-color:#3c5dcd}.progress.is-info::-ms-fill{background-color:#3c5dcd}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #3c5dcd 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#259a12}.progress.is-success::-moz-progress-bar{background-color:#259a12}.progress.is-success::-ms-fill{background-color:#259a12}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #259a12 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#a98800}.progress.is-warning::-moz-progress-bar{background-color:#a98800}.progress.is-warning::-ms-fill{background-color:#a98800}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #a98800 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#cb3c33}.progress.is-danger::-moz-progress-bar{background-color:#cb3c33}.progress.is-danger::-ms-fill{background-color:#cb3c33}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #cb3c33 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, #222 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small,#documenter .docs-sidebar form.docs-search>input.progress{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#222}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,0.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#4eb5de;border-color:#4eb5de;color:#fff}.table td.is-link,.table th.is-link{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.table td.is-info,.table th.is-info{background-color:#3c5dcd;border-color:#3c5dcd;color:#fff}.table td.is-success,.table th.is-success{background-color:#259a12;border-color:#259a12;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#a98800;border-color:#a98800;color:#fff}.table td.is-danger,.table th.is-danger{background-color:#cb3c33;border-color:#cb3c33;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#4eb5de;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#222}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#4eb5de;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:rgba(0,0,0,0)}.table thead td,.table thead th{border-width:0 0 2px;color:#222}.table tfoot{background-color:rgba(0,0,0,0)}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#222}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:0.25em 0.5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag,.tags .content kbd,.content .tags kbd,.tags .docstring>section>a.docs-sourcelink{margin-bottom:0.5rem}.tags .tag:not(:last-child),.tags .content kbd:not(:last-child),.content .tags kbd:not(:last-child),.tags .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large),.tags.are-medium .content kbd:not(.is-normal):not(.is-large),.content .tags.are-medium kbd:not(.is-normal):not(.is-large),.tags.are-medium .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium),.tags.are-large .content kbd:not(.is-normal):not(.is-medium),.content .tags.are-large kbd:not(.is-normal):not(.is-medium),.tags.are-large .docstring>section>a.docs-sourcelink:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag,.tags.is-centered .content kbd,.content .tags.is-centered kbd,.tags.is-centered .docstring>section>a.docs-sourcelink{margin-right:0.25rem;margin-left:0.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child),.tags.is-right .content kbd:not(:first-child),.content .tags.is-right kbd:not(:first-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0.5rem}.tags.is-right .tag:not(:last-child),.tags.is-right .content kbd:not(:last-child),.content .tags.is-right kbd:not(:last-child),.tags.is-right .docstring>section>a.docs-sourcelink:not(:last-child){margin-right:0}.tags.has-addons .tag,.tags.has-addons .content kbd,.content .tags.has-addons kbd,.tags.has-addons .docstring>section>a.docs-sourcelink{margin-right:0}.tags.has-addons .tag:not(:first-child),.tags.has-addons .content kbd:not(:first-child),.content .tags.has-addons kbd:not(:first-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child),.tags.has-addons .content kbd:not(:last-child),.content .tags.has-addons kbd:not(:last-child),.tags.has-addons .docstring>section>a.docs-sourcelink:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#222;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.tag:not(body) .delete,.content kbd:not(body) .delete,.docstring>section>a.docs-sourcelink:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag.is-white:not(body),.content kbd.is-white:not(body),.docstring>section>a.docs-sourcelink.is-white:not(body){background-color:#fff;color:#0a0a0a}.tag.is-black:not(body),.content kbd.is-black:not(body),.docstring>section>a.docs-sourcelink.is-black:not(body){background-color:#0a0a0a;color:#fff}.tag.is-light:not(body),.content kbd.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.tag.is-dark:not(body),.content kbd:not(body),.docstring>section>a.docs-sourcelink.is-dark:not(body),.content .docstring>section>kbd:not(body){background-color:#363636;color:#fff}.tag.is-primary:not(body),.content kbd.is-primary:not(body),.docstring>section>a.docs-sourcelink:not(body){background-color:#4eb5de;color:#fff}.tag.is-primary.is-light:not(body),.content kbd.is-primary.is-light:not(body),.docstring>section>a.docs-sourcelink.is-light:not(body){background-color:#eef8fc;color:#1a6d8e}.tag.is-link:not(body),.content kbd.is-link:not(body),.docstring>section>a.docs-sourcelink.is-link:not(body){background-color:#2e63b8;color:#fff}.tag.is-link.is-light:not(body),.content kbd.is-link.is-light:not(body),.docstring>section>a.docs-sourcelink.is-link.is-light:not(body){background-color:#eff3fb;color:#3169c4}.tag.is-info:not(body),.content kbd.is-info:not(body),.docstring>section>a.docs-sourcelink.is-info:not(body){background-color:#3c5dcd;color:#fff}.tag.is-info.is-light:not(body),.content kbd.is-info.is-light:not(body),.docstring>section>a.docs-sourcelink.is-info.is-light:not(body){background-color:#eff2fb;color:#3253c3}.tag.is-success:not(body),.content kbd.is-success:not(body),.docstring>section>a.docs-sourcelink.is-success:not(body){background-color:#259a12;color:#fff}.tag.is-success.is-light:not(body),.content kbd.is-success.is-light:not(body),.docstring>section>a.docs-sourcelink.is-success.is-light:not(body){background-color:#effded;color:#2ec016}.tag.is-warning:not(body),.content kbd.is-warning:not(body),.docstring>section>a.docs-sourcelink.is-warning:not(body){background-color:#a98800;color:#fff}.tag.is-warning.is-light:not(body),.content kbd.is-warning.is-light:not(body),.docstring>section>a.docs-sourcelink.is-warning.is-light:not(body){background-color:#fffbeb;color:#cca400}.tag.is-danger:not(body),.content kbd.is-danger:not(body),.docstring>section>a.docs-sourcelink.is-danger:not(body){background-color:#cb3c33;color:#fff}.tag.is-danger.is-light:not(body),.content kbd.is-danger.is-light:not(body),.docstring>section>a.docs-sourcelink.is-danger.is-light:not(body){background-color:#fbefef;color:#c03930}.tag.is-normal:not(body),.content kbd.is-normal:not(body),.docstring>section>a.docs-sourcelink.is-normal:not(body){font-size:.75rem}.tag.is-medium:not(body),.content kbd.is-medium:not(body),.docstring>section>a.docs-sourcelink.is-medium:not(body){font-size:1rem}.tag.is-large:not(body),.content kbd.is-large:not(body),.docstring>section>a.docs-sourcelink.is-large:not(body){font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child),.content kbd:not(body) .icon:first-child:not(:last-child),.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child),.content kbd:not(body) .icon:last-child:not(:first-child),.docstring>section>a.docs-sourcelink:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child,.content kbd:not(body) .icon:first-child:last-child,.docstring>section>a.docs-sourcelink:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag.is-delete:not(body),.content kbd.is-delete:not(body),.docstring>section>a.docs-sourcelink.is-delete:not(body){margin-left:1px;padding:0;position:relative;width:2em}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before,.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag.is-delete:not(body)::before,.content kbd.is-delete:not(body)::before,.docstring>section>a.docs-sourcelink.is-delete:not(body)::before{height:1px;width:50%}.tag.is-delete:not(body)::after,.content kbd.is-delete:not(body)::after,.docstring>section>a.docs-sourcelink.is-delete:not(body)::after{height:50%;width:1px}.tag.is-delete:not(body):hover,.content kbd.is-delete:not(body):hover,.docstring>section>a.docs-sourcelink.is-delete:not(body):hover,.tag.is-delete:not(body):focus,.content kbd.is-delete:not(body):focus,.docstring>section>a.docs-sourcelink.is-delete:not(body):focus{background-color:#e8e8e8}.tag.is-delete:not(body):active,.content kbd.is-delete:not(body):active,.docstring>section>a.docs-sourcelink.is-delete:not(body):active{background-color:#dbdbdb}.tag.is-rounded:not(body),#documenter .docs-sidebar form.docs-search>input:not(body),.content kbd.is-rounded:not(body),#documenter .docs-sidebar .content form.docs-search>input:not(body),.docstring>section>a.docs-sourcelink.is-rounded:not(body){border-radius:9999px}a.tag:hover,.docstring>section>a.docs-sourcelink:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.title .content kbd,.content .title kbd,.title .docstring>section>a.docs-sourcelink,.subtitle .tag,.subtitle .content kbd,.content .subtitle kbd,.subtitle .docstring>section>a.docs-sourcelink{vertical-align:middle}.title{color:#222;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#222;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#222;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}.select select,.textarea,.input,#documenter .docs-sidebar form.docs-search>input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#222}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input::-moz-placeholder{color:#707070}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder{color:#707070}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input:-moz-placeholder{color:#707070}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder{color:#707070}.select select:hover,.textarea:hover,.input:hover,#documenter .docs-sidebar form.docs-search>input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input,#documenter .docs-sidebar form.docs-search>input.is-hovered{border-color:#b5b5b5}.select select:focus,.textarea:focus,.input:focus,#documenter .docs-sidebar form.docs-search>input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.select select:active,.textarea:active,.input:active,#documenter .docs-sidebar form.docs-search>input:active,.select select.is-active,.is-active.textarea,.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{border-color:#2e63b8;box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select select[disabled],.textarea[disabled],.input[disabled],#documenter .docs-sidebar form.docs-search>input[disabled],fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#6b6b6b}.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,.input[disabled]::-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,.input[disabled]::-webkit-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input::-webkit-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input::-webkit-input-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,.input[disabled]:-moz-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-moz-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-moz-placeholder{color:rgba(107,107,107,0.3)}.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,.input[disabled]:-ms-input-placeholder,#documenter .docs-sidebar form.docs-search>input[disabled]:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] #documenter .docs-sidebar form.docs-search>input:-ms-input-placeholder,#documenter .docs-sidebar fieldset[disabled] form.docs-search>input:-ms-input-placeholder{color:rgba(107,107,107,0.3)}.textarea,.input,#documenter .docs-sidebar form.docs-search>input{box-shadow:inset 0 0.0625em 0.125em rgba(10,10,10,0.05);max-width:100%;width:100%}.textarea[readonly],.input[readonly],#documenter .docs-sidebar form.docs-search>input[readonly]{box-shadow:none}.is-white.textarea,.is-white.input,#documenter .docs-sidebar form.docs-search>input.is-white{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,#documenter .docs-sidebar form.docs-search>input.is-white:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-white.textarea:active,.is-white.input:active,#documenter .docs-sidebar form.docs-search>input.is-white:active,.is-white.is-active.textarea,.is-white.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.is-black.textarea,.is-black.input,#documenter .docs-sidebar form.docs-search>input.is-black{border-color:#0a0a0a}.is-black.textarea:focus,.is-black.input:focus,#documenter .docs-sidebar form.docs-search>input.is-black:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-black.textarea:active,.is-black.input:active,#documenter .docs-sidebar form.docs-search>input.is-black:active,.is-black.is-active.textarea,.is-black.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.is-light.textarea,.is-light.input,#documenter .docs-sidebar form.docs-search>input.is-light{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,#documenter .docs-sidebar form.docs-search>input.is-light:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-light.textarea:active,.is-light.input:active,#documenter .docs-sidebar form.docs-search>input.is-light:active,.is-light.is-active.textarea,.is-light.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.is-dark.textarea,.content kbd.textarea,.is-dark.input,#documenter .docs-sidebar form.docs-search>input.is-dark,.content kbd.input{border-color:#363636}.is-dark.textarea:focus,.content kbd.textarea:focus,.is-dark.input:focus,#documenter .docs-sidebar form.docs-search>input.is-dark:focus,.content kbd.input:focus,.is-dark.is-focused.textarea,.content kbd.is-focused.textarea,.is-dark.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.content kbd.is-focused.input,#documenter .docs-sidebar .content form.docs-search>input.is-focused,.is-dark.textarea:active,.content kbd.textarea:active,.is-dark.input:active,#documenter .docs-sidebar form.docs-search>input.is-dark:active,.content kbd.input:active,.is-dark.is-active.textarea,.content kbd.is-active.textarea,.is-dark.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.content kbd.is-active.input,#documenter .docs-sidebar .content form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.is-primary.textarea,.docstring>section>a.textarea.docs-sourcelink,.is-primary.input,#documenter .docs-sidebar form.docs-search>input.is-primary,.docstring>section>a.input.docs-sourcelink{border-color:#4eb5de}.is-primary.textarea:focus,.docstring>section>a.textarea.docs-sourcelink:focus,.is-primary.input:focus,#documenter .docs-sidebar form.docs-search>input.is-primary:focus,.docstring>section>a.input.docs-sourcelink:focus,.is-primary.is-focused.textarea,.docstring>section>a.is-focused.textarea.docs-sourcelink,.is-primary.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.docstring>section>a.is-focused.input.docs-sourcelink,.is-primary.textarea:active,.docstring>section>a.textarea.docs-sourcelink:active,.is-primary.input:active,#documenter .docs-sidebar form.docs-search>input.is-primary:active,.docstring>section>a.input.docs-sourcelink:active,.is-primary.is-active.textarea,.docstring>section>a.is-active.textarea.docs-sourcelink,.is-primary.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active,.docstring>section>a.is-active.input.docs-sourcelink{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.is-link.textarea,.is-link.input,#documenter .docs-sidebar form.docs-search>input.is-link{border-color:#2e63b8}.is-link.textarea:focus,.is-link.input:focus,#documenter .docs-sidebar form.docs-search>input.is-link:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-link.textarea:active,.is-link.input:active,#documenter .docs-sidebar form.docs-search>input.is-link:active,.is-link.is-active.textarea,.is-link.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.is-info.textarea,.is-info.input,#documenter .docs-sidebar form.docs-search>input.is-info{border-color:#3c5dcd}.is-info.textarea:focus,.is-info.input:focus,#documenter .docs-sidebar form.docs-search>input.is-info:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-info.textarea:active,.is-info.input:active,#documenter .docs-sidebar form.docs-search>input.is-info:active,.is-info.is-active.textarea,.is-info.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.is-success.textarea,.is-success.input,#documenter .docs-sidebar form.docs-search>input.is-success{border-color:#259a12}.is-success.textarea:focus,.is-success.input:focus,#documenter .docs-sidebar form.docs-search>input.is-success:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-success.textarea:active,.is-success.input:active,#documenter .docs-sidebar form.docs-search>input.is-success:active,.is-success.is-active.textarea,.is-success.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.is-warning.textarea,.is-warning.input,#documenter .docs-sidebar form.docs-search>input.is-warning{border-color:#a98800}.is-warning.textarea:focus,.is-warning.input:focus,#documenter .docs-sidebar form.docs-search>input.is-warning:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-warning.textarea:active,.is-warning.input:active,#documenter .docs-sidebar form.docs-search>input.is-warning:active,.is-warning.is-active.textarea,.is-warning.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.is-danger.textarea,.is-danger.input,#documenter .docs-sidebar form.docs-search>input.is-danger{border-color:#cb3c33}.is-danger.textarea:focus,.is-danger.input:focus,#documenter .docs-sidebar form.docs-search>input.is-danger:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,#documenter .docs-sidebar form.docs-search>input.is-focused,.is-danger.textarea:active,.is-danger.input:active,#documenter .docs-sidebar form.docs-search>input.is-danger:active,.is-danger.is-active.textarea,.is-danger.is-active.input,#documenter .docs-sidebar form.docs-search>input.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.is-small.textarea,.is-small.input,#documenter .docs-sidebar form.docs-search>input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input,#documenter .docs-sidebar form.docs-search>input.is-medium{font-size:1.25rem}.is-large.textarea,.is-large.input,#documenter .docs-sidebar form.docs-search>input.is-large{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input,#documenter .docs-sidebar form.docs-search>input.is-fullwidth{display:block;width:100%}.is-inline.textarea,.is-inline.input,#documenter .docs-sidebar form.docs-search>input.is-inline{display:inline;width:auto}.input.is-rounded,#documenter .docs-sidebar form.docs-search>input{border-radius:9999px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}.input.is-static,#documenter .docs-sidebar form.docs-search>input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#222}.radio[disabled],.checkbox[disabled],fieldset[disabled] .radio,fieldset[disabled] .checkbox,.radio input[disabled],.checkbox input[disabled]{color:#6b6b6b;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#2e63b8;right:1.125em;z-index:4}.select.is-rounded select,#documenter .docs-sidebar form.docs-search>input.select select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:0.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#222}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.select.is-dark:not(:hover)::after,.content kbd.select:not(:hover)::after{border-color:#363636}.select.is-dark select,.content kbd.select select{border-color:#363636}.select.is-dark select:hover,.content kbd.select select:hover,.select.is-dark select.is-hovered,.content kbd.select select.is-hovered{border-color:#292929}.select.is-dark select:focus,.content kbd.select select:focus,.select.is-dark select.is-focused,.content kbd.select select.is-focused,.select.is-dark select:active,.content kbd.select select:active,.select.is-dark select.is-active,.content kbd.select select.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.select.is-primary:not(:hover)::after,.docstring>section>a.select.docs-sourcelink:not(:hover)::after{border-color:#4eb5de}.select.is-primary select,.docstring>section>a.select.docs-sourcelink select{border-color:#4eb5de}.select.is-primary select:hover,.docstring>section>a.select.docs-sourcelink select:hover,.select.is-primary select.is-hovered,.docstring>section>a.select.docs-sourcelink select.is-hovered{border-color:#39acda}.select.is-primary select:focus,.docstring>section>a.select.docs-sourcelink select:focus,.select.is-primary select.is-focused,.docstring>section>a.select.docs-sourcelink select.is-focused,.select.is-primary select:active,.docstring>section>a.select.docs-sourcelink select:active,.select.is-primary select.is-active,.docstring>section>a.select.docs-sourcelink select.is-active{box-shadow:0 0 0 0.125em rgba(78,181,222,0.25)}.select.is-link:not(:hover)::after{border-color:#2e63b8}.select.is-link select{border-color:#2e63b8}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2958a4}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(46,99,184,0.25)}.select.is-info:not(:hover)::after{border-color:#3c5dcd}.select.is-info select{border-color:#3c5dcd}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#3151bf}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(60,93,205,0.25)}.select.is-success:not(:hover)::after{border-color:#259a12}.select.is-success select{border-color:#259a12}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#20830f}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(37,154,18,0.25)}.select.is-warning:not(:hover)::after{border-color:#a98800}.select.is-warning select{border-color:#a98800}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#8f7300}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(169,136,0,0.25)}.select.is-danger:not(:hover)::after{border-color:#cb3c33}.select.is-danger select{border-color:#cb3c33}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#b7362e}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(203,60,51,0.25)}.select.is-small,#documenter .docs-sidebar form.docs-search>input.select{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#6b6b6b !important;opacity:0.5}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:0.625em;transform:none}.select.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:rgba(0,0,0,0.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-dark .file-cta,.content kbd.file .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.content kbd.file:hover .file-cta,.file.is-dark.is-hovered .file-cta,.content kbd.file.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.content kbd.file:focus .file-cta,.file.is-dark.is-focused .file-cta,.content kbd.file.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,54,54,0.25);color:#fff}.file.is-dark:active .file-cta,.content kbd.file:active .file-cta,.file.is-dark.is-active .file-cta,.content kbd.file.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta,.docstring>section>a.file.docs-sourcelink .file-cta{background-color:#4eb5de;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.docstring>section>a.file.docs-sourcelink:hover .file-cta,.file.is-primary.is-hovered .file-cta,.docstring>section>a.file.is-hovered.docs-sourcelink .file-cta{background-color:#43b1dc;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.docstring>section>a.file.docs-sourcelink:focus .file-cta,.file.is-primary.is-focused .file-cta,.docstring>section>a.file.is-focused.docs-sourcelink .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(78,181,222,0.25);color:#fff}.file.is-primary:active .file-cta,.docstring>section>a.file.docs-sourcelink:active .file-cta,.file.is-primary.is-active .file-cta,.docstring>section>a.file.is-active.docs-sourcelink .file-cta{background-color:#39acda;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#2e63b8;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#2b5eae;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(46,99,184,0.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2958a4;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3c5dcd;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#3355c9;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(60,93,205,0.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#3151bf;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#259a12;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#228f11;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(37,154,18,0.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#20830f;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#a98800;border-color:transparent;color:#fff}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#9c7d00;border-color:transparent;color:#fff}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(169,136,0,0.25);color:#fff}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#8f7300;border-color:transparent;color:#fff}.file.is-danger .file-cta{background-color:#cb3c33;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#c13930;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(203,60,51,0.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#b7362e;border-color:transparent;color:#fff}.file.is-small,#documenter .docs-sidebar form.docs-search>input.file{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa,#documenter .docs-sidebar form.docs-search>input.is-boxed .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#222}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#222}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#222}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#222;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:0.5em}.label.is-small,#documenter .docs-sidebar form.docs-search>input.label{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:0.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark,.content kbd.help{color:#363636}.help.is-primary,.docstring>section>a.help.docs-sourcelink{color:#4eb5de}.help.is-link{color:#2e63b8}.help.is-info{color:#3c5dcd}.help.is-success{color:#259a12}.help.is-warning{color:#a98800}.help.is-danger{color:#cb3c33}.field:not(:last-child){margin-bottom:0.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:not(:first-child):not(:last-child) form.docs-search>input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:first-child:not(:only-child) form.docs-search>input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .field.has-addons .control:last-child:not(:only-child) form.docs-search>input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button.is-hovered:not([disabled]),.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):hover,.field.has-addons .control .input.is-hovered:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-hovered:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-hovered:not([disabled]),.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select.is-hovered:not([disabled]){z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button.is-focused:not([disabled]),.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button.is-active:not([disabled]),.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus,.field.has-addons .control .input.is-focused:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]),.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active,.field.has-addons .control .input.is-active:not([disabled]),.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]),#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]),.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select.is-focused:not([disabled]),.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select.is-active:not([disabled]){z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button.is-focused:not([disabled]):hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button.is-active:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):focus:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):focus:hover,.field.has-addons .control .input.is-focused:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-focused:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-focused:not([disabled]):hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input:not([disabled]):active:hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input:not([disabled]):active:hover,.field.has-addons .control .input.is-active:not([disabled]):hover,.field.has-addons .control #documenter .docs-sidebar form.docs-search>input.is-active:not([disabled]):hover,#documenter .docs-sidebar .field.has-addons .control form.docs-search>input.is-active:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select.is-focused:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select.is-active:not([disabled]):hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small,#documenter .docs-sidebar form.docs-search>input.field-label{font-size:.75rem;padding-top:0.375em}.field-label.is-normal{padding-top:0.375em}.field-label.is-medium{font-size:1.25rem;padding-top:0.375em}.field-label.is-large{font-size:1.5rem;padding-top:0.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input:focus~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#222}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-medium~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input.is-large~.icon,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-left form.docs-search>input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right #documenter .docs-sidebar form.docs-search>input,#documenter .docs-sidebar .control.has-icons-right form.docs-search>input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:0.625em;z-index:4}.control.is-loading.is-small:after,#documenter .docs-sidebar form.docs-search>input.is-loading:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#2e63b8;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#222;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small,#documenter .docs-sidebar form.docs-search>input.breadcrumb{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:#bbb;color:#222;max-width:100%;position:relative}.card-footer:first-child,.card-content:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-footer:last-child,.card-content:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);display:flex}.card-header-title{align-items:center;color:#222;display:flex;flex-grow:1;font-weight:700;padding:0.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:0.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:rgba(0,0,0,0);padding:1.5rem}.card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:#bbb;padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#222;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#2e63b8;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:0.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,0.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,0.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small,#documenter .docs-sidebar form.docs-search>input.menu{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#222;display:block;padding:0.5em 0.75em}.menu-list a:hover{background-color:#f5f5f5;color:#222}.menu-list a.is-active{background-color:#2e63b8;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#6b6b6b;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small,#documenter .docs-sidebar form.docs-search>input.message{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark,.content kbd.message{background-color:#fafafa}.message.is-dark .message-header,.content kbd.message .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body,.content kbd.message .message-body{border-color:#363636}.message.is-primary,.docstring>section>a.message.docs-sourcelink{background-color:#eef8fc}.message.is-primary .message-header,.docstring>section>a.message.docs-sourcelink .message-header{background-color:#4eb5de;color:#fff}.message.is-primary .message-body,.docstring>section>a.message.docs-sourcelink .message-body{border-color:#4eb5de;color:#1a6d8e}.message.is-link{background-color:#eff3fb}.message.is-link .message-header{background-color:#2e63b8;color:#fff}.message.is-link .message-body{border-color:#2e63b8;color:#3169c4}.message.is-info{background-color:#eff2fb}.message.is-info .message-header{background-color:#3c5dcd;color:#fff}.message.is-info .message-body{border-color:#3c5dcd;color:#3253c3}.message.is-success{background-color:#effded}.message.is-success .message-header{background-color:#259a12;color:#fff}.message.is-success .message-body{border-color:#259a12;color:#2ec016}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#a98800;color:#fff}.message.is-warning .message-body{border-color:#a98800;color:#cca400}.message.is-danger{background-color:#fbefef}.message.is-danger .message-header{background-color:#cb3c33;color:#fff}.message.is-danger .message-body{border-color:#cb3c33;color:#c03930}.message-header{align-items:center;background-color:#222;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#222;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:rgba(0,0,0,0)}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,0.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px){.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#222;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1056px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1056px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}}.navbar.is-dark,.content kbd.navbar{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.content kbd.navbar .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link,.content kbd.navbar .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.content kbd.navbar .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.content kbd.navbar .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.content kbd.navbar .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.content kbd.navbar .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.content kbd.navbar .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active,.content kbd.navbar .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after,.content kbd.navbar .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger,.content kbd.navbar .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-dark .navbar-start>.navbar-item,.content kbd.navbar .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.content kbd.navbar .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.content kbd.navbar .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link,.content kbd.navbar .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.content kbd.navbar .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.content kbd.navbar .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.content kbd.navbar .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.content kbd.navbar .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.content kbd.navbar .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.content kbd.navbar .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.content kbd.navbar .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.content kbd.navbar .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.content kbd.navbar .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.content kbd.navbar .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.content kbd.navbar .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active,.content kbd.navbar .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.content kbd.navbar .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after,.content kbd.navbar .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.content kbd.navbar .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.content kbd.navbar .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active,.content kbd.navbar .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary,.docstring>section>a.navbar.docs-sourcelink{background-color:#4eb5de;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger,.docstring>section>a.navbar.docs-sourcelink .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-primary .navbar-start>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.docstring>section>a.navbar.docs-sourcelink .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link.is-active{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after,.docstring>section>a.navbar.docs-sourcelink .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.docstring>section>a.navbar.docs-sourcelink .navbar-item.has-dropdown.is-active .navbar-link{background-color:#39acda;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active,.docstring>section>a.navbar.docs-sourcelink .navbar-dropdown a.navbar-item.is-active{background-color:#4eb5de;color:#fff}}.navbar.is-link{background-color:#2e63b8;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2958a4;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#2e63b8;color:#fff}}.navbar.is-info{background-color:#3c5dcd;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3151bf;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3c5dcd;color:#fff}}.navbar.is-success{background-color:#259a12;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20830f;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20830f;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20830f;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#259a12;color:#fff}}.navbar.is-warning{background-color:#a98800;color:#fff}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:#fff}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-warning .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:#fff}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#8f7300;color:#fff}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#a98800;color:#fff}}.navbar.is-danger{background-color:#cb3c33;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1056px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#b7362e;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#cb3c33;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#222;-moz-appearance:none;-webkit-appearance:none;appearance:none;background:none;border:none;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,0.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#222;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#2e63b8}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8}.navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#2e63b8;border-bottom-style:solid;border-bottom-width:3px;color:#2e63b8;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#2e63b8;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1055px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1056px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#2e63b8}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1), 0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small,#documenter .docs-sidebar form.docs-search>input.pagination{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-previous,.pagination.is-rounded .pagination-next,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-next{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link,#documenter .docs-sidebar form.docs-search>input.pagination .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#222;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3c5dcd}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}.pagination-previous[disabled],.pagination-previous.is-disabled,.pagination-next[disabled],.pagination-next.is-disabled,.pagination-link[disabled],.pagination-link.is-disabled{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#6b6b6b;opacity:0.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#2e63b8;border-color:#2e63b8;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:#bbb;font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading,.content kbd.panel .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active,.content kbd.panel .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon,.content kbd.panel .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading,.docstring>section>a.panel.docs-sourcelink .panel-heading{background-color:#4eb5de;color:#fff}.panel.is-primary .panel-tabs a.is-active,.docstring>section>a.panel.docs-sourcelink .panel-tabs a.is-active{border-bottom-color:#4eb5de}.panel.is-primary .panel-block.is-active .panel-icon,.docstring>section>a.panel.docs-sourcelink .panel-block.is-active .panel-icon{color:#4eb5de}.panel.is-link .panel-heading{background-color:#2e63b8;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#2e63b8}.panel.is-link .panel-block.is-active .panel-icon{color:#2e63b8}.panel.is-info .panel-heading{background-color:#3c5dcd;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3c5dcd}.panel.is-info .panel-block.is-active .panel-icon{color:#3c5dcd}.panel.is-success .panel-heading{background-color:#259a12;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#259a12}.panel.is-success .panel-block.is-active .panel-icon{color:#259a12}.panel.is-warning .panel-heading{background-color:#a98800;color:#fff}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#a98800}.panel.is-warning .panel-block.is-active .panel-icon{color:#a98800}.panel.is-danger .panel-heading{background-color:#cb3c33;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#cb3c33}.panel.is-danger .panel-block.is-active .panel-icon{color:#cb3c33}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#222;font-size:1.25em;font-weight:700;line-height:1.25;padding:0.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:0.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#222}.panel-list a:hover{color:#2e63b8}.panel-block{align-items:center;color:#222;display:flex;justify-content:flex-start;padding:0.5em 0.75em}.panel-block input[type="checkbox"]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#2e63b8;color:#363636}.panel-block.is-active .panel-icon{color:#2e63b8}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#6b6b6b;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#222;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#222;color:#222}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#2e63b8;color:#2e63b8}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:0.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:rgba(0,0,0,0) !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#2e63b8;border-color:#2e63b8;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small,#documenter .docs-sidebar form.docs-search>input.tabs{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333337%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333337%}.columns.is-mobile>.column.is-2{flex:none;width:16.66666674%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66666674%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333337%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333337%}.columns.is-mobile>.column.is-5{flex:none;width:41.66666674%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66666674%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333337%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333337%}.columns.is-mobile>.column.is-8{flex:none;width:66.66666674%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66666674%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333337%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333337%}.columns.is-mobile>.column.is-11{flex:none;width:91.66666674%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66666674%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333337%}.column.is-offset-1-mobile{margin-left:8.33333337%}.column.is-2-mobile{flex:none;width:16.66666674%}.column.is-offset-2-mobile{margin-left:16.66666674%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333337%}.column.is-offset-4-mobile{margin-left:33.33333337%}.column.is-5-mobile{flex:none;width:41.66666674%}.column.is-offset-5-mobile{margin-left:41.66666674%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333337%}.column.is-offset-7-mobile{margin-left:58.33333337%}.column.is-8-mobile{flex:none;width:66.66666674%}.column.is-offset-8-mobile{margin-left:66.66666674%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333337%}.column.is-offset-10-mobile{margin-left:83.33333337%}.column.is-11-mobile{flex:none;width:91.66666674%}.column.is-offset-11-mobile{margin-left:91.66666674%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333337%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333337%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66666674%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66666674%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333337%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333337%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66666674%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66666674%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333337%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333337%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66666674%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66666674%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333337%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333337%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66666674%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66666674%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1055px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333337%}.column.is-offset-1-touch{margin-left:8.33333337%}.column.is-2-touch{flex:none;width:16.66666674%}.column.is-offset-2-touch{margin-left:16.66666674%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333337%}.column.is-offset-4-touch{margin-left:33.33333337%}.column.is-5-touch{flex:none;width:41.66666674%}.column.is-offset-5-touch{margin-left:41.66666674%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333337%}.column.is-offset-7-touch{margin-left:58.33333337%}.column.is-8-touch{flex:none;width:66.66666674%}.column.is-offset-8-touch{margin-left:66.66666674%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333337%}.column.is-offset-10-touch{margin-left:83.33333337%}.column.is-11-touch{flex:none;width:91.66666674%}.column.is-offset-11-touch{margin-left:91.66666674%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1056px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333337%}.column.is-offset-1-desktop{margin-left:8.33333337%}.column.is-2-desktop{flex:none;width:16.66666674%}.column.is-offset-2-desktop{margin-left:16.66666674%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333337%}.column.is-offset-4-desktop{margin-left:33.33333337%}.column.is-5-desktop{flex:none;width:41.66666674%}.column.is-offset-5-desktop{margin-left:41.66666674%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333337%}.column.is-offset-7-desktop{margin-left:58.33333337%}.column.is-8-desktop{flex:none;width:66.66666674%}.column.is-offset-8-desktop{margin-left:66.66666674%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333337%}.column.is-offset-10-desktop{margin-left:83.33333337%}.column.is-11-desktop{flex:none;width:91.66666674%}.column.is-offset-11-desktop{margin-left:91.66666674%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333337%}.column.is-offset-1-widescreen{margin-left:8.33333337%}.column.is-2-widescreen{flex:none;width:16.66666674%}.column.is-offset-2-widescreen{margin-left:16.66666674%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333337%}.column.is-offset-4-widescreen{margin-left:33.33333337%}.column.is-5-widescreen{flex:none;width:41.66666674%}.column.is-offset-5-widescreen{margin-left:41.66666674%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333337%}.column.is-offset-7-widescreen{margin-left:58.33333337%}.column.is-8-widescreen{flex:none;width:66.66666674%}.column.is-offset-8-widescreen{margin-left:66.66666674%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333337%}.column.is-offset-10-widescreen{margin-left:83.33333337%}.column.is-11-widescreen{flex:none;width:91.66666674%}.column.is-offset-11-widescreen{margin-left:91.66666674%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.33333337%}.column.is-offset-1-fullhd{margin-left:8.33333337%}.column.is-2-fullhd{flex:none;width:16.66666674%}.column.is-offset-2-fullhd{margin-left:16.66666674%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333337%}.column.is-offset-4-fullhd{margin-left:33.33333337%}.column.is-5-fullhd{flex:none;width:41.66666674%}.column.is-offset-5-fullhd{margin-left:41.66666674%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333337%}.column.is-offset-7-fullhd{margin-left:58.33333337%}.column.is-8-fullhd{flex:none;width:66.66666674%}.column.is-offset-8-fullhd{margin-left:66.66666674%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333337%}.column.is-offset-10-fullhd{margin-left:83.33333337%}.column.is-11-fullhd{flex:none;width:91.66666674%}.column.is-offset-11-fullhd{margin-left:91.66666674%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1056px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: .25rem}}.columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: .5rem}}.columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: .75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1055px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1055px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1056px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1056px) and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333337%}.tile.is-2{flex:none;width:16.66666674%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333337%}.tile.is-5{flex:none;width:41.66666674%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333337%}.tile.is-8{flex:none;width:66.66666674%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333337%}.tile.is-11{flex:none;width:91.66666674%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,0.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1055px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff !important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, #fff 71%, #fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,0.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:0.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a !important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,0.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,0.7)}.hero.is-light .subtitle{color:rgba(0,0,0,0.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1055px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,0.7)}.hero.is-light .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5 !important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}.hero.is-dark,.content kbd.hero{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.content kbd.hero a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong,.content kbd.hero strong{color:inherit}.hero.is-dark .title,.content kbd.hero .title{color:#fff}.hero.is-dark .subtitle,.content kbd.hero .subtitle{color:rgba(255,255,255,0.9)}.hero.is-dark .subtitle a:not(.button),.content kbd.hero .subtitle a:not(.button),.hero.is-dark .subtitle strong,.content kbd.hero .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-dark .navbar-menu,.content kbd.hero .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.content kbd.hero .navbar-item,.hero.is-dark .navbar-link,.content kbd.hero .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-dark a.navbar-item:hover,.content kbd.hero a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.content kbd.hero a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.content kbd.hero .navbar-link:hover,.hero.is-dark .navbar-link.is-active,.content kbd.hero .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a,.content kbd.hero .tabs a{color:#fff;opacity:0.9}.hero.is-dark .tabs a:hover,.content kbd.hero .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a,.content kbd.hero .tabs li.is-active a{color:#363636 !important;opacity:1}.hero.is-dark .tabs.is-boxed a,.content kbd.hero .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a,.content kbd.hero .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.content kbd.hero .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover,.content kbd.hero .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.content kbd.hero .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.content kbd.hero .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold,.content kbd.hero.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu,.content kbd.hero.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary,.docstring>section>a.hero.docs-sourcelink{background-color:#4eb5de;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.docstring>section>a.hero.docs-sourcelink a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong,.docstring>section>a.hero.docs-sourcelink strong{color:inherit}.hero.is-primary .title,.docstring>section>a.hero.docs-sourcelink .title{color:#fff}.hero.is-primary .subtitle,.docstring>section>a.hero.docs-sourcelink .subtitle{color:rgba(255,255,255,0.9)}.hero.is-primary .subtitle a:not(.button),.docstring>section>a.hero.docs-sourcelink .subtitle a:not(.button),.hero.is-primary .subtitle strong,.docstring>section>a.hero.docs-sourcelink .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-primary .navbar-menu,.docstring>section>a.hero.docs-sourcelink .navbar-menu{background-color:#4eb5de}}.hero.is-primary .navbar-item,.docstring>section>a.hero.docs-sourcelink .navbar-item,.hero.is-primary .navbar-link,.docstring>section>a.hero.docs-sourcelink .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-primary a.navbar-item:hover,.docstring>section>a.hero.docs-sourcelink a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.docstring>section>a.hero.docs-sourcelink a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.docstring>section>a.hero.docs-sourcelink .navbar-link:hover,.hero.is-primary .navbar-link.is-active,.docstring>section>a.hero.docs-sourcelink .navbar-link.is-active{background-color:#39acda;color:#fff}.hero.is-primary .tabs a,.docstring>section>a.hero.docs-sourcelink .tabs a{color:#fff;opacity:0.9}.hero.is-primary .tabs a:hover,.docstring>section>a.hero.docs-sourcelink .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs li.is-active a{color:#4eb5de !important;opacity:1}.hero.is-primary .tabs.is-boxed a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.docstring>section>a.hero.docs-sourcelink .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#4eb5de}.hero.is-primary.is-bold,.docstring>section>a.hero.is-bold.docs-sourcelink{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu,.docstring>section>a.hero.is-bold.docs-sourcelink .navbar-menu{background-image:linear-gradient(141deg, #1bc7de 0%, #4eb5de 71%, #5fa9e7 100%)}}.hero.is-link{background-color:#2e63b8;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,0.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-link .navbar-menu{background-color:#2e63b8}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2958a4;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:0.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#2e63b8 !important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#2e63b8}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1b6098 0%, #2e63b8 71%, #2d51d2 100%)}}.hero.is-info{background-color:#3c5dcd;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,0.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-info .navbar-menu{background-color:#3c5dcd}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#3151bf;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:0.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3c5dcd !important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3c5dcd}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #215bb5 0%, #3c5dcd 71%, #4b53d8 100%)}}.hero.is-success{background-color:#259a12;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,0.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-success .navbar-menu{background-color:#259a12}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#20830f;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:0.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#259a12 !important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#259a12}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #287207 0%, #259a12 71%, #10b614 100%)}}.hero.is-warning{background-color:#a98800;color:#fff}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:#fff}.hero.is-warning .subtitle{color:rgba(255,255,255,0.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-warning .navbar-menu{background-color:#a98800}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#8f7300;color:#fff}.hero.is-warning .tabs a{color:#fff;opacity:0.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#a98800 !important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:#fff}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#a98800}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #764b00 0%, #a98800 71%, #c2bd00 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #764b00 0%, #a98800 71%, #c2bd00 100%)}}.hero.is-danger{background-color:#cb3c33;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1055px){.hero.is-danger .navbar-menu{background-color:#cb3c33}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#b7362e;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:0.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#cb3c33 !important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#cb3c33}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ac1f2e 0%, #cb3c33 71%, #d66341 100%)}}.hero.is-small .hero-body,#documenter .docs-sidebar form.docs-search>input.hero .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width: 769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width: 1056px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}h1 .docs-heading-anchor,h1 .docs-heading-anchor:hover,h1 .docs-heading-anchor:visited,h2 .docs-heading-anchor,h2 .docs-heading-anchor:hover,h2 .docs-heading-anchor:visited,h3 .docs-heading-anchor,h3 .docs-heading-anchor:hover,h3 .docs-heading-anchor:visited,h4 .docs-heading-anchor,h4 .docs-heading-anchor:hover,h4 .docs-heading-anchor:visited,h5 .docs-heading-anchor,h5 .docs-heading-anchor:hover,h5 .docs-heading-anchor:visited,h6 .docs-heading-anchor,h6 .docs-heading-anchor:hover,h6 .docs-heading-anchor:visited{color:#222}h1 .docs-heading-anchor-permalink,h2 .docs-heading-anchor-permalink,h3 .docs-heading-anchor-permalink,h4 .docs-heading-anchor-permalink,h5 .docs-heading-anchor-permalink,h6 .docs-heading-anchor-permalink{visibility:hidden;vertical-align:middle;margin-left:0.5em;font-size:0.7rem}h1 .docs-heading-anchor-permalink::before,h2 .docs-heading-anchor-permalink::before,h3 .docs-heading-anchor-permalink::before,h4 .docs-heading-anchor-permalink::before,h5 .docs-heading-anchor-permalink::before,h6 .docs-heading-anchor-permalink::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f0c1"}h1:hover .docs-heading-anchor-permalink,h2:hover .docs-heading-anchor-permalink,h3:hover .docs-heading-anchor-permalink,h4:hover .docs-heading-anchor-permalink,h5:hover .docs-heading-anchor-permalink,h6:hover .docs-heading-anchor-permalink{visibility:visible}.docs-dark-only{display:none !important}pre{position:relative;overflow:hidden}pre code,pre code.hljs{padding:0 .75rem !important;overflow:auto;display:block}pre code:first-of-type,pre code.hljs:first-of-type{padding-top:0.5rem !important}pre code:last-of-type,pre code.hljs:last-of-type{padding-bottom:0.5rem !important}pre .copy-button{opacity:0.2;transition:opacity 0.2s;position:absolute;right:0em;top:0em;padding:0.5em;width:2.5em;height:2.5em;background:transparent;border:none;font-family:"Font Awesome 6 Free";color:#222;cursor:pointer;text-align:center}pre .copy-button:focus,pre .copy-button:hover{opacity:1;background:rgba(34,34,34,0.1);color:#2e63b8}pre .copy-button.success{color:#259a12;opacity:1}pre .copy-button.error{color:#cb3c33;opacity:1}pre:hover .copy-button{opacity:1}.admonition{background-color:#f5f5f5;border-style:solid;border-width:2px;border-color:#4a4a4a;border-radius:4px;font-size:1rem}.admonition strong{color:currentColor}.admonition.is-small,#documenter .docs-sidebar form.docs-search>input.admonition{font-size:.75rem}.admonition.is-medium{font-size:1.25rem}.admonition.is-large{font-size:1.5rem}.admonition.is-default{background-color:#f5f5f5;border-color:#4a4a4a}.admonition.is-default>.admonition-header{background-color:rgba(0,0,0,0);color:#4a4a4a}.admonition.is-default>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-info{background-color:#f5f5f5;border-color:#3c5dcd}.admonition.is-info>.admonition-header{background-color:rgba(0,0,0,0);color:#3c5dcd}.admonition.is-info>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-success{background-color:#f5f5f5;border-color:#259a12}.admonition.is-success>.admonition-header{background-color:rgba(0,0,0,0);color:#259a12}.admonition.is-success>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-warning{background-color:#f5f5f5;border-color:#a98800}.admonition.is-warning>.admonition-header{background-color:rgba(0,0,0,0);color:#a98800}.admonition.is-warning>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-danger{background-color:#f5f5f5;border-color:#cb3c33}.admonition.is-danger>.admonition-header{background-color:rgba(0,0,0,0);color:#cb3c33}.admonition.is-danger>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-compat{background-color:#f5f5f5;border-color:#3489da}.admonition.is-compat>.admonition-header{background-color:rgba(0,0,0,0);color:#3489da}.admonition.is-compat>.admonition-body{color:rgba(0,0,0,0.7)}.admonition.is-todo{background-color:#f5f5f5;border-color:#9558b2}.admonition.is-todo>.admonition-header{background-color:rgba(0,0,0,0);color:#9558b2}.admonition.is-todo>.admonition-body{color:rgba(0,0,0,0.7)}.admonition-header{color:#4a4a4a;background-color:rgba(0,0,0,0);align-items:center;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.5rem .75rem;position:relative}.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;margin-right:.75rem;content:"\f06a"}details.admonition.is-details>.admonition-header{list-style:none}details.admonition.is-details>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f055"}details.admonition.is-details[open]>.admonition-header:before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f056"}.admonition-body{color:#222;padding:0.5rem .75rem}.admonition-body pre{background-color:#f5f5f5}.admonition-body code{background-color:rgba(0,0,0,0.05)}.docstring{margin-bottom:1em;background-color:rgba(0,0,0,0);border:2px solid #dbdbdb;border-radius:4px;box-shadow:2px 2px 3px rgba(10,10,10,0.1);max-width:100%}.docstring>header{cursor:pointer;display:flex;flex-grow:1;align-items:stretch;padding:0.5rem .75rem;background-color:#f5f5f5;box-shadow:0 0.125em 0.25em rgba(10,10,10,0.1);box-shadow:none;border-bottom:1px solid #dbdbdb;overflow:auto}.docstring>header code{background-color:transparent}.docstring>header .docstring-article-toggle-button{min-width:1.1rem;padding:0.2rem 0.2rem 0.2rem 0}.docstring>header .docstring-binding{margin-right:0.3em}.docstring>header .docstring-category{margin-left:0.3em}.docstring>section{position:relative;padding:.75rem .75rem;border-bottom:1px solid #dbdbdb}.docstring>section:last-child{border-bottom:none}.docstring>section>a.docs-sourcelink{transition:opacity 0.3s;opacity:0;position:absolute;right:.375rem;bottom:.375rem}.docstring>section>a.docs-sourcelink:focus{opacity:1 !important}.docstring:hover>section>a.docs-sourcelink{opacity:0.2}.docstring:focus-within>section>a.docs-sourcelink{opacity:0.2}.docstring>section:hover a.docs-sourcelink{opacity:1}.documenter-example-output{background-color:#fff}.outdated-warning-overlay{position:fixed;top:0;left:0;right:0;box-shadow:0 0 10px rgba(0,0,0,0.3);z-index:999;background-color:#f5f5f5;color:rgba(0,0,0,0.7);border-bottom:3px solid rgba(0,0,0,0);padding:10px 35px;text-align:center;font-size:15px}.outdated-warning-overlay .outdated-warning-closer{position:absolute;top:calc(50% - 10px);right:18px;cursor:pointer;width:12px}.outdated-warning-overlay a{color:#2e63b8}.outdated-warning-overlay a:hover{color:#363636}.content pre{border:2px solid #dbdbdb;border-radius:4px}.content code{font-weight:inherit}.content a code{color:#2e63b8}.content a:hover code{color:#363636}.content h1 code,.content h2 code,.content h3 code,.content h4 code,.content h5 code,.content h6 code{color:#222}.content table{display:block;width:initial;max-width:100%;overflow-x:auto}.content blockquote>ul:first-child,.content blockquote>ol:first-child,.content .admonition-body>ul:first-child,.content .admonition-body>ol:first-child{margin-top:0}pre,code{font-variant-ligatures:no-contextual}.breadcrumb a.is-disabled{cursor:default;pointer-events:none}.breadcrumb a.is-disabled,.breadcrumb a.is-disabled:hover{color:#222}.hljs{background:initial !important}.katex .katex-mathml{top:0;right:0}.katex-display,mjx-container,.MathJax_Display{margin:0.5em 0 !important}html{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}li.no-marker{list-style:none}#documenter .docs-main>article{overflow-wrap:break-word}#documenter .docs-main>article .math-container{overflow-x:auto;overflow-y:hidden}@media screen and (min-width: 1056px){#documenter .docs-main{max-width:52rem;margin-left:20rem;padding-right:1rem}}@media screen and (max-width: 1055px){#documenter .docs-main{width:100%}#documenter .docs-main>article{max-width:52rem;margin-left:auto;margin-right:auto;margin-bottom:1rem;padding:0 1rem}#documenter .docs-main>header,#documenter .docs-main>nav{max-width:100%;width:100%;margin:0}}#documenter .docs-main header.docs-navbar{background-color:#fff;border-bottom:1px solid #dbdbdb;z-index:2;min-height:4rem;margin-bottom:1rem;display:flex}#documenter .docs-main header.docs-navbar .breadcrumb{flex-grow:1;overflow-x:hidden}#documenter .docs-main header.docs-navbar .docs-sidebar-button{display:block;font-size:1.5rem;padding-bottom:0.1rem;margin-right:1rem}#documenter .docs-main header.docs-navbar .docs-right{display:flex;white-space:nowrap;gap:1rem;align-items:center}#documenter .docs-main header.docs-navbar .docs-right .docs-icon,#documenter .docs-main header.docs-navbar .docs-right .docs-label{display:inline-block}#documenter .docs-main header.docs-navbar .docs-right .docs-label{padding:0;margin-left:0.3em}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar .docs-right .docs-navbar-link{margin-left:0.4rem;margin-right:0.4rem}}#documenter .docs-main header.docs-navbar>*{margin:auto 0}@media screen and (max-width: 1055px){#documenter .docs-main header.docs-navbar{position:sticky;top:0;padding:0 1rem;transition-property:top, box-shadow;-webkit-transition-property:top, box-shadow;transition-duration:0.3s;-webkit-transition-duration:0.3s}#documenter .docs-main header.docs-navbar.headroom--not-top{box-shadow:.2rem 0rem .4rem #bbb;transition-duration:0.7s;-webkit-transition-duration:0.7s}#documenter .docs-main header.docs-navbar.headroom--unpinned.headroom--not-top.headroom--not-bottom{top:-4.5rem;transition-duration:0.7s;-webkit-transition-duration:0.7s}}#documenter .docs-main section.footnotes{border-top:1px solid #dbdbdb}#documenter .docs-main section.footnotes li .tag:first-child,#documenter .docs-main section.footnotes li .docstring>section>a.docs-sourcelink:first-child,#documenter .docs-main section.footnotes li .content kbd:first-child,.content #documenter .docs-main section.footnotes li kbd:first-child{margin-right:1em;margin-bottom:0.4em}#documenter .docs-main .docs-footer{display:flex;flex-wrap:wrap;margin-left:0;margin-right:0;border-top:1px solid #dbdbdb;padding-top:1rem;padding-bottom:1rem}@media screen and (max-width: 1055px){#documenter .docs-main .docs-footer{padding-left:1rem;padding-right:1rem}}#documenter .docs-main .docs-footer .docs-footer-nextpage,#documenter .docs-main .docs-footer .docs-footer-prevpage{flex-grow:1}#documenter .docs-main .docs-footer .docs-footer-nextpage{text-align:right}#documenter .docs-main .docs-footer .flexbox-break{flex-basis:100%;height:0}#documenter .docs-main .docs-footer .footer-message{font-size:0.8em;margin:0.5em auto 0 auto;text-align:center}#documenter .docs-sidebar{display:flex;flex-direction:column;color:#0a0a0a;background-color:#f5f5f5;border-right:1px solid #dbdbdb;padding:0;flex:0 0 18rem;z-index:5;font-size:1rem;position:fixed;left:-18rem;width:18rem;height:100%;transition:left 0.3s}#documenter .docs-sidebar.visible{left:0;box-shadow:.4rem 0rem .8rem #bbb}@media screen and (min-width: 1056px){#documenter .docs-sidebar.visible{box-shadow:none}}@media screen and (min-width: 1056px){#documenter .docs-sidebar{left:0;top:0}}#documenter .docs-sidebar .docs-logo{margin-top:1rem;padding:0 1rem}#documenter .docs-sidebar .docs-logo>img{max-height:6rem;margin:auto}#documenter .docs-sidebar .docs-package-name{flex-shrink:0;font-size:1.5rem;font-weight:700;text-align:center;white-space:nowrap;overflow:hidden;padding:0.5rem 0}#documenter .docs-sidebar .docs-package-name .docs-autofit{max-width:16.2rem}#documenter .docs-sidebar .docs-package-name a,#documenter .docs-sidebar .docs-package-name a:hover{color:#0a0a0a}#documenter .docs-sidebar .docs-version-selector{border-top:1px solid #dbdbdb;display:none;padding:0.5rem}#documenter .docs-sidebar .docs-version-selector.visible{display:flex}#documenter .docs-sidebar ul.docs-menu{flex-grow:1;user-select:none;border-top:1px solid #dbdbdb;padding-bottom:1.5rem}#documenter .docs-sidebar ul.docs-menu>li>.tocitem{font-weight:bold}#documenter .docs-sidebar ul.docs-menu>li li{font-size:.95rem;margin-left:1em;border-left:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu input.collapse-toggle{display:none}#documenter .docs-sidebar ul.docs-menu ul.collapsed{display:none}#documenter .docs-sidebar ul.docs-menu input:checked~ul.collapsed{display:block}#documenter .docs-sidebar ul.docs-menu label.tocitem{display:flex}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-label{flex-grow:2}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron{display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;font-size:.75rem;margin-left:1rem;margin-top:auto;margin-bottom:auto}#documenter .docs-sidebar ul.docs-menu label.tocitem .docs-chevron::before{font-family:"Font Awesome 6 Free";font-weight:900;content:"\f054"}#documenter .docs-sidebar ul.docs-menu input:checked~label.tocitem .docs-chevron::before{content:"\f078"}#documenter .docs-sidebar ul.docs-menu .tocitem{display:block;padding:0.5rem 0.5rem}#documenter .docs-sidebar ul.docs-menu .tocitem,#documenter .docs-sidebar ul.docs-menu .tocitem:hover{color:#0a0a0a;background:#f5f5f5}#documenter .docs-sidebar ul.docs-menu a.tocitem:hover,#documenter .docs-sidebar ul.docs-menu label.tocitem:hover{color:#0a0a0a;background-color:#ebebeb}#documenter .docs-sidebar ul.docs-menu li.is-active{border-top:1px solid #dbdbdb;border-bottom:1px solid #dbdbdb;background-color:#fff}#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem,#documenter .docs-sidebar ul.docs-menu li.is-active .tocitem:hover{background-color:#fff;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu li.is-active ul.internal .tocitem:hover{background-color:#ebebeb;color:#0a0a0a}#documenter .docs-sidebar ul.docs-menu>li.is-active:first-child{border-top:none}#documenter .docs-sidebar ul.docs-menu ul.internal{margin:0 0.5rem 0.5rem;border-top:1px solid #dbdbdb}#documenter .docs-sidebar ul.docs-menu ul.internal li{font-size:.85rem;border-left:none;margin-left:0;margin-top:0.5rem}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem{width:100%;padding:0}#documenter .docs-sidebar ul.docs-menu ul.internal .tocitem::before{content:"⚬";margin-right:0.4em}#documenter .docs-sidebar form.docs-search{margin:auto;margin-top:0.5rem;margin-bottom:0.5rem}#documenter .docs-sidebar form.docs-search>input{width:14.4rem}#documenter .docs-sidebar #documenter-search-query{color:#707070;width:14.4rem;box-shadow:inset 0 1px 2px rgba(10,10,10,0.1)}@media screen and (min-width: 1056px){#documenter .docs-sidebar ul.docs-menu{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar ul.docs-menu::-webkit-scrollbar-thumb:hover{background:#ccc}}@media screen and (max-width: 1055px){#documenter .docs-sidebar{overflow-y:auto;-webkit-overflow-scroll:touch}#documenter .docs-sidebar::-webkit-scrollbar{width:.3rem;background:none}#documenter .docs-sidebar::-webkit-scrollbar-thumb{border-radius:5px 0px 0px 5px;background:#e0e0e0}#documenter .docs-sidebar::-webkit-scrollbar-thumb:hover{background:#ccc}}kbd.search-modal-key-hints{border-radius:0.25rem;border:1px solid rgba(0,0,0,0.6);box-shadow:0 2px 0 1px rgba(0,0,0,0.6);cursor:default;font-size:0.9rem;line-height:1.5;min-width:0.75rem;text-align:center;padding:0.1rem 0.3rem;position:relative;top:-1px}.search-min-width-50{min-width:50%}.search-min-height-100{min-height:100%}.search-modal-card-body{max-height:calc(100vh - 15rem)}.search-result-link{border-radius:0.7em;transition:all 300ms}.search-result-link:hover,.search-result-link:focus{background-color:rgba(0,128,128,0.1)}.search-result-link .property-search-result-badge,.search-result-link .search-filter{transition:all 300ms}.property-search-result-badge,.search-filter{padding:0.15em 0.5em;font-size:0.8em;font-style:italic;text-transform:none !important;line-height:1.5;color:#f5f5f5;background-color:rgba(51,65,85,0.501961);border-radius:0.6rem}.search-result-link:hover .property-search-result-badge,.search-result-link:hover .search-filter,.search-result-link:focus .property-search-result-badge,.search-result-link:focus .search-filter{color:#f1f5f9;background-color:#333}.search-filter{color:#333;background-color:#f5f5f5;transition:all 300ms}.search-filter:hover,.search-filter:focus{color:#333}.search-filter-selected{color:#f5f5f5;background-color:rgba(139,0,139,0.5)}.search-filter-selected:hover,.search-filter-selected:focus{color:#f5f5f5}.search-result-highlight{background-color:#ffdd57;color:black}.search-divider{border-bottom:1px solid #dbdbdb}.search-result-title{width:85%;color:#333}.search-result-code-title{font-size:0.875rem;font-family:"JuliaMono","SFMono-Regular","Menlo","Consolas","Liberation Mono","DejaVu Sans Mono",monospace}#search-modal .modal-card-body::-webkit-scrollbar,#search-modal .filter-tabs::-webkit-scrollbar{height:10px;width:10px;background-color:transparent}#search-modal .modal-card-body::-webkit-scrollbar-thumb,#search-modal .filter-tabs::-webkit-scrollbar-thumb{background-color:gray;border-radius:1rem}#search-modal .modal-card-body::-webkit-scrollbar-track,#search-modal .filter-tabs::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.6);background-color:transparent}.w-100{width:100%}.gap-2{gap:0.5rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.ansi span.sgr1{font-weight:bolder}.ansi span.sgr2{font-weight:lighter}.ansi span.sgr3{font-style:italic}.ansi span.sgr4{text-decoration:underline}.ansi span.sgr7{color:#fff;background-color:#222}.ansi span.sgr8{color:transparent}.ansi span.sgr8 span{color:transparent}.ansi span.sgr9{text-decoration:line-through}.ansi span.sgr30{color:#242424}.ansi span.sgr31{color:#a7201f}.ansi span.sgr32{color:#066f00}.ansi span.sgr33{color:#856b00}.ansi span.sgr34{color:#2149b0}.ansi span.sgr35{color:#7d4498}.ansi span.sgr36{color:#007989}.ansi span.sgr37{color:gray}.ansi span.sgr40{background-color:#242424}.ansi span.sgr41{background-color:#a7201f}.ansi span.sgr42{background-color:#066f00}.ansi span.sgr43{background-color:#856b00}.ansi span.sgr44{background-color:#2149b0}.ansi span.sgr45{background-color:#7d4498}.ansi span.sgr46{background-color:#007989}.ansi span.sgr47{background-color:gray}.ansi span.sgr90{color:#616161}.ansi span.sgr91{color:#cb3c33}.ansi span.sgr92{color:#0e8300}.ansi span.sgr93{color:#a98800}.ansi span.sgr94{color:#3c5dcd}.ansi span.sgr95{color:#9256af}.ansi span.sgr96{color:#008fa3}.ansi span.sgr97{color:#f5f5f5}.ansi span.sgr100{background-color:#616161}.ansi span.sgr101{background-color:#cb3c33}.ansi span.sgr102{background-color:#0e8300}.ansi span.sgr103{background-color:#a98800}.ansi span.sgr104{background-color:#3c5dcd}.ansi span.sgr105{background-color:#9256af}.ansi span.sgr106{background-color:#008fa3}.ansi span.sgr107{background-color:#f5f5f5}code.language-julia-repl>span.hljs-meta{color:#066f00;font-weight:bolder}/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#F3F3F3;color:#444}.hljs-comment{color:#697070}.hljs-tag,.hljs-punctuation{color:#444a}.hljs-tag .hljs-name,.hljs-tag .hljs-attr{color:#444}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta .hljs-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-operator,.hljs-selector-pseudo{color:#ab5656}.hljs-literal{color:#695}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}.gap-4{gap:1rem} diff --git a/previews/PR826/assets/themeswap.js b/previews/PR826/assets/themeswap.js new file mode 100644 index 0000000000..9f5eebe6aa --- /dev/null +++ b/previews/PR826/assets/themeswap.js @@ -0,0 +1,84 @@ +// Small function to quickly swap out themes. Gets put into the tag.. +function set_theme_from_local_storage() { + // Initialize the theme to null, which means default + var theme = null; + // If the browser supports the localstorage and is not disabled then try to get the + // documenter theme + if (window.localStorage != null) { + // Get the user-picked theme from localStorage. May be `null`, which means the default + // theme. + theme = window.localStorage.getItem("documenter-theme"); + } + // Check if the users preference is for dark color scheme + var darkPreference = + window.matchMedia("(prefers-color-scheme: dark)").matches === true; + // Initialize a few variables for the loop: + // + // - active: will contain the index of the theme that should be active. Note that there + // is no guarantee that localStorage contains sane values. If `active` stays `null` + // we either could not find the theme or it is the default (primary) theme anyway. + // Either way, we then need to stick to the primary theme. + // + // - disabled: style sheets that should be disabled (i.e. all the theme style sheets + // that are not the currently active theme) + var active = null; + var disabled = []; + var primaryLightTheme = null; + var primaryDarkTheme = null; + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // To distinguish the default (primary) theme, it needs to have the data-theme-primary + // attribute set. + if (ss.ownerNode.getAttribute("data-theme-primary") !== null) { + primaryLightTheme = themename; + } + // Check if the theme is primary dark theme so that we could store its name in darkTheme + if (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null) { + primaryDarkTheme = themename; + } + // If we find a matching theme (and it's not the default), we'll set active to non-null + if (themename === theme) active = i; + // Store the style sheets of inactive themes so that we could disable them + if (themename !== theme) disabled.push(ss); + } + var activeTheme = null; + if (active !== null) { + // If we did find an active theme, we'll (1) add the theme--$(theme) class to + document.getElementsByTagName("html")[0].className = "theme--" + theme; + activeTheme = theme; + } else { + // If we did _not_ find an active theme, then we need to fall back to the primary theme + // which can either be dark or light, depending on the user's OS preference. + var activeTheme = darkPreference ? primaryDarkTheme : primaryLightTheme; + // In case it somehow happens that the relevant primary theme was not found in the + // preceding loop, we abort without doing anything. + if (activeTheme === null) { + console.error("Unable to determine primary theme."); + return; + } + // When switching to the primary light theme, then we must not have a class name + // for the tag. That's only for non-primary or the primary dark theme. + if (darkPreference) { + document.getElementsByTagName("html")[0].className = + "theme--" + activeTheme; + } else { + document.getElementsByTagName("html")[0].className = ""; + } + } + for (var i = 0; i < document.styleSheets.length; i++) { + var ss = document.styleSheets[i]; + // The tag of each style sheet is expected to have a data-theme-name attribute + // which must contain the name of the theme. The names in localStorage much match this. + var themename = ss.ownerNode.getAttribute("data-theme-name"); + // attribute not set => non-theme stylesheet => ignore + if (themename === null) continue; + // we'll disable all the stylesheets, except for the active one + ss.disabled = !(themename == activeTheme); + } +} +set_theme_from_local_storage(); diff --git a/previews/PR826/assets/value_function.html b/previews/PR826/assets/value_function.html new file mode 100644 index 0000000000..083868e549 --- /dev/null +++ b/previews/PR826/assets/value_function.html @@ -0,0 +1,29 @@ + + + + + + + +
+ + + diff --git a/previews/PR826/assets/warner.js b/previews/PR826/assets/warner.js new file mode 100644 index 0000000000..3f6f5d0083 --- /dev/null +++ b/previews/PR826/assets/warner.js @@ -0,0 +1,52 @@ +function maybeAddWarning() { + // DOCUMENTER_NEWEST is defined in versions.js, DOCUMENTER_CURRENT_VERSION and DOCUMENTER_STABLE + // in siteinfo.js. + // If either of these are undefined something went horribly wrong, so we abort. + if ( + window.DOCUMENTER_NEWEST === undefined || + window.DOCUMENTER_CURRENT_VERSION === undefined || + window.DOCUMENTER_STABLE === undefined + ) { + return; + } + + // Current version is not a version number, so we can't tell if it's the newest version. Abort. + if (!/v(\d+\.)*\d+/.test(window.DOCUMENTER_CURRENT_VERSION)) { + return; + } + + // Current version is newest version, so no need to add a warning. + if (window.DOCUMENTER_NEWEST === window.DOCUMENTER_CURRENT_VERSION) { + return; + } + + // Add a noindex meta tag (unless one exists) so that search engines don't index this version of the docs. + if (document.body.querySelector('meta[name="robots"]') === null) { + const meta = document.createElement("meta"); + meta.name = "robots"; + meta.content = "noindex"; + + document.getElementsByTagName("head")[0].appendChild(meta); + } + + const div = document.createElement("div"); + div.classList.add("outdated-warning-overlay"); + const closer = document.createElement("button"); + closer.classList.add("outdated-warning-closer", "delete"); + closer.addEventListener("click", function () { + document.body.removeChild(div); + }); + const href = window.documenterBaseURL + "/../" + window.DOCUMENTER_STABLE; + div.innerHTML = + 'This documentation is not for the latest stable release, but for either the development version or an older release.
Click here to go to the documentation for the latest stable release.'; + div.appendChild(closer); + document.body.appendChild(div); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", maybeAddWarning); +} else { + maybeAddWarning(); +} diff --git a/previews/PR826/changelog/index.html b/previews/PR826/changelog/index.html new file mode 100644 index 0000000000..3fd106aff7 --- /dev/null +++ b/previews/PR826/changelog/index.html @@ -0,0 +1,6 @@ + +Release notes · SDDP.jl

Release notes

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

v1.10.3 (January 22, 2025)

Other

v1.10.2 (January 13, 2025)

Fixed

Other

  • Improved test coverage (#810) (#811)
  • Improved documentation for risk measures (#813)
  • Fixed badges for GitHub actions (#816)
  • Updated API reference (#814)

v1.10.1 (November 28, 2024)

Fixed

Other

  • Documentation updates (#801)

v1.10.0 (November 19, 2024)

Added

  • Added root_node_risk_measure keyword to train (#804)

Fixed

  • Fixed a bug with cut sharing in a graph with zero-probability arcs (#797)

Other

v1.9.0 (October 17, 2024)

Added

Fixed

  • Fixed the tests to skip threading tests if running in serial (#770)
  • Fixed BanditDuality to handle the case where the standard deviation is NaN (#779)
  • Fixed an error when lagged state variables are encountered in MSPFormat (#786)
  • Fixed publication_plot with replications of different lengths (#788)
  • Fixed CTRL+C interrupting the code at unsafe points (#789)

Other

  • Documentation improvements (#771) (#772)
  • Updated printing because of changes in JuMP (#773)

v1.8.1 (August 5, 2024)

Fixed

  • Fixed various issues with SDDP.Threaded() (#761)
  • Fixed a deprecation warning for sorting a dictionary (#763)

Other

  • Updated copyright notices (#762)
  • Updated .JuliaFormatter.toml (#764)

v1.8.0 (July 24, 2024)

Added

  • Added SDDP.Threaded(), which is an experimental parallel scheme that supports solving problems using multiple threads. Some parts of SDDP.jl may not be thread-safe, and this can cause incorrect results, segfaults, or other errors. Please use with care and report any issues by opening a GitHub issue. (#758)

Other

  • Documentation improvements and fixes (#747) (#759)

v1.7.0 (June 4, 2024)

Added

  • Added sample_backward_noise_terms_with_state for creating backward pass sampling schemes that depend on the current primal state. (#742) (Thanks @arthur-brigatto)

Fixed

  • Fixed error message when publication_plot has non-finite data (#738)

Other

  • Updated the logo constructor (#730)

v1.6.7 (February 1, 2024)

Fixed

  • Fixed non-constant state dimension in the MSPFormat reader (#695)
  • Fixed SimulatorSamplingScheme for deterministic nodes (#710)
  • Fixed line search in BFGS (#711)
  • Fixed handling of NEARLY_FEASIBLE_POINT status (#726)

Other

  • Documentation improvements (#692) (#694) (#706) (#716) (#727)
  • Updated to StochOptFormat v1.0 (#705)
  • Added an experimental OuterApproximation algorithm (#709)
  • Updated .gitignore (#717)
  • Added code for MDP paper (#720) (#721)
  • Added Google analytics (#723)

v1.6.6 (September 29, 2023)

Other

v1.6.5 (September 25, 2023)

Fixed

Other

  • Updated tutorials (#677) (#678) (#682) (#683)
  • Fixed documentation preview (#679)

v1.6.4 (September 23, 2023)

Fixed

Other

  • Documentation updates (#658) (#666) (#671)
  • Switch to GitHub action for deploying docs (#668) (#670)
  • Update to Documenter@1 (#669)

v1.6.3 (September 8, 2023)

Fixed

  • Fixed default stopping rule with iteration_limit or time_limit set (#662)

Other

  • Various documentation improvements (#651) (#657) (#659) (#660)

v1.6.2 (August 24, 2023)

Fixed

  • MSPFormat now detect and exploit stagewise independent lattices (#653)
  • Fixed set_optimizer for models read from file (#654)

Other

  • Fixed typo in pglib_opf.jl (#647)
  • Fixed documentation build and added color (#652)

v1.6.1 (July 20, 2023)

Fixed

  • Fixed bugs in MSPFormat reader (#638) (#639)

Other

  • Clarified OutOfSampleMonteCarlo docstring (#643)

v1.6.0 (July 3, 2023)

Added

Other

v1.5.1 (June 30, 2023)

This release contains a number of minor code changes, but it has a large impact on the content that is printed to screen. In particular, we now log periodically, instead of each iteration, and a "good" stopping rule is used as the default if none are specified. Try using SDDP.train(model) to see the difference.

Other

  • Fixed various typos in the documentation (#617)
  • Fixed printing test after changes in JuMP (#618)
  • Set SimulationStoppingRule as the default stopping rule (#619)
  • Changed the default logging frequency. Pass log_every_seconds = 0.0 to train to revert to the old behavior. (#620)
  • Added example usage with Distributions.jl (@slwu89) (#622)
  • Removed the numerical issue @warn (#627)
  • Improved the quality of docstrings (#630)

v1.5.0 (May 14, 2023)

Added

  • Added the ability to use a different model for the forward pass. This is a novel feature that lets you train better policies when the model is non-convex or does not have a well-defined dual. See the Alternative forward models tutorial in which we train convex and non-convex formulations of the optimal power flow problem. (#611)

Other

  • Updated missing changelog entries (#608)
  • Removed global variables (#610)
  • Converted the Options struct to keyword arguments. This struct was a private implementation detail, but the change is breaking if you developed an extension to SDDP that touched these internals. (#612)
  • Fixed some typos (#613)

v1.4.0 (May 8, 2023)

Added

Fixed

  • Fixed parsing of some MSPFormat files (#602) (#604)
  • Fixed printing in header (#605)

v1.3.0 (May 3, 2023)

Added

  • Added experimental support for SDDP.MSPFormat.read_from_file (#593)

Other

  • Updated to StochOptFormat v0.3 (#600)

v1.2.1 (May 1, 2023)

Fixed

  • Fixed log_every_seconds (#597)

v1.2.0 (May 1, 2023)

Added

Other

  • Tweaked how the log is printed (#588)
  • Updated to StochOptFormat v0.2 (#592)

v1.1.4 (April 10, 2023)

Fixed

  • Logs are now flushed every iteration (#584)

Other

  • Added docstrings to various functions (#581)
  • Minor documentation updates (#580)
  • Clarified integrality documentation (#582)
  • Updated the README (#585)
  • Number of numerical issues is now printed to the log (#586)

v1.1.3 (April 2, 2023)

Other

v1.1.2 (March 18, 2023)

Other

v1.1.1 (March 16, 2023)

Other

  • Fixed email in Project.toml
  • Added notebook to documentation tutorials (#571)

v1.1.0 (January 12, 2023)

Added

v1.0.0 (January 3, 2023)

Although we're bumping MAJOR version, this is a non-breaking release. Going forward:

  • New features will bump the MINOR version
  • Bug fixes, maintenance, and documentation updates will bump the PATCH version
  • We will support only the Long Term Support (currently v1.6.7) and the latest patch (currently v1.8.4) releases of Julia. Updates to the LTS version will bump the MINOR version
  • Updates to the compat bounds of package dependencies will bump the PATCH version.

We do not intend any breaking changes to the public API, which would require a new MAJOR release. The public API is everything defined in the documentation. Anything not in the documentation is considered private and may change in any PATCH release.

Added

Other

  • Updated Plotting tools to use live plots (#563)
  • Added vale as a linter (#565)
  • Improved documentation for initializing a parallel scheme (#566)

v0.4.9 (January 3, 2023)

Added

Other

  • Added tutorial on Markov Decision Processes (#556)
  • Added two-stage newsvendor tutorial (#557)
  • Refactored the layout of the documentation (#554) (#555)
  • Updated copyright to 2023 (#558)
  • Fixed errors in the documentation (#561)

v0.4.8 (December 19, 2022)

Added

Fixed

  • Reverted then fixed (#531) because it failed to account for problems with integer variables (#546) (#551)

v0.4.7 (December 17, 2022)

Added

  • Added initial_node support to InSampleMonteCarlo and OutOfSampleMonteCarlo (#535)

Fixed

  • Rethrow InterruptException when solver is interrupted (#534)
  • Fixed numerical recovery when we need dual solutions (#531) (Thanks @bfpc)
  • Fixed re-using the dashboard = true option between solves (#538)
  • Fixed bug when no @stageobjective is set (now defaults to 0.0) (#539)
  • Fixed errors thrown when invalid inputs are provided to add_objective_state (#540)

Other

  • Drop support for Julia versions prior to 1.6 (#533)
  • Updated versions of dependencies (#522) (#533)
  • Switched to HiGHS in the documentation and tests (#533)
  • Added license headers (#519)
  • Fixed link in air conditioning example (#521) (Thanks @conema)
  • Clarified variable naming in deterministic equivalent (#525) (Thanks @lucasprocessi)
  • Added this change log (#536)
  • Cuts are now written to model.cuts.json when numerical instability is discovered. This can aid debugging because it allows to you reload the cuts as of the iteration that caused the numerical issue (#537)

v0.4.6 (March 25, 2022)

Other

  • Updated to JuMP v1.0 (#517)

v0.4.5 (March 9, 2022)

Fixed

  • Fixed issue with set_silent in a subproblem (#510)

Other

  • Fixed many typos (#500) (#501) (#506) (#511) (Thanks @bfpc)
  • Update to JuMP v0.23 (#514)
  • Added auto-regressive tutorial (#507)

v0.4.4 (December 11, 2021)

Added

  • Added BanditDuality (#471)
  • Added benchmark scripts (#475) (#476) (#490)
  • write_cuts_to_file now saves visited states (#468)

Fixed

  • Fixed BoundStalling in a deterministic policy (#470) (#474)
  • Fixed magnitude warning with zero coefficients (#483)

Other

  • Improvements to LagrangianDuality (#481) (#482) (#487)
  • Improvements to StrengthenedConicDuality (#486)
  • Switch to functional form for the tests (#478)
  • Fixed typos (#472) (Thanks @vfdev-5)
  • Update to JuMP v0.22 (#498)

v0.4.3 (August 31, 2021)

Added

  • Added biobjective solver (#462)
  • Added forward_pass_callback (#466)

Other

  • Update tutorials and documentation (#459) (#465)
  • Organize how paper materials are stored (#464)

v0.4.2 (August 24, 2021)

Fixed

  • Fixed a bug in Lagrangian duality (#457)

v0.4.1 (August 23, 2021)

Other

  • Minor changes to our implementation of LagrangianDuality (#454) (#455)

v0.4.0 (August 17, 2021)

Breaking

  • A large refactoring for how we handle stochastic integer programs. This added support for things like SDDP.ContinuousConicDuality and SDDP.LagrangianDuality. It was breaking because we removed the integrality_handler argument to PolicyGraph. (#449) (#453)

Other

  • Documentation improvements (#447) (#448) (#450)

v0.3.17 (July 6, 2021)

Added

Other

  • Display more model attributes (#438)
  • Documentation improvements (#433) (#437) (#439)

v0.3.16 (June 17, 2021)

Added

Other

  • Update risk measure docstrings (#418)

v0.3.15 (June 1, 2021)

Added

Fixed

  • Fixed scoping bug in SDDP.@stageobjective (#407)
  • Fixed a bug when the initial point is infeasible (#411)
  • Set subproblems to silent by default (#409)

Other

  • Add JuliaFormatter (#412)
  • Documentation improvements (#406) (#408)

v0.3.14 (March 30, 2021)

Fixed

  • Fixed O(N^2) behavior in get_same_children (#393)

v0.3.13 (March 27, 2021)

Fixed

  • Fixed bug in print.jl
  • Fixed compat of Reexport (#388)

v0.3.12 (March 22, 2021)

Added

  • Added problem statistics to header (#385) (#386)

Fixed

  • Fixed subtypes in visualization (#384)

v0.3.11 (March 22, 2021)

Fixed

  • Fixed constructor in direct mode (#383)

Other

  • Fix documentation (#379)

v0.3.10 (February 23, 2021)

Fixed

  • Fixed seriescolor in publication plot (#376)

v0.3.9 (February 20, 2021)

Added

  • Add option to simulate with different incoming state (#372)
  • Added warning for cuts with high dynamic range (#373)

Fixed

  • Fixed seriesalpha in publication plot (#375)

v0.3.8 (January 19, 2021)

Other

  • Documentation improvements (#367) (#369) (#370)

v0.3.7 (January 8, 2021)

Other

  • Documentation improvements (#362) (#363) (#365) (#366)
  • Bump copyright (#364)

v0.3.6 (December 17, 2020)

Other

  • Fix typos (#358)
  • Collapse navigation bar in docs (#359)
  • Update TagBot.yml (#361)

v0.3.5 (November 18, 2020)

Other

  • Update citations (#348)
  • Switch to GitHub actions (#355)

v0.3.4 (August 25, 2020)

Added

  • Added non-uniform distributionally robust risk measure (#328)
  • Added numerical recovery functions (#330)
  • Added experimental StochOptFormat (#332) (#336) (#337) (#341) (#343) (#344)
  • Added entropic risk measure (#347)

Other

  • Documentation improvements (#327) (#333) (#339) (#340)

v0.3.3 (June 19, 2020)

Added

  • Added asynchronous support for price and belief states (#325)
  • Added ForwardPass plug-in system (#320)

Fixed

  • Fix check for probabilities in Markovian graph (#322)

v0.3.2 (April 6, 2020)

Added

Other

  • Improve error message in deterministic equivalent (#312)
  • Update to RecipesBase 1.0 (#313)

v0.3.1 (February 26, 2020)

Fixed

  • Fixed filename in integrality_handlers.jl (#304)

v0.3.0 (February 20, 2020)

Breaking

  • Breaking changes to update to JuMP v0.21 (#300).

v0.2.4 (February 7, 2020)

Added

  • Added a counter for the number of total subproblem solves (#301)

Other

  • Update formatter (#298)
  • Added tests (#299)

v0.2.3 (January 24, 2020)

Added

  • Added support for convex risk measures (#294)

Fixed

  • Fixed bug when subproblem is infeasible (#296)
  • Fixed bug in deterministic equivalent (#297)

Other

  • Added example from IJOC paper (#293)

v0.2.2 (January 10, 2020)

Fixed

  • Fixed flakey time limit in tests (#291)

Other

  • Removed MathOptFormat.jl (#289)
  • Update copyright (#290)

v0.2.1 (December 19, 2019)

Added

  • Added support for approximating a Markov lattice (#282) (#285)
  • Add tools for visualizing the value function (#272) (#286)
  • Write .mof.json files on error (#284)

Other

  • Improve documentation (#281) (#283)
  • Update tests for Julia 1.3 (#287)

v0.2.0 (December 16, 2019)

This version added the asynchronous parallel implementation with a few minor breaking changes in how we iterated internally. It didn't break basic user-facing models, only implementations that implemented some of the extension features. It probably could have been a v1.1 release.

Added

  • Added asynchronous parallel implementation (#277)
  • Added roll-out algorithm for cyclic graphs (#279)

Other

  • Improved error messages in PolicyGraph (#271)
  • Added JuliaFormatter (#273) (#276)
  • Fixed compat bounds (#274) (#278)
  • Added documentation for simulating non-standard graphs (#280)

v0.1.0 (October 17, 2019)

A complete rewrite of SDDP.jl based on the policy graph framework. This was essentially a new package. It has minimal code in common with the previous implementation.

Development started on September 28, 2018 in Kokako.jl, and the code was merged into SDDP.jl on March 14, 2019.

The pull request SDDP.jl#180 lists the 29 issues that the rewrite closed.

v0.0.1 (April 18, 2018)

Initial release. Development had been underway since January 22, 2016 in the StochDualDynamicProgram.jl repository. The last development commit there was April 5, 2017. Work then continued in this repository for a year before the first tagged release.

diff --git a/previews/PR826/examples/FAST_hydro_thermal.ipynb b/previews/PR826/examples/FAST_hydro_thermal.ipynb new file mode 100644 index 0000000000..64b87c7e01 --- /dev/null +++ b/previews/PR826/examples/FAST_hydro_thermal.ipynb @@ -0,0 +1,78 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# FAST: the hydro-thermal problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "An implementation of the Hydro-thermal example from [FAST](https://github.com/leopoldcambier/FAST/tree/daea3d80a5ebb2c52f78670e34db56d53ca2e778/examples/hydro%20thermal)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function fast_hydro_thermal()\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " upper_bound = 0.0,\n", + " sense = :Max,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " @variable(sp, 0 <= x <= 8, SDDP.State, initial_value = 0.0)\n", + " @variables(sp, begin\n", + " y >= 0\n", + " p >= 0\n", + " ξ\n", + " end)\n", + " @constraints(sp, begin\n", + " p + y >= 6\n", + " x.out <= x.in - y + ξ\n", + " end)\n", + " RAINFALL = (t == 1 ? [6] : [2, 10])\n", + " SDDP.parameterize(sp, RAINFALL) do ω\n", + " return JuMP.fix(ξ, ω)\n", + " end\n", + " @stageobjective(sp, -5 * p)\n", + " end\n", + "\n", + " det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\n", + " set_silent(det)\n", + " JuMP.optimize!(det)\n", + " @test JuMP.objective_sense(det) == MOI.MAX_SENSE\n", + " @test JuMP.objective_value(det) == -10\n", + " SDDP.train(model)\n", + " @test SDDP.calculate_bound(model) == -10\n", + " return\n", + "end\n", + "\n", + "fast_hydro_thermal()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/FAST_hydro_thermal.jl b/previews/PR826/examples/FAST_hydro_thermal.jl new file mode 100644 index 0000000000..5bc0db0dd7 --- /dev/null +++ b/previews/PR826/examples/FAST_hydro_thermal.jl @@ -0,0 +1,46 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # FAST: the hydro-thermal problem + +# An implementation of the Hydro-thermal example from [FAST](https://github.com/leopoldcambier/FAST/tree/daea3d80a5ebb2c52f78670e34db56d53ca2e778/examples/hydro%20thermal) + +using SDDP, HiGHS, Test + +function fast_hydro_thermal() + model = SDDP.LinearPolicyGraph(; + stages = 2, + upper_bound = 0.0, + sense = :Max, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variable(sp, 0 <= x <= 8, SDDP.State, initial_value = 0.0) + @variables(sp, begin + y >= 0 + p >= 0 + ξ + end) + @constraints(sp, begin + p + y >= 6 + x.out <= x.in - y + ξ + end) + RAINFALL = (t == 1 ? [6] : [2, 10]) + SDDP.parameterize(sp, RAINFALL) do ω + return JuMP.fix(ξ, ω) + end + @stageobjective(sp, -5 * p) + end + + det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer) + set_silent(det) + JuMP.optimize!(det) + @test JuMP.objective_sense(det) == MOI.MAX_SENSE + @test JuMP.objective_value(det) == -10 + SDDP.train(model) + @test SDDP.calculate_bound(model) == -10 + return +end + +fast_hydro_thermal() diff --git a/previews/PR826/examples/FAST_hydro_thermal/index.html b/previews/PR826/examples/FAST_hydro_thermal/index.html new file mode 100644 index 0000000000..44e9fa3d6b --- /dev/null +++ b/previews/PR826/examples/FAST_hydro_thermal/index.html @@ -0,0 +1,78 @@ + +FAST: the hydro-thermal problem · SDDP.jl

FAST: the hydro-thermal problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

An implementation of the Hydro-thermal example from FAST

using SDDP, HiGHS, Test
+
+function fast_hydro_thermal()
+    model = SDDP.LinearPolicyGraph(;
+        stages = 2,
+        upper_bound = 0.0,
+        sense = :Max,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        @variable(sp, 0 <= x <= 8, SDDP.State, initial_value = 0.0)
+        @variables(sp, begin
+            y >= 0
+            p >= 0
+            ξ
+        end)
+        @constraints(sp, begin
+            p + y >= 6
+            x.out <= x.in - y + ξ
+        end)
+        RAINFALL = (t == 1 ? [6] : [2, 10])
+        SDDP.parameterize(sp, RAINFALL) do ω
+            return JuMP.fix(ξ, ω)
+        end
+        @stageobjective(sp, -5 * p)
+    end
+
+    det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)
+    set_silent(det)
+    JuMP.optimize!(det)
+    @test JuMP.objective_sense(det) == MOI.MAX_SENSE
+    @test JuMP.objective_value(det) == -10
+    SDDP.train(model)
+    @test SDDP.calculate_bound(model) == -10
+    return
+end
+
+fast_hydro_thermal()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 1
+  scenarios       : 2.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.GreaterThan{Float64}     : [1, 1]
+  AffExpr in MOI.LessThan{Float64}        : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [3, 4]
+  VariableRef in MOI.LessThan{Float64}    : [2, 2]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 5e+00]
+  bounds range     [8e+00, 8e+00]
+  rhs range        [6e+00, 6e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1  -2.000000e+01 -1.000000e+01  2.382040e-03         5   1
+        20   0.000000e+00 -1.000000e+01  1.353598e-02       104   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.353598e-02
+total solves   : 104
+best bound     : -1.000000e+01
+simulation ci  : -1.200000e+01 ± 4.405700e+00
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/FAST_production_management.ipynb b/previews/PR826/examples/FAST_production_management.ipynb new file mode 100644 index 0000000000..63746766a8 --- /dev/null +++ b/previews/PR826/examples/FAST_production_management.ipynb @@ -0,0 +1,74 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# FAST: the production management problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "An implementation of the Production Management example from [FAST](https://github.com/leopoldcambier/FAST/blob/daea3d80a5ebb2c52f78670e34db56d53ca2e778/examples/production management multiple stages/)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function fast_production_management(; cut_type)\n", + " DEMAND = [2, 10]\n", + " H = 3\n", + " N = 2\n", + " C = [0.2, 0.7]\n", + " S = 2 .+ [0.33, 0.54]\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = H,\n", + " lower_bound = -50.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " @variable(sp, x[1:N] >= 0, SDDP.State, initial_value = 0.0)\n", + " @variables(sp, begin\n", + " s[i = 1:N] >= 0\n", + " d\n", + " end)\n", + " @constraints(sp, begin\n", + " [i = 1:N], s[i] <= x[i].in\n", + " sum(s) <= d\n", + " end)\n", + " SDDP.parameterize(sp, t == 1 ? [0] : DEMAND) do ω\n", + " return JuMP.fix(d, ω)\n", + " end\n", + " @stageobjective(sp, sum(C[i] * x[i].out for i in 1:N) - S's)\n", + " end\n", + " SDDP.train(model; cut_type = cut_type, print_level = 2, log_frequency = 5)\n", + " @test SDDP.calculate_bound(model) ≈ -23.96 atol = 1e-2\n", + "end\n", + "\n", + "fast_production_management(; cut_type = SDDP.SINGLE_CUT)\n", + "fast_production_management(; cut_type = SDDP.MULTI_CUT)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/FAST_production_management.jl b/previews/PR826/examples/FAST_production_management.jl new file mode 100644 index 0000000000..8f29be2f39 --- /dev/null +++ b/previews/PR826/examples/FAST_production_management.jl @@ -0,0 +1,42 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # FAST: the production management problem + +# An implementation of the Production Management example from [FAST](https://github.com/leopoldcambier/FAST/blob/daea3d80a5ebb2c52f78670e34db56d53ca2e778/examples/production management multiple stages/) + +using SDDP, HiGHS, Test + +function fast_production_management(; cut_type) + DEMAND = [2, 10] + H = 3 + N = 2 + C = [0.2, 0.7] + S = 2 .+ [0.33, 0.54] + model = SDDP.LinearPolicyGraph(; + stages = H, + lower_bound = -50.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variable(sp, x[1:N] >= 0, SDDP.State, initial_value = 0.0) + @variables(sp, begin + s[i = 1:N] >= 0 + d + end) + @constraints(sp, begin + [i = 1:N], s[i] <= x[i].in + sum(s) <= d + end) + SDDP.parameterize(sp, t == 1 ? [0] : DEMAND) do ω + return JuMP.fix(d, ω) + end + @stageobjective(sp, sum(C[i] * x[i].out for i in 1:N) - S's) + end + SDDP.train(model; cut_type = cut_type, print_level = 2, log_frequency = 5) + @test SDDP.calculate_bound(model) ≈ -23.96 atol = 1e-2 +end + +fast_production_management(; cut_type = SDDP.SINGLE_CUT) +fast_production_management(; cut_type = SDDP.MULTI_CUT) diff --git a/previews/PR826/examples/FAST_production_management/index.html b/previews/PR826/examples/FAST_production_management/index.html new file mode 100644 index 0000000000..91de9178e9 --- /dev/null +++ b/previews/PR826/examples/FAST_production_management/index.html @@ -0,0 +1,38 @@ + +FAST: the production management problem · SDDP.jl

FAST: the production management problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

An implementation of the Production Management example from FAST

using SDDP, HiGHS, Test
+
+function fast_production_management(; cut_type)
+    DEMAND = [2, 10]
+    H = 3
+    N = 2
+    C = [0.2, 0.7]
+    S = 2 .+ [0.33, 0.54]
+    model = SDDP.LinearPolicyGraph(;
+        stages = H,
+        lower_bound = -50.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        @variable(sp, x[1:N] >= 0, SDDP.State, initial_value = 0.0)
+        @variables(sp, begin
+            s[i = 1:N] >= 0
+            d
+        end)
+        @constraints(sp, begin
+            [i = 1:N], s[i] <= x[i].in
+            sum(s) <= d
+        end)
+        SDDP.parameterize(sp, t == 1 ? [0] : DEMAND) do ω
+            return JuMP.fix(d, ω)
+        end
+        @stageobjective(sp, sum(C[i] * x[i].out for i in 1:N) - S's)
+    end
+    SDDP.train(model; cut_type = cut_type, print_level = 2, log_frequency = 5)
+    @test SDDP.calculate_bound(model) ≈ -23.96 atol = 1e-2
+end
+
+fast_production_management(; cut_type = SDDP.SINGLE_CUT)
+fast_production_management(; cut_type = SDDP.MULTI_CUT)
Test Passed
diff --git a/previews/PR826/examples/FAST_quickstart.ipynb b/previews/PR826/examples/FAST_quickstart.ipynb new file mode 100644 index 0000000000..347636a4a0 --- /dev/null +++ b/previews/PR826/examples/FAST_quickstart.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# FAST: the quickstart problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "An implementation of the QuickStart example from [FAST](https://github.com/leopoldcambier/FAST/tree/daea3d80a5ebb2c52f78670e34db56d53ca2e778/demo)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function fast_quickstart()\n", + " model = SDDP.PolicyGraph(\n", + " SDDP.LinearGraph(2);\n", + " lower_bound = -5,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " @variable(sp, x >= 0, SDDP.State, initial_value = 0.0)\n", + " if t == 1\n", + " @stageobjective(sp, x.out)\n", + " else\n", + " @variable(sp, s >= 0)\n", + " @constraint(sp, s <= x.in)\n", + " SDDP.parameterize(sp, [2, 3]) do ω\n", + " return JuMP.set_upper_bound(s, ω)\n", + " end\n", + " @stageobjective(sp, -2s)\n", + " end\n", + " end\n", + "\n", + " det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\n", + " set_silent(det)\n", + " JuMP.optimize!(det)\n", + " @test JuMP.objective_value(det) == -2\n", + "\n", + " SDDP.train(model; log_every_iteration = true)\n", + " @test SDDP.calculate_bound(model) == -2\n", + "end\n", + "\n", + "fast_quickstart()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/FAST_quickstart.jl b/previews/PR826/examples/FAST_quickstart.jl new file mode 100644 index 0000000000..54e917a6fa --- /dev/null +++ b/previews/PR826/examples/FAST_quickstart.jl @@ -0,0 +1,40 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # FAST: the quickstart problem + +# An implementation of the QuickStart example from [FAST](https://github.com/leopoldcambier/FAST/tree/daea3d80a5ebb2c52f78670e34db56d53ca2e778/demo) + +using SDDP, HiGHS, Test + +function fast_quickstart() + model = SDDP.PolicyGraph( + SDDP.LinearGraph(2); + lower_bound = -5, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variable(sp, x >= 0, SDDP.State, initial_value = 0.0) + if t == 1 + @stageobjective(sp, x.out) + else + @variable(sp, s >= 0) + @constraint(sp, s <= x.in) + SDDP.parameterize(sp, [2, 3]) do ω + return JuMP.set_upper_bound(s, ω) + end + @stageobjective(sp, -2s) + end + end + + det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer) + set_silent(det) + JuMP.optimize!(det) + @test JuMP.objective_value(det) == -2 + + SDDP.train(model; log_every_iteration = true) + @test SDDP.calculate_bound(model) == -2 +end + +fast_quickstart() diff --git a/previews/PR826/examples/FAST_quickstart/index.html b/previews/PR826/examples/FAST_quickstart/index.html new file mode 100644 index 0000000000..6fc4c1803d --- /dev/null +++ b/previews/PR826/examples/FAST_quickstart/index.html @@ -0,0 +1,36 @@ + +FAST: the quickstart problem · SDDP.jl

FAST: the quickstart problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

An implementation of the QuickStart example from FAST

using SDDP, HiGHS, Test
+
+function fast_quickstart()
+    model = SDDP.PolicyGraph(
+        SDDP.LinearGraph(2);
+        lower_bound = -5,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        @variable(sp, x >= 0, SDDP.State, initial_value = 0.0)
+        if t == 1
+            @stageobjective(sp, x.out)
+        else
+            @variable(sp, s >= 0)
+            @constraint(sp, s <= x.in)
+            SDDP.parameterize(sp, [2, 3]) do ω
+                return JuMP.set_upper_bound(s, ω)
+            end
+            @stageobjective(sp, -2s)
+        end
+    end
+
+    det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)
+    set_silent(det)
+    JuMP.optimize!(det)
+    @test JuMP.objective_value(det) == -2
+
+    SDDP.train(model; log_every_iteration = true)
+    @test SDDP.calculate_bound(model) == -2
+end
+
+fast_quickstart()
Test Passed
diff --git a/previews/PR826/examples/Hydro_thermal.ipynb b/previews/PR826/examples/Hydro_thermal.ipynb new file mode 100644 index 0000000000..d0d7aa6eb0 --- /dev/null +++ b/previews/PR826/examples/Hydro_thermal.ipynb @@ -0,0 +1,268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Hydro-thermal scheduling" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Problem Description" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In a hydro-thermal problem, the agent controls a hydro-electric generator and reservoir.\n", + "Each time period, they need to choose a generation quantity from thermal `g_t`, and hydro\n", + "`g_h`, in order to meet demand `w_d`, which is a stagewise-independent random variable.\n", + "The state variable, `x`, is the quantity of water in the reservoir at the start of each\n", + "time period, and it has a minimum level of 5 units and a maximum level of 15 units. We\n", + "assume that there are 10 units of water in the reservoir at the start of time, so that\n", + "`x_0 = 10`. The state-variable is connected through time by the water balance constraint:\n", + "`x.out = x.in - g_h - s + w_i,` where `x.out` is the quantity of water at the end of the\n", + "time period, `x.in` is the quantity of water at the start of the time period, `s` is the\n", + "quantity of water spilled from the reservoir, and `w_i` is a stagewise-independent random\n", + "variable that represents the inflow into the reservoir during the time period." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We assume that there are three stages, `t=1, 2, 3`, representing summer-fall, winter, and\n", + "spring, and that we are solving this problem in an infinite-horizon setting with a\n", + "discount factor of `0.95`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In each stage, the agent incurs the cost of spillage, plus the cost of thermal generation.\n", + "We assume that the cost of thermal generation is dependent on the stage `t = 1, 2, 3`, and\n", + "that in each stage, `w` is drawn from the set `(w_i, w_d) = {(0, 7.5), (3, 5), (10, 2.5)}`\n", + "with equal probability." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Importing packages" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For this example, in addition to `SDDP`, we need `HiGHS` as a solver and `Statisitics` to\n", + "compute the mean of our simulations." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using HiGHS\n", + "using SDDP\n", + "using Statistics" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Constructing the policy graph" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are three stages in our infinite-horizon problem, so we construct a\n", + "unicyclic policy graph using `SDDP.UnicyclicGraph`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "graph = SDDP.UnicyclicGraph(0.95; num_nodes = 3)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Constructing the model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Much of the macro code (i.e., lines starting with `@`) in the first part of the following\n", + "should be familiar to users of JuMP." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Inside the `do-end` block, `sp` is a standard JuMP model, and `t` is an index\n", + "for the state variable that will be called with `t = 1, 2, 3`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The state variable `x`, constructed by passing the `SDDP.State` tag to `@variable` is\n", + "actually a Julia struct with two fields: `x.in` and `x.out` corresponding to the incoming\n", + "and outgoing state variables respectively. Both `x.in` and `x.out` are standard JuMP\n", + "variables. The `initial_value` keyword provides the value of the state variable in the\n", + "root node (i.e., `x_0`)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Compared to a JuMP model, one key difference is that we use `@stageobjective`\n", + "instead of `@objective`. The `SDDP.parameterize` function takes a list of supports\n", + "for `w` and parameterizes the JuMP model `sp` by setting the right-hand sides of the\n", + "appropriate constraints (note how the constraints initially have a right-hand side of\n", + "`0`). By default, it is assumed that the realizations have uniform probability, but a\n", + "probability mass vector can also be provided." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.PolicyGraph(\n", + " graph;\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(sp, 5 <= x <= 15, SDDP.State, initial_value = 10)\n", + " @variable(sp, g_t >= 0)\n", + " @variable(sp, g_h >= 0)\n", + " @variable(sp, s >= 0)\n", + " @constraint(sp, balance, x.out - x.in + g_h + s == 0)\n", + " @constraint(sp, demand, g_h + g_t == 0)\n", + " @stageobjective(sp, s + t * g_t)\n", + " SDDP.parameterize(sp, [[0, 7.5], [3, 5], [10, 2.5]]) do w\n", + " set_normalized_rhs(balance, w[1])\n", + " return set_normalized_rhs(demand, w[2])\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Training the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Once a model has been constructed, the next step is to train the policy. This can be\n", + "achieved using `SDDP.train`. There are many options that can be passed, but\n", + "`iteration_limit` terminates the training after the prescribed number of SDDP iterations." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; iteration_limit = 100)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Simulating the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "After training, we can simulate the policy using `SDDP.simulate`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "sims = SDDP.simulate(model, 100, [:g_t])\n", + "mu = round(mean([s[1][:g_t] for s in sims]); digits = 2)\n", + "println(\"On average, $(mu) units of thermal are used in the first stage.\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Extracting the water values" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we can use `SDDP.ValueFunction` and `SDDP.evaluate` to obtain and\n", + "evaluate the value function at different points in the state-space. Note that since we\n", + "are minimizing, the price has a negative sign: each additional unit of water leads to a\n", + "decrease in the expected long-run cost." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "V = SDDP.ValueFunction(model[1])\n", + "cost, price = SDDP.evaluate(V; x = 10)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/Hydro_thermal.jl b/previews/PR826/examples/Hydro_thermal.jl new file mode 100644 index 0000000000..19b83d73f5 --- /dev/null +++ b/previews/PR826/examples/Hydro_thermal.jl @@ -0,0 +1,111 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Hydro-thermal scheduling + +# ## Problem Description + +# In a hydro-thermal problem, the agent controls a hydro-electric generator and reservoir. +# Each time period, they need to choose a generation quantity from thermal `g_t`, and hydro +# `g_h`, in order to meet demand `w_d`, which is a stagewise-independent random variable. +# The state variable, `x`, is the quantity of water in the reservoir at the start of each +# time period, and it has a minimum level of 5 units and a maximum level of 15 units. We +# assume that there are 10 units of water in the reservoir at the start of time, so that +# `x_0 = 10`. The state-variable is connected through time by the water balance constraint: +# `x.out = x.in - g_h - s + w_i,` where `x.out` is the quantity of water at the end of the +# time period, `x.in` is the quantity of water at the start of the time period, `s` is the +# quantity of water spilled from the reservoir, and `w_i` is a stagewise-independent random +# variable that represents the inflow into the reservoir during the time period. + +# We assume that there are three stages, `t=1, 2, 3`, representing summer-fall, winter, and +# spring, and that we are solving this problem in an infinite-horizon setting with a +# discount factor of `0.95`. + +# In each stage, the agent incurs the cost of spillage, plus the cost of thermal generation. +# We assume that the cost of thermal generation is dependent on the stage `t = 1, 2, 3`, and +# that in each stage, `w` is drawn from the set `(w_i, w_d) = {(0, 7.5), (3, 5), (10, 2.5)}` +# with equal probability. + +# ## Importing packages + +# For this example, in addition to `SDDP`, we need `HiGHS` as a solver and `Statisitics` to +# compute the mean of our simulations. + +using HiGHS +using SDDP +using Statistics + +# ## Constructing the policy graph + +# There are three stages in our infinite-horizon problem, so we construct a +# unicyclic policy graph using [`SDDP.UnicyclicGraph`](@ref): + +graph = SDDP.UnicyclicGraph(0.95; num_nodes = 3) + +# ## Constructing the model + +# Much of the macro code (i.e., lines starting with `@`) in the first part of the following +# should be familiar to users of JuMP. + +# Inside the `do-end` block, `sp` is a standard JuMP model, and `t` is an index +# for the state variable that will be called with `t = 1, 2, 3`. + +# The state variable `x`, constructed by passing the `SDDP.State` tag to `@variable` is +# actually a Julia struct with two fields: `x.in` and `x.out` corresponding to the incoming +# and outgoing state variables respectively. Both `x.in` and `x.out` are standard JuMP +# variables. The `initial_value` keyword provides the value of the state variable in the +# root node (i.e., `x_0`). + +# Compared to a JuMP model, one key difference is that we use [`@stageobjective`](@ref) +# instead of `@objective`. The [`SDDP.parameterize`](@ref) function takes a list of supports +# for `w` and parameterizes the JuMP model `sp` by setting the right-hand sides of the +# appropriate constraints (note how the constraints initially have a right-hand side of +# `0`). By default, it is assumed that the realizations have uniform probability, but a +# probability mass vector can also be provided. + +model = SDDP.PolicyGraph( + graph; + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable(sp, 5 <= x <= 15, SDDP.State, initial_value = 10) + @variable(sp, g_t >= 0) + @variable(sp, g_h >= 0) + @variable(sp, s >= 0) + @constraint(sp, balance, x.out - x.in + g_h + s == 0) + @constraint(sp, demand, g_h + g_t == 0) + @stageobjective(sp, s + t * g_t) + SDDP.parameterize(sp, [[0, 7.5], [3, 5], [10, 2.5]]) do w + set_normalized_rhs(balance, w[1]) + return set_normalized_rhs(demand, w[2]) + end +end + +# ## Training the policy + +# Once a model has been constructed, the next step is to train the policy. This can be +# achieved using [`SDDP.train`](@ref). There are many options that can be passed, but +# `iteration_limit` terminates the training after the prescribed number of SDDP iterations. + +SDDP.train(model; iteration_limit = 100) + +# ## Simulating the policy + +# After training, we can simulate the policy using [`SDDP.simulate`](@ref). + +sims = SDDP.simulate(model, 100, [:g_t]) +mu = round(mean([s[1][:g_t] for s in sims]); digits = 2) +println("On average, $(mu) units of thermal are used in the first stage.") + +# ## Extracting the water values + +# Finally, we can use [`SDDP.ValueFunction`](@ref) and [`SDDP.evaluate`](@ref) to obtain and +# evaluate the value function at different points in the state-space. Note that since we +# are minimizing, the price has a negative sign: each additional unit of water leads to a +# decrease in the expected long-run cost. + +V = SDDP.ValueFunction(model[1]) +cost, price = SDDP.evaluate(V; x = 10) diff --git a/previews/PR826/examples/Hydro_thermal/index.html b/previews/PR826/examples/Hydro_thermal/index.html new file mode 100644 index 0000000000..a3a6323c3d --- /dev/null +++ b/previews/PR826/examples/Hydro_thermal/index.html @@ -0,0 +1,77 @@ + +Hydro-thermal scheduling · SDDP.jl

Hydro-thermal scheduling

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

Problem Description

In a hydro-thermal problem, the agent controls a hydro-electric generator and reservoir. Each time period, they need to choose a generation quantity from thermal g_t, and hydro g_h, in order to meet demand w_d, which is a stagewise-independent random variable. The state variable, x, is the quantity of water in the reservoir at the start of each time period, and it has a minimum level of 5 units and a maximum level of 15 units. We assume that there are 10 units of water in the reservoir at the start of time, so that x_0 = 10. The state-variable is connected through time by the water balance constraint: x.out = x.in - g_h - s + w_i, where x.out is the quantity of water at the end of the time period, x.in is the quantity of water at the start of the time period, s is the quantity of water spilled from the reservoir, and w_i is a stagewise-independent random variable that represents the inflow into the reservoir during the time period.

We assume that there are three stages, t=1, 2, 3, representing summer-fall, winter, and spring, and that we are solving this problem in an infinite-horizon setting with a discount factor of 0.95.

In each stage, the agent incurs the cost of spillage, plus the cost of thermal generation. We assume that the cost of thermal generation is dependent on the stage t = 1, 2, 3, and that in each stage, w is drawn from the set (w_i, w_d) = {(0, 7.5), (3, 5), (10, 2.5)} with equal probability.

Importing packages

For this example, in addition to SDDP, we need HiGHS as a solver and Statisitics to compute the mean of our simulations.

using HiGHS
+using SDDP
+using Statistics

Constructing the policy graph

There are three stages in our infinite-horizon problem, so we construct a unicyclic policy graph using SDDP.UnicyclicGraph:

graph = SDDP.UnicyclicGraph(0.95; num_nodes = 3)
Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0
+ 3 => 1 w.p. 0.95

Constructing the model

Much of the macro code (i.e., lines starting with @) in the first part of the following should be familiar to users of JuMP.

Inside the do-end block, sp is a standard JuMP model, and t is an index for the state variable that will be called with t = 1, 2, 3.

The state variable x, constructed by passing the SDDP.State tag to @variable is actually a Julia struct with two fields: x.in and x.out corresponding to the incoming and outgoing state variables respectively. Both x.in and x.out are standard JuMP variables. The initial_value keyword provides the value of the state variable in the root node (i.e., x_0).

Compared to a JuMP model, one key difference is that we use @stageobjective instead of @objective. The SDDP.parameterize function takes a list of supports for w and parameterizes the JuMP model sp by setting the right-hand sides of the appropriate constraints (note how the constraints initially have a right-hand side of 0). By default, it is assumed that the realizations have uniform probability, but a probability mass vector can also be provided.

model = SDDP.PolicyGraph(
+    graph;
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(sp, 5 <= x <= 15, SDDP.State, initial_value = 10)
+    @variable(sp, g_t >= 0)
+    @variable(sp, g_h >= 0)
+    @variable(sp, s >= 0)
+    @constraint(sp, balance, x.out - x.in + g_h + s == 0)
+    @constraint(sp, demand, g_h + g_t == 0)
+    @stageobjective(sp, s + t * g_t)
+    SDDP.parameterize(sp, [[0, 7.5], [3, 5], [10, 2.5]]) do w
+        set_normalized_rhs(balance, w[1])
+        return set_normalized_rhs(demand, w[2])
+    end
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+

Training the policy

Once a model has been constructed, the next step is to train the policy. This can be achieved using SDDP.train. There are many options that can be passed, but iteration_limit terminates the training after the prescribed number of SDDP iterations.

SDDP.train(model; iteration_limit = 100)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 3e+00]
+  bounds range     [5e+00, 2e+01]
+  rhs range        [2e+00, 1e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.500000e+01  1.050370e+01  8.932996e-02        15   1
+        23   7.272569e+02  2.340718e+02  1.115596e+00      7953   1
+        53   6.601187e+01  2.361865e+02  2.123327e+00     14331   1
+        87   8.490627e+02  2.363852e+02  3.240750e+00     20481   1
+       100   2.664982e+02  2.364051e+02  3.554626e+00     22104   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 3.554626e+00
+total solves   : 22104
+best bound     :  2.364051e+02
+simulation ci  :  2.144053e+02 ± 4.095724e+01
+numeric issues : 0
+-------------------------------------------------------------------

Simulating the policy

After training, we can simulate the policy using SDDP.simulate.

sims = SDDP.simulate(model, 100, [:g_t])
+mu = round(mean([s[1][:g_t] for s in sims]); digits = 2)
+println("On average, $(mu) units of thermal are used in the first stage.")
On average, 2.31 units of thermal are used in the first stage.

Extracting the water values

Finally, we can use SDDP.ValueFunction and SDDP.evaluate to obtain and evaluate the value function at different points in the state-space. Note that since we are minimizing, the price has a negative sign: each additional unit of water leads to a decrease in the expected long-run cost.

V = SDDP.ValueFunction(model[1])
+cost, price = SDDP.evaluate(V; x = 10)
(233.51345083437386, Dict(:x => -0.6430308421235855))
diff --git a/previews/PR826/examples/SDDP.log b/previews/PR826/examples/SDDP.log new file mode 100644 index 0000000000..794bf46a2e --- /dev/null +++ b/previews/PR826/examples/SDDP.log @@ -0,0 +1,1608 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 1 + scenarios : 2.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.GreaterThan{Float64} : [1, 1] + AffExpr in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [3, 4] + VariableRef in MOI.LessThan{Float64} : [2, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 5e+00] + bounds range [8e+00, 8e+00] + rhs range [6e+00, 6e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 -2.000000e+01 -1.000000e+01 2.382040e-03 5 1 + 20 0.000000e+00 -1.000000e+01 1.353598e-02 104 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.353598e-02 +total solves : 104 +best bound : -1.000000e+01 +simulation ci : -1.200000e+01 ± 4.405700e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 2 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [8, 8] + AffExpr in MOI.LessThan{Float64} : [3, 3] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [2e-01, 3e+00] + bounds range [5e+01, 5e+01] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 5 -2.396000e+01 -2.396000e+01 6.918907e-03 52 1 + 10 -4.260000e+01 -2.396000e+01 1.041794e-02 92 1 + 15 -5.320000e+00 -2.396000e+01 1.411104e-02 132 1 + 20 -2.396000e+01 -2.396000e+01 1.801991e-02 172 1 + 25 -2.396000e+01 -2.396000e+01 2.295303e-02 224 1 + 30 -2.396000e+01 -2.396000e+01 2.737713e-02 264 1 + 35 -4.260000e+01 -2.396000e+01 3.208113e-02 304 1 + 40 -2.396000e+01 -2.396000e+01 8.876801e-02 344 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 8.876801e-02 +total solves : 344 +best bound : -2.396000e+01 +simulation ci : -2.612314e+01 ± 4.105831e+00 +numeric issues : 0 +------------------------------------------------------------------- + +──────────────────────────────────────────────────────────────────────────────────── + Time Allocations + ─────────────────────── ──────────────────────── + Tot / % measured: 93.3ms / 36.0% 33.4MiB / 20.3% + +Section ncalls time %tot avg alloc %tot avg +──────────────────────────────────────────────────────────────────────────────────── +backward_pass 40 20.4ms 60.7% 510μs 5.79MiB 85.6% 148KiB + solve_subproblem 160 11.6ms 34.6% 72.8μs 871KiB 12.6% 5.44KiB + get_dual_solution 160 573μs 1.7% 3.58μs 190KiB 2.7% 1.19KiB + prepare_backward_pass 160 28.6μs 0.1% 179ns 0.00B 0.0% 0.00B +forward_pass 40 8.03ms 23.9% 201μs 768KiB 11.1% 19.2KiB + solve_subproblem 120 7.07ms 21.0% 58.9μs 588KiB 8.5% 4.90KiB + get_dual_solution 120 72.3μs 0.2% 602ns 16.9KiB 0.2% 144B + sample_scenario 40 155μs 0.5% 3.87μs 24.5KiB 0.4% 627B +calculate_bound 40 5.18ms 15.4% 129μs 224KiB 3.2% 5.61KiB + get_dual_solution 40 34.7μs 0.1% 869ns 5.62KiB 0.1% 144B +get_dual_solution 36 18.1μs 0.1% 503ns 5.06KiB 0.1% 144B +──────────────────────────────────────────────────────────────────────────────────── + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 2 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [8, 8] + AffExpr in MOI.LessThan{Float64} : [3, 3] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [2e-01, 3e+00] + bounds range [5e+01, 5e+01] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 5 -4.260000e+01 -2.396000e+01 6.820917e-03 52 1 + 10 -5.320000e+00 -2.396000e+01 1.074791e-02 92 1 + 15 -2.396000e+01 -2.396000e+01 1.516891e-02 132 1 + 20 -4.260000e+01 -2.396000e+01 1.999283e-02 172 1 + 25 -2.396000e+01 -2.396000e+01 2.617383e-02 224 1 + 30 -2.396000e+01 -2.396000e+01 3.211594e-02 264 1 + 35 -2.396000e+01 -2.396000e+01 3.854394e-02 304 1 + 40 -2.396000e+01 -2.396000e+01 4.567099e-02 344 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 4.567099e-02 +total solves : 344 +best bound : -2.396000e+01 +simulation ci : -1.910970e+01 ± 3.945892e+00 +numeric issues : 0 +------------------------------------------------------------------- + +──────────────────────────────────────────────────────────────────────────────────── + Time Allocations + ─────────────────────── ──────────────────────── + Tot / % measured: 50.1ms / 82.9% 39.5MiB / 32.5% + +Section ncalls time %tot avg alloc %tot avg +──────────────────────────────────────────────────────────────────────────────────── +backward_pass 40 28.3ms 68.2% 708μs 11.9MiB 92.4% 304KiB + solve_subproblem 160 11.9ms 28.8% 74.7μs 872KiB 6.6% 5.45KiB + get_dual_solution 160 571μs 1.4% 3.57μs 190KiB 1.4% 1.19KiB + prepare_backward_pass 160 28.6μs 0.1% 178ns 0.00B 0.0% 0.00B +forward_pass 40 7.64ms 18.4% 191μs 768KiB 5.8% 19.2KiB + solve_subproblem 120 6.78ms 16.3% 56.5μs 588KiB 4.5% 4.90KiB + get_dual_solution 120 59.2μs 0.1% 493ns 16.9KiB 0.1% 144B + sample_scenario 40 142μs 0.3% 3.56μs 24.2KiB 0.2% 620B +calculate_bound 40 5.55ms 13.4% 139μs 226KiB 1.7% 5.66KiB + get_dual_solution 40 32.9μs 0.1% 822ns 5.62KiB 0.0% 144B +get_dual_solution 36 17.2μs 0.0% 477ns 5.06KiB 0.0% 144B +──────────────────────────────────────────────────────────────────────────────────── + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 1 + scenarios : 2.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [3, 4] + AffExpr in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [2, 3] + VariableRef in MOI.LessThan{Float64} : [2, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+00] + bounds range [2e+00, 5e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 0.000000e+00 -2.500000e+00 1.717091e-03 5 1 + 2 -1.500000e+00 -2.000000e+00 2.633095e-03 14 1 + 3 -1.000000e+00 -2.000000e+00 3.089190e-03 19 1 + 4 -3.000000e+00 -2.000000e+00 3.618002e-03 24 1 + 5 -2.000000e+00 -2.000000e+00 4.220009e-03 29 1 + 6 -2.000000e+00 -2.000000e+00 4.758120e-03 34 1 + 7 -2.000000e+00 -2.000000e+00 5.311966e-03 39 1 + 8 -2.000000e+00 -2.000000e+00 5.869150e-03 44 1 + 9 -2.000000e+00 -2.000000e+00 6.427050e-03 49 1 + 10 -2.000000e+00 -2.000000e+00 6.973028e-03 54 1 + 11 -2.000000e+00 -2.000000e+00 7.514000e-03 59 1 + 12 -2.000000e+00 -2.000000e+00 8.068085e-03 64 1 + 13 -2.000000e+00 -2.000000e+00 8.615017e-03 69 1 + 14 -2.000000e+00 -2.000000e+00 9.175062e-03 74 1 + 15 -2.000000e+00 -2.000000e+00 9.727001e-03 79 1 + 16 -2.000000e+00 -2.000000e+00 1.030612e-02 84 1 + 17 -2.000000e+00 -2.000000e+00 1.086116e-02 89 1 + 18 -2.000000e+00 -2.000000e+00 1.144409e-02 94 1 + 19 -2.000000e+00 -2.000000e+00 1.202917e-02 99 1 + 20 -2.000000e+00 -2.000000e+00 1.259708e-02 104 1 + 21 -2.000000e+00 -2.000000e+00 1.348400e-02 113 1 + 22 -2.000000e+00 -2.000000e+00 1.409006e-02 118 1 + 23 -2.000000e+00 -2.000000e+00 1.468205e-02 123 1 + 24 -2.000000e+00 -2.000000e+00 1.532316e-02 128 1 + 25 -2.000000e+00 -2.000000e+00 1.593709e-02 133 1 + 26 -2.000000e+00 -2.000000e+00 1.659703e-02 138 1 + 27 -2.000000e+00 -2.000000e+00 1.724315e-02 143 1 + 28 -2.000000e+00 -2.000000e+00 1.787114e-02 148 1 + 29 -2.000000e+00 -2.000000e+00 1.851606e-02 153 1 + 30 -2.000000e+00 -2.000000e+00 1.915812e-02 158 1 + 31 -2.000000e+00 -2.000000e+00 1.978707e-02 163 1 + 32 -2.000000e+00 -2.000000e+00 2.045417e-02 168 1 + 33 -2.000000e+00 -2.000000e+00 2.110600e-02 173 1 + 34 -2.000000e+00 -2.000000e+00 2.174711e-02 178 1 + 35 -2.000000e+00 -2.000000e+00 2.241015e-02 183 1 + 36 -2.000000e+00 -2.000000e+00 2.307701e-02 188 1 + 37 -2.000000e+00 -2.000000e+00 2.373719e-02 193 1 + 38 -2.000000e+00 -2.000000e+00 2.442908e-02 198 1 + 39 -2.000000e+00 -2.000000e+00 2.512217e-02 203 1 + 40 -2.000000e+00 -2.000000e+00 2.579618e-02 208 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 2.579618e-02 +total solves : 208 +best bound : -2.000000e+00 +simulation ci : -1.937500e+00 ± 1.225000e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 3e+00] + bounds range [5e+00, 2e+01] + rhs range [2e+00, 1e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.500000e+01 1.050370e+01 8.932996e-02 15 1 + 23 7.272569e+02 2.340718e+02 1.115596e+00 7953 1 + 53 6.601187e+01 2.361865e+02 2.123327e+00 14331 1 + 87 8.490627e+02 2.363852e+02 3.240750e+00 20481 1 + 100 2.664982e+02 2.364051e+02 3.554626e+00 22104 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 3.554626e+00 +total solves : 22104 +best bound : 2.364051e+02 +simulation ci : 2.144053e+02 ± 4.095724e+01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 3 + scenarios : 1.43489e+07 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [13, 13] + AffExpr in MOI.EqualTo{Float64} : [3, 3] + AffExpr in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [3, 3] + VariableRef in MOI.GreaterThan{Float64} : [7, 7] + VariableRef in MOI.LessThan{Float64} : [6, 7] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [3e-01, 2e+00] + bounds range [5e-01, 5e+00] + rhs range [2e+00, 2e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 -4.385228e+00 -4.442269e+00 1.850021e-01 1400 1 + 20 -3.881547e+00 -4.392566e+00 2.992721e-01 2800 1 + 30 -3.671354e+00 -4.376606e+00 4.218981e-01 4200 1 + 40 -4.312032e+00 -4.370551e+00 5.528450e-01 5600 1 + 50 -4.077684e+00 -4.362538e+00 6.866391e-01 7000 1 + 60 -3.609363e+00 -4.357411e+00 8.226821e-01 8400 1 + 70 -4.997551e+00 -4.355252e+00 9.584889e-01 9800 1 + 80 -4.608038e+00 -4.352744e+00 1.101198e+00 11200 1 + 90 -4.949477e+00 -4.350750e+00 1.245440e+00 12600 1 + 100 -4.165260e+00 -4.349697e+00 1.399319e+00 14000 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 1.399319e+00 +total solves : 14000 +best bound : -4.349697e+00 +simulation ci : -4.310541e+00 ± 9.196174e-02 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 1 + scenarios : 1.00000e+05 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 5] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [3, 3] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [3e-01, 2e+00] + bounds range [5e-01, 2e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 -1.442965e+00 -1.475770e+00 6.840205e-02 1050 1 + 20 -1.559536e+00 -1.471842e+00 1.059361e-01 1600 1 + 30 -1.633260e+00 -1.471526e+00 1.868749e-01 2650 1 + 40 -1.512908e+00 -1.471277e+00 2.279599e-01 3200 1 + 50 -1.504160e+00 -1.471172e+00 3.149250e-01 4250 1 + 60 -1.352909e+00 -1.471159e+00 3.596299e-01 4800 1 + 70 -1.520847e+00 -1.471079e+00 4.476480e-01 5850 1 + 80 -1.340949e+00 -1.471075e+00 4.943719e-01 6400 1 + 90 -1.260376e+00 -1.471075e+00 6.213000e-01 7450 1 + 100 -1.573293e+00 -1.471075e+00 6.693439e-01 8000 1 + 110 -1.364263e+00 -1.471075e+00 7.184119e-01 8550 1 + 120 -1.572199e+00 -1.471075e+00 7.686610e-01 9100 1 + 130 -1.548012e+00 -1.471075e+00 8.193090e-01 9650 1 + 140 -1.462561e+00 -1.471075e+00 8.706410e-01 10200 1 + 150 -1.154072e+00 -1.471075e+00 9.239759e-01 10750 1 + 160 -1.512908e+00 -1.471075e+00 9.779410e-01 11300 1 + 170 -1.486600e+00 -1.471075e+00 1.032787e+00 11850 1 + 180 -1.405274e+00 -1.471075e+00 1.085961e+00 12400 1 + 187 -1.281632e+00 -1.471075e+00 1.123397e+00 12785 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.123397e+00 +total solves : 12785 +best bound : -1.471075e+00 +simulation ci : -1.474018e+00 ± 2.599111e-02 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 4 + scenarios : 2.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [29, 29] + AffExpr in MOI.EqualTo{Float64} : [4, 5] + AffExpr in MOI.GreaterThan{Float64} : [3, 3] + AffExpr in MOI.LessThan{Float64} : [4, 4] + VariableRef in MOI.EqualTo{Float64} : [3, 3] + VariableRef in MOI.GreaterThan{Float64} : [22, 22] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+06] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 3.455904e+05 3.147347e+05 7.813931e-03 54 1 + 20 3.336455e+05 3.402383e+05 1.366687e-02 104 1 + 30 3.993519e+05 3.403155e+05 2.068496e-02 158 1 + 40 3.337559e+05 3.403155e+05 2.764297e-02 208 1 + 48 3.337559e+05 3.403155e+05 3.359890e-02 248 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 3.359890e-02 +total solves : 248 +best bound : 3.403155e+05 +simulation ci : 1.298433e+08 ± 1.785865e+08 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 4 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [29, 29] + AffExpr in MOI.EqualTo{Float64} : [4, 5] + AffExpr in MOI.GreaterThan{Float64} : [3, 3] + AffExpr in MOI.LessThan{Float64} : [4, 4] + VariableRef in MOI.EqualTo{Float64} : [3, 3] + VariableRef in MOI.GreaterThan{Float64} : [22, 22] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+05] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 4.403329e+05 3.509666e+05 1.287389e-02 92 1 + 20 4.055335e+05 4.054833e+05 2.302384e-02 172 1 + 30 4.497721e+05 4.067125e+05 3.549290e-02 264 1 + 40 3.959476e+05 4.067125e+05 4.868197e-02 344 1 + 47 3.959476e+05 4.067125e+05 5.870891e-02 400 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 5.870891e-02 +total solves : 400 +best bound : 4.067125e+05 +simulation ci : 2.695840e+07 ± 3.645323e+07 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 10 + state variables : 4 + scenarios : 2.70000e+01 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [24, 24] + AffExpr in MOI.EqualTo{Float64} : [3, 3] + AffExpr in MOI.GreaterThan{Float64} : [1, 1] + AffExpr in MOI.LessThan{Float64} : [1, 6] + VariableRef in MOI.GreaterThan{Float64} : [20, 20] + VariableRef in MOI.LessThan{Float64} : [1, 2] +numerical stability report + matrix range [1e+00, 8e+01] + objective range [1e+00, 1e+03] + bounds range [6e+01, 6e+01] + rhs range [2e+02, 3e+03] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 8.316000e+03 0.000000e+00 9.017110e-02 14 1 + 40 7.539730e+03 4.074139e+03 2.150581e-01 776 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 2.150581e-01 +total solves : 776 +best bound : 4.074139e+03 +simulation ci : 4.487432e+03 ± 7.254646e+02 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.Integer : [3, 3] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 3e+02] + bounds range [1e+02, 2e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1L 7.000000e+04 6.250000e+04 5.673270e-01 8 1 + 20L 6.000000e+04 6.250000e+04 6.624181e-01 172 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 6.624181e-01 +total solves : 172 +best bound : 6.250000e+04 +simulation ci : 6.550000e+04 ± 8.348941e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.Integer : [3, 3] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 3e+02] + bounds range [1e+02, 2e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 7.000000e+04 6.250000e+04 3.490210e-03 8 1 + 20 5.500000e+04 6.250000e+04 4.241204e-02 172 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 4.241204e-02 +total solves : 172 +best bound : 6.250000e+04 +simulation ci : 6.475000e+04 ± 9.603974e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 5] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 3e+02] + bounds range [1e+02, 2e+02] + rhs range [1e+02, 3e+02] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.000000e+04 6.250000e+04 5.037785e-03 5 1 + 10 5.500000e+04 6.250000e+04 1.889682e-02 50 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 1.889682e-02 +total solves : 50 +best bound : 6.250000e+04 +simulation ci : 6.100000e+04 ± 1.271904e+04 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 2 + scenarios : 1.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [2, 3] + VariableRef in MOI.LessThan{Float64} : [3, 3] + VariableRef in MOI.ZeroOne : [3, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 6e+00] + bounds range [1e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1L 6.000000e+00 9.000000e+00 3.842783e-02 6 1 + 20L 9.000000e+00 9.000000e+00 7.850599e-02 123 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 7.850599e-02 +total solves : 123 +best bound : 9.000000e+00 +simulation ci : 8.850000e+00 ± 2.940000e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 7 + state variables : 2 + scenarios : 8.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 7] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [3, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 4e+00] + bounds range [1e+03, 1e+03] + rhs range [6e+01, 8e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 5 -1.136868e-13 4.739851e-01 1.224494e-02 87 1 + 10 0.000000e+00 1.484161e+00 1.887894e-02 142 1 + 15 -8.870299e+00 1.514085e+00 2.539992e-02 197 1 + 20 -1.428571e+00 1.514085e+00 3.299689e-02 252 1 + 25 -2.479988e+01 1.514085e+00 8.514595e-02 339 1 + 30 -1.428571e+00 1.514085e+00 9.319401e-02 394 1 + 35 1.421085e-14 1.514085e+00 1.017458e-01 449 1 + 40 -1.428571e+00 1.514085e+00 1.108580e-01 504 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.108580e-01 +total solves : 504 +best bound : 1.514085e+00 +simulation ci : 1.858631e+00 ± 6.043640e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 7 + state variables : 2 + scenarios : 3.20000e+01 + existing cuts : false +options + solver : serial mode + risk measure : #4 + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 7] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [2, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [2e-02, 4e+00] + bounds range [1e+03, 1e+03] + rhs range [6e+01, 8e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 2.284531e+01 1.288927e+00 1.517451e-01 278 1 + 20 2.825352e+00 1.278410e+00 1.710870e-01 428 1 + 30 -3.575118e+00 1.278410e+00 2.029240e-01 706 1 + 40 3.508198e+01 1.278410e+00 2.247581e-01 856 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 2.247581e-01 +total solves : 856 +best bound : 1.278410e+00 +simulation ci : 3.347300e+00 ± 5.636199e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 7 + state variables : 2 + scenarios : 3.20000e+01 + existing cuts : false +options + solver : serial mode + risk measure : #4 + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 7] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [2, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [2e-02, 4e+00] + bounds range [1e+03, 1e+03] + rhs range [6e+01, 8e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 1.886566e+01 1.333680e+00 3.389382e-02 278 1 + 20 2.820962e+01 1.278410e+00 8.811092e-02 428 1 + 30 3.508198e+01 1.278410e+00 1.314609e-01 706 1 + 40 7.320288e+00 1.278410e+00 1.710138e-01 856 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.710138e-01 +total solves : 856 +best bound : 1.278410e+00 +simulation ci : 4.559468e+00 ± 6.581091e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 4 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + AffExpr in MOI.GreaterThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [3, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+00] + bounds range [2e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 4.787277e+00 9.346930e+00 1.594582e+00 900 1 + 20 6.374753e+00 1.361934e+01 1.762047e+00 1720 1 + 30 2.813321e+01 1.651297e+01 2.082734e+00 3036 1 + 40 1.654759e+01 1.632970e+01 2.446471e+00 4192 1 + 50 3.570941e+00 1.846889e+01 2.723258e+00 5020 1 + 60 1.087425e+01 1.890254e+01 2.998680e+00 5808 1 + 70 9.381610e+00 1.940320e+01 3.290027e+00 6540 1 + 80 5.648731e+01 1.962435e+01 3.513474e+00 7088 1 + 90 3.879273e+01 1.981008e+01 4.009326e+00 8180 1 + 100 7.870187e+00 1.997117e+01 4.224532e+00 8664 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 4.224532e+00 +total solves : 8664 +best bound : 1.997117e+01 +simulation ci : 2.275399e+01 ± 4.541987e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 2 + scenarios : 3.20000e+01 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [10, 10] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + AffExpr in MOI.GreaterThan{Float64} : [2, 2] + AffExpr in MOI.LessThan{Float64} : [6, 6] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 6] + VariableRef in MOI.LessThan{Float64} : [6, 6] + VariableRef in MOI.ZeroOne : [5, 5] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 6e+00] + bounds range [1e+00, 1e+01] + rhs range [1e+00, 1e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 5 9.000000e+00 9.002950e+00 1.311002e-01 235 1 + 10 4.000000e+00 9.002950e+00 1.745272e-01 310 1 + 15 4.000000e+00 9.002950e+00 1.954491e-01 385 1 + 20 4.000000e+00 9.002950e+00 2.164991e-01 460 1 + 25 1.000000e+01 9.002950e+00 2.896552e-01 695 1 + 30 5.000000e+00 9.002950e+00 3.117862e-01 770 1 + 35 1.000000e+01 9.002950e+00 3.349741e-01 845 1 + 40 5.000000e+00 9.002950e+00 3.584852e-01 920 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 3.584852e-01 +total solves : 920 +best bound : 9.002950e+00 +simulation ci : 6.375000e+00 ± 7.930178e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 4 + scenarios : 2.16000e+02 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [18, 18] + AffExpr in MOI.EqualTo{Float64} : [4, 4] + AffExpr in MOI.GreaterThan{Float64} : [4, 4] + AffExpr in MOI.LessThan{Float64} : [12, 12] + VariableRef in MOI.EqualTo{Float64} : [4, 4] + VariableRef in MOI.GreaterThan{Float64} : [9, 10] + VariableRef in MOI.LessThan{Float64} : [10, 10] + VariableRef in MOI.ZeroOne : [9, 9] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 4e+00] + bounds range [1e+00, 2e+01] + rhs range [1e+00, 1e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 1.000000e+01 6.868919e+00 1.015241e-01 510 1 + 20 2.000000e+00 6.834387e+00 1.529911e-01 720 1 + 30 1.200000e+01 6.834387e+00 2.917202e-01 1230 1 + 40 7.000000e+00 6.823805e+00 3.456950e-01 1440 1 + 50 7.000000e+00 6.823805e+00 4.892981e-01 1950 1 + 60 5.000000e+00 6.823805e+00 5.427852e-01 2160 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 5.427852e-01 +total solves : 2160 +best bound : 6.823805e+00 +simulation ci : 6.183333e+00 ± 6.258900e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 5 + scenarios : 3.27680e+04 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [14, 14] + AffExpr in MOI.GreaterThan{Float64} : [7, 7] + AffExpr in MOI.LessThan{Float64} : [4, 4] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.Integer : [5, 5] + VariableRef in MOI.LessThan{Float64} : [5, 6] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 5e+05] + bounds range [1e+00, 1e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 2.549668e+06 2.078257e+06 5.081830e-01 920 1 + 20 5.494568e+05 2.078257e+06 6.979780e-01 1340 1 + 30 4.985879e+04 2.078257e+06 1.224111e+00 2260 1 + 40 3.799447e+06 2.078257e+06 1.419322e+00 2680 1 + 50 1.049867e+06 2.078257e+06 1.962126e+00 3600 1 + 60 3.985191e+04 2.078257e+06 2.160364e+00 4020 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 2.160364e+00 +total solves : 4020 +best bound : 2.078257e+06 +simulation ci : 2.031697e+06 ± 3.922745e+05 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 5 + scenarios : 3.27680e+04 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [14, 14] + AffExpr in MOI.GreaterThan{Float64} : [7, 7] + AffExpr in MOI.LessThan{Float64} : [4, 4] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.Integer : [5, 5] + VariableRef in MOI.LessThan{Float64} : [5, 6] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 5e+05] + bounds range [1e+00, 1e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10L 4.986663e+04 2.079119e+06 1.095144e+00 920 1 + 20L 3.799878e+06 2.079330e+06 1.974077e+00 1340 1 + 30L 3.003923e+04 2.079457e+06 3.204113e+00 2260 1 + 40L 5.549882e+06 2.079457e+06 4.155357e+00 2680 1 + 50L 2.799466e+06 2.079457e+06 5.457508e+00 3600 1 + 60L 3.549880e+06 2.079457e+06 6.407687e+00 4020 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 6.407687e+00 +total solves : 4020 +best bound : 2.079457e+06 +simulation ci : 2.352204e+06 ± 5.377531e+05 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [8, 8] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+01] + bounds range [5e+00, 2e+01] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 100 2.500000e+01 1.188965e+02 7.671340e-01 1946 1 + 200 2.500000e+01 1.191634e+02 9.609759e-01 3920 1 + 300 0.000000e+00 1.191666e+02 1.161596e+00 5902 1 + 330 2.500000e+01 1.191667e+02 1.201401e+00 6224 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.201401e+00 +total solves : 6224 +best bound : 1.191667e+02 +simulation ci : 2.158333e+01 ± 3.290252e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [8, 8] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+01] + bounds range [5e+00, 2e+01] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 100 0.000000e+00 1.191285e+02 2.730379e-01 2874 1 + 200 2.500000e+00 1.191666e+02 4.916561e-01 4855 1 + 282 7.500000e+00 1.191667e+02 6.215470e-01 5733 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 6.215470e-01 +total solves : 5733 +best bound : 1.191667e+02 +simulation ci : 2.104610e+01 ± 3.492245e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [3, 3] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+00] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 4.000000e+00 1.997089e+01 6.259179e-02 1204 1 + 20 8.000000e+00 2.000000e+01 8.261085e-02 1420 1 + 30 1.600000e+01 2.000000e+01 1.445110e-01 2628 1 + 40 8.000000e+00 2.000000e+01 1.655278e-01 2834 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.655278e-01 +total solves : 2834 +best bound : 2.000000e+01 +simulation ci : 1.625000e+01 ± 4.766381e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [3, 3] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+00] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.000000e+00 1.500000e+00 1.561880e-03 3 1 + 40 4.000000e+00 2.000000e+00 4.108381e-02 578 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 4.108381e-02 +total solves : 578 +best bound : 2.000000e+00 +simulation ci : 1.950000e+00 ± 5.568095e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 8.51840e+04 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.EqualTo{Float64} : [1, 3] + AffExpr in MOI.LessThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [3, 4] + VariableRef in MOI.LessThan{Float64} : [3, 3] +numerical stability report + matrix range [8e-01, 2e+00] + objective range [1e+00, 2e+00] + bounds range [1e+00, 1e+02] + rhs range [5e+01, 5e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 5.250000e+00 4.888859e+00 1.595211e-01 1350 1 + 20 4.350000e+00 4.105855e+00 2.411652e-01 2700 1 + 30 5.000000e+00 4.100490e+00 3.299210e-01 4050 1 + 40 3.500000e+00 4.097376e+00 4.261100e-01 5400 1 + 50 5.250000e+00 4.095859e+00 5.261869e-01 6750 1 + 60 3.643750e+00 4.093342e+00 6.732740e-01 8100 1 + 70 2.643750e+00 4.091818e+00 7.792270e-01 9450 1 + 80 5.087500e+00 4.091591e+00 8.864172e-01 10800 1 + 90 5.062500e+00 4.091309e+00 9.957671e-01 12150 1 + 100 4.843750e+00 4.087004e+00 1.113616e+00 13500 1 + 110 3.437500e+00 4.086094e+00 1.231364e+00 14850 1 + 120 3.375000e+00 4.085926e+00 1.350189e+00 16200 1 + 130 5.025000e+00 4.085866e+00 1.472597e+00 17550 1 + 140 5.000000e+00 4.085734e+00 1.594332e+00 18900 1 + 150 3.500000e+00 4.085655e+00 1.718657e+00 20250 1 + 160 4.281250e+00 4.085454e+00 1.838317e+00 21600 1 + 170 4.562500e+00 4.085425e+00 1.961061e+00 22950 1 + 180 5.768750e+00 4.085425e+00 2.084075e+00 24300 1 + 190 3.468750e+00 4.085359e+00 2.214355e+00 25650 1 + 200 4.131250e+00 4.085225e+00 2.359565e+00 27000 1 + 210 4.512500e+00 4.085157e+00 2.485728e+00 28350 1 + 220 4.900000e+00 4.085153e+00 2.613694e+00 29700 1 + 230 4.025000e+00 4.085134e+00 2.746500e+00 31050 1 + 240 4.468750e+00 4.085116e+00 2.880736e+00 32400 1 + 250 4.062500e+00 4.085075e+00 3.013214e+00 33750 1 + 260 4.875000e+00 4.085037e+00 3.148938e+00 35100 1 + 270 3.850000e+00 4.085011e+00 3.283243e+00 36450 1 + 280 4.912500e+00 4.084992e+00 3.455010e+00 37800 1 + 290 2.987500e+00 4.084986e+00 3.595104e+00 39150 1 + 300 3.825000e+00 4.084957e+00 3.737324e+00 40500 1 + 310 3.250000e+00 4.084911e+00 3.879043e+00 41850 1 + 320 3.600000e+00 4.084896e+00 4.019821e+00 43200 1 + 330 3.925000e+00 4.084896e+00 4.151342e+00 44550 1 + 340 4.500000e+00 4.084893e+00 4.291215e+00 45900 1 + 350 5.000000e+00 4.084891e+00 4.430990e+00 47250 1 + 360 3.075000e+00 4.084866e+00 4.568457e+00 48600 1 + 370 3.500000e+00 4.084861e+00 4.712302e+00 49950 1 + 380 3.356250e+00 4.084857e+00 4.858324e+00 51300 1 + 390 5.500000e+00 4.084846e+00 5.008902e+00 52650 1 + 400 4.475000e+00 4.084846e+00 5.152340e+00 54000 1 + 410 3.750000e+00 4.084843e+00 5.297444e+00 55350 1 + 420 3.687500e+00 4.084843e+00 5.445094e+00 56700 1 + 430 4.337500e+00 4.084825e+00 5.595158e+00 58050 1 + 440 5.750000e+00 4.084825e+00 5.731260e+00 59400 1 + 450 4.925000e+00 4.084792e+00 5.887173e+00 60750 1 + 460 3.600000e+00 4.084792e+00 6.039129e+00 62100 1 + 470 4.387500e+00 4.084792e+00 6.183893e+00 63450 1 + 480 4.000000e+00 4.084792e+00 6.337753e+00 64800 1 + 490 2.975000e+00 4.084788e+00 6.485263e+00 66150 1 + 500 3.125000e+00 4.084788e+00 6.632709e+00 67500 1 + 510 4.250000e+00 4.084788e+00 6.818290e+00 68850 1 + 520 4.512500e+00 4.084786e+00 6.963902e+00 70200 1 + 530 3.875000e+00 4.084786e+00 7.120886e+00 71550 1 + 540 4.387500e+00 4.084781e+00 7.278300e+00 72900 1 + 550 5.281250e+00 4.084780e+00 7.436060e+00 74250 1 + 560 4.650000e+00 4.084780e+00 7.582303e+00 75600 1 + 570 3.062500e+00 4.084780e+00 7.729968e+00 76950 1 + 580 3.187500e+00 4.084780e+00 7.878736e+00 78300 1 + 590 3.812500e+00 4.084780e+00 8.021174e+00 79650 1 + 600 3.637500e+00 4.084774e+00 8.174244e+00 81000 1 + 610 3.950000e+00 4.084765e+00 8.326051e+00 82350 1 + 620 4.625000e+00 4.084760e+00 8.477735e+00 83700 1 + 630 4.218750e+00 4.084760e+00 8.632540e+00 85050 1 + 640 3.025000e+00 4.084755e+00 8.786802e+00 86400 1 + 650 2.993750e+00 4.084751e+00 8.934419e+00 87750 1 + 660 3.262500e+00 4.084746e+00 9.084889e+00 89100 1 + 670 3.625000e+00 4.084746e+00 9.238606e+00 90450 1 + 680 2.981250e+00 4.084746e+00 9.394347e+00 91800 1 + 690 4.187500e+00 4.084746e+00 9.545753e+00 93150 1 + 700 4.500000e+00 4.084746e+00 9.693811e+00 94500 1 + 710 3.225000e+00 4.084746e+00 9.871742e+00 95850 1 + 720 4.375000e+00 4.084746e+00 1.002700e+01 97200 1 + 730 2.650000e+00 4.084746e+00 1.018808e+01 98550 1 + 740 3.250000e+00 4.084746e+00 1.034249e+01 99900 1 + 750 4.725000e+00 4.084746e+00 1.051434e+01 101250 1 + 760 3.375000e+00 4.084746e+00 1.068121e+01 102600 1 + 770 5.375000e+00 4.084746e+00 1.084486e+01 103950 1 + 780 4.068750e+00 4.084746e+00 1.101535e+01 105300 1 + 790 4.412500e+00 4.084746e+00 1.118774e+01 106650 1 + 800 4.350000e+00 4.084746e+00 1.135563e+01 108000 1 + 810 5.887500e+00 4.084746e+00 1.152466e+01 109350 1 + 820 4.912500e+00 4.084746e+00 1.168614e+01 110700 1 + 830 4.387500e+00 4.084746e+00 1.184156e+01 112050 1 + 840 3.675000e+00 4.084746e+00 1.201129e+01 113400 1 + 850 5.375000e+00 4.084746e+00 1.217270e+01 114750 1 + 860 3.562500e+00 4.084746e+00 1.233892e+01 116100 1 + 870 3.075000e+00 4.084746e+00 1.250823e+01 117450 1 + 880 3.625000e+00 4.084746e+00 1.267001e+01 118800 1 + 890 2.937500e+00 4.084746e+00 1.285520e+01 120150 1 + 900 4.450000e+00 4.084746e+00 1.303096e+01 121500 1 + 910 4.200000e+00 4.084746e+00 1.319607e+01 122850 1 + 920 3.687500e+00 4.084746e+00 1.336546e+01 124200 1 + 930 4.725000e+00 4.084746e+00 1.353421e+01 125550 1 + 940 4.018750e+00 4.084746e+00 1.369507e+01 126900 1 + 950 4.675000e+00 4.084746e+00 1.385280e+01 128250 1 + 960 3.375000e+00 4.084746e+00 1.401454e+01 129600 1 + 970 3.812500e+00 4.084746e+00 1.417292e+01 130950 1 + 980 3.112500e+00 4.084746e+00 1.433634e+01 132300 1 + 990 3.600000e+00 4.084746e+00 1.450062e+01 133650 1 + 1000 5.500000e+00 4.084746e+00 1.466909e+01 135000 1 + 1010 3.187500e+00 4.084746e+00 1.483119e+01 136350 1 + 1020 4.900000e+00 4.084746e+00 1.499588e+01 137700 1 + 1030 3.637500e+00 4.084746e+00 1.517314e+01 139050 1 + 1040 3.975000e+00 4.084746e+00 1.536138e+01 140400 1 + 1050 4.750000e+00 4.084746e+00 1.553015e+01 141750 1 + 1060 4.437500e+00 4.084746e+00 1.571412e+01 143100 1 + 1070 5.000000e+00 4.084746e+00 1.588702e+01 144450 1 + 1080 4.143750e+00 4.084746e+00 1.606369e+01 145800 1 + 1090 5.625000e+00 4.084746e+00 1.623112e+01 147150 1 + 1100 3.475000e+00 4.084746e+00 1.640484e+01 148500 1 + 1110 4.156250e+00 4.084746e+00 1.658396e+01 149850 1 + 1120 4.450000e+00 4.084746e+00 1.675759e+01 151200 1 + 1130 3.312500e+00 4.084741e+00 1.693208e+01 152550 1 + 1140 5.375000e+00 4.084741e+00 1.709987e+01 153900 1 + 1150 4.800000e+00 4.084737e+00 1.728024e+01 155250 1 + 1160 3.300000e+00 4.084737e+00 1.745245e+01 156600 1 + 1170 4.356250e+00 4.084737e+00 1.764803e+01 157950 1 + 1180 3.900000e+00 4.084737e+00 1.782710e+01 159300 1 + 1190 4.450000e+00 4.084737e+00 1.800597e+01 160650 1 + 1200 5.156250e+00 4.084737e+00 1.818581e+01 162000 1 + 1210 4.500000e+00 4.084737e+00 1.835251e+01 163350 1 + 1220 4.875000e+00 4.084737e+00 1.853644e+01 164700 1 + 1230 4.000000e+00 4.084737e+00 1.870808e+01 166050 1 + 1240 4.062500e+00 4.084737e+00 1.888267e+01 167400 1 + 1250 5.450000e+00 4.084737e+00 1.906262e+01 168750 1 + 1260 4.500000e+00 4.084737e+00 1.924321e+01 170100 1 + 1270 4.125000e+00 4.084737e+00 1.941998e+01 171450 1 + 1280 3.750000e+00 4.084737e+00 1.960133e+01 172800 1 + 1290 4.475000e+00 4.084737e+00 1.979600e+01 174150 1 + 1300 4.987500e+00 4.084737e+00 1.996438e+01 175500 1 + 1303 3.750000e+00 4.084737e+00 2.001024e+01 175905 1 +------------------------------------------------------------------- +status : time_limit +total time (s) : 2.001024e+01 +total solves : 175905 +best bound : 4.084737e+00 +simulation ci : 4.068022e+00 ± 3.982545e-02 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 8.51840e+04 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [6, 6] + AffExpr in MOI.EqualTo{Float64} : [1, 3] + AffExpr in MOI.LessThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [3, 4] + VariableRef in MOI.LessThan{Float64} : [3, 3] +numerical stability report + matrix range [8e-01, 2e+00] + objective range [1e+00, 2e+00] + bounds range [1e+00, 1e+02] + rhs range [5e+01, 5e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 5.168750e+00 5.149174e+00 1.744030e-01 1350 1 + 20 4.725000e+00 4.747147e+00 5.363460e-01 2700 1 + 30 4.275000e+00 4.061313e+00 9.793379e-01 4050 1 + 40 5.156250e+00 4.044283e+00 1.504181e+00 5400 1 + 50 5.200000e+00 4.042803e+00 2.110943e+00 6750 1 + 60 5.437500e+00 4.041070e+00 2.819180e+00 8100 1 + 70 5.218750e+00 4.041028e+00 3.624163e+00 9450 1 + 80 3.631250e+00 4.040924e+00 4.613025e+00 10800 1 + 90 2.650000e+00 4.040697e+00 5.738630e+00 12150 1 + 100 4.043750e+00 4.039630e+00 6.921485e+00 13500 1 + 110 5.556250e+00 4.037631e+00 8.200977e+00 14850 1 + 120 2.925000e+00 4.037627e+00 9.584426e+00 16200 1 + 130 4.000000e+00 4.037635e+00 1.117631e+01 17550 1 + 140 3.843750e+00 4.037579e+00 1.277923e+01 18900 1 + 150 3.968750e+00 4.037579e+00 1.469843e+01 20250 1 + 160 3.975000e+00 4.037559e+00 1.665195e+01 21600 1 + 170 2.981250e+00 4.037556e+00 1.860083e+01 22950 1 + 177 3.225000e+00 4.037556e+00 2.005081e+01 23895 1 +------------------------------------------------------------------- +status : time_limit +total time (s) : 2.005081e+01 +total solves : 23895 +best bound : 4.037556e+00 +simulation ci : 4.019138e+00 ± 1.161944e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 8 + state variables : 1 + scenarios : 1.00000e+08 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + AffExpr in MOI.GreaterThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.LessThan{Float64} : [1, 2] + VariableRef in MOI.ZeroOne : [1, 1] +numerical stability report + matrix range [1e+00, 2e+00] + objective range [5e-01, 1e+00] + bounds range [1e+00, 1e+00] + rhs range [1e+00, 1e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 3.399508e+00 1.165253e+00 3.668449e-01 1680 1 + 20 4.155346e+00 1.165253e+00 4.579740e-01 2560 1 + 30 2.166143e+00 1.165481e+00 8.287990e-01 4240 1 + 40 3.506633e+00 1.165481e+00 9.219449e-01 5120 1 + 50 3.447815e+00 1.165481e+00 1.299035e+00 6800 1 + 60 3.070275e+00 1.167043e+00 1.396288e+00 7680 1 + 70 3.662595e+00 1.167043e+00 1.780279e+00 9360 1 + 80 2.777826e+00 1.167299e+00 1.882254e+00 10240 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.882254e+00 +total solves : 10240 +best bound : 1.167299e+00 +simulation ci : 3.302376e+00 ± 1.028287e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 2 + scenarios : 4.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 11] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + AffExpr in MOI.LessThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 7] + VariableRef in MOI.Integer : [2, 2] + VariableRef in MOI.LessThan{Float64} : [4, 7] + VariableRef in MOI.ZeroOne : [4, 4] +numerical stability report + matrix range [1e+00, 6e+00] + objective range [1e+00, 3e+01] + bounds range [1e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 -4.000000e+01 -5.809615e+01 3.117514e-02 78 1 + 20 -4.000000e+01 -5.809615e+01 6.307220e-02 148 1 + 30 -4.700000e+01 -5.809615e+01 1.020651e-01 226 1 + 40 -4.700000e+01 -5.809615e+01 1.357501e-01 296 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.357501e-01 +total solves : 296 +best bound : -5.809615e+01 +simulation ci : -5.496250e+01 ± 7.974877e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 2 + scenarios : 9.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 11] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + AffExpr in MOI.LessThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 7] + VariableRef in MOI.Integer : [2, 2] + VariableRef in MOI.LessThan{Float64} : [4, 7] + VariableRef in MOI.ZeroOne : [4, 4] +numerical stability report + matrix range [1e+00, 6e+00] + objective range [1e+00, 3e+01] + bounds range [1e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 -4.700000e+01 -6.196125e+01 3.870416e-02 138 1 + 20 -4.700000e+01 -6.196125e+01 7.434416e-02 258 1 + 30 -8.200000e+01 -6.196125e+01 1.228361e-01 396 1 + 40 -4.700000e+01 -6.196125e+01 1.602201e-01 516 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.602201e-01 +total solves : 516 +best bound : -6.196125e+01 +simulation ci : -5.958750e+01 ± 6.241506e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 2 + scenarios : 3.60000e+01 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 11] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + AffExpr in MOI.LessThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [5, 7] + VariableRef in MOI.Integer : [2, 2] + VariableRef in MOI.LessThan{Float64} : [4, 7] + VariableRef in MOI.ZeroOne : [4, 4] +numerical stability report + matrix range [1e+00, 6e+00] + objective range [1e+00, 3e+01] + bounds range [1e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 10 -8.200000e+01 -6.546793e+01 7.412124e-02 462 1 + 20 -4.000000e+01 -6.546793e+01 1.323652e-01 852 1 + 30 -8.200000e+01 -6.546793e+01 2.462361e-01 1314 1 + 40 -5.900000e+01 -6.546793e+01 3.054502e-01 1704 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 3.054502e-01 +total solves : 1704 +best bound : -6.546793e+01 +simulation ci : -6.143750e+01 ± 4.873947e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 2 + scenarios : 2.70000e+01 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + AffExpr in MOI.LessThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [2, 3] + VariableRef in MOI.LessThan{Float64} : [3, 3] + VariableRef in MOI.ZeroOne : [4, 4] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 6e+00] + bounds range [1e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1L 1.200000e+01 1.414815e+01 4.120493e-02 11 1 + 40L 1.200000e+01 8.000000e+00 4.114580e-01 602 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 4.114580e-01 +total solves : 602 +best bound : 8.000000e+00 +simulation ci : 8.625000e+00 ± 8.978763e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 3 + scenarios : 3.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 19] + AffExpr in MOI.EqualTo{Float64} : [3, 3] + AffExpr in MOI.GreaterThan{Float64} : [3, 3] + AffExpr in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [3, 16] + VariableRef in MOI.LessThan{Float64} : [1, 2] +numerical stability report + matrix range [1e+00, 2e+01] + objective range [1e+00, 1e+03] + bounds range [6e+03, 5e+05] + rhs range [2e+02, 5e+02] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 -9.800000e+04 4.922260e+05 8.680820e-02 6 1 + 40 1.670000e+05 1.083900e+05 1.148281e-01 240 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 1.148281e-01 +total solves : 240 +best bound : 1.083900e+05 +simulation ci : 9.026160e+04 ± 1.919853e+04 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.0.log b/previews/PR826/examples/SDDP_0.0.log new file mode 100644 index 0000000000..9300902139 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.0.log @@ -0,0 +1,32 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 5] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 0.000000e+00 0.000000e+00 4.596949e-03 36 1 + 10 0.000000e+00 0.000000e+00 2.298403e-02 360 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.298403e-02 +total solves : 360 +best bound : 0.000000e+00 +simulation ci : 0.000000e+00 ± 0.000000e+00 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.0625.log b/previews/PR826/examples/SDDP_0.0625.log new file mode 100644 index 0000000000..3d9cb2759d --- /dev/null +++ b/previews/PR826/examples/SDDP_0.0625.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 53] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.437500e+01 5.937500e+01 2.995014e-03 3375 1 + 10 3.750000e+01 5.938557e+01 2.948904e-02 3699 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.948904e-02 +total solves : 3699 +best bound : 5.938557e+01 +simulation ci : 5.906250e+01 ± 1.352595e+01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.125.log b/previews/PR826/examples/SDDP_0.125.log new file mode 100644 index 0000000000..97a99eb67d --- /dev/null +++ b/previews/PR826/examples/SDDP_0.125.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 33] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.675000e+02 1.129545e+02 2.702951e-03 1891 1 + 10 1.362500e+02 1.129771e+02 2.858090e-02 2215 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.858090e-02 +total solves : 2215 +best bound : 1.129771e+02 +simulation ci : 1.176375e+02 ± 1.334615e+01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.25.log b/previews/PR826/examples/SDDP_0.25.log new file mode 100644 index 0000000000..86c778fc92 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.25.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 19] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.887500e+02 1.995243e+02 3.114939e-03 1149 1 + 10 2.962500e+02 2.052855e+02 2.890491e-02 1473 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.890491e-02 +total solves : 1473 +best bound : 2.052855e+02 +simulation ci : 2.040201e+02 ± 3.876873e+01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.375.log b/previews/PR826/examples/SDDP_0.375.log new file mode 100644 index 0000000000..eef6aae211 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.375.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 36] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 2.562500e+02 2.788373e+02 3.043890e-03 2262 1 + 10 2.375000e+02 2.795671e+02 3.205705e-02 2586 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 3.205705e-02 +total solves : 2586 +best bound : 2.795671e+02 +simulation ci : 2.375000e+02 ± 3.099032e+01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.5.log b/previews/PR826/examples/SDDP_0.5.log new file mode 100644 index 0000000000..d7630190a4 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.5.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 14] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 4.850000e+02 3.349793e+02 2.889872e-03 778 1 + 10 3.550000e+02 3.468286e+02 2.922487e-02 1102 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.922487e-02 +total solves : 1102 +best bound : 3.468286e+02 +simulation ci : 3.948309e+02 ± 7.954180e+01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.625.log b/previews/PR826/examples/SDDP_0.625.log new file mode 100644 index 0000000000..c2f898a606 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.625.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 41] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.812500e+02 4.072952e+02 3.933907e-03 2633 1 + 10 5.818750e+02 4.080500e+02 3.461695e-02 2957 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 3.461695e-02 +total solves : 2957 +best bound : 4.080500e+02 +simulation ci : 4.235323e+02 ± 1.029245e+02 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.75.log b/previews/PR826/examples/SDDP_0.75.log new file mode 100644 index 0000000000..8bd90607f4 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.75.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 25] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.737500e+02 4.626061e+02 3.158092e-03 1520 1 + 10 2.450000e+02 4.658509e+02 3.224707e-02 1844 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 3.224707e-02 +total solves : 1844 +best bound : 4.658509e+02 +simulation ci : 3.907376e+02 ± 9.045105e+01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_0.875.log b/previews/PR826/examples/SDDP_0.875.log new file mode 100644 index 0000000000..6f5080cd98 --- /dev/null +++ b/previews/PR826/examples/SDDP_0.875.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 47] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 8.525000e+02 5.197742e+02 3.314972e-03 3004 1 + 10 4.493750e+02 5.211793e+02 3.488708e-02 3328 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 3.488708e-02 +total solves : 3328 +best bound : 5.211793e+02 +simulation ci : 5.268125e+02 ± 1.227709e+02 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/SDDP_1.0.log b/previews/PR826/examples/SDDP_1.0.log new file mode 100644 index 0000000000..5ba5b34740 --- /dev/null +++ b/previews/PR826/examples/SDDP_1.0.log @@ -0,0 +1,33 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.33100e+03 + existing cuts : true +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [3, 7] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 8] + VariableRef in MOI.LessThan{Float64} : [5, 6] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 6.750000e+02 5.500000e+02 2.655029e-03 407 1 + 10 4.500000e+02 5.733959e+02 2.791214e-02 731 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.791214e-02 +total solves : 731 +best bound : 5.733959e+02 +simulation ci : 5.000000e+02 ± 1.079583e+02 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/examples/StochDynamicProgramming.jl_multistock.ipynb b/previews/PR826/examples/StochDynamicProgramming.jl_multistock.ipynb new file mode 100644 index 0000000000..97dd4b0e07 --- /dev/null +++ b/previews/PR826/examples/StochDynamicProgramming.jl_multistock.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# StochDynamicProgramming: the multistock problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example comes from [StochDynamicProgramming.jl](https://github.com/JuliaOpt/StochDynamicProgramming.jl/tree/f68b9da541c2f811ce24fc76f6065803a0715c2f/examples/multistock-example.jl)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function test_multistock_example()\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 5,\n", + " lower_bound = -5.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, stage\n", + " @variable(\n", + " subproblem,\n", + " 0 <= stock[i = 1:3] <= 1,\n", + " SDDP.State,\n", + " initial_value = 0.5\n", + " )\n", + " @variables(subproblem, begin\n", + " 0 <= control[i = 1:3] <= 0.5\n", + " ξ[i = 1:3] # Dummy for RHS noise.\n", + " end)\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " sum(control) - 0.5 * 3 <= 0\n", + " [i = 1:3], stock[i].out == stock[i].in + control[i] - ξ[i]\n", + " end\n", + " )\n", + " Ξ = collect(\n", + " Base.product((0.0, 0.15, 0.3), (0.0, 0.15, 0.3), (0.0, 0.15, 0.3)),\n", + " )[:]\n", + " SDDP.parameterize(subproblem, Ξ) do ω\n", + " return JuMP.fix.(ξ, ω)\n", + " end\n", + " @stageobjective(subproblem, (sin(3 * stage) - 1) * sum(control))\n", + " end\n", + " SDDP.train(\n", + " model;\n", + " iteration_limit = 100,\n", + " cut_type = SDDP.SINGLE_CUT,\n", + " log_frequency = 10,\n", + " )\n", + " @test SDDP.calculate_bound(model) ≈ -4.349 atol = 0.01\n", + "\n", + " simulation_results = SDDP.simulate(model, 5000)\n", + " @test length(simulation_results) == 5000\n", + " μ = SDDP.Statistics.mean(\n", + " sum(data[:stage_objective] for data in simulation) for\n", + " simulation in simulation_results\n", + " )\n", + " @test μ ≈ -4.349 atol = 0.1\n", + " return\n", + "end\n", + "\n", + "test_multistock_example()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/StochDynamicProgramming.jl_multistock.jl b/previews/PR826/examples/StochDynamicProgramming.jl_multistock.jl new file mode 100644 index 0000000000..57b646be0f --- /dev/null +++ b/previews/PR826/examples/StochDynamicProgramming.jl_multistock.jl @@ -0,0 +1,61 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # StochDynamicProgramming: the multistock problem + +# This example comes from [StochDynamicProgramming.jl](https://github.com/JuliaOpt/StochDynamicProgramming.jl/tree/f68b9da541c2f811ce24fc76f6065803a0715c2f/examples/multistock-example.jl). + +using SDDP, HiGHS, Test + +function test_multistock_example() + model = SDDP.LinearPolicyGraph(; + stages = 5, + lower_bound = -5.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, stage + @variable( + subproblem, + 0 <= stock[i = 1:3] <= 1, + SDDP.State, + initial_value = 0.5 + ) + @variables(subproblem, begin + 0 <= control[i = 1:3] <= 0.5 + ξ[i = 1:3] # Dummy for RHS noise. + end) + @constraints( + subproblem, + begin + sum(control) - 0.5 * 3 <= 0 + [i = 1:3], stock[i].out == stock[i].in + control[i] - ξ[i] + end + ) + Ξ = collect( + Base.product((0.0, 0.15, 0.3), (0.0, 0.15, 0.3), (0.0, 0.15, 0.3)), + )[:] + SDDP.parameterize(subproblem, Ξ) do ω + return JuMP.fix.(ξ, ω) + end + @stageobjective(subproblem, (sin(3 * stage) - 1) * sum(control)) + end + SDDP.train( + model; + iteration_limit = 100, + cut_type = SDDP.SINGLE_CUT, + log_frequency = 10, + ) + @test SDDP.calculate_bound(model) ≈ -4.349 atol = 0.01 + + simulation_results = SDDP.simulate(model, 5000) + @test length(simulation_results) == 5000 + μ = SDDP.Statistics.mean( + sum(data[:stage_objective] for data in simulation) for + simulation in simulation_results + ) + @test μ ≈ -4.349 atol = 0.1 + return +end + +test_multistock_example() diff --git a/previews/PR826/examples/StochDynamicProgramming.jl_multistock/index.html b/previews/PR826/examples/StochDynamicProgramming.jl_multistock/index.html new file mode 100644 index 0000000000..8c97f9a952 --- /dev/null +++ b/previews/PR826/examples/StochDynamicProgramming.jl_multistock/index.html @@ -0,0 +1,101 @@ + +StochDynamicProgramming: the multistock problem · SDDP.jl

StochDynamicProgramming: the multistock problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example comes from StochDynamicProgramming.jl.

using SDDP, HiGHS, Test
+
+function test_multistock_example()
+    model = SDDP.LinearPolicyGraph(;
+        stages = 5,
+        lower_bound = -5.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, stage
+        @variable(
+            subproblem,
+            0 <= stock[i = 1:3] <= 1,
+            SDDP.State,
+            initial_value = 0.5
+        )
+        @variables(subproblem, begin
+            0 <= control[i = 1:3] <= 0.5
+            ξ[i = 1:3]  # Dummy for RHS noise.
+        end)
+        @constraints(
+            subproblem,
+            begin
+                sum(control) - 0.5 * 3 <= 0
+                [i = 1:3], stock[i].out == stock[i].in + control[i] - ξ[i]
+            end
+        )
+        Ξ = collect(
+            Base.product((0.0, 0.15, 0.3), (0.0, 0.15, 0.3), (0.0, 0.15, 0.3)),
+        )[:]
+        SDDP.parameterize(subproblem, Ξ) do ω
+            return JuMP.fix.(ξ, ω)
+        end
+        @stageobjective(subproblem, (sin(3 * stage) - 1) * sum(control))
+    end
+    SDDP.train(
+        model;
+        iteration_limit = 100,
+        cut_type = SDDP.SINGLE_CUT,
+        log_frequency = 10,
+    )
+    @test SDDP.calculate_bound(model) ≈ -4.349 atol = 0.01
+
+    simulation_results = SDDP.simulate(model, 5000)
+    @test length(simulation_results) == 5000
+    μ = SDDP.Statistics.mean(
+        sum(data[:stage_objective] for data in simulation) for
+        simulation in simulation_results
+    )
+    @test μ ≈ -4.349 atol = 0.1
+    return
+end
+
+test_multistock_example()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 5
+  state variables : 3
+  scenarios       : 1.43489e+07
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [13, 13]
+  AffExpr in MOI.EqualTo{Float64}         : [3, 3]
+  AffExpr in MOI.LessThan{Float64}        : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [3, 3]
+  VariableRef in MOI.GreaterThan{Float64} : [7, 7]
+  VariableRef in MOI.LessThan{Float64}    : [6, 7]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [3e-01, 2e+00]
+  bounds range     [5e-01, 5e+00]
+  rhs range        [2e+00, 2e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10  -4.385228e+00 -4.442269e+00  1.850021e-01      1400   1
+        20  -3.881547e+00 -4.392566e+00  2.992721e-01      2800   1
+        30  -3.671354e+00 -4.376606e+00  4.218981e-01      4200   1
+        40  -4.312032e+00 -4.370551e+00  5.528450e-01      5600   1
+        50  -4.077684e+00 -4.362538e+00  6.866391e-01      7000   1
+        60  -3.609363e+00 -4.357411e+00  8.226821e-01      8400   1
+        70  -4.997551e+00 -4.355252e+00  9.584889e-01      9800   1
+        80  -4.608038e+00 -4.352744e+00  1.101198e+00     11200   1
+        90  -4.949477e+00 -4.350750e+00  1.245440e+00     12600   1
+       100  -4.165260e+00 -4.349697e+00  1.399319e+00     14000   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 1.399319e+00
+total solves   : 14000
+best bound     : -4.349697e+00
+simulation ci  : -4.310541e+00 ± 9.196174e-02
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/StochDynamicProgramming.jl_stock.ipynb b/previews/PR826/examples/StochDynamicProgramming.jl_stock.ipynb new file mode 100644 index 0000000000..c88de1b75f --- /dev/null +++ b/previews/PR826/examples/StochDynamicProgramming.jl_stock.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# StochDynamicProgramming: the stock problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example comes from [StochDynamicProgramming.jl](https://github.com/JuliaOpt/StochDynamicProgramming.jl/tree/f68b9da541c2f811ce24fc76f6065803a0715c2f/examples/stock-example.jl)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function stock_example()\n", + " model = SDDP.PolicyGraph(\n", + " SDDP.LinearGraph(5);\n", + " lower_bound = -2,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, stage\n", + " @variable(sp, 0 <= state <= 1, SDDP.State, initial_value = 0.5)\n", + " @variable(sp, 0 <= control <= 0.5)\n", + " @variable(sp, ξ)\n", + " @constraint(sp, state.out == state.in - control + ξ)\n", + " SDDP.parameterize(sp, 0.0:1/30:0.3) do ω\n", + " return JuMP.fix(ξ, ω)\n", + " end\n", + " @stageobjective(sp, (sin(3 * stage) - 1) * control)\n", + " end\n", + " SDDP.train(model; log_frequency = 10)\n", + " @test SDDP.calculate_bound(model) ≈ -1.471 atol = 0.001\n", + " simulation_results = SDDP.simulate(model, 1_000)\n", + " @test length(simulation_results) == 1_000\n", + " μ = SDDP.Statistics.mean(\n", + " sum(data[:stage_objective] for data in simulation) for\n", + " simulation in simulation_results\n", + " )\n", + " @test μ ≈ -1.471 atol = 0.05\n", + " return\n", + "end\n", + "\n", + "stock_example()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/StochDynamicProgramming.jl_stock.jl b/previews/PR826/examples/StochDynamicProgramming.jl_stock.jl new file mode 100644 index 0000000000..b3605f411f --- /dev/null +++ b/previews/PR826/examples/StochDynamicProgramming.jl_stock.jl @@ -0,0 +1,39 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # StochDynamicProgramming: the stock problem + +# This example comes from [StochDynamicProgramming.jl](https://github.com/JuliaOpt/StochDynamicProgramming.jl/tree/f68b9da541c2f811ce24fc76f6065803a0715c2f/examples/stock-example.jl). + +using SDDP, HiGHS, Test + +function stock_example() + model = SDDP.PolicyGraph( + SDDP.LinearGraph(5); + lower_bound = -2, + optimizer = HiGHS.Optimizer, + ) do sp, stage + @variable(sp, 0 <= state <= 1, SDDP.State, initial_value = 0.5) + @variable(sp, 0 <= control <= 0.5) + @variable(sp, ξ) + @constraint(sp, state.out == state.in - control + ξ) + SDDP.parameterize(sp, 0.0:1/30:0.3) do ω + return JuMP.fix(ξ, ω) + end + @stageobjective(sp, (sin(3 * stage) - 1) * control) + end + SDDP.train(model; log_frequency = 10) + @test SDDP.calculate_bound(model) ≈ -1.471 atol = 0.001 + simulation_results = SDDP.simulate(model, 1_000) + @test length(simulation_results) == 1_000 + μ = SDDP.Statistics.mean( + sum(data[:stage_objective] for data in simulation) for + simulation in simulation_results + ) + @test μ ≈ -1.471 atol = 0.05 + return +end + +stock_example() diff --git a/previews/PR826/examples/StochDynamicProgramming.jl_stock/index.html b/previews/PR826/examples/StochDynamicProgramming.jl_stock/index.html new file mode 100644 index 0000000000..b94b8ffe61 --- /dev/null +++ b/previews/PR826/examples/StochDynamicProgramming.jl_stock/index.html @@ -0,0 +1,87 @@ + +StochDynamicProgramming: the stock problem · SDDP.jl

StochDynamicProgramming: the stock problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example comes from StochDynamicProgramming.jl.

using SDDP, HiGHS, Test
+
+function stock_example()
+    model = SDDP.PolicyGraph(
+        SDDP.LinearGraph(5);
+        lower_bound = -2,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, stage
+        @variable(sp, 0 <= state <= 1, SDDP.State, initial_value = 0.5)
+        @variable(sp, 0 <= control <= 0.5)
+        @variable(sp, ξ)
+        @constraint(sp, state.out == state.in - control + ξ)
+        SDDP.parameterize(sp, 0.0:1/30:0.3) do ω
+            return JuMP.fix(ξ, ω)
+        end
+        @stageobjective(sp, (sin(3 * stage) - 1) * control)
+    end
+    SDDP.train(model; log_frequency = 10)
+    @test SDDP.calculate_bound(model) ≈ -1.471 atol = 0.001
+    simulation_results = SDDP.simulate(model, 1_000)
+    @test length(simulation_results) == 1_000
+    μ = SDDP.Statistics.mean(
+        sum(data[:stage_objective] for data in simulation) for
+        simulation in simulation_results
+    )
+    @test μ ≈ -1.471 atol = 0.05
+    return
+end
+
+stock_example()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 5
+  state variables : 1
+  scenarios       : 1.00000e+05
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [5, 5]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [3, 3]
+  VariableRef in MOI.LessThan{Float64}    : [2, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [3e-01, 2e+00]
+  bounds range     [5e-01, 2e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10  -1.442965e+00 -1.475770e+00  6.840205e-02      1050   1
+        20  -1.559536e+00 -1.471842e+00  1.059361e-01      1600   1
+        30  -1.633260e+00 -1.471526e+00  1.868749e-01      2650   1
+        40  -1.512908e+00 -1.471277e+00  2.279599e-01      3200   1
+        50  -1.504160e+00 -1.471172e+00  3.149250e-01      4250   1
+        60  -1.352909e+00 -1.471159e+00  3.596299e-01      4800   1
+        70  -1.520847e+00 -1.471079e+00  4.476480e-01      5850   1
+        80  -1.340949e+00 -1.471075e+00  4.943719e-01      6400   1
+        90  -1.260376e+00 -1.471075e+00  6.213000e-01      7450   1
+       100  -1.573293e+00 -1.471075e+00  6.693439e-01      8000   1
+       110  -1.364263e+00 -1.471075e+00  7.184119e-01      8550   1
+       120  -1.572199e+00 -1.471075e+00  7.686610e-01      9100   1
+       130  -1.548012e+00 -1.471075e+00  8.193090e-01      9650   1
+       140  -1.462561e+00 -1.471075e+00  8.706410e-01     10200   1
+       150  -1.154072e+00 -1.471075e+00  9.239759e-01     10750   1
+       160  -1.512908e+00 -1.471075e+00  9.779410e-01     11300   1
+       170  -1.486600e+00 -1.471075e+00  1.032787e+00     11850   1
+       180  -1.405274e+00 -1.471075e+00  1.085961e+00     12400   1
+       187  -1.281632e+00 -1.471075e+00  1.123397e+00     12785   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.123397e+00
+total solves   : 12785
+best bound     : -1.471075e+00
+simulation ci  : -1.474018e+00 ± 2.599111e-02
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.ipynb b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.ipynb new file mode 100644 index 0000000000..4194655524 --- /dev/null +++ b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# StructDualDynProg: Problem 5.2, 2 stages" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example comes from [StochasticDualDynamicProgramming.jl](https://github.com/blegat/StochasticDualDynamicProgramming.jl/blob/fe5ef82db6befd7c8f11c023a639098ecb85737d/test/prob5.2_2stages.jl)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function test_prob52_2stages()\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, stage\n", + " # ========== Problem data ==========\n", + " n = 4\n", + " m = 3\n", + " i_c = [16, 5, 32, 2]\n", + " C = [25, 80, 6.5, 160]\n", + " T = [8760, 7000, 1500] / 8760\n", + " D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])]\n", + " p2 = [0.9, 0.1]\n", + " # ========== State Variables ==========\n", + " @variable(subproblem, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0)\n", + " # ========== Variables ==========\n", + " @variables(subproblem, begin\n", + " y[1:n, 1:m] >= 0\n", + " v[1:n] >= 0\n", + " penalty >= 0\n", + " rhs_noise[1:m] # Dummy variable for RHS noise term.\n", + " end)\n", + " # ========== Constraints ==========\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " [i = 1:n], x[i].out == x[i].in + v[i]\n", + " [i = 1:n], sum(y[i, :]) <= x[i].in\n", + " [j = 1:m], sum(y[:, j]) + penalty >= rhs_noise[j]\n", + " end\n", + " )\n", + " if stage == 2\n", + " # No investment in last stage.\n", + " @constraint(subproblem, sum(v) == 0)\n", + " end\n", + " # ========== Uncertainty ==========\n", + " if stage != 1 # no uncertainty in first stage\n", + " SDDP.parameterize(subproblem, 1:size(D2, 2), p2) do ω\n", + " for j in 1:m\n", + " JuMP.fix(rhs_noise[j], D2[j, ω])\n", + " end\n", + " end\n", + " end\n", + " # ========== Stage objective ==========\n", + " @stageobjective(subproblem, i_c' * v + C' * y * T + 1e6 * penalty)\n", + " return\n", + " end\n", + " SDDP.train(model; log_frequency = 10)\n", + " @test SDDP.calculate_bound(model) ≈ 340315.52 atol = 0.1\n", + " return\n", + "end\n", + "\n", + "test_prob52_2stages()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.jl b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.jl new file mode 100644 index 0000000000..872931b07c --- /dev/null +++ b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages.jl @@ -0,0 +1,65 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # StructDualDynProg: Problem 5.2, 2 stages + +# This example comes from [StochasticDualDynamicProgramming.jl](https://github.com/blegat/StochasticDualDynamicProgramming.jl/blob/fe5ef82db6befd7c8f11c023a639098ecb85737d/test/prob5.2_2stages.jl) + +using SDDP, HiGHS, Test + +function test_prob52_2stages() + model = SDDP.LinearPolicyGraph(; + stages = 2, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, stage + ## ========== Problem data ========== + n = 4 + m = 3 + i_c = [16, 5, 32, 2] + C = [25, 80, 6.5, 160] + T = [8760, 7000, 1500] / 8760 + D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])] + p2 = [0.9, 0.1] + ## ========== State Variables ========== + @variable(subproblem, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0) + ## ========== Variables ========== + @variables(subproblem, begin + y[1:n, 1:m] >= 0 + v[1:n] >= 0 + penalty >= 0 + rhs_noise[1:m] # Dummy variable for RHS noise term. + end) + ## ========== Constraints ========== + @constraints( + subproblem, + begin + [i = 1:n], x[i].out == x[i].in + v[i] + [i = 1:n], sum(y[i, :]) <= x[i].in + [j = 1:m], sum(y[:, j]) + penalty >= rhs_noise[j] + end + ) + if stage == 2 + ## No investment in last stage. + @constraint(subproblem, sum(v) == 0) + end + ## ========== Uncertainty ========== + if stage != 1 # no uncertainty in first stage + SDDP.parameterize(subproblem, 1:size(D2, 2), p2) do ω + for j in 1:m + JuMP.fix(rhs_noise[j], D2[j, ω]) + end + end + end + ## ========== Stage objective ========== + @stageobjective(subproblem, i_c' * v + C' * y * T + 1e6 * penalty) + return + end + SDDP.train(model; log_frequency = 10) + @test SDDP.calculate_bound(model) ≈ 340315.52 atol = 0.1 + return +end + +test_prob52_2stages() diff --git a/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages/index.html b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages/index.html new file mode 100644 index 0000000000..edf3cdc058 --- /dev/null +++ b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_2stages/index.html @@ -0,0 +1,101 @@ + +StructDualDynProg: Problem 5.2, 2 stages · SDDP.jl

StructDualDynProg: Problem 5.2, 2 stages

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example comes from StochasticDualDynamicProgramming.jl

using SDDP, HiGHS, Test
+
+function test_prob52_2stages()
+    model = SDDP.LinearPolicyGraph(;
+        stages = 2,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, stage
+        # ========== Problem data ==========
+        n = 4
+        m = 3
+        i_c = [16, 5, 32, 2]
+        C = [25, 80, 6.5, 160]
+        T = [8760, 7000, 1500] / 8760
+        D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])]
+        p2 = [0.9, 0.1]
+        # ========== State Variables ==========
+        @variable(subproblem, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0)
+        # ========== Variables ==========
+        @variables(subproblem, begin
+            y[1:n, 1:m] >= 0
+            v[1:n] >= 0
+            penalty >= 0
+            rhs_noise[1:m]  # Dummy variable for RHS noise term.
+        end)
+        # ========== Constraints ==========
+        @constraints(
+            subproblem,
+            begin
+                [i = 1:n], x[i].out == x[i].in + v[i]
+                [i = 1:n], sum(y[i, :]) <= x[i].in
+                [j = 1:m], sum(y[:, j]) + penalty >= rhs_noise[j]
+            end
+        )
+        if stage == 2
+            # No investment in last stage.
+            @constraint(subproblem, sum(v) == 0)
+        end
+        # ========== Uncertainty ==========
+        if stage != 1 # no uncertainty in first stage
+            SDDP.parameterize(subproblem, 1:size(D2, 2), p2) do ω
+                for j in 1:m
+                    JuMP.fix(rhs_noise[j], D2[j, ω])
+                end
+            end
+        end
+        # ========== Stage objective ==========
+        @stageobjective(subproblem, i_c' * v + C' * y * T + 1e6 * penalty)
+        return
+    end
+    SDDP.train(model; log_frequency = 10)
+    @test SDDP.calculate_bound(model) ≈ 340315.52 atol = 0.1
+    return
+end
+
+test_prob52_2stages()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 4
+  scenarios       : 2.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [29, 29]
+  AffExpr in MOI.EqualTo{Float64}         : [4, 5]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 3]
+  AffExpr in MOI.LessThan{Float64}        : [4, 4]
+  VariableRef in MOI.EqualTo{Float64}     : [3, 3]
+  VariableRef in MOI.GreaterThan{Float64} : [22, 22]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+06]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   3.455904e+05  3.147347e+05  7.813931e-03        54   1
+        20   3.336455e+05  3.402383e+05  1.366687e-02       104   1
+        30   3.993519e+05  3.403155e+05  2.068496e-02       158   1
+        40   3.337559e+05  3.403155e+05  2.764297e-02       208   1
+        48   3.337559e+05  3.403155e+05  3.359890e-02       248   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 3.359890e-02
+total solves   : 248
+best bound     :  3.403155e+05
+simulation ci  :  1.298433e+08 ± 1.785865e+08
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.ipynb b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.ipynb new file mode 100644 index 0000000000..e769cf2b55 --- /dev/null +++ b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.ipynb @@ -0,0 +1,92 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# StructDualDynProg: Problem 5.2, 3 stages" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example comes from [StochasticDualDynamicProgramming.jl](https://github.com/blegat/StochasticDualDynamicProgramming.jl/blob/fe5ef82db6befd7c8f11c023a639098ecb85737d/test/prob5.2_3stages.jl)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function test_prob52_3stages()\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " n = 4\n", + " m = 3\n", + " i_c = [16, 5, 32, 2]\n", + " C = [25, 80, 6.5, 160]\n", + " T = [8760, 7000, 1500] / 8760\n", + " D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])]\n", + " p2 = [0.9, 0.1]\n", + " @variable(sp, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0)\n", + " @variables(sp, begin\n", + " y[1:n, 1:m] >= 0\n", + " v[1:n] >= 0\n", + " penalty >= 0\n", + " ξ[j = 1:m]\n", + " end)\n", + " @constraints(sp, begin\n", + " [i = 1:n], x[i].out == x[i].in + v[i]\n", + " [i = 1:n], sum(y[i, :]) <= x[i].in\n", + " [j = 1:m], sum(y[:, j]) + penalty >= ξ[j]\n", + " end)\n", + " @stageobjective(sp, i_c'v + C' * y * T + 1e5 * penalty)\n", + " if t != 1 # no uncertainty in first stage\n", + " SDDP.parameterize(sp, 1:size(D2, 2), p2) do ω\n", + " for j in 1:m\n", + " JuMP.fix(ξ[j], D2[j, ω])\n", + " end\n", + " end\n", + " end\n", + " if t == 3\n", + " @constraint(sp, sum(v) == 0)\n", + " end\n", + " end\n", + "\n", + " det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\n", + " set_silent(det)\n", + " JuMP.optimize!(det)\n", + " @test JuMP.objective_value(det) ≈ 406712.49 atol = 0.1\n", + "\n", + " SDDP.train(model; log_frequency = 10)\n", + " @test SDDP.calculate_bound(model) ≈ 406712.49 atol = 0.1\n", + " return\n", + "end\n", + "\n", + "test_prob52_3stages()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.jl b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.jl new file mode 100644 index 0000000000..b48525a8b1 --- /dev/null +++ b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages.jl @@ -0,0 +1,60 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # StructDualDynProg: Problem 5.2, 3 stages + +# This example comes from [StochasticDualDynamicProgramming.jl](https://github.com/blegat/StochasticDualDynamicProgramming.jl/blob/fe5ef82db6befd7c8f11c023a639098ecb85737d/test/prob5.2_3stages.jl). + +using SDDP, HiGHS, Test + +function test_prob52_3stages() + model = SDDP.LinearPolicyGraph(; + stages = 3, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + n = 4 + m = 3 + i_c = [16, 5, 32, 2] + C = [25, 80, 6.5, 160] + T = [8760, 7000, 1500] / 8760 + D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])] + p2 = [0.9, 0.1] + @variable(sp, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0) + @variables(sp, begin + y[1:n, 1:m] >= 0 + v[1:n] >= 0 + penalty >= 0 + ξ[j = 1:m] + end) + @constraints(sp, begin + [i = 1:n], x[i].out == x[i].in + v[i] + [i = 1:n], sum(y[i, :]) <= x[i].in + [j = 1:m], sum(y[:, j]) + penalty >= ξ[j] + end) + @stageobjective(sp, i_c'v + C' * y * T + 1e5 * penalty) + if t != 1 # no uncertainty in first stage + SDDP.parameterize(sp, 1:size(D2, 2), p2) do ω + for j in 1:m + JuMP.fix(ξ[j], D2[j, ω]) + end + end + end + if t == 3 + @constraint(sp, sum(v) == 0) + end + end + + det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer) + set_silent(det) + JuMP.optimize!(det) + @test JuMP.objective_value(det) ≈ 406712.49 atol = 0.1 + + SDDP.train(model; log_frequency = 10) + @test SDDP.calculate_bound(model) ≈ 406712.49 atol = 0.1 + return +end + +test_prob52_3stages() diff --git a/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages/index.html b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages/index.html new file mode 100644 index 0000000000..256113e10f --- /dev/null +++ b/previews/PR826/examples/StructDualDynProg.jl_prob5.2_3stages/index.html @@ -0,0 +1,96 @@ + +StructDualDynProg: Problem 5.2, 3 stages · SDDP.jl

StructDualDynProg: Problem 5.2, 3 stages

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example comes from StochasticDualDynamicProgramming.jl.

using SDDP, HiGHS, Test
+
+function test_prob52_3stages()
+    model = SDDP.LinearPolicyGraph(;
+        stages = 3,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        n = 4
+        m = 3
+        i_c = [16, 5, 32, 2]
+        C = [25, 80, 6.5, 160]
+        T = [8760, 7000, 1500] / 8760
+        D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])]
+        p2 = [0.9, 0.1]
+        @variable(sp, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0)
+        @variables(sp, begin
+            y[1:n, 1:m] >= 0
+            v[1:n] >= 0
+            penalty >= 0
+            ξ[j = 1:m]
+        end)
+        @constraints(sp, begin
+            [i = 1:n], x[i].out == x[i].in + v[i]
+            [i = 1:n], sum(y[i, :]) <= x[i].in
+            [j = 1:m], sum(y[:, j]) + penalty >= ξ[j]
+        end)
+        @stageobjective(sp, i_c'v + C' * y * T + 1e5 * penalty)
+        if t != 1 # no uncertainty in first stage
+            SDDP.parameterize(sp, 1:size(D2, 2), p2) do ω
+                for j in 1:m
+                    JuMP.fix(ξ[j], D2[j, ω])
+                end
+            end
+        end
+        if t == 3
+            @constraint(sp, sum(v) == 0)
+        end
+    end
+
+    det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)
+    set_silent(det)
+    JuMP.optimize!(det)
+    @test JuMP.objective_value(det) ≈ 406712.49 atol = 0.1
+
+    SDDP.train(model; log_frequency = 10)
+    @test SDDP.calculate_bound(model) ≈ 406712.49 atol = 0.1
+    return
+end
+
+test_prob52_3stages()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 4
+  scenarios       : 4.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [29, 29]
+  AffExpr in MOI.EqualTo{Float64}         : [4, 5]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 3]
+  AffExpr in MOI.LessThan{Float64}        : [4, 4]
+  VariableRef in MOI.EqualTo{Float64}     : [3, 3]
+  VariableRef in MOI.GreaterThan{Float64} : [22, 22]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+05]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   4.403329e+05  3.509666e+05  1.287389e-02        92   1
+        20   4.055335e+05  4.054833e+05  2.302384e-02       172   1
+        30   4.497721e+05  4.067125e+05  3.549290e-02       264   1
+        40   3.959476e+05  4.067125e+05  4.868197e-02       344   1
+        47   3.959476e+05  4.067125e+05  5.870891e-02       400   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 5.870891e-02
+total solves   : 400
+best bound     :  4.067125e+05
+simulation ci  :  2.695840e+07 ± 3.645323e+07
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/agriculture_mccardle_farm.ipynb b/previews/PR826/examples/agriculture_mccardle_farm.ipynb new file mode 100644 index 0000000000..acba7471f7 --- /dev/null +++ b/previews/PR826/examples/agriculture_mccardle_farm.ipynb @@ -0,0 +1,182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# The farm planning problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are four stages. The first stage is a deterministic planning stage. The\n", + "next three are wait-and-see operational stages. The uncertainty in the three\n", + "operational stages is a Markov chain for weather. There are three Markov\n", + "states: dry, normal, and wet." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Inspired by R. McCardle, Farm management optimization. Masters thesis,\n", + "University of Louisville, Louisville, Kentucky, United States of America\n", + "(2009)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "All data, including short variable names, is taken from that thesis." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function test_mccardle_farm_model()\n", + " S = [ # cutting, stage\n", + " 0 1 2\n", + " 0 0 1\n", + " 0 0 0\n", + " ]\n", + " t = [60, 60, 245] # days in period\n", + " D = [210, 210, 858] # demand\n", + " q = [ # selling price per bale\n", + " [4.5 4.5 4.5; 4.5 4.5 4.5; 4.5 4.5 4.5],\n", + " [5.5 5.5 5.5; 5.5 5.5 5.5; 5.5 5.5 5.5],\n", + " [6.5 6.5 6.5; 6.5 6.5 6.5; 6.5 6.5 6.5],\n", + " ]\n", + " b = [ # predicted yield (bales/acres) from cutting i in weather j.\n", + " 30 75 37.5\n", + " 15 37.5 18.25\n", + " 7.5 18.75 9.325\n", + " ]\n", + " w = 3000 # max storage\n", + " C = [50 50 50; 50 50 50; 50 50 50] # cost to grow hay\n", + " r = [ # Cost per bale of hay from cutting i during weather condition j.\n", + " [5 5 5; 5 5 5; 5 5 5],\n", + " [6 6 6; 6 6 6; 6 6 6],\n", + " [7 7 7; 7 7 7; 7 7 7],\n", + " ]\n", + " M = 60.0 # max acreage for planting\n", + " H = 0.0 # initial inventory\n", + " V = [0.05, 0.05, 0.05] # inventory cost\n", + " L = 3000.0 # max demand for hay\n", + "\n", + " graph = SDDP.MarkovianGraph([\n", + " ones(Float64, 1, 1),\n", + " [0.14 0.69 0.17],\n", + " [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17],\n", + " [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17],\n", + " ])\n", + "\n", + " model = SDDP.PolicyGraph(\n", + " graph;\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, index\n", + " stage, weather = index\n", + " # ===================== State Variables =====================\n", + " # Area planted.\n", + " @variable(subproblem, 0 <= acres <= M, SDDP.State, initial_value = M)\n", + " @variable(\n", + " subproblem,\n", + " bales[i = 1:3] >= 0,\n", + " SDDP.State,\n", + " initial_value = (i == 1 ? H : 0)\n", + " )\n", + " # ===================== Variables =====================\n", + " @variables(subproblem, begin\n", + " buy[1:3] >= 0 # Quantity of bales to buy from each cutting.\n", + " sell[1:3] >= 0 # Quantity of bales to sell from each cutting.\n", + " eat[1:3] >= 0 # Quantity of bales to eat from each cutting.\n", + " pen_p[1:3] >= 0 # Penalties\n", + " pen_n[1:3] >= 0 # Penalties\n", + " end)\n", + " # ===================== Constraints =====================\n", + " if stage == 1\n", + " @constraint(subproblem, acres.out <= acres.in)\n", + " @constraint(subproblem, [i = 1:3], bales[i].in == bales[i].out)\n", + " else\n", + " @expression(\n", + " subproblem,\n", + " cut_ex[c = 1:3],\n", + " bales[c].in + buy[c] - eat[c] - sell[c] + pen_p[c] - pen_n[c]\n", + " )\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " # Cannot plant more land than previously cropped.\n", + " acres.out <= acres.in\n", + " # In each stage we need to meet demand.\n", + " sum(eat) >= D[stage-1]\n", + " # We can buy and sell other cuttings.\n", + " bales[stage-1].out ==\n", + " cut_ex[stage-1] + acres.in * b[stage-1, weather]\n", + " [c = 1:3; c != stage - 1], bales[c].out == cut_ex[c]\n", + " # There is some maximum storage.\n", + " sum(bales[i].out for i in 1:3) <= w\n", + " # We can only sell what is in storage.\n", + " [c = 1:3], sell[c] <= bales[c].in\n", + " # Maximum sales quantity.\n", + " sum(sell) <= L\n", + " end\n", + " )\n", + " end\n", + " # ===================== Stage objective =====================\n", + " if stage == 1\n", + " @stageobjective(subproblem, 0.0)\n", + " else\n", + " @stageobjective(\n", + " subproblem,\n", + " 1000 * (sum(pen_p) + sum(pen_n)) +\n", + " # cost of growing\n", + " C[stage-1, weather] * acres.in +\n", + " sum(\n", + " # inventory cost\n", + " V[stage-1] * bales[cutting].in * t[stage-1] +\n", + " # purchase cost\n", + " r[cutting][stage-1, weather] * buy[cutting] +\n", + " # feed cost\n", + " S[cutting, stage-1] * eat[cutting] -\n", + " # sell reward\n", + " q[cutting][stage-1, weather] * sell[cutting] for\n", + " cutting in 1:3\n", + " )\n", + " )\n", + " end\n", + " return\n", + " end\n", + " SDDP.train(model)\n", + " @test SDDP.termination_status(model) == :simulation_stopping\n", + " @test SDDP.calculate_bound(model) ≈ 4074.1391 atol = 1e-5\n", + "end\n", + "\n", + "test_mccardle_farm_model()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/agriculture_mccardle_farm.jl b/previews/PR826/examples/agriculture_mccardle_farm.jl new file mode 100644 index 0000000000..c78e74b9b9 --- /dev/null +++ b/previews/PR826/examples/agriculture_mccardle_farm.jl @@ -0,0 +1,140 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # The farm planning problem + +# There are four stages. The first stage is a deterministic planning stage. The +# next three are wait-and-see operational stages. The uncertainty in the three +# operational stages is a Markov chain for weather. There are three Markov +# states: dry, normal, and wet. + +# Inspired by R. McCardle, Farm management optimization. Masters thesis, +# University of Louisville, Louisville, Kentucky, United States of America +# (2009). + +# All data, including short variable names, is taken from that thesis. + +using SDDP, HiGHS, Test + +function test_mccardle_farm_model() + S = [ # cutting, stage + 0 1 2 + 0 0 1 + 0 0 0 + ] + t = [60, 60, 245] # days in period + D = [210, 210, 858] # demand + q = [ # selling price per bale + [4.5 4.5 4.5; 4.5 4.5 4.5; 4.5 4.5 4.5], + [5.5 5.5 5.5; 5.5 5.5 5.5; 5.5 5.5 5.5], + [6.5 6.5 6.5; 6.5 6.5 6.5; 6.5 6.5 6.5], + ] + b = [ # predicted yield (bales/acres) from cutting i in weather j. + 30 75 37.5 + 15 37.5 18.25 + 7.5 18.75 9.325 + ] + w = 3000 # max storage + C = [50 50 50; 50 50 50; 50 50 50] # cost to grow hay + r = [ # Cost per bale of hay from cutting i during weather condition j. + [5 5 5; 5 5 5; 5 5 5], + [6 6 6; 6 6 6; 6 6 6], + [7 7 7; 7 7 7; 7 7 7], + ] + M = 60.0 # max acreage for planting + H = 0.0 # initial inventory + V = [0.05, 0.05, 0.05] # inventory cost + L = 3000.0 # max demand for hay + + graph = SDDP.MarkovianGraph([ + ones(Float64, 1, 1), + [0.14 0.69 0.17], + [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17], + [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17], + ]) + + model = SDDP.PolicyGraph( + graph; + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, index + stage, weather = index + ## ===================== State Variables ===================== + ## Area planted. + @variable(subproblem, 0 <= acres <= M, SDDP.State, initial_value = M) + @variable( + subproblem, + bales[i = 1:3] >= 0, + SDDP.State, + initial_value = (i == 1 ? H : 0) + ) + ## ===================== Variables ===================== + @variables(subproblem, begin + buy[1:3] >= 0 # Quantity of bales to buy from each cutting. + sell[1:3] >= 0 # Quantity of bales to sell from each cutting. + eat[1:3] >= 0 # Quantity of bales to eat from each cutting. + pen_p[1:3] >= 0 # Penalties + pen_n[1:3] >= 0 # Penalties + end) + ## ===================== Constraints ===================== + if stage == 1 + @constraint(subproblem, acres.out <= acres.in) + @constraint(subproblem, [i = 1:3], bales[i].in == bales[i].out) + else + @expression( + subproblem, + cut_ex[c = 1:3], + bales[c].in + buy[c] - eat[c] - sell[c] + pen_p[c] - pen_n[c] + ) + @constraints( + subproblem, + begin + ## Cannot plant more land than previously cropped. + acres.out <= acres.in + ## In each stage we need to meet demand. + sum(eat) >= D[stage-1] + ## We can buy and sell other cuttings. + bales[stage-1].out == + cut_ex[stage-1] + acres.in * b[stage-1, weather] + [c = 1:3; c != stage - 1], bales[c].out == cut_ex[c] + ## There is some maximum storage. + sum(bales[i].out for i in 1:3) <= w + ## We can only sell what is in storage. + [c = 1:3], sell[c] <= bales[c].in + ## Maximum sales quantity. + sum(sell) <= L + end + ) + end + ## ===================== Stage objective ===================== + if stage == 1 + @stageobjective(subproblem, 0.0) + else + @stageobjective( + subproblem, + 1000 * (sum(pen_p) + sum(pen_n)) + + ## cost of growing + C[stage-1, weather] * acres.in + + sum( + ## inventory cost + V[stage-1] * bales[cutting].in * t[stage-1] + + ## purchase cost + r[cutting][stage-1, weather] * buy[cutting] + + ## feed cost + S[cutting, stage-1] * eat[cutting] - + ## sell reward + q[cutting][stage-1, weather] * sell[cutting] for + cutting in 1:3 + ) + ) + end + return + end + SDDP.train(model) + @test SDDP.termination_status(model) == :simulation_stopping + @test SDDP.calculate_bound(model) ≈ 4074.1391 atol = 1e-5 +end + +test_mccardle_farm_model() diff --git a/previews/PR826/examples/agriculture_mccardle_farm/index.html b/previews/PR826/examples/agriculture_mccardle_farm/index.html new file mode 100644 index 0000000000..b3cd936e7f --- /dev/null +++ b/previews/PR826/examples/agriculture_mccardle_farm/index.html @@ -0,0 +1,127 @@ + +The farm planning problem · SDDP.jl

The farm planning problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

There are four stages. The first stage is a deterministic planning stage. The next three are wait-and-see operational stages. The uncertainty in the three operational stages is a Markov chain for weather. There are three Markov states: dry, normal, and wet.

Inspired by R. McCardle, Farm management optimization. Masters thesis, University of Louisville, Louisville, Kentucky, United States of America (2009).

All data, including short variable names, is taken from that thesis.

using SDDP, HiGHS, Test
+
+function test_mccardle_farm_model()
+    S = [  # cutting, stage
+        0 1 2
+        0 0 1
+        0 0 0
+    ]
+    t = [60, 60, 245]  # days in period
+    D = [210, 210, 858]  # demand
+    q = [  # selling price per bale
+        [4.5 4.5 4.5; 4.5 4.5 4.5; 4.5 4.5 4.5],
+        [5.5 5.5 5.5; 5.5 5.5 5.5; 5.5 5.5 5.5],
+        [6.5 6.5 6.5; 6.5 6.5 6.5; 6.5 6.5 6.5],
+    ]
+    b = [  # predicted yield (bales/acres) from cutting i in weather j.
+        30 75 37.5
+        15 37.5 18.25
+        7.5 18.75 9.325
+    ]
+    w = 3000  # max storage
+    C = [50 50 50; 50 50 50; 50 50 50]  # cost to grow hay
+    r = [  # Cost per bale of hay from cutting i during weather condition j.
+        [5 5 5; 5 5 5; 5 5 5],
+        [6 6 6; 6 6 6; 6 6 6],
+        [7 7 7; 7 7 7; 7 7 7],
+    ]
+    M = 60.0  # max acreage for planting
+    H = 0.0  # initial inventory
+    V = [0.05, 0.05, 0.05]  # inventory cost
+    L = 3000.0  # max demand for hay
+
+    graph = SDDP.MarkovianGraph([
+        ones(Float64, 1, 1),
+        [0.14 0.69 0.17],
+        [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17],
+        [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17],
+    ])
+
+    model = SDDP.PolicyGraph(
+        graph;
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, index
+        stage, weather = index
+        # ===================== State Variables =====================
+        # Area planted.
+        @variable(subproblem, 0 <= acres <= M, SDDP.State, initial_value = M)
+        @variable(
+            subproblem,
+            bales[i = 1:3] >= 0,
+            SDDP.State,
+            initial_value = (i == 1 ? H : 0)
+        )
+        # ===================== Variables =====================
+        @variables(subproblem, begin
+            buy[1:3] >= 0  # Quantity of bales to buy from each cutting.
+            sell[1:3] >= 0 # Quantity of bales to sell from each cutting.
+            eat[1:3] >= 0  # Quantity of bales to eat from each cutting.
+            pen_p[1:3] >= 0  # Penalties
+            pen_n[1:3] >= 0  # Penalties
+        end)
+        # ===================== Constraints =====================
+        if stage == 1
+            @constraint(subproblem, acres.out <= acres.in)
+            @constraint(subproblem, [i = 1:3], bales[i].in == bales[i].out)
+        else
+            @expression(
+                subproblem,
+                cut_ex[c = 1:3],
+                bales[c].in + buy[c] - eat[c] - sell[c] + pen_p[c] - pen_n[c]
+            )
+            @constraints(
+                subproblem,
+                begin
+                    # Cannot plant more land than previously cropped.
+                    acres.out <= acres.in
+                    # In each stage we need to meet demand.
+                    sum(eat) >= D[stage-1]
+                    # We can buy and sell other cuttings.
+                    bales[stage-1].out ==
+                    cut_ex[stage-1] + acres.in * b[stage-1, weather]
+                    [c = 1:3; c != stage - 1], bales[c].out == cut_ex[c]
+                    # There is some maximum storage.
+                    sum(bales[i].out for i in 1:3) <= w
+                    # We can only sell what is in storage.
+                    [c = 1:3], sell[c] <= bales[c].in
+                    # Maximum sales quantity.
+                    sum(sell) <= L
+                end
+            )
+        end
+        # ===================== Stage objective =====================
+        if stage == 1
+            @stageobjective(subproblem, 0.0)
+        else
+            @stageobjective(
+                subproblem,
+                1000 * (sum(pen_p) + sum(pen_n)) +
+                # cost of growing
+                C[stage-1, weather] * acres.in +
+                sum(
+                    # inventory cost
+                    V[stage-1] * bales[cutting].in * t[stage-1] +
+                    # purchase cost
+                    r[cutting][stage-1, weather] * buy[cutting] +
+                    # feed cost
+                    S[cutting, stage-1] * eat[cutting] -
+                    # sell reward
+                    q[cutting][stage-1, weather] * sell[cutting] for
+                    cutting in 1:3
+                )
+            )
+        end
+        return
+    end
+    SDDP.train(model)
+    @test SDDP.termination_status(model) == :simulation_stopping
+    @test SDDP.calculate_bound(model) ≈ 4074.1391 atol = 1e-5
+end
+
+test_mccardle_farm_model()
Test Passed
diff --git a/previews/PR826/examples/air_conditioning.ipynb b/previews/PR826/examples/air_conditioning.ipynb new file mode 100644 index 0000000000..e4ff4f4371 --- /dev/null +++ b/previews/PR826/examples/air_conditioning.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Air conditioning" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Taken from [Anthony Papavasiliou's notes on SDDP](https://web.archive.org/web/20200504214809/https://perso.uclouvain.be/anthony.papavasiliou/public_html/SDDP.pdf)\n", + "This is a variation of the problem that first appears in the book\n", + "Introduction to Stochastic Programming by Birge and Louveaux, 1997,\n", + "Springer-Verlag, New York, on page 237, Example 1. For a rescaled problem,\n", + "they reported an optimal value of 6.25 with a first-stage solution of x1 = 2\n", + "(production)and y1 = 1 (store production). On this variation, without rescaling,\n", + "it would be equivalent to 62500, 200 and 100, respectively." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Consider the following problem\n", + "* Produce air conditioners for 3 months\n", + "* 200 units/month at 100 \\$/unit\n", + "* Overtime costs 300 \\$/unit\n", + "* Known demand of 100 units for period 1\n", + "* Equally likely demand, 100 or 300 units, for periods 2, 3\n", + "* Storage cost is 50 \\$/unit\n", + "* All demand must be met" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The known optimal solution is \\$62,500" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function air_conditioning_model(duality_handler)\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, stage\n", + " @variable(\n", + " sp,\n", + " 0 <= stored_production <= 100,\n", + " Int,\n", + " SDDP.State,\n", + " initial_value = 0\n", + " )\n", + " @variable(sp, 0 <= production <= 200, Int)\n", + " @variable(sp, overtime >= 0, Int)\n", + " @variable(sp, demand)\n", + " DEMAND = [[100.0], [100.0, 300.0], [100.0, 300.0]]\n", + " SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, DEMAND[stage])\n", + " @constraint(\n", + " sp,\n", + " stored_production.out ==\n", + " stored_production.in + production + overtime - demand\n", + " )\n", + " @stageobjective(\n", + " sp,\n", + " 100 * production + 300 * overtime + 50 * stored_production.out\n", + " )\n", + " end\n", + " SDDP.train(model; duality_handler = duality_handler)\n", + " lb = SDDP.calculate_bound(model)\n", + " println(\"Lower bound is: $lb\")\n", + " @test isapprox(lb, 62_500.0, atol = 0.1)\n", + " sims = SDDP.simulate(model, 1, [:production, :stored_production])\n", + " x1 = sims[1][1][:production]\n", + " y1 = sims[1][1][:stored_production].out\n", + " @test isapprox(x1, 200, atol = 0.1)\n", + " @test isapprox(y1, 100, atol = 0.1)\n", + " println(\n", + " \"With first stage solutions $(x1) (production) and $(y1) (stored_production).\",\n", + " )\n", + " return\n", + "end\n", + "\n", + "for duality_handler in [SDDP.LagrangianDuality(), SDDP.ContinuousConicDuality()]\n", + " air_conditioning_model(duality_handler)\n", + "end" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/air_conditioning.jl b/previews/PR826/examples/air_conditioning.jl new file mode 100644 index 0000000000..e1f1ee3637 --- /dev/null +++ b/previews/PR826/examples/air_conditioning.jl @@ -0,0 +1,74 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Air conditioning + +# Taken from [Anthony Papavasiliou's notes on SDDP](https://web.archive.org/web/20200504214809/https://perso.uclouvain.be/anthony.papavasiliou/public_html/SDDP.pdf) +# This is a variation of the problem that first appears in the book +# Introduction to Stochastic Programming by Birge and Louveaux, 1997, +# Springer-Verlag, New York, on page 237, Example 1. For a rescaled problem, +# they reported an optimal value of 6.25 with a first-stage solution of x1 = 2 +# (production)and y1 = 1 (store production). On this variation, without rescaling, +# it would be equivalent to 62500, 200 and 100, respectively. + +# Consider the following problem +# * Produce air conditioners for 3 months +# * 200 units/month at 100 \$/unit +# * Overtime costs 300 \$/unit +# * Known demand of 100 units for period 1 +# * Equally likely demand, 100 or 300 units, for periods 2, 3 +# * Storage cost is 50 \$/unit +# * All demand must be met + +# The known optimal solution is \$62,500 + +using SDDP, HiGHS, Test + +function air_conditioning_model(duality_handler) + model = SDDP.LinearPolicyGraph(; + stages = 3, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do sp, stage + @variable( + sp, + 0 <= stored_production <= 100, + Int, + SDDP.State, + initial_value = 0 + ) + @variable(sp, 0 <= production <= 200, Int) + @variable(sp, overtime >= 0, Int) + @variable(sp, demand) + DEMAND = [[100.0], [100.0, 300.0], [100.0, 300.0]] + SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, DEMAND[stage]) + @constraint( + sp, + stored_production.out == + stored_production.in + production + overtime - demand + ) + @stageobjective( + sp, + 100 * production + 300 * overtime + 50 * stored_production.out + ) + end + SDDP.train(model; duality_handler = duality_handler) + lb = SDDP.calculate_bound(model) + println("Lower bound is: $lb") + @test isapprox(lb, 62_500.0, atol = 0.1) + sims = SDDP.simulate(model, 1, [:production, :stored_production]) + x1 = sims[1][1][:production] + y1 = sims[1][1][:stored_production].out + @test isapprox(x1, 200, atol = 0.1) + @test isapprox(y1, 100, atol = 0.1) + println( + "With first stage solutions $(x1) (production) and $(y1) (stored_production).", + ) + return +end + +for duality_handler in [SDDP.LagrangianDuality(), SDDP.ContinuousConicDuality()] + air_conditioning_model(duality_handler) +end diff --git a/previews/PR826/examples/air_conditioning/index.html b/previews/PR826/examples/air_conditioning/index.html new file mode 100644 index 0000000000..26f44bfc50 --- /dev/null +++ b/previews/PR826/examples/air_conditioning/index.html @@ -0,0 +1,132 @@ + +Air conditioning · SDDP.jl

Air conditioning

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

Taken from Anthony Papavasiliou's notes on SDDP This is a variation of the problem that first appears in the book Introduction to Stochastic Programming by Birge and Louveaux, 1997, Springer-Verlag, New York, on page 237, Example 1. For a rescaled problem, they reported an optimal value of 6.25 with a first-stage solution of x1 = 2 (production)and y1 = 1 (store production). On this variation, without rescaling, it would be equivalent to 62500, 200 and 100, respectively.

Consider the following problem

  • Produce air conditioners for 3 months
  • 200 units/month at 100 $/unit
  • Overtime costs 300 $/unit
  • Known demand of 100 units for period 1
  • Equally likely demand, 100 or 300 units, for periods 2, 3
  • Storage cost is 50 $/unit
  • All demand must be met

The known optimal solution is $62,500

using SDDP, HiGHS, Test
+
+function air_conditioning_model(duality_handler)
+    model = SDDP.LinearPolicyGraph(;
+        stages = 3,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, stage
+        @variable(
+            sp,
+            0 <= stored_production <= 100,
+            Int,
+            SDDP.State,
+            initial_value = 0
+        )
+        @variable(sp, 0 <= production <= 200, Int)
+        @variable(sp, overtime >= 0, Int)
+        @variable(sp, demand)
+        DEMAND = [[100.0], [100.0, 300.0], [100.0, 300.0]]
+        SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, DEMAND[stage])
+        @constraint(
+            sp,
+            stored_production.out ==
+            stored_production.in + production + overtime - demand
+        )
+        @stageobjective(
+            sp,
+            100 * production + 300 * overtime + 50 * stored_production.out
+        )
+    end
+    SDDP.train(model; duality_handler = duality_handler)
+    lb = SDDP.calculate_bound(model)
+    println("Lower bound is: $lb")
+    @test isapprox(lb, 62_500.0, atol = 0.1)
+    sims = SDDP.simulate(model, 1, [:production, :stored_production])
+    x1 = sims[1][1][:production]
+    y1 = sims[1][1][:stored_production].out
+    @test isapprox(x1, 200, atol = 0.1)
+    @test isapprox(y1, 100, atol = 0.1)
+    println(
+        "With first stage solutions $(x1) (production) and $(y1) (stored_production).",
+    )
+    return
+end
+
+for duality_handler in [SDDP.LagrangianDuality(), SDDP.ContinuousConicDuality()]
+    air_conditioning_model(duality_handler)
+end
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 4.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 4]
+  VariableRef in MOI.Integer              : [3, 3]
+  VariableRef in MOI.LessThan{Float64}    : [2, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 3e+02]
+  bounds range     [1e+02, 2e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1L  7.000000e+04  6.250000e+04  5.673270e-01         8   1
+        20L  6.000000e+04  6.250000e+04  6.624181e-01       172   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 6.624181e-01
+total solves   : 172
+best bound     :  6.250000e+04
+simulation ci  :  6.550000e+04 ± 8.348941e+03
+numeric issues : 0
+-------------------------------------------------------------------
+
+Lower bound is: 62500.0
+With first stage solutions 200.0 (production) and 100.0 (stored_production).
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 4.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 4]
+  VariableRef in MOI.Integer              : [3, 3]
+  VariableRef in MOI.LessThan{Float64}    : [2, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 3e+02]
+  bounds range     [1e+02, 2e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   7.000000e+04  6.250000e+04  3.490210e-03         8   1
+        20   5.500000e+04  6.250000e+04  4.241204e-02       172   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 4.241204e-02
+total solves   : 172
+best bound     :  6.250000e+04
+simulation ci  :  6.475000e+04 ± 9.603974e+03
+numeric issues : 0
+-------------------------------------------------------------------
+
+Lower bound is: 62500.0
+With first stage solutions 200.0 (production) and 100.0 (stored_production).
diff --git a/previews/PR826/examples/air_conditioning_forward.ipynb b/previews/PR826/examples/air_conditioning_forward.ipynb new file mode 100644 index 0000000000..11332c0471 --- /dev/null +++ b/previews/PR826/examples/air_conditioning_forward.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Training with a different forward model" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS\n", + "import Test\n", + "\n", + "function create_air_conditioning_model(; convex::Bool)\n", + " return SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " @variable(sp, 0 <= x <= 100, SDDP.State, initial_value = 0)\n", + " @variable(sp, 0 <= u_production <= 200)\n", + " @variable(sp, u_overtime >= 0)\n", + " if !convex\n", + " set_integer(x.out)\n", + " set_integer(u_production)\n", + " set_integer(u_overtime)\n", + " end\n", + " @constraint(sp, demand, x.in - x.out + u_production + u_overtime == 0)\n", + " Ω = [[100.0], [100.0, 300.0], [100.0, 300.0]]\n", + " SDDP.parameterize(ω -> JuMP.set_normalized_rhs(demand, ω), sp, Ω[t])\n", + " @stageobjective(sp, 100 * u_production + 300 * u_overtime + 50 * x.out)\n", + " end\n", + "end\n", + "\n", + "convex = create_air_conditioning_model(; convex = true)\n", + "non_convex = create_air_conditioning_model(; convex = false)\n", + "SDDP.train(\n", + " convex;\n", + " forward_pass = SDDP.AlternativeForwardPass(non_convex),\n", + " post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),\n", + " iteration_limit = 10,\n", + ")\n", + "Test.@test isapprox(SDDP.calculate_bound(non_convex), 62_500.0, atol = 0.1)\n", + "Test.@test isapprox(SDDP.calculate_bound(convex), 62_500.0, atol = 0.1)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/air_conditioning_forward.jl b/previews/PR826/examples/air_conditioning_forward.jl new file mode 100644 index 0000000000..1f1f5d5906 --- /dev/null +++ b/previews/PR826/examples/air_conditioning_forward.jl @@ -0,0 +1,42 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Training with a different forward model + +using SDDP +import HiGHS +import Test + +function create_air_conditioning_model(; convex::Bool) + return SDDP.LinearPolicyGraph(; + stages = 3, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variable(sp, 0 <= x <= 100, SDDP.State, initial_value = 0) + @variable(sp, 0 <= u_production <= 200) + @variable(sp, u_overtime >= 0) + if !convex + set_integer(x.out) + set_integer(u_production) + set_integer(u_overtime) + end + @constraint(sp, demand, x.in - x.out + u_production + u_overtime == 0) + Ω = [[100.0], [100.0, 300.0], [100.0, 300.0]] + SDDP.parameterize(ω -> JuMP.set_normalized_rhs(demand, ω), sp, Ω[t]) + @stageobjective(sp, 100 * u_production + 300 * u_overtime + 50 * x.out) + end +end + +convex = create_air_conditioning_model(; convex = true) +non_convex = create_air_conditioning_model(; convex = false) +SDDP.train( + convex; + forward_pass = SDDP.AlternativeForwardPass(non_convex), + post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex), + iteration_limit = 10, +) +Test.@test isapprox(SDDP.calculate_bound(non_convex), 62_500.0, atol = 0.1) +Test.@test isapprox(SDDP.calculate_bound(convex), 62_500.0, atol = 0.1) diff --git a/previews/PR826/examples/air_conditioning_forward/index.html b/previews/PR826/examples/air_conditioning_forward/index.html new file mode 100644 index 0000000000..43d9d6a978 --- /dev/null +++ b/previews/PR826/examples/air_conditioning_forward/index.html @@ -0,0 +1,40 @@ + +Training with a different forward model · SDDP.jl

Training with a different forward model

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP
+import HiGHS
+import Test
+
+function create_air_conditioning_model(; convex::Bool)
+    return SDDP.LinearPolicyGraph(;
+        stages = 3,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        @variable(sp, 0 <= x <= 100, SDDP.State, initial_value = 0)
+        @variable(sp, 0 <= u_production <= 200)
+        @variable(sp, u_overtime >= 0)
+        if !convex
+            set_integer(x.out)
+            set_integer(u_production)
+            set_integer(u_overtime)
+        end
+        @constraint(sp, demand, x.in - x.out + u_production + u_overtime == 0)
+        Ω = [[100.0], [100.0, 300.0], [100.0, 300.0]]
+        SDDP.parameterize(ω -> JuMP.set_normalized_rhs(demand, ω), sp, Ω[t])
+        @stageobjective(sp, 100 * u_production + 300 * u_overtime + 50 * x.out)
+    end
+end
+
+convex = create_air_conditioning_model(; convex = true)
+non_convex = create_air_conditioning_model(; convex = false)
+SDDP.train(
+    convex;
+    forward_pass = SDDP.AlternativeForwardPass(non_convex),
+    post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),
+    iteration_limit = 10,
+)
+Test.@test isapprox(SDDP.calculate_bound(non_convex), 62_500.0, atol = 0.1)
+Test.@test isapprox(SDDP.calculate_bound(convex), 62_500.0, atol = 0.1)
Test Passed
diff --git a/previews/PR826/examples/all_blacks.ipynb b/previews/PR826/examples/all_blacks.ipynb new file mode 100644 index 0000000000..5979dc32d9 --- /dev/null +++ b/previews/PR826/examples/all_blacks.ipynb @@ -0,0 +1,67 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Deterministic All Blacks" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function all_blacks()\n", + " # Number of time periods, number of seats, R_ij = revenue from selling seat\n", + " # i at time j, offer_ij = whether an offer for seat i will come at time j\n", + " (T, N, R, offer) = (3, 2, [3 3 6; 3 3 6], [1 1 0; 1 0 1])\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = T,\n", + " sense = :Max,\n", + " upper_bound = 100.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, stage\n", + " # Seat remaining?\n", + " @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1)\n", + " # Action: accept offer, or don't accept offer\n", + " @variable(sp, accept_offer, Bin)\n", + " # Balance on seats\n", + " @constraint(\n", + " sp,\n", + " [i in 1:N],\n", + " x[i].out == x[i].in - offer[i, stage] * accept_offer\n", + " )\n", + " @stageobjective(\n", + " sp,\n", + " sum(R[i, stage] * offer[i, stage] * accept_offer for i in 1:N)\n", + " )\n", + " end\n", + " SDDP.train(model; duality_handler = SDDP.LagrangianDuality())\n", + " @test SDDP.calculate_bound(model) ≈ 9.0\n", + " return\n", + "end\n", + "\n", + "all_blacks()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/all_blacks.jl b/previews/PR826/examples/all_blacks.jl new file mode 100644 index 0000000000..5b3cb764dd --- /dev/null +++ b/previews/PR826/examples/all_blacks.jl @@ -0,0 +1,40 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Deterministic All Blacks + +using SDDP, HiGHS, Test + +function all_blacks() + ## Number of time periods, number of seats, R_ij = revenue from selling seat + ## i at time j, offer_ij = whether an offer for seat i will come at time j + (T, N, R, offer) = (3, 2, [3 3 6; 3 3 6], [1 1 0; 1 0 1]) + model = SDDP.LinearPolicyGraph(; + stages = T, + sense = :Max, + upper_bound = 100.0, + optimizer = HiGHS.Optimizer, + ) do sp, stage + ## Seat remaining? + @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1) + ## Action: accept offer, or don't accept offer + @variable(sp, accept_offer, Bin) + ## Balance on seats + @constraint( + sp, + [i in 1:N], + x[i].out == x[i].in - offer[i, stage] * accept_offer + ) + @stageobjective( + sp, + sum(R[i, stage] * offer[i, stage] * accept_offer for i in 1:N) + ) + end + SDDP.train(model; duality_handler = SDDP.LagrangianDuality()) + @test SDDP.calculate_bound(model) ≈ 9.0 + return +end + +all_blacks() diff --git a/previews/PR826/examples/all_blacks/index.html b/previews/PR826/examples/all_blacks/index.html new file mode 100644 index 0000000000..27a42c8b94 --- /dev/null +++ b/previews/PR826/examples/all_blacks/index.html @@ -0,0 +1,73 @@ + +Deterministic All Blacks · SDDP.jl

Deterministic All Blacks

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP, HiGHS, Test
+
+function all_blacks()
+    # Number of time periods, number of seats, R_ij = revenue from selling seat
+    # i at time j, offer_ij = whether an offer for seat i will come at time j
+    (T, N, R, offer) = (3, 2, [3 3 6; 3 3 6], [1 1 0; 1 0 1])
+    model = SDDP.LinearPolicyGraph(;
+        stages = T,
+        sense = :Max,
+        upper_bound = 100.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, stage
+        # Seat remaining?
+        @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1)
+        # Action: accept offer, or don't accept offer
+        @variable(sp, accept_offer, Bin)
+        # Balance on seats
+        @constraint(
+            sp,
+            [i in 1:N],
+            x[i].out == x[i].in - offer[i, stage] * accept_offer
+        )
+        @stageobjective(
+            sp,
+            sum(R[i, stage] * offer[i, stage] * accept_offer for i in 1:N)
+        )
+    end
+    SDDP.train(model; duality_handler = SDDP.LagrangianDuality())
+    @test SDDP.calculate_bound(model) ≈ 9.0
+    return
+end
+
+all_blacks()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 2
+  scenarios       : 1.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [2, 3]
+  VariableRef in MOI.LessThan{Float64}    : [3, 3]
+  VariableRef in MOI.ZeroOne              : [3, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 6e+00]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1L  6.000000e+00  9.000000e+00  3.842783e-02         6   1
+        20L  9.000000e+00  9.000000e+00  7.850599e-02       123   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 7.850599e-02
+total solves   : 123
+best bound     :  9.000000e+00
+simulation ci  :  8.850000e+00 ± 2.940000e-01
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/asset_management_simple.ipynb b/previews/PR826/examples/asset_management_simple.ipynb new file mode 100644 index 0000000000..951f91114d --- /dev/null +++ b/previews/PR826/examples/asset_management_simple.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Asset management" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Taken from the book\n", + "J.R. Birge, F. Louveaux, Introduction to Stochastic Programming,\n", + "Springer Series in Operations Research and Financial Engineering,\n", + "Springer New York, New York, NY, 2011" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function asset_management_simple()\n", + " model = SDDP.PolicyGraph(\n", + " SDDP.MarkovianGraph(\n", + " Array{Float64,2}[\n", + " [1.0]',\n", + " [0.5 0.5],\n", + " [0.5 0.5; 0.5 0.5],\n", + " [0.5 0.5; 0.5 0.5],\n", + " ],\n", + " );\n", + " lower_bound = -1_000.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, index\n", + " (stage, markov_state) = index\n", + " r_stock = [1.25, 1.06]\n", + " r_bonds = [1.14, 1.12]\n", + " @variable(subproblem, stocks >= 0, SDDP.State, initial_value = 0.0)\n", + " @variable(subproblem, bonds >= 0, SDDP.State, initial_value = 0.0)\n", + " if stage == 1\n", + " @constraint(subproblem, stocks.out + bonds.out == 55)\n", + " @stageobjective(subproblem, 0)\n", + " elseif 1 < stage < 4\n", + " @constraint(\n", + " subproblem,\n", + " r_stock[markov_state] * stocks.in +\n", + " r_bonds[markov_state] * bonds.in == stocks.out + bonds.out\n", + " )\n", + " @stageobjective(subproblem, 0)\n", + " else\n", + " @variable(subproblem, over >= 0)\n", + " @variable(subproblem, short >= 0)\n", + " @constraint(\n", + " subproblem,\n", + " r_stock[markov_state] * stocks.in +\n", + " r_bonds[markov_state] * bonds.in - over + short == 80\n", + " )\n", + " @stageobjective(subproblem, -over + 4 * short)\n", + " end\n", + " end\n", + " SDDP.train(model; log_frequency = 5)\n", + " @test SDDP.calculate_bound(model) ≈ 1.514 atol = 1e-4\n", + " return\n", + "end\n", + "\n", + "asset_management_simple()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/asset_management_simple.jl b/previews/PR826/examples/asset_management_simple.jl new file mode 100644 index 0000000000..33040ed05e --- /dev/null +++ b/previews/PR826/examples/asset_management_simple.jl @@ -0,0 +1,59 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Asset management + +# Taken from the book +# J.R. Birge, F. Louveaux, Introduction to Stochastic Programming, +# Springer Series in Operations Research and Financial Engineering, +# Springer New York, New York, NY, 2011 + +using SDDP, HiGHS, Test + +function asset_management_simple() + model = SDDP.PolicyGraph( + SDDP.MarkovianGraph( + Array{Float64,2}[ + [1.0]', + [0.5 0.5], + [0.5 0.5; 0.5 0.5], + [0.5 0.5; 0.5 0.5], + ], + ); + lower_bound = -1_000.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, index + (stage, markov_state) = index + r_stock = [1.25, 1.06] + r_bonds = [1.14, 1.12] + @variable(subproblem, stocks >= 0, SDDP.State, initial_value = 0.0) + @variable(subproblem, bonds >= 0, SDDP.State, initial_value = 0.0) + if stage == 1 + @constraint(subproblem, stocks.out + bonds.out == 55) + @stageobjective(subproblem, 0) + elseif 1 < stage < 4 + @constraint( + subproblem, + r_stock[markov_state] * stocks.in + + r_bonds[markov_state] * bonds.in == stocks.out + bonds.out + ) + @stageobjective(subproblem, 0) + else + @variable(subproblem, over >= 0) + @variable(subproblem, short >= 0) + @constraint( + subproblem, + r_stock[markov_state] * stocks.in + + r_bonds[markov_state] * bonds.in - over + short == 80 + ) + @stageobjective(subproblem, -over + 4 * short) + end + end + SDDP.train(model; log_frequency = 5) + @test SDDP.calculate_bound(model) ≈ 1.514 atol = 1e-4 + return +end + +asset_management_simple() diff --git a/previews/PR826/examples/asset_management_simple/index.html b/previews/PR826/examples/asset_management_simple/index.html new file mode 100644 index 0000000000..d6647ef552 --- /dev/null +++ b/previews/PR826/examples/asset_management_simple/index.html @@ -0,0 +1,92 @@ + +Asset management · SDDP.jl

Asset management

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

Taken from the book J.R. Birge, F. Louveaux, Introduction to Stochastic Programming, Springer Series in Operations Research and Financial Engineering, Springer New York, New York, NY, 2011

using SDDP, HiGHS, Test
+
+function asset_management_simple()
+    model = SDDP.PolicyGraph(
+        SDDP.MarkovianGraph(
+            Array{Float64,2}[
+                [1.0]',
+                [0.5 0.5],
+                [0.5 0.5; 0.5 0.5],
+                [0.5 0.5; 0.5 0.5],
+            ],
+        );
+        lower_bound = -1_000.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, index
+        (stage, markov_state) = index
+        r_stock = [1.25, 1.06]
+        r_bonds = [1.14, 1.12]
+        @variable(subproblem, stocks >= 0, SDDP.State, initial_value = 0.0)
+        @variable(subproblem, bonds >= 0, SDDP.State, initial_value = 0.0)
+        if stage == 1
+            @constraint(subproblem, stocks.out + bonds.out == 55)
+            @stageobjective(subproblem, 0)
+        elseif 1 < stage < 4
+            @constraint(
+                subproblem,
+                r_stock[markov_state] * stocks.in +
+                r_bonds[markov_state] * bonds.in == stocks.out + bonds.out
+            )
+            @stageobjective(subproblem, 0)
+        else
+            @variable(subproblem, over >= 0)
+            @variable(subproblem, short >= 0)
+            @constraint(
+                subproblem,
+                r_stock[markov_state] * stocks.in +
+                r_bonds[markov_state] * bonds.in - over + short == 80
+            )
+            @stageobjective(subproblem, -over + 4 * short)
+        end
+    end
+    SDDP.train(model; log_frequency = 5)
+    @test SDDP.calculate_bound(model) ≈ 1.514 atol = 1e-4
+    return
+end
+
+asset_management_simple()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 7
+  state variables : 2
+  scenarios       : 8.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [5, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [3, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 4e+00]
+  bounds range     [1e+03, 1e+03]
+  rhs range        [6e+01, 8e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         5  -1.136868e-13  4.739851e-01  1.224494e-02        87   1
+        10   0.000000e+00  1.484161e+00  1.887894e-02       142   1
+        15  -8.870299e+00  1.514085e+00  2.539992e-02       197   1
+        20  -1.428571e+00  1.514085e+00  3.299689e-02       252   1
+        25  -2.479988e+01  1.514085e+00  8.514595e-02       339   1
+        30  -1.428571e+00  1.514085e+00  9.319401e-02       394   1
+        35   1.421085e-14  1.514085e+00  1.017458e-01       449   1
+        40  -1.428571e+00  1.514085e+00  1.108580e-01       504   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.108580e-01
+total solves   : 504
+best bound     :  1.514085e+00
+simulation ci  :  1.858631e+00 ± 6.043640e+00
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/asset_management_stagewise.ipynb b/previews/PR826/examples/asset_management_stagewise.ipynb new file mode 100644 index 0000000000..27acd94d2d --- /dev/null +++ b/previews/PR826/examples/asset_management_stagewise.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Asset management with modifications" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A modified version of the Asset Management Problem Taken from the book\n", + "J.R. Birge, F. Louveaux, Introduction to Stochastic Programming,\n", + "Springer Series in Operations Research and Financial Engineering,\n", + "Springer New York, New York, NY, 2011" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function asset_management_stagewise(; cut_type)\n", + " w_s = [1.25, 1.06]\n", + " w_b = [1.14, 1.12]\n", + " Phi = [-1, 5]\n", + " Psi = [0.02, 0.0]\n", + "\n", + " model = SDDP.MarkovianPolicyGraph(;\n", + " sense = :Max,\n", + " transition_matrices = Array{Float64,2}[\n", + " [1.0]',\n", + " [0.5 0.5],\n", + " [0.5 0.5; 0.5 0.5],\n", + " [0.5 0.5; 0.5 0.5],\n", + " ],\n", + " upper_bound = 1000.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, node\n", + " t, i = node\n", + " @variable(subproblem, xs >= 0, SDDP.State, initial_value = 0)\n", + " @variable(subproblem, xb >= 0, SDDP.State, initial_value = 0)\n", + " if t == 1\n", + " @constraint(subproblem, xs.out + xb.out == 55 + xs.in + xb.in)\n", + " @stageobjective(subproblem, 0)\n", + " elseif t == 2 || t == 3\n", + " @variable(subproblem, phi)\n", + " @constraint(\n", + " subproblem,\n", + " w_s[i] * xs.in + w_b[i] * xb.in + phi == xs.out + xb.out\n", + " )\n", + " SDDP.parameterize(subproblem, [1, 2], [0.6, 0.4]) do ω\n", + " JuMP.fix(phi, Phi[ω])\n", + " @stageobjective(subproblem, Psi[ω] * xs.out)\n", + " end\n", + " else\n", + " @variable(subproblem, u >= 0)\n", + " @variable(subproblem, v >= 0)\n", + " @constraint(\n", + " subproblem,\n", + " w_s[i] * xs.in + w_b[i] * xb.in + u - v == 80,\n", + " )\n", + " @stageobjective(subproblem, -4u + v)\n", + " end\n", + " end\n", + " SDDP.train(\n", + " model;\n", + " cut_type = cut_type,\n", + " log_frequency = 10,\n", + " risk_measure = (node) -> begin\n", + " if node[1] != 3\n", + " SDDP.Expectation()\n", + " else\n", + " SDDP.EAVaR(; lambda = 0.5, beta = 0.5)\n", + " end\n", + " end,\n", + " )\n", + " @test SDDP.calculate_bound(model) ≈ 1.278 atol = 1e-3\n", + " return\n", + "end\n", + "\n", + "asset_management_stagewise(; cut_type = SDDP.SINGLE_CUT)\n", + "\n", + "asset_management_stagewise(; cut_type = SDDP.MULTI_CUT)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/asset_management_stagewise.jl b/previews/PR826/examples/asset_management_stagewise.jl new file mode 100644 index 0000000000..59302186fe --- /dev/null +++ b/previews/PR826/examples/asset_management_stagewise.jl @@ -0,0 +1,76 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Asset management with modifications + +# A modified version of the Asset Management Problem Taken from the book +# J.R. Birge, F. Louveaux, Introduction to Stochastic Programming, +# Springer Series in Operations Research and Financial Engineering, +# Springer New York, New York, NY, 2011 + +using SDDP, HiGHS, Test + +function asset_management_stagewise(; cut_type) + w_s = [1.25, 1.06] + w_b = [1.14, 1.12] + Phi = [-1, 5] + Psi = [0.02, 0.0] + + model = SDDP.MarkovianPolicyGraph(; + sense = :Max, + transition_matrices = Array{Float64,2}[ + [1.0]', + [0.5 0.5], + [0.5 0.5; 0.5 0.5], + [0.5 0.5; 0.5 0.5], + ], + upper_bound = 1000.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, node + t, i = node + @variable(subproblem, xs >= 0, SDDP.State, initial_value = 0) + @variable(subproblem, xb >= 0, SDDP.State, initial_value = 0) + if t == 1 + @constraint(subproblem, xs.out + xb.out == 55 + xs.in + xb.in) + @stageobjective(subproblem, 0) + elseif t == 2 || t == 3 + @variable(subproblem, phi) + @constraint( + subproblem, + w_s[i] * xs.in + w_b[i] * xb.in + phi == xs.out + xb.out + ) + SDDP.parameterize(subproblem, [1, 2], [0.6, 0.4]) do ω + JuMP.fix(phi, Phi[ω]) + @stageobjective(subproblem, Psi[ω] * xs.out) + end + else + @variable(subproblem, u >= 0) + @variable(subproblem, v >= 0) + @constraint( + subproblem, + w_s[i] * xs.in + w_b[i] * xb.in + u - v == 80, + ) + @stageobjective(subproblem, -4u + v) + end + end + SDDP.train( + model; + cut_type = cut_type, + log_frequency = 10, + risk_measure = (node) -> begin + if node[1] != 3 + SDDP.Expectation() + else + SDDP.EAVaR(; lambda = 0.5, beta = 0.5) + end + end, + ) + @test SDDP.calculate_bound(model) ≈ 1.278 atol = 1e-3 + return +end + +asset_management_stagewise(; cut_type = SDDP.SINGLE_CUT) + +asset_management_stagewise(; cut_type = SDDP.MULTI_CUT) diff --git a/previews/PR826/examples/asset_management_stagewise/index.html b/previews/PR826/examples/asset_management_stagewise/index.html new file mode 100644 index 0000000000..10041dfd78 --- /dev/null +++ b/previews/PR826/examples/asset_management_stagewise/index.html @@ -0,0 +1,145 @@ + +Asset management with modifications · SDDP.jl

Asset management with modifications

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

A modified version of the Asset Management Problem Taken from the book J.R. Birge, F. Louveaux, Introduction to Stochastic Programming, Springer Series in Operations Research and Financial Engineering, Springer New York, New York, NY, 2011

using SDDP, HiGHS, Test
+
+function asset_management_stagewise(; cut_type)
+    w_s = [1.25, 1.06]
+    w_b = [1.14, 1.12]
+    Phi = [-1, 5]
+    Psi = [0.02, 0.0]
+
+    model = SDDP.MarkovianPolicyGraph(;
+        sense = :Max,
+        transition_matrices = Array{Float64,2}[
+            [1.0]',
+            [0.5 0.5],
+            [0.5 0.5; 0.5 0.5],
+            [0.5 0.5; 0.5 0.5],
+        ],
+        upper_bound = 1000.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, node
+        t, i = node
+        @variable(subproblem, xs >= 0, SDDP.State, initial_value = 0)
+        @variable(subproblem, xb >= 0, SDDP.State, initial_value = 0)
+        if t == 1
+            @constraint(subproblem, xs.out + xb.out == 55 + xs.in + xb.in)
+            @stageobjective(subproblem, 0)
+        elseif t == 2 || t == 3
+            @variable(subproblem, phi)
+            @constraint(
+                subproblem,
+                w_s[i] * xs.in + w_b[i] * xb.in + phi == xs.out + xb.out
+            )
+            SDDP.parameterize(subproblem, [1, 2], [0.6, 0.4]) do ω
+                JuMP.fix(phi, Phi[ω])
+                @stageobjective(subproblem, Psi[ω] * xs.out)
+            end
+        else
+            @variable(subproblem, u >= 0)
+            @variable(subproblem, v >= 0)
+            @constraint(
+                subproblem,
+                w_s[i] * xs.in + w_b[i] * xb.in + u - v == 80,
+            )
+            @stageobjective(subproblem, -4u + v)
+        end
+    end
+    SDDP.train(
+        model;
+        cut_type = cut_type,
+        log_frequency = 10,
+        risk_measure = (node) -> begin
+            if node[1] != 3
+                SDDP.Expectation()
+            else
+                SDDP.EAVaR(; lambda = 0.5, beta = 0.5)
+            end
+        end,
+    )
+    @test SDDP.calculate_bound(model) ≈ 1.278 atol = 1e-3
+    return
+end
+
+asset_management_stagewise(; cut_type = SDDP.SINGLE_CUT)
+
+asset_management_stagewise(; cut_type = SDDP.MULTI_CUT)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 7
+  state variables : 2
+  scenarios       : 3.20000e+01
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : #4
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [5, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [2, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [2e-02, 4e+00]
+  bounds range     [1e+03, 1e+03]
+  rhs range        [6e+01, 8e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   2.284531e+01  1.288927e+00  1.517451e-01       278   1
+        20   2.825352e+00  1.278410e+00  1.710870e-01       428   1
+        30  -3.575118e+00  1.278410e+00  2.029240e-01       706   1
+        40   3.508198e+01  1.278410e+00  2.247581e-01       856   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 2.247581e-01
+total solves   : 856
+best bound     :  1.278410e+00
+simulation ci  :  3.347300e+00 ± 5.636199e+00
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 7
+  state variables : 2
+  scenarios       : 3.20000e+01
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : #4
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [5, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [2, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [2e-02, 4e+00]
+  bounds range     [1e+03, 1e+03]
+  rhs range        [6e+01, 8e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   1.886566e+01  1.333680e+00  3.389382e-02       278   1
+        20   2.820962e+01  1.278410e+00  8.811092e-02       428   1
+        30   3.508198e+01  1.278410e+00  1.314609e-01       706   1
+        40   7.320288e+00  1.278410e+00  1.710138e-01       856   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.710138e-01
+total solves   : 856
+best bound     :  1.278410e+00
+simulation ci  :  4.559468e+00 ± 6.581091e+00
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/belief.ipynb b/previews/PR826/examples/belief.ipynb new file mode 100644 index 0000000000..80b834fded --- /dev/null +++ b/previews/PR826/examples/belief.ipynb @@ -0,0 +1,99 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Partially observable inventory management" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Random, Statistics, Test\n", + "\n", + "function inventory_management_problem()\n", + " demand_values = [1.0, 2.0]\n", + " demand_prob = Dict(:Ah => [0.2, 0.8], :Bh => [0.8, 0.2])\n", + " graph = SDDP.Graph(\n", + " :root_node,\n", + " [:Ad, :Ah, :Bd, :Bh],\n", + " [\n", + " (:root_node => :Ad, 0.5),\n", + " (:root_node => :Bd, 0.5),\n", + " (:Ad => :Ah, 1.0),\n", + " (:Ah => :Ad, 0.8),\n", + " (:Ah => :Bd, 0.1),\n", + " (:Bd => :Bh, 1.0),\n", + " (:Bh => :Bd, 0.8),\n", + " (:Bh => :Ad, 0.1),\n", + " ],\n", + " )\n", + " SDDP.add_ambiguity_set(graph, [:Ad, :Bd], 1e2)\n", + " SDDP.add_ambiguity_set(graph, [:Ah, :Bh], 1e2)\n", + "\n", + " model = SDDP.PolicyGraph(\n", + " graph;\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, node\n", + " @variables(\n", + " subproblem,\n", + " begin\n", + " 0 <= inventory <= 2, (SDDP.State, initial_value = 0.0)\n", + " buy >= 0\n", + " demand\n", + " end\n", + " )\n", + " @constraint(subproblem, demand == inventory.in - inventory.out + buy)\n", + " if node == :Ad || node == :Bd || node == :D\n", + " JuMP.fix(demand, 0)\n", + " @stageobjective(subproblem, buy)\n", + " else\n", + " SDDP.parameterize(subproblem, demand_values, demand_prob[node]) do ω\n", + " return JuMP.fix(demand, ω)\n", + " end\n", + " @stageobjective(subproblem, 2 * buy + inventory.out)\n", + " end\n", + " end\n", + " # Train the policy.\n", + " Random.seed!(123)\n", + " SDDP.train(\n", + " model;\n", + " iteration_limit = 100,\n", + " cut_type = SDDP.SINGLE_CUT,\n", + " log_frequency = 10,\n", + " parallel_scheme = SDDP.Serial(),\n", + " )\n", + " results = SDDP.simulate(model, 500; parallel_scheme = SDDP.Serial())\n", + " objectives =\n", + " [sum(s[:stage_objective] for s in simulation) for simulation in results]\n", + " sample_mean = round(Statistics.mean(objectives); digits = 2)\n", + " sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2)\n", + " @test SDDP.calculate_bound(model) ≈ sample_mean atol = sample_ci\n", + " return\n", + "end\n", + "\n", + "inventory_management_problem()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/belief.jl b/previews/PR826/examples/belief.jl new file mode 100644 index 0000000000..acf693d133 --- /dev/null +++ b/previews/PR826/examples/belief.jl @@ -0,0 +1,72 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Partially observable inventory management + +using SDDP, HiGHS, Random, Statistics, Test + +function inventory_management_problem() + demand_values = [1.0, 2.0] + demand_prob = Dict(:Ah => [0.2, 0.8], :Bh => [0.8, 0.2]) + graph = SDDP.Graph( + :root_node, + [:Ad, :Ah, :Bd, :Bh], + [ + (:root_node => :Ad, 0.5), + (:root_node => :Bd, 0.5), + (:Ad => :Ah, 1.0), + (:Ah => :Ad, 0.8), + (:Ah => :Bd, 0.1), + (:Bd => :Bh, 1.0), + (:Bh => :Bd, 0.8), + (:Bh => :Ad, 0.1), + ], + ) + SDDP.add_ambiguity_set(graph, [:Ad, :Bd], 1e2) + SDDP.add_ambiguity_set(graph, [:Ah, :Bh], 1e2) + + model = SDDP.PolicyGraph( + graph; + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, node + @variables( + subproblem, + begin + 0 <= inventory <= 2, (SDDP.State, initial_value = 0.0) + buy >= 0 + demand + end + ) + @constraint(subproblem, demand == inventory.in - inventory.out + buy) + if node == :Ad || node == :Bd || node == :D + JuMP.fix(demand, 0) + @stageobjective(subproblem, buy) + else + SDDP.parameterize(subproblem, demand_values, demand_prob[node]) do ω + return JuMP.fix(demand, ω) + end + @stageobjective(subproblem, 2 * buy + inventory.out) + end + end + ## Train the policy. + Random.seed!(123) + SDDP.train( + model; + iteration_limit = 100, + cut_type = SDDP.SINGLE_CUT, + log_frequency = 10, + parallel_scheme = SDDP.Serial(), + ) + results = SDDP.simulate(model, 500; parallel_scheme = SDDP.Serial()) + objectives = + [sum(s[:stage_objective] for s in simulation) for simulation in results] + sample_mean = round(Statistics.mean(objectives); digits = 2) + sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2) + @test SDDP.calculate_bound(model) ≈ sample_mean atol = sample_ci + return +end + +inventory_management_problem() diff --git a/previews/PR826/examples/belief/index.html b/previews/PR826/examples/belief/index.html new file mode 100644 index 0000000000..20de343b44 --- /dev/null +++ b/previews/PR826/examples/belief/index.html @@ -0,0 +1,114 @@ + +Partially observable inventory management · SDDP.jl

Partially observable inventory management

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP, HiGHS, Random, Statistics, Test
+
+function inventory_management_problem()
+    demand_values = [1.0, 2.0]
+    demand_prob = Dict(:Ah => [0.2, 0.8], :Bh => [0.8, 0.2])
+    graph = SDDP.Graph(
+        :root_node,
+        [:Ad, :Ah, :Bd, :Bh],
+        [
+            (:root_node => :Ad, 0.5),
+            (:root_node => :Bd, 0.5),
+            (:Ad => :Ah, 1.0),
+            (:Ah => :Ad, 0.8),
+            (:Ah => :Bd, 0.1),
+            (:Bd => :Bh, 1.0),
+            (:Bh => :Bd, 0.8),
+            (:Bh => :Ad, 0.1),
+        ],
+    )
+    SDDP.add_ambiguity_set(graph, [:Ad, :Bd], 1e2)
+    SDDP.add_ambiguity_set(graph, [:Ah, :Bh], 1e2)
+
+    model = SDDP.PolicyGraph(
+        graph;
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, node
+        @variables(
+            subproblem,
+            begin
+                0 <= inventory <= 2, (SDDP.State, initial_value = 0.0)
+                buy >= 0
+                demand
+            end
+        )
+        @constraint(subproblem, demand == inventory.in - inventory.out + buy)
+        if node == :Ad || node == :Bd || node == :D
+            JuMP.fix(demand, 0)
+            @stageobjective(subproblem, buy)
+        else
+            SDDP.parameterize(subproblem, demand_values, demand_prob[node]) do ω
+                return JuMP.fix(demand, ω)
+            end
+            @stageobjective(subproblem, 2 * buy + inventory.out)
+        end
+    end
+    # Train the policy.
+    Random.seed!(123)
+    SDDP.train(
+        model;
+        iteration_limit = 100,
+        cut_type = SDDP.SINGLE_CUT,
+        log_frequency = 10,
+        parallel_scheme = SDDP.Serial(),
+    )
+    results = SDDP.simulate(model, 500; parallel_scheme = SDDP.Serial())
+    objectives =
+        [sum(s[:stage_objective] for s in simulation) for simulation in results]
+    sample_mean = round(Statistics.mean(objectives); digits = 2)
+    sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2)
+    @test SDDP.calculate_bound(model) ≈ sample_mean atol = sample_ci
+    return
+end
+
+inventory_management_problem()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 4
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  AffExpr in MOI.GreaterThan{Float64}     : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [3, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+00]
+  bounds range     [2e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   4.787277e+00  9.346930e+00  1.594582e+00       900   1
+        20   6.374753e+00  1.361934e+01  1.762047e+00      1720   1
+        30   2.813321e+01  1.651297e+01  2.082734e+00      3036   1
+        40   1.654759e+01  1.632970e+01  2.446471e+00      4192   1
+        50   3.570941e+00  1.846889e+01  2.723258e+00      5020   1
+        60   1.087425e+01  1.890254e+01  2.998680e+00      5808   1
+        70   9.381610e+00  1.940320e+01  3.290027e+00      6540   1
+        80   5.648731e+01  1.962435e+01  3.513474e+00      7088   1
+        90   3.879273e+01  1.981008e+01  4.009326e+00      8180   1
+       100   7.870187e+00  1.997117e+01  4.224532e+00      8664   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 4.224532e+00
+total solves   : 8664
+best bound     :  1.997117e+01
+simulation ci  :  2.275399e+01 ± 4.541987e+00
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/biobjective_hydro.ipynb b/previews/PR826/examples/biobjective_hydro.ipynb new file mode 100644 index 0000000000..81a813a784 --- /dev/null +++ b/previews/PR826/examples/biobjective_hydro.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Biobjective hydro-thermal" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Statistics, Test\n", + "\n", + "function biobjective_example()\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, _\n", + " @variable(subproblem, 0 <= v <= 200, SDDP.State, initial_value = 50)\n", + " @variables(subproblem, begin\n", + " 0 <= g[i = 1:2] <= 100\n", + " 0 <= u <= 150\n", + " s >= 0\n", + " shortage_cost >= 0\n", + " end)\n", + " @expressions(subproblem, begin\n", + " objective_1, g[1] + 10 * g[2]\n", + " objective_2, shortage_cost\n", + " end)\n", + " @constraints(subproblem, begin\n", + " inflow_constraint, v.out == v.in - u - s\n", + " g[1] + g[2] + u == 150\n", + " shortage_cost >= 40 - v.out\n", + " shortage_cost >= 60 - 2 * v.out\n", + " shortage_cost >= 80 - 4 * v.out\n", + " end)\n", + " # You must call this for a biobjective problem!\n", + " SDDP.initialize_biobjective_subproblem(subproblem)\n", + " SDDP.parameterize(subproblem, 0.0:5:50.0) do ω\n", + " JuMP.set_normalized_rhs(inflow_constraint, ω)\n", + " # You must call `set_biobjective_functions` from within\n", + " # `SDDP.parameterize`.\n", + " return SDDP.set_biobjective_functions(\n", + " subproblem,\n", + " objective_1,\n", + " objective_2,\n", + " )\n", + " end\n", + " end\n", + " pareto_weights =\n", + " SDDP.train_biobjective(model; solution_limit = 10, iteration_limit = 10)\n", + " solutions = [(k, v) for (k, v) in pareto_weights]\n", + " sort!(solutions; by = x -> x[1])\n", + " @test length(solutions) == 10\n", + " # Test for convexity! The gradient must be decreasing as we move from left\n", + " # to right.\n", + " gradient(a, b) = (b[2] - a[2]) / (b[1] - a[1])\n", + " grad = Inf\n", + " for i in 1:9\n", + " new_grad = gradient(solutions[i], solutions[i+1])\n", + " @test new_grad < grad\n", + " grad = new_grad\n", + " end\n", + " return\n", + "end\n", + "\n", + "biobjective_example()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/biobjective_hydro.jl b/previews/PR826/examples/biobjective_hydro.jl new file mode 100644 index 0000000000..ec3afbdbbd --- /dev/null +++ b/previews/PR826/examples/biobjective_hydro.jl @@ -0,0 +1,64 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Biobjective hydro-thermal + +using SDDP, HiGHS, Statistics, Test + +function biobjective_example() + model = SDDP.LinearPolicyGraph(; + stages = 3, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, _ + @variable(subproblem, 0 <= v <= 200, SDDP.State, initial_value = 50) + @variables(subproblem, begin + 0 <= g[i = 1:2] <= 100 + 0 <= u <= 150 + s >= 0 + shortage_cost >= 0 + end) + @expressions(subproblem, begin + objective_1, g[1] + 10 * g[2] + objective_2, shortage_cost + end) + @constraints(subproblem, begin + inflow_constraint, v.out == v.in - u - s + g[1] + g[2] + u == 150 + shortage_cost >= 40 - v.out + shortage_cost >= 60 - 2 * v.out + shortage_cost >= 80 - 4 * v.out + end) + ## You must call this for a biobjective problem! + SDDP.initialize_biobjective_subproblem(subproblem) + SDDP.parameterize(subproblem, 0.0:5:50.0) do ω + JuMP.set_normalized_rhs(inflow_constraint, ω) + ## You must call `set_biobjective_functions` from within + ## `SDDP.parameterize`. + return SDDP.set_biobjective_functions( + subproblem, + objective_1, + objective_2, + ) + end + end + pareto_weights = + SDDP.train_biobjective(model; solution_limit = 10, iteration_limit = 10) + solutions = [(k, v) for (k, v) in pareto_weights] + sort!(solutions; by = x -> x[1]) + @test length(solutions) == 10 + ## Test for convexity! The gradient must be decreasing as we move from left + ## to right. + gradient(a, b) = (b[2] - a[2]) / (b[1] - a[1]) + grad = Inf + for i in 1:9 + new_grad = gradient(solutions[i], solutions[i+1]) + @test new_grad < grad + grad = new_grad + end + return +end + +biobjective_example() diff --git a/previews/PR826/examples/biobjective_hydro/index.html b/previews/PR826/examples/biobjective_hydro/index.html new file mode 100644 index 0000000000..a708cae747 --- /dev/null +++ b/previews/PR826/examples/biobjective_hydro/index.html @@ -0,0 +1,389 @@ + +Biobjective hydro-thermal · SDDP.jl

Biobjective hydro-thermal

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP, HiGHS, Statistics, Test
+
+function biobjective_example()
+    model = SDDP.LinearPolicyGraph(;
+        stages = 3,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, _
+        @variable(subproblem, 0 <= v <= 200, SDDP.State, initial_value = 50)
+        @variables(subproblem, begin
+            0 <= g[i = 1:2] <= 100
+            0 <= u <= 150
+            s >= 0
+            shortage_cost >= 0
+        end)
+        @expressions(subproblem, begin
+            objective_1, g[1] + 10 * g[2]
+            objective_2, shortage_cost
+        end)
+        @constraints(subproblem, begin
+                inflow_constraint, v.out == v.in - u - s
+                g[1] + g[2] + u == 150
+                shortage_cost >= 40 - v.out
+                shortage_cost >= 60 - 2 * v.out
+                shortage_cost >= 80 - 4 * v.out
+            end)
+        # You must call this for a biobjective problem!
+        SDDP.initialize_biobjective_subproblem(subproblem)
+        SDDP.parameterize(subproblem, 0.0:5:50.0) do ω
+            JuMP.set_normalized_rhs(inflow_constraint, ω)
+            # You must call `set_biobjective_functions` from within
+            # `SDDP.parameterize`.
+            return SDDP.set_biobjective_functions(
+                subproblem,
+                objective_1,
+                objective_2,
+            )
+        end
+    end
+    pareto_weights =
+        SDDP.train_biobjective(model; solution_limit = 10, iteration_limit = 10)
+    solutions = [(k, v) for (k, v) in pareto_weights]
+    sort!(solutions; by = x -> x[1])
+    @test length(solutions) == 10
+    # Test for convexity! The gradient must be decreasing as we move from left
+    # to right.
+    gradient(a, b) = (b[2] - a[2]) / (b[1] - a[1])
+    grad = Inf
+    for i in 1:9
+        new_grad = gradient(solutions[i], solutions[i+1])
+        @test new_grad < grad
+        grad = new_grad
+    end
+    return
+end
+
+biobjective_example()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 5]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   0.000000e+00  0.000000e+00  4.596949e-03        36   1
+        10   0.000000e+00  0.000000e+00  2.298403e-02       360   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.298403e-02
+total solves   : 360
+best bound     :  0.000000e+00
+simulation ci  :  0.000000e+00 ± 0.000000e+00
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 7]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   6.750000e+02  5.500000e+02  2.655029e-03       407   1
+        10   4.500000e+02  5.733959e+02  2.791214e-02       731   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.791214e-02
+total solves   : 731
+best bound     :  5.733959e+02
+simulation ci  :  5.000000e+02 ± 1.079583e+02
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 14]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   4.850000e+02  3.349793e+02  2.889872e-03       778   1
+        10   3.550000e+02  3.468286e+02  2.922487e-02      1102   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.922487e-02
+total solves   : 1102
+best bound     :  3.468286e+02
+simulation ci  :  3.948309e+02 ± 7.954180e+01
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 19]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.887500e+02  1.995243e+02  3.114939e-03      1149   1
+        10   2.962500e+02  2.052855e+02  2.890491e-02      1473   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.890491e-02
+total solves   : 1473
+best bound     :  2.052855e+02
+simulation ci  :  2.040201e+02 ± 3.876873e+01
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 25]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.737500e+02  4.626061e+02  3.158092e-03      1520   1
+        10   2.450000e+02  4.658509e+02  3.224707e-02      1844   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 3.224707e-02
+total solves   : 1844
+best bound     :  4.658509e+02
+simulation ci  :  3.907376e+02 ± 9.045105e+01
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 33]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.675000e+02  1.129545e+02  2.702951e-03      1891   1
+        10   1.362500e+02  1.129771e+02  2.858090e-02      2215   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.858090e-02
+total solves   : 2215
+best bound     :  1.129771e+02
+simulation ci  :  1.176375e+02 ± 1.334615e+01
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 36]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   2.562500e+02  2.788373e+02  3.043890e-03      2262   1
+        10   2.375000e+02  2.795671e+02  3.205705e-02      2586   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 3.205705e-02
+total solves   : 2586
+best bound     :  2.795671e+02
+simulation ci  :  2.375000e+02 ± 3.099032e+01
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 41]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.812500e+02  4.072952e+02  3.933907e-03      2633   1
+        10   5.818750e+02  4.080500e+02  3.461695e-02      2957   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 3.461695e-02
+total solves   : 2957
+best bound     :  4.080500e+02
+simulation ci  :  4.235323e+02 ± 1.029245e+02
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 47]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   8.525000e+02  5.197742e+02  3.314972e-03      3004   1
+        10   4.493750e+02  5.211793e+02  3.488708e-02      3328   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 3.488708e-02
+total solves   : 3328
+best bound     :  5.211793e+02
+simulation ci  :  5.268125e+02 ± 1.227709e+02
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.33100e+03
+  existing cuts   : true
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 53]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.437500e+01  5.937500e+01  2.995014e-03      3375   1
+        10   3.750000e+01  5.938557e+01  2.948904e-02      3699   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.948904e-02
+total solves   : 3699
+best bound     :  5.938557e+01
+simulation ci  :  5.906250e+01 ± 1.352595e+01
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/booking_management.ipynb b/previews/PR826/examples/booking_management.ipynb new file mode 100644 index 0000000000..6b20fb14e9 --- /dev/null +++ b/previews/PR826/examples/booking_management.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Booking management" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example concerns the acceptance of booking requests for rooms in a\n", + "hotel in the lead up to a large event." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Each stage, we receive a booking request and can choose to accept or decline\n", + "it. Once accepted, bookings cannot be terminated." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function booking_management_model(num_days, num_rooms, num_requests)\n", + " # maximum revenue that could be accrued.\n", + " max_revenue = (num_rooms + num_requests) * num_days * num_rooms\n", + " # booking_requests is a vector of {0,1} arrays of size\n", + " # (num_days x num_rooms) if the room is requested.\n", + " booking_requests = Array{Int,2}[]\n", + " for room in 1:num_rooms\n", + " for day in 1:num_days\n", + " # note: length_of_stay is 0 indexed to avoid unnecessary +/- 1\n", + " # on the indexing\n", + " for length_of_stay in 0:(num_days-day)\n", + " req = zeros(Int, (num_rooms, num_days))\n", + " req[room:room, day.+(0:length_of_stay)] .= 1\n", + " push!(booking_requests, req)\n", + " end\n", + " end\n", + " end\n", + "\n", + " return model = SDDP.LinearPolicyGraph(;\n", + " stages = num_requests,\n", + " upper_bound = max_revenue,\n", + " sense = :Max,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, stage\n", + " @variable(\n", + " sp,\n", + " 0 <= vacancy[room = 1:num_rooms, day = 1:num_days] <= 1,\n", + " SDDP.State,\n", + " Bin,\n", + " initial_value = 1\n", + " )\n", + " @variables(\n", + " sp,\n", + " begin\n", + " # Accept request for booking of room for length of time.\n", + " 0 <= accept_request <= 1, Bin\n", + " # Accept a booking for an individual room on an individual day.\n", + " 0 <= room_request_accepted[1:num_rooms, 1:num_days] <= 1, Bin\n", + " # Helper for JuMP.fix\n", + " req[1:num_rooms, 1:num_days]\n", + " end\n", + " )\n", + " for room in 1:num_rooms, day in 1:num_days\n", + " @constraints(\n", + " sp,\n", + " begin\n", + " # Update vacancy if we accept a room request\n", + " vacancy[room, day].out ==\n", + " vacancy[room, day].in - room_request_accepted[room, day]\n", + " # Can't accept a request of a filled room\n", + " room_request_accepted[room, day] <= vacancy[room, day].in\n", + " # Can't accept invididual room request if entire request is declined\n", + " room_request_accepted[room, day] <= accept_request\n", + " # Can't accept request if room not requested\n", + " room_request_accepted[room, day] <= req[room, day]\n", + " # Accept all individual rooms is entire request is accepted\n", + " room_request_accepted[room, day] + (1 - accept_request) >= req[room, day]\n", + " end\n", + " )\n", + " end\n", + " SDDP.parameterize(sp, booking_requests) do request\n", + " return JuMP.fix.(req, request)\n", + " end\n", + " @stageobjective(\n", + " sp,\n", + " sum(\n", + " (room + stage - 1) * room_request_accepted[room, day] for\n", + " room in 1:num_rooms for day in 1:num_days\n", + " )\n", + " )\n", + " end\n", + "end\n", + "\n", + "function booking_management(duality_handler)\n", + " m_1_2_5 = booking_management_model(1, 2, 5)\n", + " SDDP.train(m_1_2_5; log_frequency = 5, duality_handler = duality_handler)\n", + " if duality_handler == SDDP.ContinuousConicDuality()\n", + " @test SDDP.calculate_bound(m_1_2_5) >= 7.25 - 1e-4\n", + " else\n", + " @test isapprox(SDDP.calculate_bound(m_1_2_5), 7.25, atol = 0.02)\n", + " end\n", + "\n", + " m_2_2_3 = booking_management_model(2, 2, 3)\n", + " SDDP.train(m_2_2_3; log_frequency = 10, duality_handler = duality_handler)\n", + " if duality_handler == SDDP.ContinuousConicDuality()\n", + " @test SDDP.calculate_bound(m_1_2_5) > 6.13\n", + " else\n", + " @test isapprox(SDDP.calculate_bound(m_2_2_3), 6.13, atol = 0.02)\n", + " end\n", + "end\n", + "\n", + "booking_management(SDDP.ContinuousConicDuality())" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "New version of HiGHS stalls\n", + "booking_management(SDDP.LagrangianDuality())" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/booking_management.jl b/previews/PR826/examples/booking_management.jl new file mode 100644 index 0000000000..ebd43efec6 --- /dev/null +++ b/previews/PR826/examples/booking_management.jl @@ -0,0 +1,110 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Booking management + +# This example concerns the acceptance of booking requests for rooms in a +# hotel in the lead up to a large event. + +# Each stage, we receive a booking request and can choose to accept or decline +# it. Once accepted, bookings cannot be terminated. + +using SDDP, HiGHS, Test + +function booking_management_model(num_days, num_rooms, num_requests) + ## maximum revenue that could be accrued. + max_revenue = (num_rooms + num_requests) * num_days * num_rooms + ## booking_requests is a vector of {0,1} arrays of size + ## (num_days x num_rooms) if the room is requested. + booking_requests = Array{Int,2}[] + for room in 1:num_rooms + for day in 1:num_days + ## note: length_of_stay is 0 indexed to avoid unnecessary +/- 1 + ## on the indexing + for length_of_stay in 0:(num_days-day) + req = zeros(Int, (num_rooms, num_days)) + req[room:room, day.+(0:length_of_stay)] .= 1 + push!(booking_requests, req) + end + end + end + + return model = SDDP.LinearPolicyGraph(; + stages = num_requests, + upper_bound = max_revenue, + sense = :Max, + optimizer = HiGHS.Optimizer, + ) do sp, stage + @variable( + sp, + 0 <= vacancy[room = 1:num_rooms, day = 1:num_days] <= 1, + SDDP.State, + Bin, + initial_value = 1 + ) + @variables( + sp, + begin + ## Accept request for booking of room for length of time. + 0 <= accept_request <= 1, Bin + ## Accept a booking for an individual room on an individual day. + 0 <= room_request_accepted[1:num_rooms, 1:num_days] <= 1, Bin + ## Helper for JuMP.fix + req[1:num_rooms, 1:num_days] + end + ) + for room in 1:num_rooms, day in 1:num_days + @constraints( + sp, + begin + ## Update vacancy if we accept a room request + vacancy[room, day].out == + vacancy[room, day].in - room_request_accepted[room, day] + ## Can't accept a request of a filled room + room_request_accepted[room, day] <= vacancy[room, day].in + ## Can't accept invididual room request if entire request is declined + room_request_accepted[room, day] <= accept_request + ## Can't accept request if room not requested + room_request_accepted[room, day] <= req[room, day] + ## Accept all individual rooms is entire request is accepted + room_request_accepted[room, day] + (1 - accept_request) >= req[room, day] + end + ) + end + SDDP.parameterize(sp, booking_requests) do request + return JuMP.fix.(req, request) + end + @stageobjective( + sp, + sum( + (room + stage - 1) * room_request_accepted[room, day] for + room in 1:num_rooms for day in 1:num_days + ) + ) + end +end + +function booking_management(duality_handler) + m_1_2_5 = booking_management_model(1, 2, 5) + SDDP.train(m_1_2_5; log_frequency = 5, duality_handler = duality_handler) + if duality_handler == SDDP.ContinuousConicDuality() + @test SDDP.calculate_bound(m_1_2_5) >= 7.25 - 1e-4 + else + @test isapprox(SDDP.calculate_bound(m_1_2_5), 7.25, atol = 0.02) + end + + m_2_2_3 = booking_management_model(2, 2, 3) + SDDP.train(m_2_2_3; log_frequency = 10, duality_handler = duality_handler) + if duality_handler == SDDP.ContinuousConicDuality() + @test SDDP.calculate_bound(m_1_2_5) > 6.13 + else + @test isapprox(SDDP.calculate_bound(m_2_2_3), 6.13, atol = 0.02) + end +end + +booking_management(SDDP.ContinuousConicDuality()) + +# New version of HiGHS stalls +# booking_management(SDDP.LagrangianDuality()) diff --git a/previews/PR826/examples/booking_management/index.html b/previews/PR826/examples/booking_management/index.html new file mode 100644 index 0000000000..341c06f8c6 --- /dev/null +++ b/previews/PR826/examples/booking_management/index.html @@ -0,0 +1,99 @@ + +Booking management · SDDP.jl

Booking management

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example concerns the acceptance of booking requests for rooms in a hotel in the lead up to a large event.

Each stage, we receive a booking request and can choose to accept or decline it. Once accepted, bookings cannot be terminated.

using SDDP, HiGHS, Test
+
+function booking_management_model(num_days, num_rooms, num_requests)
+    # maximum revenue that could be accrued.
+    max_revenue = (num_rooms + num_requests) * num_days * num_rooms
+    # booking_requests is a vector of {0,1} arrays of size
+    # (num_days x num_rooms) if the room is requested.
+    booking_requests = Array{Int,2}[]
+    for room in 1:num_rooms
+        for day in 1:num_days
+            # note: length_of_stay is 0 indexed to avoid unnecessary +/- 1
+            # on the indexing
+            for length_of_stay in 0:(num_days-day)
+                req = zeros(Int, (num_rooms, num_days))
+                req[room:room, day.+(0:length_of_stay)] .= 1
+                push!(booking_requests, req)
+            end
+        end
+    end
+
+    return model = SDDP.LinearPolicyGraph(;
+        stages = num_requests,
+        upper_bound = max_revenue,
+        sense = :Max,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, stage
+        @variable(
+            sp,
+            0 <= vacancy[room = 1:num_rooms, day = 1:num_days] <= 1,
+            SDDP.State,
+            Bin,
+            initial_value = 1
+        )
+        @variables(
+            sp,
+            begin
+                # Accept request for booking of room for length of time.
+                0 <= accept_request <= 1, Bin
+                # Accept a booking for an individual room on an individual day.
+                0 <= room_request_accepted[1:num_rooms, 1:num_days] <= 1, Bin
+                # Helper for JuMP.fix
+                req[1:num_rooms, 1:num_days]
+            end
+        )
+        for room in 1:num_rooms, day in 1:num_days
+            @constraints(
+                sp,
+                begin
+                    # Update vacancy if we accept a room request
+                    vacancy[room, day].out ==
+                    vacancy[room, day].in - room_request_accepted[room, day]
+                    # Can't accept a request of a filled room
+                    room_request_accepted[room, day] <= vacancy[room, day].in
+                    # Can't accept invididual room request if entire request is declined
+                    room_request_accepted[room, day] <= accept_request
+                    # Can't accept request if room not requested
+                    room_request_accepted[room, day] <= req[room, day]
+                    # Accept all individual rooms is entire request is accepted
+                    room_request_accepted[room, day] + (1 - accept_request) >= req[room, day]
+                end
+            )
+        end
+        SDDP.parameterize(sp, booking_requests) do request
+            return JuMP.fix.(req, request)
+        end
+        @stageobjective(
+            sp,
+            sum(
+                (room + stage - 1) * room_request_accepted[room, day] for
+                room in 1:num_rooms for day in 1:num_days
+            )
+        )
+    end
+end
+
+function booking_management(duality_handler)
+    m_1_2_5 = booking_management_model(1, 2, 5)
+    SDDP.train(m_1_2_5; log_frequency = 5, duality_handler = duality_handler)
+    if duality_handler == SDDP.ContinuousConicDuality()
+        @test SDDP.calculate_bound(m_1_2_5) >= 7.25 - 1e-4
+    else
+        @test isapprox(SDDP.calculate_bound(m_1_2_5), 7.25, atol = 0.02)
+    end
+
+    m_2_2_3 = booking_management_model(2, 2, 3)
+    SDDP.train(m_2_2_3; log_frequency = 10, duality_handler = duality_handler)
+    if duality_handler == SDDP.ContinuousConicDuality()
+        @test SDDP.calculate_bound(m_1_2_5) > 6.13
+    else
+        @test isapprox(SDDP.calculate_bound(m_2_2_3), 6.13, atol = 0.02)
+    end
+end
+
+booking_management(SDDP.ContinuousConicDuality())
Test Passed

New version of HiGHS stalls booking_management(SDDP.LagrangianDuality())

diff --git a/previews/PR826/examples/generation_expansion.ipynb b/previews/PR826/examples/generation_expansion.ipynb new file mode 100644 index 0000000000..870bc4f905 --- /dev/null +++ b/previews/PR826/examples/generation_expansion.ipynb @@ -0,0 +1,120 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Generation expansion" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS\n", + "import Test\n", + "\n", + "function generation_expansion(duality_handler)\n", + " build_cost = 1e4\n", + " use_cost = 4\n", + " num_units = 5\n", + " capacities = ones(num_units)\n", + " demand_vals =\n", + " 0.5 * [\n", + " 5 5 5 5 5 5 5 5\n", + " 4 3 1 3 0 9 8 17\n", + " 0 9 4 2 19 19 13 7\n", + " 25 11 4 14 4 6 15 12\n", + " 6 7 5 3 8 4 17 13\n", + " ]\n", + " # Cost of unmet demand\n", + " penalty = 5e5\n", + " # Discounting rate\n", + " rho = 0.99\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 5,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, stage\n", + " @variable(\n", + " sp,\n", + " 0 <= invested[1:num_units] <= 1,\n", + " SDDP.State,\n", + " Int,\n", + " initial_value = 0\n", + " )\n", + " @variables(sp, begin\n", + " generation >= 0\n", + " unmet >= 0\n", + " demand\n", + " end)\n", + "\n", + " @constraints(\n", + " sp,\n", + " begin\n", + " # Can't un-invest\n", + " investment[i in 1:num_units], invested[i].out >= invested[i].in\n", + " # Generation capacity\n", + " sum(capacities[i] * invested[i].out for i in 1:num_units) >=\n", + " generation\n", + " # Meet demand or pay a penalty\n", + " unmet >= demand - sum(generation)\n", + " # For fewer iterations order the units to break symmetry, units are identical (tougher numerically)\n", + " [j in 1:(num_units-1)], invested[j].out <= invested[j+1].out\n", + " end\n", + " )\n", + " # Demand is uncertain\n", + " SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, demand_vals[stage, :])\n", + "\n", + " @expression(\n", + " sp,\n", + " investment_cost,\n", + " build_cost *\n", + " sum(invested[i].out - invested[i].in for i in 1:num_units)\n", + " )\n", + " @stageobjective(\n", + " sp,\n", + " (investment_cost + generation * use_cost) * rho^(stage - 1) +\n", + " penalty * unmet\n", + " )\n", + " end\n", + " if get(ARGS, 1, \"\") == \"--write\"\n", + " # Run `$ julia generation_expansion.jl --write` to update the benchmark\n", + " # model directory\n", + " model_dir = joinpath(@__DIR__, \"..\", \"..\", \"..\", \"benchmarks\", \"models\")\n", + " SDDP.write_to_file(\n", + " model,\n", + " joinpath(model_dir, \"generation_expansion.sof.json.gz\");\n", + " test_scenarios = 100,\n", + " )\n", + " exit(0)\n", + " end\n", + " SDDP.train(model; log_frequency = 10, duality_handler = duality_handler)\n", + " Test.@test SDDP.calculate_bound(model) ≈ 2.078860e6 atol = 1e3\n", + " return\n", + "end\n", + "\n", + "generation_expansion(SDDP.ContinuousConicDuality())\n", + "generation_expansion(SDDP.LagrangianDuality())" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/generation_expansion.jl b/previews/PR826/examples/generation_expansion.jl new file mode 100644 index 0000000000..7a2e675700 --- /dev/null +++ b/previews/PR826/examples/generation_expansion.jl @@ -0,0 +1,93 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Generation expansion + +using SDDP +import HiGHS +import Test + +function generation_expansion(duality_handler) + build_cost = 1e4 + use_cost = 4 + num_units = 5 + capacities = ones(num_units) + demand_vals = + 0.5 * [ + 5 5 5 5 5 5 5 5 + 4 3 1 3 0 9 8 17 + 0 9 4 2 19 19 13 7 + 25 11 4 14 4 6 15 12 + 6 7 5 3 8 4 17 13 + ] + ## Cost of unmet demand + penalty = 5e5 + ## Discounting rate + rho = 0.99 + model = SDDP.LinearPolicyGraph(; + stages = 5, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do sp, stage + @variable( + sp, + 0 <= invested[1:num_units] <= 1, + SDDP.State, + Int, + initial_value = 0 + ) + @variables(sp, begin + generation >= 0 + unmet >= 0 + demand + end) + + @constraints( + sp, + begin + ## Can't un-invest + investment[i in 1:num_units], invested[i].out >= invested[i].in + ## Generation capacity + sum(capacities[i] * invested[i].out for i in 1:num_units) >= + generation + ## Meet demand or pay a penalty + unmet >= demand - sum(generation) + ## For fewer iterations order the units to break symmetry, units are identical (tougher numerically) + [j in 1:(num_units-1)], invested[j].out <= invested[j+1].out + end + ) + ## Demand is uncertain + SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, demand_vals[stage, :]) + + @expression( + sp, + investment_cost, + build_cost * + sum(invested[i].out - invested[i].in for i in 1:num_units) + ) + @stageobjective( + sp, + (investment_cost + generation * use_cost) * rho^(stage - 1) + + penalty * unmet + ) + end + if get(ARGS, 1, "") == "--write" + ## Run `$ julia generation_expansion.jl --write` to update the benchmark + ## model directory + model_dir = joinpath(@__DIR__, "..", "..", "..", "benchmarks", "models") + SDDP.write_to_file( + model, + joinpath(model_dir, "generation_expansion.sof.json.gz"); + test_scenarios = 100, + ) + exit(0) + end + SDDP.train(model; log_frequency = 10, duality_handler = duality_handler) + Test.@test SDDP.calculate_bound(model) ≈ 2.078860e6 atol = 1e3 + return +end + +generation_expansion(SDDP.ContinuousConicDuality()) +generation_expansion(SDDP.LagrangianDuality()) diff --git a/previews/PR826/examples/generation_expansion/index.html b/previews/PR826/examples/generation_expansion/index.html new file mode 100644 index 0000000000..627f5f1058 --- /dev/null +++ b/previews/PR826/examples/generation_expansion/index.html @@ -0,0 +1,175 @@ + +Generation expansion · SDDP.jl

Generation expansion

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP
+import HiGHS
+import Test
+
+function generation_expansion(duality_handler)
+    build_cost = 1e4
+    use_cost = 4
+    num_units = 5
+    capacities = ones(num_units)
+    demand_vals =
+        0.5 * [
+            5 5 5 5 5 5 5 5
+            4 3 1 3 0 9 8 17
+            0 9 4 2 19 19 13 7
+            25 11 4 14 4 6 15 12
+            6 7 5 3 8 4 17 13
+        ]
+    # Cost of unmet demand
+    penalty = 5e5
+    # Discounting rate
+    rho = 0.99
+    model = SDDP.LinearPolicyGraph(;
+        stages = 5,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, stage
+        @variable(
+            sp,
+            0 <= invested[1:num_units] <= 1,
+            SDDP.State,
+            Int,
+            initial_value = 0
+        )
+        @variables(sp, begin
+            generation >= 0
+            unmet >= 0
+            demand
+        end)
+
+        @constraints(
+            sp,
+            begin
+                # Can't un-invest
+                investment[i in 1:num_units], invested[i].out >= invested[i].in
+                # Generation capacity
+                sum(capacities[i] * invested[i].out for i in 1:num_units) >=
+                generation
+                # Meet demand or pay a penalty
+                unmet >= demand - sum(generation)
+                # For fewer iterations order the units to break symmetry, units are identical (tougher numerically)
+                [j in 1:(num_units-1)], invested[j].out <= invested[j+1].out
+            end
+        )
+        # Demand is uncertain
+        SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, demand_vals[stage, :])
+
+        @expression(
+            sp,
+            investment_cost,
+            build_cost *
+            sum(invested[i].out - invested[i].in for i in 1:num_units)
+        )
+        @stageobjective(
+            sp,
+            (investment_cost + generation * use_cost) * rho^(stage - 1) +
+            penalty * unmet
+        )
+    end
+    if get(ARGS, 1, "") == "--write"
+        # Run `$ julia generation_expansion.jl --write` to update the benchmark
+        # model directory
+        model_dir = joinpath(@__DIR__, "..", "..", "..", "benchmarks", "models")
+        SDDP.write_to_file(
+            model,
+            joinpath(model_dir, "generation_expansion.sof.json.gz");
+            test_scenarios = 100,
+        )
+        exit(0)
+    end
+    SDDP.train(model; log_frequency = 10, duality_handler = duality_handler)
+    Test.@test SDDP.calculate_bound(model) ≈ 2.078860e6 atol = 1e3
+    return
+end
+
+generation_expansion(SDDP.ContinuousConicDuality())
+generation_expansion(SDDP.LagrangianDuality())
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 5
+  state variables : 5
+  scenarios       : 3.27680e+04
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [14, 14]
+  AffExpr in MOI.GreaterThan{Float64}     : [7, 7]
+  AffExpr in MOI.LessThan{Float64}        : [4, 4]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.Integer              : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 5e+05]
+  bounds range     [1e+00, 1e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   2.549668e+06  2.078257e+06  5.081830e-01       920   1
+        20   5.494568e+05  2.078257e+06  6.979780e-01      1340   1
+        30   4.985879e+04  2.078257e+06  1.224111e+00      2260   1
+        40   3.799447e+06  2.078257e+06  1.419322e+00      2680   1
+        50   1.049867e+06  2.078257e+06  1.962126e+00      3600   1
+        60   3.985191e+04  2.078257e+06  2.160364e+00      4020   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 2.160364e+00
+total solves   : 4020
+best bound     :  2.078257e+06
+simulation ci  :  2.031697e+06 ± 3.922745e+05
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 5
+  state variables : 5
+  scenarios       : 3.27680e+04
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [14, 14]
+  AffExpr in MOI.GreaterThan{Float64}     : [7, 7]
+  AffExpr in MOI.LessThan{Float64}        : [4, 4]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 8]
+  VariableRef in MOI.Integer              : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [5, 6]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 5e+05]
+  bounds range     [1e+00, 1e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10L  4.986663e+04  2.079119e+06  1.095144e+00       920   1
+        20L  3.799878e+06  2.079330e+06  1.974077e+00      1340   1
+        30L  3.003923e+04  2.079457e+06  3.204113e+00      2260   1
+        40L  5.549882e+06  2.079457e+06  4.155357e+00      2680   1
+        50L  2.799466e+06  2.079457e+06  5.457508e+00      3600   1
+        60L  3.549880e+06  2.079457e+06  6.407687e+00      4020   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 6.407687e+00
+total solves   : 4020
+best bound     :  2.079457e+06
+simulation ci  :  2.352204e+06 ± 5.377531e+05
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/hydro_valley.ipynb b/previews/PR826/examples/hydro_valley.ipynb new file mode 100644 index 0000000000..0ee77f18bf --- /dev/null +++ b/previews/PR826/examples/hydro_valley.ipynb @@ -0,0 +1,358 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Hydro valleys" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This problem is a version of the hydro-thermal scheduling problem. The goal is\n", + "to operate two hydro-dams in a valley chain over time in the face of inflow\n", + "and price uncertainty." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Turbine response curves are modelled by piecewise linear functions which map\n", + "the flow rate into a power. These can be controlled by specifying the\n", + "breakpoints in the piecewise linear function as the knots in the Turbine\n", + "struct." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The model can be created using the `hydro_valley_model` function. It has a few\n", + "keyword arguments to allow automated testing of the library.\n", + "`hasstagewiseinflows` determines if the RHS noise constraint should be added.\n", + "`hasmarkovprice` determines if the price uncertainty (modelled by a Markov\n", + "chain) should be added." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the third stage, the Markov chain has some unreachable states to test\n", + "some code-paths in the library." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can also set the sense to :Min or :Max (the objective and bound are\n", + "flipped appropriately)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test, Random\n", + "\n", + "struct Turbine\n", + " flowknots::Vector{Float64}\n", + " powerknots::Vector{Float64}\n", + "end\n", + "\n", + "struct Reservoir\n", + " min::Float64\n", + " max::Float64\n", + " initial::Float64\n", + " turbine::Turbine\n", + " spill_cost::Float64\n", + " inflows::Vector{Float64}\n", + "end\n", + "\n", + "function hydro_valley_model(;\n", + " hasstagewiseinflows::Bool = true,\n", + " hasmarkovprice::Bool = true,\n", + " sense::Symbol = :Max,\n", + ")\n", + " valley_chain = [\n", + " Reservoir(\n", + " 0,\n", + " 200,\n", + " 200,\n", + " Turbine([50, 60, 70], [55, 65, 70]),\n", + " 1000,\n", + " [0, 20, 50],\n", + " ),\n", + " Reservoir(\n", + " 0,\n", + " 200,\n", + " 200,\n", + " Turbine([50, 60, 70], [55, 65, 70]),\n", + " 1000,\n", + " [0, 0, 20],\n", + " ),\n", + " ]\n", + "\n", + " turbine(i) = valley_chain[i].turbine\n", + "\n", + " # Prices[t, Markov state]\n", + " prices = [\n", + " 1 2 0\n", + " 2 1 0\n", + " 3 4 0\n", + " ]\n", + "\n", + " # Transition matrix\n", + " if hasmarkovprice\n", + " transition =\n", + " Array{Float64,2}[[1.0]', [0.6 0.4], [0.6 0.4 0.0; 0.3 0.7 0.0]]\n", + " else\n", + " transition = [ones(Float64, (1, 1)) for t in 1:3]\n", + " end\n", + "\n", + " flipobj = (sense == :Max) ? 1.0 : -1.0\n", + " lower = (sense == :Max) ? -Inf : -1e6\n", + " upper = (sense == :Max) ? 1e6 : Inf\n", + "\n", + " N = length(valley_chain)\n", + "\n", + " # Initialise SDDP Model\n", + " return m = SDDP.MarkovianPolicyGraph(;\n", + " sense = sense,\n", + " lower_bound = lower,\n", + " upper_bound = upper,\n", + " transition_matrices = transition,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, node\n", + " t, markov_state = node\n", + "\n", + " # ------------------------------------------------------------------\n", + " # SDDP State Variables\n", + " # Level of upper reservoir\n", + " @variable(\n", + " subproblem,\n", + " valley_chain[r].min <= reservoir[r = 1:N] <= valley_chain[r].max,\n", + " SDDP.State,\n", + " initial_value = valley_chain[r].initial\n", + " )\n", + "\n", + " # ------------------------------------------------------------------\n", + " # Additional variables\n", + " @variables(\n", + " subproblem,\n", + " begin\n", + " outflow[r = 1:N] >= 0\n", + " spill[r = 1:N] >= 0\n", + " inflow[r = 1:N] >= 0\n", + " generation_quantity >= 0 # Total quantity of water\n", + " # Proportion of levels to dispatch on\n", + " 0 <=\n", + " dispatch[r = 1:N, level = 1:length(turbine(r).flowknots)] <=\n", + " 1\n", + " rainfall[i = 1:N]\n", + " end\n", + " )\n", + "\n", + " # ------------------------------------------------------------------\n", + " # Constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " # flow from upper reservoir\n", + " reservoir[1].out ==\n", + " reservoir[1].in + inflow[1] - outflow[1] - spill[1]\n", + "\n", + " # other flows\n", + " flow[i = 2:N],\n", + " reservoir[i].out ==\n", + " reservoir[i].in + inflow[i] - outflow[i] - spill[i] +\n", + " outflow[i-1] +\n", + " spill[i-1]\n", + "\n", + " # Total quantity generated\n", + " generation_quantity == sum(\n", + " turbine(r).powerknots[level] * dispatch[r, level] for\n", + " r in 1:N for level in 1:length(turbine(r).powerknots)\n", + " )\n", + "\n", + " # ------------------------------------------------------------------\n", + " # Flow out\n", + " turbineflow[r = 1:N],\n", + " outflow[r] == sum(\n", + " turbine(r).flowknots[level] * dispatch[r, level] for\n", + " level in 1:length(turbine(r).flowknots)\n", + " )\n", + "\n", + " # Dispatch combination of levels\n", + " dispatched[r = 1:N],\n", + " sum(\n", + " dispatch[r, level] for\n", + " level in 1:length(turbine(r).flowknots)\n", + " ) <= 1\n", + " end\n", + " )\n", + "\n", + " # rainfall noises\n", + " if hasstagewiseinflows && t > 1 # in future stages random inflows\n", + " @constraint(subproblem, inflow_noise[i = 1:N], inflow[i] <= rainfall[i])\n", + "\n", + " SDDP.parameterize(\n", + " subproblem,\n", + " [\n", + " (valley_chain[1].inflows[i], valley_chain[2].inflows[i]) for i in 1:length(transition)\n", + " ],\n", + " ) do ω\n", + " for i in 1:N\n", + " JuMP.fix(rainfall[i], ω[i])\n", + " end\n", + " end\n", + " else # in the first stage deterministic inflow\n", + " @constraint(\n", + " subproblem,\n", + " initial_inflow_noise[i = 1:N],\n", + " inflow[i] <= valley_chain[i].inflows[1]\n", + " )\n", + " end\n", + "\n", + " # ------------------------------------------------------------------\n", + " # Objective Function\n", + " if hasmarkovprice\n", + " @stageobjective(\n", + " subproblem,\n", + " flipobj * (\n", + " prices[t, markov_state] * generation_quantity -\n", + " sum(valley_chain[i].spill_cost * spill[i] for i in 1:N)\n", + " )\n", + " )\n", + " else\n", + " @stageobjective(\n", + " subproblem,\n", + " flipobj * (\n", + " prices[t, 1] * generation_quantity -\n", + " sum(valley_chain[i].spill_cost * spill[i] for i in 1:N)\n", + " )\n", + " )\n", + " end\n", + " end\n", + "end\n", + "\n", + "function test_hydro_valley_model()\n", + "\n", + " # For repeatability\n", + " Random.seed!(11111)\n", + "\n", + " # deterministic\n", + " deterministic_model = hydro_valley_model(;\n", + " hasmarkovprice = false,\n", + " hasstagewiseinflows = false,\n", + " )\n", + " SDDP.train(\n", + " deterministic_model;\n", + " iteration_limit = 10,\n", + " cut_deletion_minimum = 1,\n", + " print_level = 0,\n", + " )\n", + " @test SDDP.calculate_bound(deterministic_model) ≈ 835.0 atol = 1e-3\n", + "\n", + " # stagewise inflows\n", + " stagewise_model = hydro_valley_model(; hasmarkovprice = false)\n", + " SDDP.train(stagewise_model; iteration_limit = 20, print_level = 0)\n", + " @test SDDP.calculate_bound(stagewise_model) ≈ 838.33 atol = 1e-2\n", + "\n", + " # Markov prices\n", + " markov_model = hydro_valley_model(; hasstagewiseinflows = false)\n", + " SDDP.train(markov_model; iteration_limit = 10, print_level = 0)\n", + " @test SDDP.calculate_bound(markov_model) ≈ 851.8 atol = 1e-2\n", + "\n", + " # stagewise inflows and Markov prices\n", + " markov_stagewise_model =\n", + " hydro_valley_model(; hasstagewiseinflows = true, hasmarkovprice = true)\n", + " SDDP.train(markov_stagewise_model; iteration_limit = 10, print_level = 0)\n", + " @test SDDP.calculate_bound(markov_stagewise_model) ≈ 855.0 atol = 1.0\n", + "\n", + " # risk averse stagewise inflows and Markov prices\n", + " riskaverse_model = hydro_valley_model()\n", + " SDDP.train(\n", + " riskaverse_model;\n", + " risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.66),\n", + " iteration_limit = 10,\n", + " print_level = 0,\n", + " )\n", + " @test SDDP.calculate_bound(riskaverse_model) ≈ 828.157 atol = 1.0\n", + "\n", + " # stagewise inflows and Markov prices\n", + " worst_case_model = hydro_valley_model(; sense = :Min)\n", + " SDDP.train(\n", + " worst_case_model;\n", + " risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.0),\n", + " iteration_limit = 10,\n", + " print_level = 0,\n", + " )\n", + " @test SDDP.calculate_bound(worst_case_model) ≈ -780.867 atol = 1.0\n", + "\n", + " # stagewise inflows and Markov prices\n", + " cutselection_model = hydro_valley_model()\n", + " SDDP.train(\n", + " cutselection_model;\n", + " iteration_limit = 10,\n", + " print_level = 0,\n", + " cut_deletion_minimum = 2,\n", + " )\n", + " @test SDDP.calculate_bound(cutselection_model) ≈ 855.0 atol = 1.0\n", + "\n", + " # Distributionally robust Optimization\n", + " dro_model = hydro_valley_model(; hasmarkovprice = false)\n", + " SDDP.train(\n", + " dro_model;\n", + " risk_measure = SDDP.ModifiedChiSquared(sqrt(2 / 3) - 1e-6),\n", + " iteration_limit = 10,\n", + " print_level = 0,\n", + " )\n", + " @test SDDP.calculate_bound(dro_model) ≈ 835.0 atol = 1.0\n", + "\n", + " dro_model = hydro_valley_model(; hasmarkovprice = false)\n", + " SDDP.train(\n", + " dro_model;\n", + " risk_measure = SDDP.ModifiedChiSquared(1 / 6),\n", + " iteration_limit = 20,\n", + " print_level = 0,\n", + " )\n", + " @test SDDP.calculate_bound(dro_model) ≈ 836.695 atol = 1.0\n", + " # (Note) radius ≈ sqrt(2/3), will set all noise probabilities to zero except the worst case noise\n", + " # (Why?):\n", + " # The distance from the uniform distribution (the assumed \"true\" distribution)\n", + " # to a corner of a unit simplex is sqrt(S-1)/sqrt(S) if we have S scenarios. The corner\n", + " # of a unit simplex is just a unit vector, i.e.: [0 ... 0 1 0 ... 0]. With this probability\n", + " # vector, only one noise has a non-zero probablity.\n", + " # In the worst case rhsnoise (0 inflows) the profit is:\n", + " # Reservoir1: 70 * $3 + 70 * $2 + 65 * $1 +\n", + " # Reservoir2: 70 * $3 + 70 * $2 + 70 * $1\n", + " ### = $835\n", + "end\n", + "\n", + "test_hydro_valley_model()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/hydro_valley.jl b/previews/PR826/examples/hydro_valley.jl new file mode 100644 index 0000000000..d509f62fc6 --- /dev/null +++ b/previews/PR826/examples/hydro_valley.jl @@ -0,0 +1,306 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Hydro valleys + +# This problem is a version of the hydro-thermal scheduling problem. The goal is +# to operate two hydro-dams in a valley chain over time in the face of inflow +# and price uncertainty. + +# Turbine response curves are modelled by piecewise linear functions which map +# the flow rate into a power. These can be controlled by specifying the +# breakpoints in the piecewise linear function as the knots in the Turbine +# struct. + +# The model can be created using the `hydro_valley_model` function. It has a few +# keyword arguments to allow automated testing of the library. +# `hasstagewiseinflows` determines if the RHS noise constraint should be added. +# `hasmarkovprice` determines if the price uncertainty (modelled by a Markov +# chain) should be added. + +# In the third stage, the Markov chain has some unreachable states to test +# some code-paths in the library. + +# We can also set the sense to :Min or :Max (the objective and bound are +# flipped appropriately). + +using SDDP, HiGHS, Test, Random + +struct Turbine + flowknots::Vector{Float64} + powerknots::Vector{Float64} +end + +struct Reservoir + min::Float64 + max::Float64 + initial::Float64 + turbine::Turbine + spill_cost::Float64 + inflows::Vector{Float64} +end + +function hydro_valley_model(; + hasstagewiseinflows::Bool = true, + hasmarkovprice::Bool = true, + sense::Symbol = :Max, +) + valley_chain = [ + Reservoir( + 0, + 200, + 200, + Turbine([50, 60, 70], [55, 65, 70]), + 1000, + [0, 20, 50], + ), + Reservoir( + 0, + 200, + 200, + Turbine([50, 60, 70], [55, 65, 70]), + 1000, + [0, 0, 20], + ), + ] + + turbine(i) = valley_chain[i].turbine + + ## Prices[t, Markov state] + prices = [ + 1 2 0 + 2 1 0 + 3 4 0 + ] + + ## Transition matrix + if hasmarkovprice + transition = + Array{Float64,2}[[1.0]', [0.6 0.4], [0.6 0.4 0.0; 0.3 0.7 0.0]] + else + transition = [ones(Float64, (1, 1)) for t in 1:3] + end + + flipobj = (sense == :Max) ? 1.0 : -1.0 + lower = (sense == :Max) ? -Inf : -1e6 + upper = (sense == :Max) ? 1e6 : Inf + + N = length(valley_chain) + + ## Initialise SDDP Model + return m = SDDP.MarkovianPolicyGraph(; + sense = sense, + lower_bound = lower, + upper_bound = upper, + transition_matrices = transition, + optimizer = HiGHS.Optimizer, + ) do subproblem, node + t, markov_state = node + + ## ------------------------------------------------------------------ + ## SDDP State Variables + ## Level of upper reservoir + @variable( + subproblem, + valley_chain[r].min <= reservoir[r = 1:N] <= valley_chain[r].max, + SDDP.State, + initial_value = valley_chain[r].initial + ) + + ## ------------------------------------------------------------------ + ## Additional variables + @variables( + subproblem, + begin + outflow[r = 1:N] >= 0 + spill[r = 1:N] >= 0 + inflow[r = 1:N] >= 0 + generation_quantity >= 0 # Total quantity of water + ## Proportion of levels to dispatch on + 0 <= + dispatch[r = 1:N, level = 1:length(turbine(r).flowknots)] <= + 1 + rainfall[i = 1:N] + end + ) + + ## ------------------------------------------------------------------ + ## Constraints + @constraints( + subproblem, + begin + ## flow from upper reservoir + reservoir[1].out == + reservoir[1].in + inflow[1] - outflow[1] - spill[1] + + ## other flows + flow[i = 2:N], + reservoir[i].out == + reservoir[i].in + inflow[i] - outflow[i] - spill[i] + + outflow[i-1] + + spill[i-1] + + ## Total quantity generated + generation_quantity == sum( + turbine(r).powerknots[level] * dispatch[r, level] for + r in 1:N for level in 1:length(turbine(r).powerknots) + ) + + ## ------------------------------------------------------------------ + ## Flow out + turbineflow[r = 1:N], + outflow[r] == sum( + turbine(r).flowknots[level] * dispatch[r, level] for + level in 1:length(turbine(r).flowknots) + ) + + ## Dispatch combination of levels + dispatched[r = 1:N], + sum( + dispatch[r, level] for + level in 1:length(turbine(r).flowknots) + ) <= 1 + end + ) + + ## rainfall noises + if hasstagewiseinflows && t > 1 # in future stages random inflows + @constraint(subproblem, inflow_noise[i = 1:N], inflow[i] <= rainfall[i]) + + SDDP.parameterize( + subproblem, + [ + (valley_chain[1].inflows[i], valley_chain[2].inflows[i]) for i in 1:length(transition) + ], + ) do ω + for i in 1:N + JuMP.fix(rainfall[i], ω[i]) + end + end + else # in the first stage deterministic inflow + @constraint( + subproblem, + initial_inflow_noise[i = 1:N], + inflow[i] <= valley_chain[i].inflows[1] + ) + end + + ## ------------------------------------------------------------------ + ## Objective Function + if hasmarkovprice + @stageobjective( + subproblem, + flipobj * ( + prices[t, markov_state] * generation_quantity - + sum(valley_chain[i].spill_cost * spill[i] for i in 1:N) + ) + ) + else + @stageobjective( + subproblem, + flipobj * ( + prices[t, 1] * generation_quantity - + sum(valley_chain[i].spill_cost * spill[i] for i in 1:N) + ) + ) + end + end +end + +function test_hydro_valley_model() + + ## For repeatability + Random.seed!(11111) + + ## deterministic + deterministic_model = hydro_valley_model(; + hasmarkovprice = false, + hasstagewiseinflows = false, + ) + SDDP.train( + deterministic_model; + iteration_limit = 10, + cut_deletion_minimum = 1, + print_level = 0, + ) + @test SDDP.calculate_bound(deterministic_model) ≈ 835.0 atol = 1e-3 + + ## stagewise inflows + stagewise_model = hydro_valley_model(; hasmarkovprice = false) + SDDP.train(stagewise_model; iteration_limit = 20, print_level = 0) + @test SDDP.calculate_bound(stagewise_model) ≈ 838.33 atol = 1e-2 + + ## Markov prices + markov_model = hydro_valley_model(; hasstagewiseinflows = false) + SDDP.train(markov_model; iteration_limit = 10, print_level = 0) + @test SDDP.calculate_bound(markov_model) ≈ 851.8 atol = 1e-2 + + ## stagewise inflows and Markov prices + markov_stagewise_model = + hydro_valley_model(; hasstagewiseinflows = true, hasmarkovprice = true) + SDDP.train(markov_stagewise_model; iteration_limit = 10, print_level = 0) + @test SDDP.calculate_bound(markov_stagewise_model) ≈ 855.0 atol = 1.0 + + ## risk averse stagewise inflows and Markov prices + riskaverse_model = hydro_valley_model() + SDDP.train( + riskaverse_model; + risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.66), + iteration_limit = 10, + print_level = 0, + ) + @test SDDP.calculate_bound(riskaverse_model) ≈ 828.157 atol = 1.0 + + ## stagewise inflows and Markov prices + worst_case_model = hydro_valley_model(; sense = :Min) + SDDP.train( + worst_case_model; + risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.0), + iteration_limit = 10, + print_level = 0, + ) + @test SDDP.calculate_bound(worst_case_model) ≈ -780.867 atol = 1.0 + + ## stagewise inflows and Markov prices + cutselection_model = hydro_valley_model() + SDDP.train( + cutselection_model; + iteration_limit = 10, + print_level = 0, + cut_deletion_minimum = 2, + ) + @test SDDP.calculate_bound(cutselection_model) ≈ 855.0 atol = 1.0 + + ## Distributionally robust Optimization + dro_model = hydro_valley_model(; hasmarkovprice = false) + SDDP.train( + dro_model; + risk_measure = SDDP.ModifiedChiSquared(sqrt(2 / 3) - 1e-6), + iteration_limit = 10, + print_level = 0, + ) + @test SDDP.calculate_bound(dro_model) ≈ 835.0 atol = 1.0 + + dro_model = hydro_valley_model(; hasmarkovprice = false) + SDDP.train( + dro_model; + risk_measure = SDDP.ModifiedChiSquared(1 / 6), + iteration_limit = 20, + print_level = 0, + ) + @test SDDP.calculate_bound(dro_model) ≈ 836.695 atol = 1.0 + ## (Note) radius ≈ sqrt(2/3), will set all noise probabilities to zero except the worst case noise + ## (Why?): + ## The distance from the uniform distribution (the assumed "true" distribution) + ## to a corner of a unit simplex is sqrt(S-1)/sqrt(S) if we have S scenarios. The corner + ## of a unit simplex is just a unit vector, i.e.: [0 ... 0 1 0 ... 0]. With this probability + ## vector, only one noise has a non-zero probablity. + ## In the worst case rhsnoise (0 inflows) the profit is: + ## Reservoir1: 70 * $3 + 70 * $2 + 65 * $1 + + ## Reservoir2: 70 * $3 + 70 * $2 + 70 * $1 + ### = $835 +end + +test_hydro_valley_model() diff --git a/previews/PR826/examples/hydro_valley/index.html b/previews/PR826/examples/hydro_valley/index.html new file mode 100644 index 0000000000..60a92c1a78 --- /dev/null +++ b/previews/PR826/examples/hydro_valley/index.html @@ -0,0 +1,283 @@ + +Hydro valleys · SDDP.jl

Hydro valleys

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This problem is a version of the hydro-thermal scheduling problem. The goal is to operate two hydro-dams in a valley chain over time in the face of inflow and price uncertainty.

Turbine response curves are modelled by piecewise linear functions which map the flow rate into a power. These can be controlled by specifying the breakpoints in the piecewise linear function as the knots in the Turbine struct.

The model can be created using the hydro_valley_model function. It has a few keyword arguments to allow automated testing of the library. hasstagewiseinflows determines if the RHS noise constraint should be added. hasmarkovprice determines if the price uncertainty (modelled by a Markov chain) should be added.

In the third stage, the Markov chain has some unreachable states to test some code-paths in the library.

We can also set the sense to :Min or :Max (the objective and bound are flipped appropriately).

using SDDP, HiGHS, Test, Random
+
+struct Turbine
+    flowknots::Vector{Float64}
+    powerknots::Vector{Float64}
+end
+
+struct Reservoir
+    min::Float64
+    max::Float64
+    initial::Float64
+    turbine::Turbine
+    spill_cost::Float64
+    inflows::Vector{Float64}
+end
+
+function hydro_valley_model(;
+    hasstagewiseinflows::Bool = true,
+    hasmarkovprice::Bool = true,
+    sense::Symbol = :Max,
+)
+    valley_chain = [
+        Reservoir(
+            0,
+            200,
+            200,
+            Turbine([50, 60, 70], [55, 65, 70]),
+            1000,
+            [0, 20, 50],
+        ),
+        Reservoir(
+            0,
+            200,
+            200,
+            Turbine([50, 60, 70], [55, 65, 70]),
+            1000,
+            [0, 0, 20],
+        ),
+    ]
+
+    turbine(i) = valley_chain[i].turbine
+
+    # Prices[t, Markov state]
+    prices = [
+        1 2 0
+        2 1 0
+        3 4 0
+    ]
+
+    # Transition matrix
+    if hasmarkovprice
+        transition =
+            Array{Float64,2}[[1.0]', [0.6 0.4], [0.6 0.4 0.0; 0.3 0.7 0.0]]
+    else
+        transition = [ones(Float64, (1, 1)) for t in 1:3]
+    end
+
+    flipobj = (sense == :Max) ? 1.0 : -1.0
+    lower = (sense == :Max) ? -Inf : -1e6
+    upper = (sense == :Max) ? 1e6 : Inf
+
+    N = length(valley_chain)
+
+    # Initialise SDDP Model
+    return m = SDDP.MarkovianPolicyGraph(;
+        sense = sense,
+        lower_bound = lower,
+        upper_bound = upper,
+        transition_matrices = transition,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, node
+        t, markov_state = node
+
+        # ------------------------------------------------------------------
+        #   SDDP State Variables
+        # Level of upper reservoir
+        @variable(
+            subproblem,
+            valley_chain[r].min <= reservoir[r = 1:N] <= valley_chain[r].max,
+            SDDP.State,
+            initial_value = valley_chain[r].initial
+        )
+
+        # ------------------------------------------------------------------
+        #   Additional variables
+        @variables(
+            subproblem,
+            begin
+                outflow[r = 1:N] >= 0
+                spill[r = 1:N] >= 0
+                inflow[r = 1:N] >= 0
+                generation_quantity >= 0 # Total quantity of water
+                # Proportion of levels to dispatch on
+                0 <=
+                dispatch[r = 1:N, level = 1:length(turbine(r).flowknots)] <=
+                1
+                rainfall[i = 1:N]
+            end
+        )
+
+        # ------------------------------------------------------------------
+        # Constraints
+        @constraints(
+            subproblem,
+            begin
+                # flow from upper reservoir
+                reservoir[1].out ==
+                reservoir[1].in + inflow[1] - outflow[1] - spill[1]
+
+                # other flows
+                flow[i = 2:N],
+                reservoir[i].out ==
+                reservoir[i].in + inflow[i] - outflow[i] - spill[i] +
+                outflow[i-1] +
+                spill[i-1]
+
+                # Total quantity generated
+                generation_quantity == sum(
+                    turbine(r).powerknots[level] * dispatch[r, level] for
+                    r in 1:N for level in 1:length(turbine(r).powerknots)
+                )
+
+                # ------------------------------------------------------------------
+                # Flow out
+                turbineflow[r = 1:N],
+                outflow[r] == sum(
+                    turbine(r).flowknots[level] * dispatch[r, level] for
+                    level in 1:length(turbine(r).flowknots)
+                )
+
+                # Dispatch combination of levels
+                dispatched[r = 1:N],
+                sum(
+                    dispatch[r, level] for
+                    level in 1:length(turbine(r).flowknots)
+                ) <= 1
+            end
+        )
+
+        # rainfall noises
+        if hasstagewiseinflows && t > 1 # in future stages random inflows
+            @constraint(subproblem, inflow_noise[i = 1:N], inflow[i] <= rainfall[i])
+
+            SDDP.parameterize(
+                subproblem,
+                [
+                    (valley_chain[1].inflows[i], valley_chain[2].inflows[i]) for i in 1:length(transition)
+                ],
+            ) do ω
+                for i in 1:N
+                    JuMP.fix(rainfall[i], ω[i])
+                end
+            end
+        else # in the first stage deterministic inflow
+            @constraint(
+                subproblem,
+                initial_inflow_noise[i = 1:N],
+                inflow[i] <= valley_chain[i].inflows[1]
+            )
+        end
+
+        # ------------------------------------------------------------------
+        #   Objective Function
+        if hasmarkovprice
+            @stageobjective(
+                subproblem,
+                flipobj * (
+                    prices[t, markov_state] * generation_quantity -
+                    sum(valley_chain[i].spill_cost * spill[i] for i in 1:N)
+                )
+            )
+        else
+            @stageobjective(
+                subproblem,
+                flipobj * (
+                    prices[t, 1] * generation_quantity -
+                    sum(valley_chain[i].spill_cost * spill[i] for i in 1:N)
+                )
+            )
+        end
+    end
+end
+
+function test_hydro_valley_model()
+
+    # For repeatability
+    Random.seed!(11111)
+
+    # deterministic
+    deterministic_model = hydro_valley_model(;
+        hasmarkovprice = false,
+        hasstagewiseinflows = false,
+    )
+    SDDP.train(
+        deterministic_model;
+        iteration_limit = 10,
+        cut_deletion_minimum = 1,
+        print_level = 0,
+    )
+    @test SDDP.calculate_bound(deterministic_model) ≈ 835.0 atol = 1e-3
+
+    # stagewise inflows
+    stagewise_model = hydro_valley_model(; hasmarkovprice = false)
+    SDDP.train(stagewise_model; iteration_limit = 20, print_level = 0)
+    @test SDDP.calculate_bound(stagewise_model) ≈ 838.33 atol = 1e-2
+
+    # Markov prices
+    markov_model = hydro_valley_model(; hasstagewiseinflows = false)
+    SDDP.train(markov_model; iteration_limit = 10, print_level = 0)
+    @test SDDP.calculate_bound(markov_model) ≈ 851.8 atol = 1e-2
+
+    # stagewise inflows and Markov prices
+    markov_stagewise_model =
+        hydro_valley_model(; hasstagewiseinflows = true, hasmarkovprice = true)
+    SDDP.train(markov_stagewise_model; iteration_limit = 10, print_level = 0)
+    @test SDDP.calculate_bound(markov_stagewise_model) ≈ 855.0 atol = 1.0
+
+    # risk averse stagewise inflows and Markov prices
+    riskaverse_model = hydro_valley_model()
+    SDDP.train(
+        riskaverse_model;
+        risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.66),
+        iteration_limit = 10,
+        print_level = 0,
+    )
+    @test SDDP.calculate_bound(riskaverse_model) ≈ 828.157 atol = 1.0
+
+    # stagewise inflows and Markov prices
+    worst_case_model = hydro_valley_model(; sense = :Min)
+    SDDP.train(
+        worst_case_model;
+        risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.0),
+        iteration_limit = 10,
+        print_level = 0,
+    )
+    @test SDDP.calculate_bound(worst_case_model) ≈ -780.867 atol = 1.0
+
+    # stagewise inflows and Markov prices
+    cutselection_model = hydro_valley_model()
+    SDDP.train(
+        cutselection_model;
+        iteration_limit = 10,
+        print_level = 0,
+        cut_deletion_minimum = 2,
+    )
+    @test SDDP.calculate_bound(cutselection_model) ≈ 855.0 atol = 1.0
+
+    # Distributionally robust Optimization
+    dro_model = hydro_valley_model(; hasmarkovprice = false)
+    SDDP.train(
+        dro_model;
+        risk_measure = SDDP.ModifiedChiSquared(sqrt(2 / 3) - 1e-6),
+        iteration_limit = 10,
+        print_level = 0,
+    )
+    @test SDDP.calculate_bound(dro_model) ≈ 835.0 atol = 1.0
+
+    dro_model = hydro_valley_model(; hasmarkovprice = false)
+    SDDP.train(
+        dro_model;
+        risk_measure = SDDP.ModifiedChiSquared(1 / 6),
+        iteration_limit = 20,
+        print_level = 0,
+    )
+    @test SDDP.calculate_bound(dro_model) ≈ 836.695 atol = 1.0
+    # (Note) radius ≈ sqrt(2/3), will set all noise probabilities to zero except the worst case noise
+    # (Why?):
+    # The distance from the uniform distribution (the assumed "true" distribution)
+    # to a corner of a unit simplex is sqrt(S-1)/sqrt(S) if we have S scenarios. The corner
+    # of a unit simplex is just a unit vector, i.e.: [0 ... 0 1 0 ... 0]. With this probability
+    # vector, only one noise has a non-zero probablity.
+    # In the worst case rhsnoise (0 inflows) the profit is:
+    #  Reservoir1: 70 * $3 + 70 * $2 + 65 * $1 +
+    #  Reservoir2: 70 * $3 + 70 * $2 + 70 * $1
+    ###  = $835
+end
+
+test_hydro_valley_model()
Test Passed
diff --git a/previews/PR826/examples/infinite_horizon_hydro_thermal.ipynb b/previews/PR826/examples/infinite_horizon_hydro_thermal.ipynb new file mode 100644 index 0000000000..9cc0649063 --- /dev/null +++ b/previews/PR826/examples/infinite_horizon_hydro_thermal.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Infinite horizon hydro-thermal" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test, Statistics\n", + "\n", + "function infinite_hydro_thermal(; cut_type)\n", + " Ω = [\n", + " (inflow = 0.0, demand = 7.5),\n", + " (inflow = 5.0, demand = 5),\n", + " (inflow = 10.0, demand = 2.5),\n", + " ]\n", + " graph = SDDP.Graph(\n", + " :root_node,\n", + " [:week],\n", + " [(:root_node => :week, 1.0), (:week => :week, 0.9)],\n", + " )\n", + " model = SDDP.PolicyGraph(\n", + " graph;\n", + " lower_bound = 0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, node\n", + " @variable(\n", + " subproblem,\n", + " 5.0 <= reservoir <= 15.0,\n", + " SDDP.State,\n", + " initial_value = 10.0\n", + " )\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " spill >= 0\n", + " inflow\n", + " demand\n", + " end)\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " reservoir.out == reservoir.in - hydro_generation - spill + inflow\n", + " hydro_generation + thermal_generation == demand\n", + " end\n", + " )\n", + " @stageobjective(subproblem, 10 * spill + thermal_generation)\n", + " SDDP.parameterize(subproblem, Ω) do ω\n", + " JuMP.fix(inflow, ω.inflow)\n", + " return JuMP.fix(demand, ω.demand)\n", + " end\n", + " end\n", + " SDDP.train(\n", + " model;\n", + " cut_type = cut_type,\n", + " log_frequency = 100,\n", + " sampling_scheme = SDDP.InSampleMonteCarlo(; terminate_on_cycle = true),\n", + " parallel_scheme = SDDP.Serial(),\n", + " cycle_discretization_delta = 0.1,\n", + " )\n", + " @test SDDP.calculate_bound(model) ≈ 119.167 atol = 0.1\n", + "\n", + " results = SDDP.simulate(model, 500)\n", + " objectives =\n", + " [sum(s[:stage_objective] for s in simulation) for simulation in results]\n", + " sample_mean = round(Statistics.mean(objectives); digits = 2)\n", + " sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2)\n", + " println(\"Confidence_interval = $(sample_mean) ± $(sample_ci)\")\n", + " @test sample_mean - sample_ci <= 119.167 <= sample_mean + sample_ci\n", + " return\n", + "end\n", + "\n", + "infinite_hydro_thermal(; cut_type = SDDP.SINGLE_CUT)\n", + "infinite_hydro_thermal(; cut_type = SDDP.MULTI_CUT)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/infinite_horizon_hydro_thermal.jl b/previews/PR826/examples/infinite_horizon_hydro_thermal.jl new file mode 100644 index 0000000000..e11295bf2e --- /dev/null +++ b/previews/PR826/examples/infinite_horizon_hydro_thermal.jl @@ -0,0 +1,73 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Infinite horizon hydro-thermal + +using SDDP, HiGHS, Test, Statistics + +function infinite_hydro_thermal(; cut_type) + Ω = [ + (inflow = 0.0, demand = 7.5), + (inflow = 5.0, demand = 5), + (inflow = 10.0, demand = 2.5), + ] + graph = SDDP.Graph( + :root_node, + [:week], + [(:root_node => :week, 1.0), (:week => :week, 0.9)], + ) + model = SDDP.PolicyGraph( + graph; + lower_bound = 0, + optimizer = HiGHS.Optimizer, + ) do subproblem, node + @variable( + subproblem, + 5.0 <= reservoir <= 15.0, + SDDP.State, + initial_value = 10.0 + ) + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + spill >= 0 + inflow + demand + end) + @constraints( + subproblem, + begin + reservoir.out == reservoir.in - hydro_generation - spill + inflow + hydro_generation + thermal_generation == demand + end + ) + @stageobjective(subproblem, 10 * spill + thermal_generation) + SDDP.parameterize(subproblem, Ω) do ω + JuMP.fix(inflow, ω.inflow) + return JuMP.fix(demand, ω.demand) + end + end + SDDP.train( + model; + cut_type = cut_type, + log_frequency = 100, + sampling_scheme = SDDP.InSampleMonteCarlo(; terminate_on_cycle = true), + parallel_scheme = SDDP.Serial(), + cycle_discretization_delta = 0.1, + ) + @test SDDP.calculate_bound(model) ≈ 119.167 atol = 0.1 + + results = SDDP.simulate(model, 500) + objectives = + [sum(s[:stage_objective] for s in simulation) for simulation in results] + sample_mean = round(Statistics.mean(objectives); digits = 2) + sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2) + println("Confidence_interval = $(sample_mean) ± $(sample_ci)") + @test sample_mean - sample_ci <= 119.167 <= sample_mean + sample_ci + return +end + +infinite_hydro_thermal(; cut_type = SDDP.SINGLE_CUT) +infinite_hydro_thermal(; cut_type = SDDP.MULTI_CUT) diff --git a/previews/PR826/examples/infinite_horizon_hydro_thermal/index.html b/previews/PR826/examples/infinite_horizon_hydro_thermal/index.html new file mode 100644 index 0000000000..5c40ed36a8 --- /dev/null +++ b/previews/PR826/examples/infinite_horizon_hydro_thermal/index.html @@ -0,0 +1,149 @@ + +Infinite horizon hydro-thermal · SDDP.jl

Infinite horizon hydro-thermal

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP, HiGHS, Test, Statistics
+
+function infinite_hydro_thermal(; cut_type)
+    Ω = [
+        (inflow = 0.0, demand = 7.5),
+        (inflow = 5.0, demand = 5),
+        (inflow = 10.0, demand = 2.5),
+    ]
+    graph = SDDP.Graph(
+        :root_node,
+        [:week],
+        [(:root_node => :week, 1.0), (:week => :week, 0.9)],
+    )
+    model = SDDP.PolicyGraph(
+        graph;
+        lower_bound = 0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, node
+        @variable(
+            subproblem,
+            5.0 <= reservoir <= 15.0,
+            SDDP.State,
+            initial_value = 10.0
+        )
+        @variables(subproblem, begin
+            thermal_generation >= 0
+            hydro_generation >= 0
+            spill >= 0
+            inflow
+            demand
+        end)
+        @constraints(
+            subproblem,
+            begin
+                reservoir.out == reservoir.in - hydro_generation - spill + inflow
+                hydro_generation + thermal_generation == demand
+            end
+        )
+        @stageobjective(subproblem, 10 * spill + thermal_generation)
+        SDDP.parameterize(subproblem, Ω) do ω
+            JuMP.fix(inflow, ω.inflow)
+            return JuMP.fix(demand, ω.demand)
+        end
+    end
+    SDDP.train(
+        model;
+        cut_type = cut_type,
+        log_frequency = 100,
+        sampling_scheme = SDDP.InSampleMonteCarlo(; terminate_on_cycle = true),
+        parallel_scheme = SDDP.Serial(),
+        cycle_discretization_delta = 0.1,
+    )
+    @test SDDP.calculate_bound(model) ≈ 119.167 atol = 0.1
+
+    results = SDDP.simulate(model, 500)
+    objectives =
+        [sum(s[:stage_objective] for s in simulation) for simulation in results]
+    sample_mean = round(Statistics.mean(objectives); digits = 2)
+    sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2)
+    println("Confidence_interval = $(sample_mean) ± $(sample_ci)")
+    @test sample_mean - sample_ci <= 119.167 <= sample_mean + sample_ci
+    return
+end
+
+infinite_hydro_thermal(; cut_type = SDDP.SINGLE_CUT)
+infinite_hydro_thermal(; cut_type = SDDP.MULTI_CUT)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [8, 8]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+01]
+  bounds range     [5e+00, 2e+01]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+       100   2.500000e+01  1.188965e+02  7.671340e-01      1946   1
+       200   2.500000e+01  1.191634e+02  9.609759e-01      3920   1
+       300   0.000000e+00  1.191666e+02  1.161596e+00      5902   1
+       330   2.500000e+01  1.191667e+02  1.201401e+00      6224   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.201401e+00
+total solves   : 6224
+best bound     :  1.191667e+02
+simulation ci  :  2.158333e+01 ± 3.290252e+00
+numeric issues : 0
+-------------------------------------------------------------------
+
+Confidence_interval = 120.33 ± 12.06
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [8, 8]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+01]
+  bounds range     [5e+00, 2e+01]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+       100   0.000000e+00  1.191285e+02  2.730379e-01      2874   1
+       200   2.500000e+00  1.191666e+02  4.916561e-01      4855   1
+       282   7.500000e+00  1.191667e+02  6.215470e-01      5733   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 6.215470e-01
+total solves   : 5733
+best bound     :  1.191667e+02
+simulation ci  :  2.104610e+01 ± 3.492245e+00
+numeric issues : 0
+-------------------------------------------------------------------
+
+Confidence_interval = 116.06 ± 13.65
diff --git a/previews/PR826/examples/infinite_horizon_trivial.ipynb b/previews/PR826/examples/infinite_horizon_trivial.ipynb new file mode 100644 index 0000000000..78996c2634 --- /dev/null +++ b/previews/PR826/examples/infinite_horizon_trivial.ipynb @@ -0,0 +1,57 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Infinite horizon trivial" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function infinite_trivial()\n", + " graph = SDDP.Graph(\n", + " :root_node,\n", + " [:week],\n", + " [(:root_node => :week, 1.0), (:week => :week, 0.9)],\n", + " )\n", + " model = SDDP.PolicyGraph(\n", + " graph;\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, node\n", + " @variable(subproblem, state, SDDP.State, initial_value = 0)\n", + " @constraint(subproblem, state.in == state.out)\n", + " @stageobjective(subproblem, 2.0)\n", + " end\n", + " SDDP.train(model; log_frequency = 10)\n", + " @test SDDP.calculate_bound(model) ≈ 2.0 / (1 - 0.9) atol = 1e-3\n", + " return\n", + "end\n", + "\n", + "infinite_trivial()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/infinite_horizon_trivial.jl b/previews/PR826/examples/infinite_horizon_trivial.jl new file mode 100644 index 0000000000..1d3934a0cc --- /dev/null +++ b/previews/PR826/examples/infinite_horizon_trivial.jl @@ -0,0 +1,30 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Infinite horizon trivial + +using SDDP, HiGHS, Test + +function infinite_trivial() + graph = SDDP.Graph( + :root_node, + [:week], + [(:root_node => :week, 1.0), (:week => :week, 0.9)], + ) + model = SDDP.PolicyGraph( + graph; + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, node + @variable(subproblem, state, SDDP.State, initial_value = 0) + @constraint(subproblem, state.in == state.out) + @stageobjective(subproblem, 2.0) + end + SDDP.train(model; log_frequency = 10) + @test SDDP.calculate_bound(model) ≈ 2.0 / (1 - 0.9) atol = 1e-3 + return +end + +infinite_trivial() diff --git a/previews/PR826/examples/infinite_horizon_trivial/index.html b/previews/PR826/examples/infinite_horizon_trivial/index.html new file mode 100644 index 0000000000..002b95fda0 --- /dev/null +++ b/previews/PR826/examples/infinite_horizon_trivial/index.html @@ -0,0 +1,63 @@ + +Infinite horizon trivial · SDDP.jl

Infinite horizon trivial

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP, HiGHS, Test
+
+function infinite_trivial()
+    graph = SDDP.Graph(
+        :root_node,
+        [:week],
+        [(:root_node => :week, 1.0), (:week => :week, 0.9)],
+    )
+    model = SDDP.PolicyGraph(
+        graph;
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, node
+        @variable(subproblem, state, SDDP.State, initial_value = 0)
+        @constraint(subproblem, state.in == state.out)
+        @stageobjective(subproblem, 2.0)
+    end
+    SDDP.train(model; log_frequency = 10)
+    @test SDDP.calculate_bound(model) ≈ 2.0 / (1 - 0.9) atol = 1e-3
+    return
+end
+
+infinite_trivial()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [3, 3]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+00]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   4.000000e+00  1.997089e+01  6.259179e-02      1204   1
+        20   8.000000e+00  2.000000e+01  8.261085e-02      1420   1
+        30   1.600000e+01  2.000000e+01  1.445110e-01      2628   1
+        40   8.000000e+00  2.000000e+01  1.655278e-01      2834   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.655278e-01
+total solves   : 2834
+best bound     :  2.000000e+01
+simulation ci  :  1.625000e+01 ± 4.766381e+00
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/no_strong_duality.ipynb b/previews/PR826/examples/no_strong_duality.ipynb new file mode 100644 index 0000000000..efa7edd8ce --- /dev/null +++ b/previews/PR826/examples/no_strong_duality.ipynb @@ -0,0 +1,64 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# No strong duality" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example is interesting, because strong duality doesn't hold for the\n", + "extensive form (see if you can show why!), but we still converge." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function no_strong_duality()\n", + " model = SDDP.PolicyGraph(\n", + " SDDP.Graph(\n", + " :root,\n", + " [:node],\n", + " [(:root => :node, 1.0), (:node => :node, 0.5)],\n", + " );\n", + " optimizer = HiGHS.Optimizer,\n", + " lower_bound = 0.0,\n", + " ) do sp, t\n", + " @variable(sp, x, SDDP.State, initial_value = 1.0)\n", + " @stageobjective(sp, x.out)\n", + " @constraint(sp, x.in == x.out)\n", + " end\n", + " SDDP.train(model)\n", + " @test SDDP.calculate_bound(model) ≈ 2.0 atol = 1e-5\n", + " return\n", + "end\n", + "\n", + "no_strong_duality()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/no_strong_duality.jl b/previews/PR826/examples/no_strong_duality.jl new file mode 100644 index 0000000000..1918f64a2f --- /dev/null +++ b/previews/PR826/examples/no_strong_duality.jl @@ -0,0 +1,32 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # No strong duality + +# This example is interesting, because strong duality doesn't hold for the +# extensive form (see if you can show why!), but we still converge. + +using SDDP, HiGHS, Test + +function no_strong_duality() + model = SDDP.PolicyGraph( + SDDP.Graph( + :root, + [:node], + [(:root => :node, 1.0), (:node => :node, 0.5)], + ); + optimizer = HiGHS.Optimizer, + lower_bound = 0.0, + ) do sp, t + @variable(sp, x, SDDP.State, initial_value = 1.0) + @stageobjective(sp, x.out) + @constraint(sp, x.in == x.out) + end + SDDP.train(model) + @test SDDP.calculate_bound(model) ≈ 2.0 atol = 1e-5 + return +end + +no_strong_duality() diff --git a/previews/PR826/examples/no_strong_duality/index.html b/previews/PR826/examples/no_strong_duality/index.html new file mode 100644 index 0000000000..37f2809a1d --- /dev/null +++ b/previews/PR826/examples/no_strong_duality/index.html @@ -0,0 +1,60 @@ + +No strong duality · SDDP.jl

No strong duality

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example is interesting, because strong duality doesn't hold for the extensive form (see if you can show why!), but we still converge.

using SDDP, HiGHS, Test
+
+function no_strong_duality()
+    model = SDDP.PolicyGraph(
+        SDDP.Graph(
+            :root,
+            [:node],
+            [(:root => :node, 1.0), (:node => :node, 0.5)],
+        );
+        optimizer = HiGHS.Optimizer,
+        lower_bound = 0.0,
+    ) do sp, t
+        @variable(sp, x, SDDP.State, initial_value = 1.0)
+        @stageobjective(sp, x.out)
+        @constraint(sp, x.in == x.out)
+    end
+    SDDP.train(model)
+    @test SDDP.calculate_bound(model) ≈ 2.0 atol = 1e-5
+    return
+end
+
+no_strong_duality()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [3, 3]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+00]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.000000e+00  1.500000e+00  1.561880e-03         3   1
+        40   4.000000e+00  2.000000e+00  4.108381e-02       578   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 4.108381e-02
+total solves   : 578
+best bound     :  2.000000e+00
+simulation ci  :  1.950000e+00 ± 5.568095e-01
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/objective_state_newsvendor.ipynb b/previews/PR826/examples/objective_state_newsvendor.ipynb new file mode 100644 index 0000000000..8120cfbb80 --- /dev/null +++ b/previews/PR826/examples/objective_state_newsvendor.ipynb @@ -0,0 +1,114 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Newsvendor" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example is based on the classical newsvendor problem, but features an\n", + "AR(1) spot-price.\n", + "```\n", + " V(x[t-1], ω[t]) = max p[t] × u[t]\n", + " subject to x[t] = x[t-1] - u[t] + ω[t]\n", + " u[t] ∈ [0, 1]\n", + " x[t] ≥ 0\n", + " p[t] = p[t-1] + ϕ[t]\n", + "```\n", + "The initial conditions are\n", + "```\n", + "x[0] = 2.0\n", + "p[0] = 1.5\n", + "ω[t] ~ {0, 0.05, 0.10, ..., 0.45, 0.5} with uniform probability.\n", + "ϕ[t] ~ {-0.25, -0.125, 0.125, 0.25} with uniform probability.\n", + "```" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Statistics, Test\n", + "\n", + "function joint_distribution(; kwargs...)\n", + " names = tuple([first(kw) for kw in kwargs]...)\n", + " values = tuple([last(kw) for kw in kwargs]...)\n", + " output_type = NamedTuple{names,Tuple{eltype.(values)...}}\n", + " distribution = map(output_type, Base.product(values...))\n", + " return distribution[:]\n", + "end\n", + "\n", + "function newsvendor_example(; cut_type)\n", + " model = SDDP.PolicyGraph(\n", + " SDDP.LinearGraph(3);\n", + " sense = :Max,\n", + " upper_bound = 50.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, stage\n", + " @variables(subproblem, begin\n", + " x >= 0, (SDDP.State, initial_value = 2)\n", + " 0 <= u <= 1\n", + " w\n", + " end)\n", + " @constraint(subproblem, x.out == x.in - u + w)\n", + " SDDP.add_objective_state(\n", + " subproblem;\n", + " initial_value = 1.5,\n", + " lower_bound = 0.75,\n", + " upper_bound = 2.25,\n", + " lipschitz = 100.0,\n", + " ) do y, ω\n", + " return y + ω.price_noise\n", + " end\n", + " noise_terms = joint_distribution(;\n", + " demand = 0:0.05:0.5,\n", + " price_noise = [-0.25, -0.125, 0.125, 0.25],\n", + " )\n", + " SDDP.parameterize(subproblem, noise_terms) do ω\n", + " JuMP.fix(w, ω.demand)\n", + " price = SDDP.objective_state(subproblem)\n", + " @stageobjective(subproblem, price * u)\n", + " end\n", + " end\n", + " SDDP.train(\n", + " model;\n", + " log_frequency = 10,\n", + " time_limit = 20.0,\n", + " cut_type = cut_type,\n", + " )\n", + " @test SDDP.calculate_bound(model) ≈ 4.04 atol = 0.05\n", + " results = SDDP.simulate(model, 500)\n", + " objectives =\n", + " [sum(s[:stage_objective] for s in simulation) for simulation in results]\n", + " @test round(Statistics.mean(objectives); digits = 2) ≈ 4.04 atol = 0.1\n", + " return\n", + "end\n", + "\n", + "newsvendor_example(; cut_type = SDDP.SINGLE_CUT)\n", + "newsvendor_example(; cut_type = SDDP.MULTI_CUT)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/objective_state_newsvendor.jl b/previews/PR826/examples/objective_state_newsvendor.jl new file mode 100644 index 0000000000..e403ddfb88 --- /dev/null +++ b/previews/PR826/examples/objective_state_newsvendor.jl @@ -0,0 +1,81 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Newsvendor + +# This example is based on the classical newsvendor problem, but features an +# AR(1) spot-price. +# ``` +# V(x[t-1], ω[t]) = max p[t] × u[t] +# subject to x[t] = x[t-1] - u[t] + ω[t] +# u[t] ∈ [0, 1] +# x[t] ≥ 0 +# p[t] = p[t-1] + ϕ[t] +# ``` +# The initial conditions are +# ``` +# x[0] = 2.0 +# p[0] = 1.5 +# ω[t] ~ {0, 0.05, 0.10, ..., 0.45, 0.5} with uniform probability. +# ϕ[t] ~ {-0.25, -0.125, 0.125, 0.25} with uniform probability. +# ``` +using SDDP, HiGHS, Statistics, Test + +function joint_distribution(; kwargs...) + names = tuple([first(kw) for kw in kwargs]...) + values = tuple([last(kw) for kw in kwargs]...) + output_type = NamedTuple{names,Tuple{eltype.(values)...}} + distribution = map(output_type, Base.product(values...)) + return distribution[:] +end + +function newsvendor_example(; cut_type) + model = SDDP.PolicyGraph( + SDDP.LinearGraph(3); + sense = :Max, + upper_bound = 50.0, + optimizer = HiGHS.Optimizer, + ) do subproblem, stage + @variables(subproblem, begin + x >= 0, (SDDP.State, initial_value = 2) + 0 <= u <= 1 + w + end) + @constraint(subproblem, x.out == x.in - u + w) + SDDP.add_objective_state( + subproblem; + initial_value = 1.5, + lower_bound = 0.75, + upper_bound = 2.25, + lipschitz = 100.0, + ) do y, ω + return y + ω.price_noise + end + noise_terms = joint_distribution(; + demand = 0:0.05:0.5, + price_noise = [-0.25, -0.125, 0.125, 0.25], + ) + SDDP.parameterize(subproblem, noise_terms) do ω + JuMP.fix(w, ω.demand) + price = SDDP.objective_state(subproblem) + @stageobjective(subproblem, price * u) + end + end + SDDP.train( + model; + log_frequency = 10, + time_limit = 20.0, + cut_type = cut_type, + ) + @test SDDP.calculate_bound(model) ≈ 4.04 atol = 0.05 + results = SDDP.simulate(model, 500) + objectives = + [sum(s[:stage_objective] for s in simulation) for simulation in results] + @test round(Statistics.mean(objectives); digits = 2) ≈ 4.04 atol = 0.1 + return +end + +newsvendor_example(; cut_type = SDDP.SINGLE_CUT) +newsvendor_example(; cut_type = SDDP.MULTI_CUT) diff --git a/previews/PR826/examples/objective_state_newsvendor/index.html b/previews/PR826/examples/objective_state_newsvendor/index.html new file mode 100644 index 0000000000..963bc17356 --- /dev/null +++ b/previews/PR826/examples/objective_state_newsvendor/index.html @@ -0,0 +1,289 @@ + +Newsvendor · SDDP.jl

Newsvendor

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example is based on the classical newsvendor problem, but features an AR(1) spot-price.

   V(x[t-1], ω[t]) =         max p[t] × u[t]
+                      subject to x[t] = x[t-1] - u[t] + ω[t]
+                                 u[t] ∈ [0, 1]
+                                 x[t] ≥ 0
+                                 p[t] = p[t-1] + ϕ[t]

The initial conditions are

x[0] = 2.0
+p[0] = 1.5
+ω[t] ~ {0, 0.05, 0.10, ..., 0.45, 0.5} with uniform probability.
+ϕ[t] ~ {-0.25, -0.125, 0.125, 0.25} with uniform probability.
using SDDP, HiGHS, Statistics, Test
+
+function joint_distribution(; kwargs...)
+    names = tuple([first(kw) for kw in kwargs]...)
+    values = tuple([last(kw) for kw in kwargs]...)
+    output_type = NamedTuple{names,Tuple{eltype.(values)...}}
+    distribution = map(output_type, Base.product(values...))
+    return distribution[:]
+end
+
+function newsvendor_example(; cut_type)
+    model = SDDP.PolicyGraph(
+        SDDP.LinearGraph(3);
+        sense = :Max,
+        upper_bound = 50.0,
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, stage
+        @variables(subproblem, begin
+            x >= 0, (SDDP.State, initial_value = 2)
+            0 <= u <= 1
+            w
+        end)
+        @constraint(subproblem, x.out == x.in - u + w)
+        SDDP.add_objective_state(
+            subproblem;
+            initial_value = 1.5,
+            lower_bound = 0.75,
+            upper_bound = 2.25,
+            lipschitz = 100.0,
+        ) do y, ω
+            return y + ω.price_noise
+        end
+        noise_terms = joint_distribution(;
+            demand = 0:0.05:0.5,
+            price_noise = [-0.25, -0.125, 0.125, 0.25],
+        )
+        SDDP.parameterize(subproblem, noise_terms) do ω
+            JuMP.fix(w, ω.demand)
+            price = SDDP.objective_state(subproblem)
+            @stageobjective(subproblem, price * u)
+        end
+    end
+    SDDP.train(
+        model;
+        log_frequency = 10,
+        time_limit = 20.0,
+        cut_type = cut_type,
+    )
+    @test SDDP.calculate_bound(model) ≈ 4.04 atol = 0.05
+    results = SDDP.simulate(model, 500)
+    objectives =
+        [sum(s[:stage_objective] for s in simulation) for simulation in results]
+    @test round(Statistics.mean(objectives); digits = 2) ≈ 4.04 atol = 0.1
+    return
+end
+
+newsvendor_example(; cut_type = SDDP.SINGLE_CUT)
+newsvendor_example(; cut_type = SDDP.MULTI_CUT)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 8.51840e+04
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 3]
+  AffExpr in MOI.LessThan{Float64}        : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [3, 4]
+  VariableRef in MOI.LessThan{Float64}    : [3, 3]
+numerical stability report
+  matrix range     [8e-01, 2e+00]
+  objective range  [1e+00, 2e+00]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [5e+01, 5e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   5.250000e+00  4.888859e+00  1.595211e-01      1350   1
+        20   4.350000e+00  4.105855e+00  2.411652e-01      2700   1
+        30   5.000000e+00  4.100490e+00  3.299210e-01      4050   1
+        40   3.500000e+00  4.097376e+00  4.261100e-01      5400   1
+        50   5.250000e+00  4.095859e+00  5.261869e-01      6750   1
+        60   3.643750e+00  4.093342e+00  6.732740e-01      8100   1
+        70   2.643750e+00  4.091818e+00  7.792270e-01      9450   1
+        80   5.087500e+00  4.091591e+00  8.864172e-01     10800   1
+        90   5.062500e+00  4.091309e+00  9.957671e-01     12150   1
+       100   4.843750e+00  4.087004e+00  1.113616e+00     13500   1
+       110   3.437500e+00  4.086094e+00  1.231364e+00     14850   1
+       120   3.375000e+00  4.085926e+00  1.350189e+00     16200   1
+       130   5.025000e+00  4.085866e+00  1.472597e+00     17550   1
+       140   5.000000e+00  4.085734e+00  1.594332e+00     18900   1
+       150   3.500000e+00  4.085655e+00  1.718657e+00     20250   1
+       160   4.281250e+00  4.085454e+00  1.838317e+00     21600   1
+       170   4.562500e+00  4.085425e+00  1.961061e+00     22950   1
+       180   5.768750e+00  4.085425e+00  2.084075e+00     24300   1
+       190   3.468750e+00  4.085359e+00  2.214355e+00     25650   1
+       200   4.131250e+00  4.085225e+00  2.359565e+00     27000   1
+       210   4.512500e+00  4.085157e+00  2.485728e+00     28350   1
+       220   4.900000e+00  4.085153e+00  2.613694e+00     29700   1
+       230   4.025000e+00  4.085134e+00  2.746500e+00     31050   1
+       240   4.468750e+00  4.085116e+00  2.880736e+00     32400   1
+       250   4.062500e+00  4.085075e+00  3.013214e+00     33750   1
+       260   4.875000e+00  4.085037e+00  3.148938e+00     35100   1
+       270   3.850000e+00  4.085011e+00  3.283243e+00     36450   1
+       280   4.912500e+00  4.084992e+00  3.455010e+00     37800   1
+       290   2.987500e+00  4.084986e+00  3.595104e+00     39150   1
+       300   3.825000e+00  4.084957e+00  3.737324e+00     40500   1
+       310   3.250000e+00  4.084911e+00  3.879043e+00     41850   1
+       320   3.600000e+00  4.084896e+00  4.019821e+00     43200   1
+       330   3.925000e+00  4.084896e+00  4.151342e+00     44550   1
+       340   4.500000e+00  4.084893e+00  4.291215e+00     45900   1
+       350   5.000000e+00  4.084891e+00  4.430990e+00     47250   1
+       360   3.075000e+00  4.084866e+00  4.568457e+00     48600   1
+       370   3.500000e+00  4.084861e+00  4.712302e+00     49950   1
+       380   3.356250e+00  4.084857e+00  4.858324e+00     51300   1
+       390   5.500000e+00  4.084846e+00  5.008902e+00     52650   1
+       400   4.475000e+00  4.084846e+00  5.152340e+00     54000   1
+       410   3.750000e+00  4.084843e+00  5.297444e+00     55350   1
+       420   3.687500e+00  4.084843e+00  5.445094e+00     56700   1
+       430   4.337500e+00  4.084825e+00  5.595158e+00     58050   1
+       440   5.750000e+00  4.084825e+00  5.731260e+00     59400   1
+       450   4.925000e+00  4.084792e+00  5.887173e+00     60750   1
+       460   3.600000e+00  4.084792e+00  6.039129e+00     62100   1
+       470   4.387500e+00  4.084792e+00  6.183893e+00     63450   1
+       480   4.000000e+00  4.084792e+00  6.337753e+00     64800   1
+       490   2.975000e+00  4.084788e+00  6.485263e+00     66150   1
+       500   3.125000e+00  4.084788e+00  6.632709e+00     67500   1
+       510   4.250000e+00  4.084788e+00  6.818290e+00     68850   1
+       520   4.512500e+00  4.084786e+00  6.963902e+00     70200   1
+       530   3.875000e+00  4.084786e+00  7.120886e+00     71550   1
+       540   4.387500e+00  4.084781e+00  7.278300e+00     72900   1
+       550   5.281250e+00  4.084780e+00  7.436060e+00     74250   1
+       560   4.650000e+00  4.084780e+00  7.582303e+00     75600   1
+       570   3.062500e+00  4.084780e+00  7.729968e+00     76950   1
+       580   3.187500e+00  4.084780e+00  7.878736e+00     78300   1
+       590   3.812500e+00  4.084780e+00  8.021174e+00     79650   1
+       600   3.637500e+00  4.084774e+00  8.174244e+00     81000   1
+       610   3.950000e+00  4.084765e+00  8.326051e+00     82350   1
+       620   4.625000e+00  4.084760e+00  8.477735e+00     83700   1
+       630   4.218750e+00  4.084760e+00  8.632540e+00     85050   1
+       640   3.025000e+00  4.084755e+00  8.786802e+00     86400   1
+       650   2.993750e+00  4.084751e+00  8.934419e+00     87750   1
+       660   3.262500e+00  4.084746e+00  9.084889e+00     89100   1
+       670   3.625000e+00  4.084746e+00  9.238606e+00     90450   1
+       680   2.981250e+00  4.084746e+00  9.394347e+00     91800   1
+       690   4.187500e+00  4.084746e+00  9.545753e+00     93150   1
+       700   4.500000e+00  4.084746e+00  9.693811e+00     94500   1
+       710   3.225000e+00  4.084746e+00  9.871742e+00     95850   1
+       720   4.375000e+00  4.084746e+00  1.002700e+01     97200   1
+       730   2.650000e+00  4.084746e+00  1.018808e+01     98550   1
+       740   3.250000e+00  4.084746e+00  1.034249e+01     99900   1
+       750   4.725000e+00  4.084746e+00  1.051434e+01    101250   1
+       760   3.375000e+00  4.084746e+00  1.068121e+01    102600   1
+       770   5.375000e+00  4.084746e+00  1.084486e+01    103950   1
+       780   4.068750e+00  4.084746e+00  1.101535e+01    105300   1
+       790   4.412500e+00  4.084746e+00  1.118774e+01    106650   1
+       800   4.350000e+00  4.084746e+00  1.135563e+01    108000   1
+       810   5.887500e+00  4.084746e+00  1.152466e+01    109350   1
+       820   4.912500e+00  4.084746e+00  1.168614e+01    110700   1
+       830   4.387500e+00  4.084746e+00  1.184156e+01    112050   1
+       840   3.675000e+00  4.084746e+00  1.201129e+01    113400   1
+       850   5.375000e+00  4.084746e+00  1.217270e+01    114750   1
+       860   3.562500e+00  4.084746e+00  1.233892e+01    116100   1
+       870   3.075000e+00  4.084746e+00  1.250823e+01    117450   1
+       880   3.625000e+00  4.084746e+00  1.267001e+01    118800   1
+       890   2.937500e+00  4.084746e+00  1.285520e+01    120150   1
+       900   4.450000e+00  4.084746e+00  1.303096e+01    121500   1
+       910   4.200000e+00  4.084746e+00  1.319607e+01    122850   1
+       920   3.687500e+00  4.084746e+00  1.336546e+01    124200   1
+       930   4.725000e+00  4.084746e+00  1.353421e+01    125550   1
+       940   4.018750e+00  4.084746e+00  1.369507e+01    126900   1
+       950   4.675000e+00  4.084746e+00  1.385280e+01    128250   1
+       960   3.375000e+00  4.084746e+00  1.401454e+01    129600   1
+       970   3.812500e+00  4.084746e+00  1.417292e+01    130950   1
+       980   3.112500e+00  4.084746e+00  1.433634e+01    132300   1
+       990   3.600000e+00  4.084746e+00  1.450062e+01    133650   1
+      1000   5.500000e+00  4.084746e+00  1.466909e+01    135000   1
+      1010   3.187500e+00  4.084746e+00  1.483119e+01    136350   1
+      1020   4.900000e+00  4.084746e+00  1.499588e+01    137700   1
+      1030   3.637500e+00  4.084746e+00  1.517314e+01    139050   1
+      1040   3.975000e+00  4.084746e+00  1.536138e+01    140400   1
+      1050   4.750000e+00  4.084746e+00  1.553015e+01    141750   1
+      1060   4.437500e+00  4.084746e+00  1.571412e+01    143100   1
+      1070   5.000000e+00  4.084746e+00  1.588702e+01    144450   1
+      1080   4.143750e+00  4.084746e+00  1.606369e+01    145800   1
+      1090   5.625000e+00  4.084746e+00  1.623112e+01    147150   1
+      1100   3.475000e+00  4.084746e+00  1.640484e+01    148500   1
+      1110   4.156250e+00  4.084746e+00  1.658396e+01    149850   1
+      1120   4.450000e+00  4.084746e+00  1.675759e+01    151200   1
+      1130   3.312500e+00  4.084741e+00  1.693208e+01    152550   1
+      1140   5.375000e+00  4.084741e+00  1.709987e+01    153900   1
+      1150   4.800000e+00  4.084737e+00  1.728024e+01    155250   1
+      1160   3.300000e+00  4.084737e+00  1.745245e+01    156600   1
+      1170   4.356250e+00  4.084737e+00  1.764803e+01    157950   1
+      1180   3.900000e+00  4.084737e+00  1.782710e+01    159300   1
+      1190   4.450000e+00  4.084737e+00  1.800597e+01    160650   1
+      1200   5.156250e+00  4.084737e+00  1.818581e+01    162000   1
+      1210   4.500000e+00  4.084737e+00  1.835251e+01    163350   1
+      1220   4.875000e+00  4.084737e+00  1.853644e+01    164700   1
+      1230   4.000000e+00  4.084737e+00  1.870808e+01    166050   1
+      1240   4.062500e+00  4.084737e+00  1.888267e+01    167400   1
+      1250   5.450000e+00  4.084737e+00  1.906262e+01    168750   1
+      1260   4.500000e+00  4.084737e+00  1.924321e+01    170100   1
+      1270   4.125000e+00  4.084737e+00  1.941998e+01    171450   1
+      1280   3.750000e+00  4.084737e+00  1.960133e+01    172800   1
+      1290   4.475000e+00  4.084737e+00  1.979600e+01    174150   1
+      1300   4.987500e+00  4.084737e+00  1.996438e+01    175500   1
+      1303   3.750000e+00  4.084737e+00  2.001024e+01    175905   1
+-------------------------------------------------------------------
+status         : time_limit
+total time (s) : 2.001024e+01
+total solves   : 175905
+best bound     :  4.084737e+00
+simulation ci  :  4.068022e+00 ± 3.982545e-02
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 8.51840e+04
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [6, 6]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 3]
+  AffExpr in MOI.LessThan{Float64}        : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [3, 4]
+  VariableRef in MOI.LessThan{Float64}    : [3, 3]
+numerical stability report
+  matrix range     [8e-01, 2e+00]
+  objective range  [1e+00, 2e+00]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [5e+01, 5e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   5.168750e+00  5.149174e+00  1.744030e-01      1350   1
+        20   4.725000e+00  4.747147e+00  5.363460e-01      2700   1
+        30   4.275000e+00  4.061313e+00  9.793379e-01      4050   1
+        40   5.156250e+00  4.044283e+00  1.504181e+00      5400   1
+        50   5.200000e+00  4.042803e+00  2.110943e+00      6750   1
+        60   5.437500e+00  4.041070e+00  2.819180e+00      8100   1
+        70   5.218750e+00  4.041028e+00  3.624163e+00      9450   1
+        80   3.631250e+00  4.040924e+00  4.613025e+00     10800   1
+        90   2.650000e+00  4.040697e+00  5.738630e+00     12150   1
+       100   4.043750e+00  4.039630e+00  6.921485e+00     13500   1
+       110   5.556250e+00  4.037631e+00  8.200977e+00     14850   1
+       120   2.925000e+00  4.037627e+00  9.584426e+00     16200   1
+       130   4.000000e+00  4.037635e+00  1.117631e+01     17550   1
+       140   3.843750e+00  4.037579e+00  1.277923e+01     18900   1
+       150   3.968750e+00  4.037579e+00  1.469843e+01     20250   1
+       160   3.975000e+00  4.037559e+00  1.665195e+01     21600   1
+       170   2.981250e+00  4.037556e+00  1.860083e+01     22950   1
+       177   3.225000e+00  4.037556e+00  2.005081e+01     23895   1
+-------------------------------------------------------------------
+status         : time_limit
+total time (s) : 2.005081e+01
+total solves   : 23895
+best bound     :  4.037556e+00
+simulation ci  :  4.019138e+00 ± 1.161944e-01
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/sldp_example_one.ipynb b/previews/PR826/examples/sldp_example_one.ipynb new file mode 100644 index 0000000000..83a0e2a4ec --- /dev/null +++ b/previews/PR826/examples/sldp_example_one.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# SLDP: example 1" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example is derived from Section 4.2 of the paper:\n", + "Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz\n", + "Dynamic Programming. Optimization Online. [PDF](http://www.optimization-online.org/DB_FILE/2019/05/7193.pdf)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function sldp_example_one()\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 8,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " @variable(sp, x, SDDP.State, initial_value = 2.0)\n", + " @variables(sp, begin\n", + " x⁺ >= 0\n", + " x⁻ >= 0\n", + " 0 <= u <= 1, Bin\n", + " ω\n", + " end)\n", + " @stageobjective(sp, 0.9^(t - 1) * (x⁺ + x⁻))\n", + " @constraints(sp, begin\n", + " x.out == x.in + 2 * u - 1 + ω\n", + " x⁺ >= x.out\n", + " x⁻ >= -x.out\n", + " end)\n", + " points = [\n", + " -0.3089653673606697,\n", + " -0.2718277412744214,\n", + " -0.09611178608243474,\n", + " 0.24645863921577763,\n", + " 0.5204224537256875,\n", + " ]\n", + " return SDDP.parameterize(φ -> JuMP.fix(ω, φ), sp, [points; -points])\n", + " end\n", + " SDDP.train(model; log_frequency = 10)\n", + " @test SDDP.calculate_bound(model) <= 1.1675\n", + " return\n", + "end\n", + "\n", + "sldp_example_one()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/sldp_example_one.jl b/previews/PR826/examples/sldp_example_one.jl new file mode 100644 index 0000000000..82ace8aabe --- /dev/null +++ b/previews/PR826/examples/sldp_example_one.jl @@ -0,0 +1,47 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # SLDP: example 1 + +# This example is derived from Section 4.2 of the paper: +# Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz +# Dynamic Programming. Optimization Online. [PDF](http://www.optimization-online.org/DB_FILE/2019/05/7193.pdf) + +using SDDP, HiGHS, Test + +function sldp_example_one() + model = SDDP.LinearPolicyGraph(; + stages = 8, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variable(sp, x, SDDP.State, initial_value = 2.0) + @variables(sp, begin + x⁺ >= 0 + x⁻ >= 0 + 0 <= u <= 1, Bin + ω + end) + @stageobjective(sp, 0.9^(t - 1) * (x⁺ + x⁻)) + @constraints(sp, begin + x.out == x.in + 2 * u - 1 + ω + x⁺ >= x.out + x⁻ >= -x.out + end) + points = [ + -0.3089653673606697, + -0.2718277412744214, + -0.09611178608243474, + 0.24645863921577763, + 0.5204224537256875, + ] + return SDDP.parameterize(φ -> JuMP.fix(ω, φ), sp, [points; -points]) + end + SDDP.train(model; log_frequency = 10) + @test SDDP.calculate_bound(model) <= 1.1675 + return +end + +sldp_example_one() diff --git a/previews/PR826/examples/sldp_example_one/index.html b/previews/PR826/examples/sldp_example_one/index.html new file mode 100644 index 0000000000..3550836922 --- /dev/null +++ b/previews/PR826/examples/sldp_example_one/index.html @@ -0,0 +1,84 @@ + +SLDP: example 1 · SDDP.jl

SLDP: example 1

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example is derived from Section 4.2 of the paper: Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz Dynamic Programming. Optimization Online. PDF

using SDDP, HiGHS, Test
+
+function sldp_example_one()
+    model = SDDP.LinearPolicyGraph(;
+        stages = 8,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        @variable(sp, x, SDDP.State, initial_value = 2.0)
+        @variables(sp, begin
+            x⁺ >= 0
+            x⁻ >= 0
+            0 <= u <= 1, Bin
+            ω
+        end)
+        @stageobjective(sp, 0.9^(t - 1) * (x⁺ + x⁻))
+        @constraints(sp, begin
+            x.out == x.in + 2 * u - 1 + ω
+            x⁺ >= x.out
+            x⁻ >= -x.out
+        end)
+        points = [
+            -0.3089653673606697,
+            -0.2718277412744214,
+            -0.09611178608243474,
+            0.24645863921577763,
+            0.5204224537256875,
+        ]
+        return SDDP.parameterize(φ -> JuMP.fix(ω, φ), sp, [points; -points])
+    end
+    SDDP.train(model; log_frequency = 10)
+    @test SDDP.calculate_bound(model) <= 1.1675
+    return
+end
+
+sldp_example_one()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 8
+  state variables : 1
+  scenarios       : 1.00000e+08
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  AffExpr in MOI.GreaterThan{Float64}     : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 4]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+  VariableRef in MOI.ZeroOne              : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 2e+00]
+  objective range  [5e-01, 1e+00]
+  bounds range     [1e+00, 1e+00]
+  rhs range        [1e+00, 1e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10   3.399508e+00  1.165253e+00  3.668449e-01      1680   1
+        20   4.155346e+00  1.165253e+00  4.579740e-01      2560   1
+        30   2.166143e+00  1.165481e+00  8.287990e-01      4240   1
+        40   3.506633e+00  1.165481e+00  9.219449e-01      5120   1
+        50   3.447815e+00  1.165481e+00  1.299035e+00      6800   1
+        60   3.070275e+00  1.167043e+00  1.396288e+00      7680   1
+        70   3.662595e+00  1.167043e+00  1.780279e+00      9360   1
+        80   2.777826e+00  1.167299e+00  1.882254e+00     10240   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.882254e+00
+total solves   : 10240
+best bound     :  1.167299e+00
+simulation ci  :  3.302376e+00 ± 1.028287e-01
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/sldp_example_two.ipynb b/previews/PR826/examples/sldp_example_two.ipynb new file mode 100644 index 0000000000..32291e70fd --- /dev/null +++ b/previews/PR826/examples/sldp_example_two.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# SLDP: example 2" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example is derived from Section 4.3 of the paper:\n", + "Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz\n", + "Dynamic Programming. Optimization Online. [PDF](http://www.optimization-online.org/DB_FILE/2019/05/7193.pdf)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS\n", + "import Test\n", + "\n", + "function sldp_example_two(; first_stage_integer::Bool = true, N = 2)\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " lower_bound = -100.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " @variable(sp, 0 <= x[1:2] <= 5, SDDP.State, initial_value = 0.0)\n", + " if t == 1\n", + " if first_stage_integer\n", + " @variable(sp, 0 <= u[1:2] <= 5, Int)\n", + " @constraint(sp, [i = 1:2], u[i] == x[i].out)\n", + " end\n", + " @stageobjective(sp, -1.5 * x[1].out - 4 * x[2].out)\n", + " else\n", + " @variable(sp, 0 <= y[1:4] <= 1, Bin)\n", + " @variable(sp, ω[1:2])\n", + " @stageobjective(sp, -16 * y[1] - 19 * y[2] - 23 * y[3] - 28 * y[4])\n", + " @constraint(\n", + " sp,\n", + " 2 * y[1] + 3 * y[2] + 4 * y[3] + 5 * y[4] <= ω[1] - x[1].in\n", + " )\n", + " @constraint(\n", + " sp,\n", + " 6 * y[1] + 1 * y[2] + 3 * y[3] + 2 * y[4] <= ω[2] - x[2].in\n", + " )\n", + " steps = range(5; stop = 15, length = N)\n", + " SDDP.parameterize(sp, [[i, j] for i in steps for j in steps]) do φ\n", + " return JuMP.fix.(ω, φ)\n", + " end\n", + " end\n", + " end\n", + " if get(ARGS, 1, \"\") == \"--write\"\n", + " # Run `$ julia sldp_example_two.jl --write` to update the benchmark\n", + " # model directory\n", + " model_dir = joinpath(@__DIR__, \"..\", \"..\", \"..\", \"benchmarks\", \"models\")\n", + " SDDP.write_to_file(\n", + " model,\n", + " joinpath(model_dir, \"sldp_example_two_$(N).sof.json.gz\");\n", + " test_scenarios = 30,\n", + " )\n", + " return\n", + " end\n", + " SDDP.train(model; log_frequency = 10)\n", + " bound = SDDP.calculate_bound(model)\n", + "\n", + " if N == 2\n", + " Test.@test bound <= -57.0\n", + " elseif N == 3\n", + " Test.@test bound <= -59.33\n", + " elseif N == 6\n", + " Test.@test bound <= -61.22\n", + " end\n", + " return\n", + "end\n", + "\n", + "sldp_example_two(; N = 2)\n", + "sldp_example_two(; N = 3)\n", + "sldp_example_two(; N = 6)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/sldp_example_two.jl b/previews/PR826/examples/sldp_example_two.jl new file mode 100644 index 0000000000..9adb7d8f5c --- /dev/null +++ b/previews/PR826/examples/sldp_example_two.jl @@ -0,0 +1,73 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # SLDP: example 2 + +# This example is derived from Section 4.3 of the paper: +# Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz +# Dynamic Programming. Optimization Online. [PDF](http://www.optimization-online.org/DB_FILE/2019/05/7193.pdf) + +using SDDP +import HiGHS +import Test + +function sldp_example_two(; first_stage_integer::Bool = true, N = 2) + model = SDDP.LinearPolicyGraph(; + stages = 2, + lower_bound = -100.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variable(sp, 0 <= x[1:2] <= 5, SDDP.State, initial_value = 0.0) + if t == 1 + if first_stage_integer + @variable(sp, 0 <= u[1:2] <= 5, Int) + @constraint(sp, [i = 1:2], u[i] == x[i].out) + end + @stageobjective(sp, -1.5 * x[1].out - 4 * x[2].out) + else + @variable(sp, 0 <= y[1:4] <= 1, Bin) + @variable(sp, ω[1:2]) + @stageobjective(sp, -16 * y[1] - 19 * y[2] - 23 * y[3] - 28 * y[4]) + @constraint( + sp, + 2 * y[1] + 3 * y[2] + 4 * y[3] + 5 * y[4] <= ω[1] - x[1].in + ) + @constraint( + sp, + 6 * y[1] + 1 * y[2] + 3 * y[3] + 2 * y[4] <= ω[2] - x[2].in + ) + steps = range(5; stop = 15, length = N) + SDDP.parameterize(sp, [[i, j] for i in steps for j in steps]) do φ + return JuMP.fix.(ω, φ) + end + end + end + if get(ARGS, 1, "") == "--write" + ## Run `$ julia sldp_example_two.jl --write` to update the benchmark + ## model directory + model_dir = joinpath(@__DIR__, "..", "..", "..", "benchmarks", "models") + SDDP.write_to_file( + model, + joinpath(model_dir, "sldp_example_two_$(N).sof.json.gz"); + test_scenarios = 30, + ) + return + end + SDDP.train(model; log_frequency = 10) + bound = SDDP.calculate_bound(model) + + if N == 2 + Test.@test bound <= -57.0 + elseif N == 3 + Test.@test bound <= -59.33 + elseif N == 6 + Test.@test bound <= -61.22 + end + return +end + +sldp_example_two(; N = 2) +sldp_example_two(; N = 3) +sldp_example_two(; N = 6) diff --git a/previews/PR826/examples/sldp_example_two/index.html b/previews/PR826/examples/sldp_example_two/index.html new file mode 100644 index 0000000000..17aec39127 --- /dev/null +++ b/previews/PR826/examples/sldp_example_two/index.html @@ -0,0 +1,191 @@ + +SLDP: example 2 · SDDP.jl

SLDP: example 2

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example is derived from Section 4.3 of the paper: Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz Dynamic Programming. Optimization Online. PDF

using SDDP
+import HiGHS
+import Test
+
+function sldp_example_two(; first_stage_integer::Bool = true, N = 2)
+    model = SDDP.LinearPolicyGraph(;
+        stages = 2,
+        lower_bound = -100.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        @variable(sp, 0 <= x[1:2] <= 5, SDDP.State, initial_value = 0.0)
+        if t == 1
+            if first_stage_integer
+                @variable(sp, 0 <= u[1:2] <= 5, Int)
+                @constraint(sp, [i = 1:2], u[i] == x[i].out)
+            end
+            @stageobjective(sp, -1.5 * x[1].out - 4 * x[2].out)
+        else
+            @variable(sp, 0 <= y[1:4] <= 1, Bin)
+            @variable(sp, ω[1:2])
+            @stageobjective(sp, -16 * y[1] - 19 * y[2] - 23 * y[3] - 28 * y[4])
+            @constraint(
+                sp,
+                2 * y[1] + 3 * y[2] + 4 * y[3] + 5 * y[4] <= ω[1] - x[1].in
+            )
+            @constraint(
+                sp,
+                6 * y[1] + 1 * y[2] + 3 * y[3] + 2 * y[4] <= ω[2] - x[2].in
+            )
+            steps = range(5; stop = 15, length = N)
+            SDDP.parameterize(sp, [[i, j] for i in steps for j in steps]) do φ
+                return JuMP.fix.(ω, φ)
+            end
+        end
+    end
+    if get(ARGS, 1, "") == "--write"
+        # Run `$ julia sldp_example_two.jl --write` to update the benchmark
+        # model directory
+        model_dir = joinpath(@__DIR__, "..", "..", "..", "benchmarks", "models")
+        SDDP.write_to_file(
+            model,
+            joinpath(model_dir, "sldp_example_two_$(N).sof.json.gz");
+            test_scenarios = 30,
+        )
+        return
+    end
+    SDDP.train(model; log_frequency = 10)
+    bound = SDDP.calculate_bound(model)
+
+    if N == 2
+        Test.@test bound <= -57.0
+    elseif N == 3
+        Test.@test bound <= -59.33
+    elseif N == 6
+        Test.@test bound <= -61.22
+    end
+    return
+end
+
+sldp_example_two(; N = 2)
+sldp_example_two(; N = 3)
+sldp_example_two(; N = 6)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 2
+  scenarios       : 4.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 11]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  AffExpr in MOI.LessThan{Float64}        : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 7]
+  VariableRef in MOI.Integer              : [2, 2]
+  VariableRef in MOI.LessThan{Float64}    : [4, 7]
+  VariableRef in MOI.ZeroOne              : [4, 4]
+numerical stability report
+  matrix range     [1e+00, 6e+00]
+  objective range  [1e+00, 3e+01]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10  -4.000000e+01 -5.809615e+01  3.117514e-02        78   1
+        20  -4.000000e+01 -5.809615e+01  6.307220e-02       148   1
+        30  -4.700000e+01 -5.809615e+01  1.020651e-01       226   1
+        40  -4.700000e+01 -5.809615e+01  1.357501e-01       296   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.357501e-01
+total solves   : 296
+best bound     : -5.809615e+01
+simulation ci  : -5.496250e+01 ± 7.974877e+00
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 2
+  scenarios       : 9.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 11]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  AffExpr in MOI.LessThan{Float64}        : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 7]
+  VariableRef in MOI.Integer              : [2, 2]
+  VariableRef in MOI.LessThan{Float64}    : [4, 7]
+  VariableRef in MOI.ZeroOne              : [4, 4]
+numerical stability report
+  matrix range     [1e+00, 6e+00]
+  objective range  [1e+00, 3e+01]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10  -4.700000e+01 -6.196125e+01  3.870416e-02       138   1
+        20  -4.700000e+01 -6.196125e+01  7.434416e-02       258   1
+        30  -8.200000e+01 -6.196125e+01  1.228361e-01       396   1
+        40  -4.700000e+01 -6.196125e+01  1.602201e-01       516   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.602201e-01
+total solves   : 516
+best bound     : -6.196125e+01
+simulation ci  : -5.958750e+01 ± 6.241506e+00
+numeric issues : 0
+-------------------------------------------------------------------
+
+-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 2
+  scenarios       : 3.60000e+01
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 11]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  AffExpr in MOI.LessThan{Float64}        : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 7]
+  VariableRef in MOI.Integer              : [2, 2]
+  VariableRef in MOI.LessThan{Float64}    : [4, 7]
+  VariableRef in MOI.ZeroOne              : [4, 4]
+numerical stability report
+  matrix range     [1e+00, 6e+00]
+  objective range  [1e+00, 3e+01]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+        10  -8.200000e+01 -6.546793e+01  7.412124e-02       462   1
+        20  -4.000000e+01 -6.546793e+01  1.323652e-01       852   1
+        30  -8.200000e+01 -6.546793e+01  2.462361e-01      1314   1
+        40  -5.900000e+01 -6.546793e+01  3.054502e-01      1704   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 3.054502e-01
+total solves   : 1704
+best bound     : -6.546793e+01
+simulation ci  : -6.143750e+01 ± 4.873947e+00
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/stochastic_all_blacks.ipynb b/previews/PR826/examples/stochastic_all_blacks.ipynb new file mode 100644 index 0000000000..10e053b36f --- /dev/null +++ b/previews/PR826/examples/stochastic_all_blacks.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Stochastic All Blacks" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS, Test\n", + "\n", + "function stochastic_all_blacks()\n", + " # Number of time periods\n", + " T = 3\n", + " # Number of seats\n", + " N = 2\n", + " # R_ij = price of seat i at time j\n", + " R = [3 3 6; 3 3 6]\n", + " # Number of noises\n", + " s = 3\n", + " offers = [\n", + " [[1, 1], [0, 0], [1, 1]],\n", + " [[1, 0], [0, 0], [0, 0]],\n", + " [[0, 1], [1, 0], [1, 1]],\n", + " ]\n", + "\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = T,\n", + " sense = :Max,\n", + " upper_bound = 100.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, stage\n", + " # Seat remaining?\n", + " @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1)\n", + " # Action: accept offer, or don't accept offer\n", + " # We are allowed to accept some of the seats offered but not others\n", + " @variable(sp, accept_offer[1:N], Bin)\n", + " @variable(sp, offers_made[1:N])\n", + " # Balance on seats\n", + " @constraint(\n", + " sp,\n", + " balance[i in 1:N],\n", + " x[i].in - x[i].out == accept_offer[i]\n", + " )\n", + " @stageobjective(sp, sum(R[i, stage] * accept_offer[i] for i in 1:N))\n", + " SDDP.parameterize(sp, offers[stage]) do o\n", + " return JuMP.fix.(offers_made, o)\n", + " end\n", + " @constraint(sp, accept_offer .<= offers_made)\n", + " end\n", + "\n", + " SDDP.train(model; duality_handler = SDDP.LagrangianDuality())\n", + " @test SDDP.calculate_bound(model) ≈ 8.0\n", + " return\n", + "end\n", + "\n", + "stochastic_all_blacks()" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/stochastic_all_blacks.jl b/previews/PR826/examples/stochastic_all_blacks.jl new file mode 100644 index 0000000000..d4080a5455 --- /dev/null +++ b/previews/PR826/examples/stochastic_all_blacks.jl @@ -0,0 +1,55 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Stochastic All Blacks + +using SDDP, HiGHS, Test + +function stochastic_all_blacks() + ## Number of time periods + T = 3 + ## Number of seats + N = 2 + ## R_ij = price of seat i at time j + R = [3 3 6; 3 3 6] + ## Number of noises + s = 3 + offers = [ + [[1, 1], [0, 0], [1, 1]], + [[1, 0], [0, 0], [0, 0]], + [[0, 1], [1, 0], [1, 1]], + ] + + model = SDDP.LinearPolicyGraph(; + stages = T, + sense = :Max, + upper_bound = 100.0, + optimizer = HiGHS.Optimizer, + ) do sp, stage + ## Seat remaining? + @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1) + ## Action: accept offer, or don't accept offer + ## We are allowed to accept some of the seats offered but not others + @variable(sp, accept_offer[1:N], Bin) + @variable(sp, offers_made[1:N]) + ## Balance on seats + @constraint( + sp, + balance[i in 1:N], + x[i].in - x[i].out == accept_offer[i] + ) + @stageobjective(sp, sum(R[i, stage] * accept_offer[i] for i in 1:N)) + SDDP.parameterize(sp, offers[stage]) do o + return JuMP.fix.(offers_made, o) + end + @constraint(sp, accept_offer .<= offers_made) + end + + SDDP.train(model; duality_handler = SDDP.LagrangianDuality()) + @test SDDP.calculate_bound(model) ≈ 8.0 + return +end + +stochastic_all_blacks() diff --git a/previews/PR826/examples/stochastic_all_blacks/index.html b/previews/PR826/examples/stochastic_all_blacks/index.html new file mode 100644 index 0000000000..d6e73d35e7 --- /dev/null +++ b/previews/PR826/examples/stochastic_all_blacks/index.html @@ -0,0 +1,90 @@ + +Stochastic All Blacks · SDDP.jl

Stochastic All Blacks

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

using SDDP, HiGHS, Test
+
+function stochastic_all_blacks()
+    # Number of time periods
+    T = 3
+    # Number of seats
+    N = 2
+    # R_ij = price of seat i at time j
+    R = [3 3 6; 3 3 6]
+    # Number of noises
+    s = 3
+    offers = [
+        [[1, 1], [0, 0], [1, 1]],
+        [[1, 0], [0, 0], [0, 0]],
+        [[0, 1], [1, 0], [1, 1]],
+    ]
+
+    model = SDDP.LinearPolicyGraph(;
+        stages = T,
+        sense = :Max,
+        upper_bound = 100.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, stage
+        # Seat remaining?
+        @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1)
+        # Action: accept offer, or don't accept offer
+        # We are allowed to accept some of the seats offered but not others
+        @variable(sp, accept_offer[1:N], Bin)
+        @variable(sp, offers_made[1:N])
+        # Balance on seats
+        @constraint(
+            sp,
+            balance[i in 1:N],
+            x[i].in - x[i].out == accept_offer[i]
+        )
+        @stageobjective(sp, sum(R[i, stage] * accept_offer[i] for i in 1:N))
+        SDDP.parameterize(sp, offers[stage]) do o
+            return JuMP.fix.(offers_made, o)
+        end
+        @constraint(sp, accept_offer .<= offers_made)
+    end
+
+    SDDP.train(model; duality_handler = SDDP.LagrangianDuality())
+    @test SDDP.calculate_bound(model) ≈ 8.0
+    return
+end
+
+stochastic_all_blacks()
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 2
+  scenarios       : 2.70000e+01
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  AffExpr in MOI.LessThan{Float64}        : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [2, 3]
+  VariableRef in MOI.LessThan{Float64}    : [3, 3]
+  VariableRef in MOI.ZeroOne              : [4, 4]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 6e+00]
+  bounds range     [1e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1L  1.200000e+01  1.414815e+01  4.120493e-02        11   1
+        40L  1.200000e+01  8.000000e+00  4.114580e-01       602   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 4.114580e-01
+total solves   : 602
+best bound     :  8.000000e+00
+simulation ci  :  8.625000e+00 ± 8.978763e-01
+numeric issues : 0
+-------------------------------------------------------------------
diff --git a/previews/PR826/examples/the_farmers_problem.ipynb b/previews/PR826/examples/the_farmers_problem.ipynb new file mode 100644 index 0000000000..0dcf73e25f --- /dev/null +++ b/previews/PR826/examples/the_farmers_problem.ipynb @@ -0,0 +1,614 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# The farmer's problem\n", + "\n", + "_This problem is taken from Section 1.1 of the book Birge, J. R., & Louveaux,\n", + "F. (2011). Introduction to Stochastic Programming. New York, NY: Springer New\n", + "York. Paragraphs in quotes are taken verbatim._" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Problem description" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> Consider a European farmer who specializes in raising wheat, corn, and sugar\n", + "> beets on his 500 acres of land. During the winter, [they want] to decide how\n", + "> much land to devote to each crop.\n", + ">\n", + "> The farmer knows that at least 200 tons (T) of wheat and 240 T of corn are\n", + "> needed for cattle feed. These amounts can be raised on the farm or bought\n", + "> from a wholesaler. Any production in excess of the feeding requirement would\n", + "> be sold.\n", + ">\n", + "> Over the last decade, mean selling prices have been \\$170 and \\$150 per\n", + "> ton of wheat and corn, respectively. The purchase prices are 40% more than\n", + "> this due to the wholesaler’s margin and transportation costs.\n", + ">\n", + "> Another profitable crop is sugar beet, which [they expect] to sell at\n", + "> \\$36/T; however, the European Commission imposes a quota on sugar beet\n", + "> production. Any amount in excess of the quota can be sold only at \\$10/T.\n", + "> The farmer’s quota for next year is 6000 T.\"\n", + ">\n", + "> Based on past experience, the farmer knows that the mean yield on [their]\n", + "> land is roughly 2.5 T, 3 T, and 20 T per acre for wheat, corn, and sugar\n", + "> beets, respectively.\n", + ">\n", + "> [To introduce uncertainty,] assume some correlation among the yields of the\n", + "> different crops. A very simplified representation of this would be to assume\n", + "> that years are good, fair, or bad for all crops, resulting in above average,\n", + "> average, or below average yields for all crops. To fix these ideas, _above_\n", + "> and _below_ average indicate a yield 20% above or below the mean yield." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Problem data" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The area of the farm." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "MAX_AREA = 500.0" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "There are three crops:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "CROPS = [:wheat, :corn, :sugar_beet]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Each of the crops has a different planting cost (\\$/acre)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "PLANTING_COST = Dict(:wheat => 150.0, :corn => 230.0, :sugar_beet => 260.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The farmer requires a minimum quantity of wheat and corn, but not of sugar\n", + "beet (tonnes)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "MIN_QUANTITIES = Dict(:wheat => 200.0, :corn => 240.0, :sugar_beet => 0.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "In Europe, there is a quota system for producing crops. The farmer owns the\n", + "following quota for each crop (tonnes):" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "QUOTA_MAX = Dict(:wheat => Inf, :corn => Inf, :sugar_beet => 6_000.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The farmer can sell crops produced under the quota for the following amounts\n", + "(\\$/tonne):" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SELL_IN_QUOTA = Dict(:wheat => 170.0, :corn => 150.0, :sugar_beet => 36.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "If they sell more than their allotted quota, the farmer earns the following on\n", + "each tonne of crop above the quota (\\$/tonne):" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SELL_NO_QUOTA = Dict(:wheat => 0.0, :corn => 0.0, :sugar_beet => 10.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The purchase prices for wheat and corn are 40% more than their sales price.\n", + "However, the description does not address the purchase price of sugar beet.\n", + "Therefore, we use a large value of \\$1,000/tonne." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "BUY_PRICE = Dict(:wheat => 238.0, :corn => 210.0, :sugar_beet => 1_000.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "On average, each crop has the following yield in tonnes/acre:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "MEAN_YIELD = Dict(:wheat => 2.5, :corn => 3.0, :sugar_beet => 20.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "However, the yield is random. In good years, the yield is +20% above average,\n", + "and in bad years, the yield is -20% below average." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "YIELD_MULTIPLIER = Dict(:good => 1.2, :fair => 1.0, :bad => 0.8)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Mathematical formulation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## SDDP.jl code" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> In what follows, we make heavy use of the fact that you can look up\n", + "> variables by their symbol name in a JuMP model as follows:\n", + "> ```julia\n", + "> @variable(model, x)\n", + "> model[:x]\n", + "> ```\n", + "> Read the [JuMP documentation](http://jump.dev/JuMP.jl/stable)\n", + "> if this isn't familiar to you." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First up, load `SDDP.jl` and a solver. For this example, we use\n", + "[`HiGHS.jl`](https://github.com/jump-dev/HiGHS.jl)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### State variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "State variables are the information that flows between stages. In our example,\n", + "the state variables are the areas of land devoted to growing each crop." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function add_state_variables(subproblem)\n", + " @variable(subproblem, area[c = CROPS] >= 0, SDDP.State, initial_value = 0)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### First stage problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can only plant a maximum of 500 acres, and we want to minimize the planting\n", + "cost" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function create_first_stage_problem(subproblem)\n", + " @constraint(\n", + " subproblem,\n", + " sum(subproblem[:area][c].out for c in CROPS) <= MAX_AREA\n", + " )\n", + " @stageobjective(\n", + " subproblem,\n", + " -sum(PLANTING_COST[c] * subproblem[:area][c].out for c in CROPS)\n", + " )\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Second stage problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now let's consider the second stage problem. This is more complicated than\n", + "the first stage, so we've broken it down into four sections:\n", + "1) control variables\n", + "2) constraints\n", + "3) the objective\n", + "4) the uncertainty" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, let's add the second stage control variables." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "#### Variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We add four types of control variables. Technically, the `yield` isn't a\n", + "control variable. However, we add it as a dummy \"helper\" variable because it\n", + "will be used when we add uncertainty." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function second_stage_variables(subproblem)\n", + " @variables(subproblem, begin\n", + " 0 <= yield[c = CROPS] # tonnes/acre\n", + " 0 <= buy[c = CROPS] # tonnes\n", + " 0 <= sell_in_quota[c = CROPS] <= QUOTA_MAX[c] # tonnes\n", + " 0 <= sell_no_quota[c = CROPS] # tonnes\n", + " end)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "#### Constraints" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We need to define is the minimum quantity constraint. This ensures that\n", + "`MIN_QUANTITIES[c]` of each crop is produced." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function second_stage_constraint_min_quantity(subproblem)\n", + " @constraint(\n", + " subproblem,\n", + " [c = CROPS],\n", + " subproblem[:yield][c] + subproblem[:buy][c] -\n", + " subproblem[:sell_in_quota][c] - subproblem[:sell_no_quota][c] >=\n", + " MIN_QUANTITIES[c]\n", + " )\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "#### Objective" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The objective of the second stage is to maximise revenue from selling crops,\n", + "less the cost of buying corn and wheat if necessary to meet the minimum\n", + "quantity constraint." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function second_stage_objective(subproblem)\n", + " @stageobjective(\n", + " subproblem,\n", + " sum(\n", + " SELL_IN_QUOTA[c] * subproblem[:sell_in_quota][c] +\n", + " SELL_NO_QUOTA[c] * subproblem[:sell_no_quota][c] -\n", + " BUY_PRICE[c] * subproblem[:buy][c] for c in CROPS\n", + " )\n", + " )\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "#### Random variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Then, in the `SDDP.parameterize` function, we set the coefficient\n", + "using `JuMP.set_normalized_coefficient`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function second_stage_uncertainty(subproblem)\n", + " @constraint(\n", + " subproblem,\n", + " uncertainty[c = CROPS],\n", + " 1.0 * subproblem[:area][c].in == subproblem[:yield][c]\n", + " )\n", + " SDDP.parameterize(subproblem, [:good, :fair, :bad]) do ω\n", + " for c in CROPS\n", + " JuMP.set_normalized_coefficient(\n", + " uncertainty[c],\n", + " subproblem[:area][c].in,\n", + " MEAN_YIELD[c] * YIELD_MULTIPLIER[ω],\n", + " )\n", + " end\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Putting it all together" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we're ready to build the multistage stochastic programming model. In\n", + "addition to the things already discussed, we need a few extra pieces of\n", + "information.\n", + "\n", + "First, we are maximizing, so we set `sense = :Max`. Second, we need to provide\n", + "a valid upper bound. (See Choosing an initial bound for more on this.)\n", + "We know from Birge and Louveaux that the optimal solution is \\$108,390. So,\n", + "let's choose \\$500,000 just to be safe." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is the full model." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " sense = :Max,\n", + " upper_bound = 500_000.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, stage\n", + " add_state_variables(subproblem)\n", + " if stage == 1\n", + " create_first_stage_problem(subproblem)\n", + " else\n", + " second_stage_variables(subproblem)\n", + " second_stage_constraint_min_quantity(subproblem)\n", + " second_stage_uncertainty(subproblem)\n", + " second_stage_objective(subproblem)\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Training a policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now that we've built a model, we need to train it using `SDDP.train`.\n", + "The keyword `iteration_limit` stops the training after 40 iterations. See\n", + "Choose a stopping rule for other ways to stop the training." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; iteration_limit = 40)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Checking the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Birge and Louveaux report that the optimal objective value is \\$108,390.\n", + "Check that we got the correct solution using `SDDP.calculate_bound`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@assert isapprox(SDDP.calculate_bound(model), 108_390.0, atol = 0.1)" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/the_farmers_problem.jl b/previews/PR826/examples/the_farmers_problem.jl new file mode 100644 index 0000000000..4d914e984a --- /dev/null +++ b/previews/PR826/examples/the_farmers_problem.jl @@ -0,0 +1,258 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # The farmer's problem +# +# _This problem is taken from Section 1.1 of the book Birge, J. R., & Louveaux, +# F. (2011). Introduction to Stochastic Programming. New York, NY: Springer New +# York. Paragraphs in quotes are taken verbatim._ + +# ## Problem description + +# > Consider a European farmer who specializes in raising wheat, corn, and sugar +# > beets on his 500 acres of land. During the winter, [they want] to decide how +# > much land to devote to each crop. +# > +# > The farmer knows that at least 200 tons (T) of wheat and 240 T of corn are +# > needed for cattle feed. These amounts can be raised on the farm or bought +# > from a wholesaler. Any production in excess of the feeding requirement would +# > be sold. +# > +# > Over the last decade, mean selling prices have been \$170 and \$150 per +# > ton of wheat and corn, respectively. The purchase prices are 40% more than +# > this due to the wholesaler’s margin and transportation costs. +# > +# > Another profitable crop is sugar beet, which [they expect] to sell at +# > \$36/T; however, the European Commission imposes a quota on sugar beet +# > production. Any amount in excess of the quota can be sold only at \$10/T. +# > The farmer’s quota for next year is 6000 T." +# > +# > Based on past experience, the farmer knows that the mean yield on [their] +# > land is roughly 2.5 T, 3 T, and 20 T per acre for wheat, corn, and sugar +# > beets, respectively. +# > +# > [To introduce uncertainty,] assume some correlation among the yields of the +# > different crops. A very simplified representation of this would be to assume +# > that years are good, fair, or bad for all crops, resulting in above average, +# > average, or below average yields for all crops. To fix these ideas, _above_ +# > and _below_ average indicate a yield 20% above or below the mean yield. + +# ## Problem data + +# The area of the farm. + +MAX_AREA = 500.0 + +# There are three crops: + +CROPS = [:wheat, :corn, :sugar_beet] + +# Each of the crops has a different planting cost (\$/acre). + +PLANTING_COST = Dict(:wheat => 150.0, :corn => 230.0, :sugar_beet => 260.0) + +# The farmer requires a minimum quantity of wheat and corn, but not of sugar +# beet (tonnes). + +MIN_QUANTITIES = Dict(:wheat => 200.0, :corn => 240.0, :sugar_beet => 0.0) + +# In Europe, there is a quota system for producing crops. The farmer owns the +# following quota for each crop (tonnes): + +QUOTA_MAX = Dict(:wheat => Inf, :corn => Inf, :sugar_beet => 6_000.0) + +# The farmer can sell crops produced under the quota for the following amounts +# (\$/tonne): + +SELL_IN_QUOTA = Dict(:wheat => 170.0, :corn => 150.0, :sugar_beet => 36.0) + +# If they sell more than their allotted quota, the farmer earns the following on +# each tonne of crop above the quota (\$/tonne): + +SELL_NO_QUOTA = Dict(:wheat => 0.0, :corn => 0.0, :sugar_beet => 10.0) + +# The purchase prices for wheat and corn are 40% more than their sales price. +# However, the description does not address the purchase price of sugar beet. +# Therefore, we use a large value of \$1,000/tonne. + +BUY_PRICE = Dict(:wheat => 238.0, :corn => 210.0, :sugar_beet => 1_000.0) + +# On average, each crop has the following yield in tonnes/acre: + +MEAN_YIELD = Dict(:wheat => 2.5, :corn => 3.0, :sugar_beet => 20.0) + +# However, the yield is random. In good years, the yield is +20% above average, +# and in bad years, the yield is -20% below average. + +YIELD_MULTIPLIER = Dict(:good => 1.2, :fair => 1.0, :bad => 0.8) + +# ## Mathematical formulation + +# ## SDDP.jl code + +# !!! note +# In what follows, we make heavy use of the fact that you can look up +# variables by their symbol name in a JuMP model as follows: +# ```julia +# @variable(model, x) +# model[:x] +# ``` +# Read the [JuMP documentation](http://jump.dev/JuMP.jl/stable) +# if this isn't familiar to you. + +# First up, load `SDDP.jl` and a solver. For this example, we use +# [`HiGHS.jl`](https://github.com/jump-dev/HiGHS.jl). + +using SDDP, HiGHS + +# ### State variables + +# State variables are the information that flows between stages. In our example, +# the state variables are the areas of land devoted to growing each crop. + +function add_state_variables(subproblem) + @variable(subproblem, area[c = CROPS] >= 0, SDDP.State, initial_value = 0) +end + +# ### First stage problem + +# We can only plant a maximum of 500 acres, and we want to minimize the planting +# cost + +function create_first_stage_problem(subproblem) + @constraint( + subproblem, + sum(subproblem[:area][c].out for c in CROPS) <= MAX_AREA + ) + @stageobjective( + subproblem, + -sum(PLANTING_COST[c] * subproblem[:area][c].out for c in CROPS) + ) +end + +# ### Second stage problem + +# Now let's consider the second stage problem. This is more complicated than +# the first stage, so we've broken it down into four sections: +# 1) control variables +# 2) constraints +# 3) the objective +# 4) the uncertainty + +# First, let's add the second stage control variables. + +# #### Variables + +# We add four types of control variables. Technically, the `yield` isn't a +# control variable. However, we add it as a dummy "helper" variable because it +# will be used when we add uncertainty. + +function second_stage_variables(subproblem) + @variables(subproblem, begin + 0 <= yield[c = CROPS] # tonnes/acre + 0 <= buy[c = CROPS] # tonnes + 0 <= sell_in_quota[c = CROPS] <= QUOTA_MAX[c] # tonnes + 0 <= sell_no_quota[c = CROPS] # tonnes + end) +end + +# #### Constraints + +# We need to define is the minimum quantity constraint. This ensures that +# `MIN_QUANTITIES[c]` of each crop is produced. + +function second_stage_constraint_min_quantity(subproblem) + @constraint( + subproblem, + [c = CROPS], + subproblem[:yield][c] + subproblem[:buy][c] - + subproblem[:sell_in_quota][c] - subproblem[:sell_no_quota][c] >= + MIN_QUANTITIES[c] + ) +end + +# #### Objective + +# The objective of the second stage is to maximise revenue from selling crops, +# less the cost of buying corn and wheat if necessary to meet the minimum +# quantity constraint. + +function second_stage_objective(subproblem) + @stageobjective( + subproblem, + sum( + SELL_IN_QUOTA[c] * subproblem[:sell_in_quota][c] + + SELL_NO_QUOTA[c] * subproblem[:sell_no_quota][c] - + BUY_PRICE[c] * subproblem[:buy][c] for c in CROPS + ) + ) +end + +# #### Random variables + +# Then, in the [`SDDP.parameterize`](@ref) function, we set the coefficient +# using `JuMP.set_normalized_coefficient`. + +function second_stage_uncertainty(subproblem) + @constraint( + subproblem, + uncertainty[c = CROPS], + 1.0 * subproblem[:area][c].in == subproblem[:yield][c] + ) + SDDP.parameterize(subproblem, [:good, :fair, :bad]) do ω + for c in CROPS + JuMP.set_normalized_coefficient( + uncertainty[c], + subproblem[:area][c].in, + MEAN_YIELD[c] * YIELD_MULTIPLIER[ω], + ) + end + end +end + +# ### Putting it all together + +# Now we're ready to build the multistage stochastic programming model. In +# addition to the things already discussed, we need a few extra pieces of +# information. +# +# First, we are maximizing, so we set `sense = :Max`. Second, we need to provide +# a valid upper bound. (See [Choosing an initial bound](@ref) for more on this.) +# We know from Birge and Louveaux that the optimal solution is \$108,390. So, +# let's choose \$500,000 just to be safe. + +# Here is the full model. + +model = SDDP.LinearPolicyGraph(; + stages = 2, + sense = :Max, + upper_bound = 500_000.0, + optimizer = HiGHS.Optimizer, +) do subproblem, stage + add_state_variables(subproblem) + if stage == 1 + create_first_stage_problem(subproblem) + else + second_stage_variables(subproblem) + second_stage_constraint_min_quantity(subproblem) + second_stage_uncertainty(subproblem) + second_stage_objective(subproblem) + end +end + +# ## Training a policy + +# Now that we've built a model, we need to train it using [`SDDP.train`](@ref). +# The keyword `iteration_limit` stops the training after 40 iterations. See +# [Choose a stopping rule](@ref) for other ways to stop the training. + +SDDP.train(model; iteration_limit = 40) + +# ## Checking the policy + +# Birge and Louveaux report that the optimal objective value is \$108,390. +# Check that we got the correct solution using [`SDDP.calculate_bound`](@ref): + +@assert isapprox(SDDP.calculate_bound(model), 108_390.0, atol = 0.1) diff --git a/previews/PR826/examples/the_farmers_problem/index.html b/previews/PR826/examples/the_farmers_problem/index.html new file mode 100644 index 0000000000..09cde93540 --- /dev/null +++ b/previews/PR826/examples/the_farmers_problem/index.html @@ -0,0 +1,137 @@ + +The farmer's problem · SDDP.jl

The farmer's problem

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This problem is taken from Section 1.1 of the book Birge, J. R., & Louveaux, F. (2011). Introduction to Stochastic Programming. New York, NY: Springer New York. Paragraphs in quotes are taken verbatim.

Problem description

Consider a European farmer who specializes in raising wheat, corn, and sugar beets on his 500 acres of land. During the winter, [they want] to decide how much land to devote to each crop.

The farmer knows that at least 200 tons (T) of wheat and 240 T of corn are needed for cattle feed. These amounts can be raised on the farm or bought from a wholesaler. Any production in excess of the feeding requirement would be sold.

Over the last decade, mean selling prices have been $170 and $150 per ton of wheat and corn, respectively. The purchase prices are 40% more than this due to the wholesaler’s margin and transportation costs.

Another profitable crop is sugar beet, which [they expect] to sell at $36/T; however, the European Commission imposes a quota on sugar beet production. Any amount in excess of the quota can be sold only at $10/T. The farmer’s quota for next year is 6000 T."

Based on past experience, the farmer knows that the mean yield on [their] land is roughly 2.5 T, 3 T, and 20 T per acre for wheat, corn, and sugar beets, respectively.

[To introduce uncertainty,] assume some correlation among the yields of the different crops. A very simplified representation of this would be to assume that years are good, fair, or bad for all crops, resulting in above average, average, or below average yields for all crops. To fix these ideas, above and below average indicate a yield 20% above or below the mean yield.

Problem data

The area of the farm.

MAX_AREA = 500.0
500.0

There are three crops:

CROPS = [:wheat, :corn, :sugar_beet]
3-element Vector{Symbol}:
+ :wheat
+ :corn
+ :sugar_beet

Each of the crops has a different planting cost ($/acre).

PLANTING_COST = Dict(:wheat => 150.0, :corn => 230.0, :sugar_beet => 260.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => 150.0
+  :sugar_beet => 260.0
+  :corn       => 230.0

The farmer requires a minimum quantity of wheat and corn, but not of sugar beet (tonnes).

MIN_QUANTITIES = Dict(:wheat => 200.0, :corn => 240.0, :sugar_beet => 0.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => 200.0
+  :sugar_beet => 0.0
+  :corn       => 240.0

In Europe, there is a quota system for producing crops. The farmer owns the following quota for each crop (tonnes):

QUOTA_MAX = Dict(:wheat => Inf, :corn => Inf, :sugar_beet => 6_000.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => Inf
+  :sugar_beet => 6000.0
+  :corn       => Inf

The farmer can sell crops produced under the quota for the following amounts ($/tonne):

SELL_IN_QUOTA = Dict(:wheat => 170.0, :corn => 150.0, :sugar_beet => 36.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => 170.0
+  :sugar_beet => 36.0
+  :corn       => 150.0

If they sell more than their allotted quota, the farmer earns the following on each tonne of crop above the quota ($/tonne):

SELL_NO_QUOTA = Dict(:wheat => 0.0, :corn => 0.0, :sugar_beet => 10.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => 0.0
+  :sugar_beet => 10.0
+  :corn       => 0.0

The purchase prices for wheat and corn are 40% more than their sales price. However, the description does not address the purchase price of sugar beet. Therefore, we use a large value of $1,000/tonne.

BUY_PRICE = Dict(:wheat => 238.0, :corn => 210.0, :sugar_beet => 1_000.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => 238.0
+  :sugar_beet => 1000.0
+  :corn       => 210.0

On average, each crop has the following yield in tonnes/acre:

MEAN_YIELD = Dict(:wheat => 2.5, :corn => 3.0, :sugar_beet => 20.0)
Dict{Symbol, Float64} with 3 entries:
+  :wheat      => 2.5
+  :sugar_beet => 20.0
+  :corn       => 3.0

However, the yield is random. In good years, the yield is +20% above average, and in bad years, the yield is -20% below average.

YIELD_MULTIPLIER = Dict(:good => 1.2, :fair => 1.0, :bad => 0.8)
Dict{Symbol, Float64} with 3 entries:
+  :bad  => 0.8
+  :good => 1.2
+  :fair => 1.0

Mathematical formulation

SDDP.jl code

Note

In what follows, we make heavy use of the fact that you can look up variables by their symbol name in a JuMP model as follows:

@variable(model, x)
+model[:x]

Read the JuMP documentation if this isn't familiar to you.

First up, load SDDP.jl and a solver. For this example, we use HiGHS.jl.

using SDDP, HiGHS

State variables

State variables are the information that flows between stages. In our example, the state variables are the areas of land devoted to growing each crop.

function add_state_variables(subproblem)
+    @variable(subproblem, area[c = CROPS] >= 0, SDDP.State, initial_value = 0)
+end
add_state_variables (generic function with 1 method)

First stage problem

We can only plant a maximum of 500 acres, and we want to minimize the planting cost

function create_first_stage_problem(subproblem)
+    @constraint(
+        subproblem,
+        sum(subproblem[:area][c].out for c in CROPS) <= MAX_AREA
+    )
+    @stageobjective(
+        subproblem,
+        -sum(PLANTING_COST[c] * subproblem[:area][c].out for c in CROPS)
+    )
+end
create_first_stage_problem (generic function with 1 method)

Second stage problem

Now let's consider the second stage problem. This is more complicated than the first stage, so we've broken it down into four sections:

  1. control variables
  2. constraints
  3. the objective
  4. the uncertainty

First, let's add the second stage control variables.

Variables

We add four types of control variables. Technically, the yield isn't a control variable. However, we add it as a dummy "helper" variable because it will be used when we add uncertainty.

function second_stage_variables(subproblem)
+    @variables(subproblem, begin
+        0 <= yield[c = CROPS]                          # tonnes/acre
+        0 <= buy[c = CROPS]                            # tonnes
+        0 <= sell_in_quota[c = CROPS] <= QUOTA_MAX[c]  # tonnes
+        0 <= sell_no_quota[c = CROPS]                  # tonnes
+    end)
+end
second_stage_variables (generic function with 1 method)

Constraints

We need to define is the minimum quantity constraint. This ensures that MIN_QUANTITIES[c] of each crop is produced.

function second_stage_constraint_min_quantity(subproblem)
+    @constraint(
+        subproblem,
+        [c = CROPS],
+        subproblem[:yield][c] + subproblem[:buy][c] -
+        subproblem[:sell_in_quota][c] - subproblem[:sell_no_quota][c] >=
+        MIN_QUANTITIES[c]
+    )
+end
second_stage_constraint_min_quantity (generic function with 1 method)

Objective

The objective of the second stage is to maximise revenue from selling crops, less the cost of buying corn and wheat if necessary to meet the minimum quantity constraint.

function second_stage_objective(subproblem)
+    @stageobjective(
+        subproblem,
+        sum(
+            SELL_IN_QUOTA[c] * subproblem[:sell_in_quota][c] +
+            SELL_NO_QUOTA[c] * subproblem[:sell_no_quota][c] -
+            BUY_PRICE[c] * subproblem[:buy][c] for c in CROPS
+        )
+    )
+end
second_stage_objective (generic function with 1 method)

Random variables

Then, in the SDDP.parameterize function, we set the coefficient using JuMP.set_normalized_coefficient.

function second_stage_uncertainty(subproblem)
+    @constraint(
+        subproblem,
+        uncertainty[c = CROPS],
+        1.0 * subproblem[:area][c].in == subproblem[:yield][c]
+    )
+    SDDP.parameterize(subproblem, [:good, :fair, :bad]) do ω
+        for c in CROPS
+            JuMP.set_normalized_coefficient(
+                uncertainty[c],
+                subproblem[:area][c].in,
+                MEAN_YIELD[c] * YIELD_MULTIPLIER[ω],
+            )
+        end
+    end
+end
second_stage_uncertainty (generic function with 1 method)

Putting it all together

Now we're ready to build the multistage stochastic programming model. In addition to the things already discussed, we need a few extra pieces of information.

First, we are maximizing, so we set sense = :Max. Second, we need to provide a valid upper bound. (See Choosing an initial bound for more on this.) We know from Birge and Louveaux that the optimal solution is $108,390. So, let's choose $500,000 just to be safe.

Here is the full model.

model = SDDP.LinearPolicyGraph(;
+    stages = 2,
+    sense = :Max,
+    upper_bound = 500_000.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, stage
+    add_state_variables(subproblem)
+    if stage == 1
+        create_first_stage_problem(subproblem)
+    else
+        second_stage_variables(subproblem)
+        second_stage_constraint_min_quantity(subproblem)
+        second_stage_uncertainty(subproblem)
+        second_stage_objective(subproblem)
+    end
+end
A policy graph with 2 nodes.
+ Node indices: 1, 2
+

Training a policy

Now that we've built a model, we need to train it using SDDP.train. The keyword iteration_limit stops the training after 40 iterations. See Choose a stopping rule for other ways to stop the training.

SDDP.train(model; iteration_limit = 40)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 3
+  scenarios       : 3.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 19]
+  AffExpr in MOI.EqualTo{Float64}         : [3, 3]
+  AffExpr in MOI.GreaterThan{Float64}     : [3, 3]
+  AffExpr in MOI.LessThan{Float64}        : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [3, 16]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+numerical stability report
+  matrix range     [1e+00, 2e+01]
+  objective range  [1e+00, 1e+03]
+  bounds range     [6e+03, 5e+05]
+  rhs range        [2e+02, 5e+02]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1  -9.800000e+04  4.922260e+05  8.680820e-02         6   1
+        40   1.670000e+05  1.083900e+05  1.148281e-01       240   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 1.148281e-01
+total solves   : 240
+best bound     :  1.083900e+05
+simulation ci  :  9.026160e+04 ± 1.919853e+04
+numeric issues : 0
+-------------------------------------------------------------------

Checking the policy

Birge and Louveaux report that the optimal objective value is $108,390. Check that we got the correct solution using SDDP.calculate_bound:

@assert isapprox(SDDP.calculate_bound(model), 108_390.0, atol = 0.1)
diff --git a/previews/PR826/examples/vehicle_location.ipynb b/previews/PR826/examples/vehicle_location.ipynb new file mode 100644 index 0000000000..d0529fd143 --- /dev/null +++ b/previews/PR826/examples/vehicle_location.ipynb @@ -0,0 +1,171 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Vehicle location" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This problem is a version of the Ambulance dispatch problem. A hospital is\n", + "located at 0 on the number line that stretches from 0 to 100. Ambulance bases\n", + "are located at points 20, 40, 60, 80, and 100. When not responding to a call,\n", + "Ambulances must be located at a base, or the hospital. In this example there\n", + "are three ambulances." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Example location:\n", + "\n", + " H B B B B B\n", + " 0 ---- 20 ---- 40 ---- 60 ---- 80 ---- 100" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Each stage, a call comes in from somewhere on the number line. The agent must\n", + "decide which ambulance to dispatch. They pay the cost of twice the driving\n", + "distance. If an ambulance is not dispatched in a stage, the ambulance can be\n", + "relocated to a different base in preparation for future calls. This incurs a\n", + "cost of the driving distance." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS\n", + "import Test\n", + "\n", + "function vehicle_location_model(duality_handler)\n", + " hospital_location = 0\n", + " bases = vcat(hospital_location, [20, 40, 60, 80, 100])\n", + " vehicles = [1, 2, 3]\n", + " requests = 0:10:100\n", + " shift_cost(src, dest) = abs(src - dest)\n", + " function dispatch_cost(base, request)\n", + " return 2 * (abs(request - hospital_location) + abs(request - base))\n", + " end\n", + " # Initial state of emergency vehicles at bases. All ambulances start at the\n", + " # hospital.\n", + " initial_state(b, v) = b == hospital_location ? 1.0 : 0.0\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 10,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do sp, t\n", + " # Current location of each vehicle at each base.\n", + " @variable(\n", + " sp,\n", + " 0 <= location[b = bases, v = vehicles] <= 1,\n", + " SDDP.State,\n", + " initial_value = initial_state(b, v)\n", + " )\n", + " @variables(sp, begin\n", + " # Which vehicle is dispatched?\n", + " 0 <= dispatch[bases, vehicles] <= 1, Bin\n", + " # Shifting vehicles between bases: [src, dest, vehicle]\n", + " 0 <= shift[bases, bases, vehicles] <= 1, Bin\n", + " end)\n", + " # Flow of vehicles in and out of bases:\n", + " @expression(\n", + " sp,\n", + " base_balance[b in bases, v in vehicles],\n", + " location[b, v].in - dispatch[b, v] - sum(shift[b, :, v]) +\n", + " sum(shift[:, b, v])\n", + " )\n", + " @constraints(\n", + " sp,\n", + " begin\n", + " # Only one vehicle dispatched to call.\n", + " sum(dispatch) == 1\n", + " # Can only dispatch vehicle from base if vehicle is at that base.\n", + " [b in bases, v in vehicles],\n", + " dispatch[b, v] <= location[b, v].in\n", + " # Can only shift vehicle if vehicle is at that src base.\n", + " [b in bases, v in vehicles],\n", + " sum(shift[b, :, v]) <= location[b, v].in\n", + " # Can only shift vehicle if vehicle is not being dispatched.\n", + " [b in bases, v in vehicles],\n", + " sum(shift[b, :, v]) + dispatch[b, v] <= 1\n", + " # Can't shift to same base.\n", + " [b in bases, v in vehicles], shift[b, b, v] == 0\n", + " # Update states for non-home/non-hospital bases.\n", + " [b in bases[2:end], v in vehicles],\n", + " location[b, v].out == base_balance[b, v]\n", + " # Update states for home/hospital bases.\n", + " [v in vehicles],\n", + " location[hospital_location, v].out ==\n", + " base_balance[hospital_location, v] + sum(dispatch[:, v])\n", + " end\n", + " )\n", + " SDDP.parameterize(sp, requests) do request\n", + " @stageobjective(\n", + " sp,\n", + " sum(\n", + " # Distance to travel from base to emergency and then to hospital.\n", + " dispatch[b, v] * dispatch_cost(b, request) +\n", + " # Distance travelled by vehicles relocating bases.\n", + " sum(\n", + " shift_cost(b, dest) * shift[b, dest, v] for\n", + " dest in bases\n", + " ) for b in bases, v in vehicles\n", + " )\n", + " )\n", + " end\n", + " end\n", + " if get(ARGS, 1, \"\") == \"--write\"\n", + " # Run `$ julia vehicle_location.jl --write` to update the benchmark\n", + " # model directory\n", + " model_dir = joinpath(@__DIR__, \"..\", \"..\", \"..\", \"benchmarks\", \"models\")\n", + " SDDP.write_to_file(\n", + " model,\n", + " joinpath(model_dir, \"vehicle_location.sof.json.gz\");\n", + " test_scenarios = 100,\n", + " )\n", + " exit(0)\n", + " end\n", + " SDDP.train(\n", + " model;\n", + " iteration_limit = 20,\n", + " log_frequency = 10,\n", + " cut_deletion_minimum = 100,\n", + " duality_handler = duality_handler,\n", + " )\n", + " Test.@test SDDP.calculate_bound(model) >= 1000\n", + " return\n", + "end\n", + "\n", + "# TODO(odow): find out why this fails\n", + "# vehicle_location_model(SDDP.ContinuousConicDuality())" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/examples/vehicle_location.jl b/previews/PR826/examples/vehicle_location.jl new file mode 100644 index 0000000000..92b766c354 --- /dev/null +++ b/previews/PR826/examples/vehicle_location.jl @@ -0,0 +1,129 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Vehicle location + +# This problem is a version of the Ambulance dispatch problem. A hospital is +# located at 0 on the number line that stretches from 0 to 100. Ambulance bases +# are located at points 20, 40, 60, 80, and 100. When not responding to a call, +# Ambulances must be located at a base, or the hospital. In this example there +# are three ambulances. + +# Example location: +# +# H B B B B B +# 0 ---- 20 ---- 40 ---- 60 ---- 80 ---- 100 + +# Each stage, a call comes in from somewhere on the number line. The agent must +# decide which ambulance to dispatch. They pay the cost of twice the driving +# distance. If an ambulance is not dispatched in a stage, the ambulance can be +# relocated to a different base in preparation for future calls. This incurs a +# cost of the driving distance. + +using SDDP +import HiGHS +import Test + +function vehicle_location_model(duality_handler) + hospital_location = 0 + bases = vcat(hospital_location, [20, 40, 60, 80, 100]) + vehicles = [1, 2, 3] + requests = 0:10:100 + shift_cost(src, dest) = abs(src - dest) + function dispatch_cost(base, request) + return 2 * (abs(request - hospital_location) + abs(request - base)) + end + ## Initial state of emergency vehicles at bases. All ambulances start at the + ## hospital. + initial_state(b, v) = b == hospital_location ? 1.0 : 0.0 + model = SDDP.LinearPolicyGraph(; + stages = 10, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + ## Current location of each vehicle at each base. + @variable( + sp, + 0 <= location[b = bases, v = vehicles] <= 1, + SDDP.State, + initial_value = initial_state(b, v) + ) + @variables(sp, begin + ## Which vehicle is dispatched? + 0 <= dispatch[bases, vehicles] <= 1, Bin + ## Shifting vehicles between bases: [src, dest, vehicle] + 0 <= shift[bases, bases, vehicles] <= 1, Bin + end) + ## Flow of vehicles in and out of bases: + @expression( + sp, + base_balance[b in bases, v in vehicles], + location[b, v].in - dispatch[b, v] - sum(shift[b, :, v]) + + sum(shift[:, b, v]) + ) + @constraints( + sp, + begin + ## Only one vehicle dispatched to call. + sum(dispatch) == 1 + ## Can only dispatch vehicle from base if vehicle is at that base. + [b in bases, v in vehicles], + dispatch[b, v] <= location[b, v].in + ## Can only shift vehicle if vehicle is at that src base. + [b in bases, v in vehicles], + sum(shift[b, :, v]) <= location[b, v].in + ## Can only shift vehicle if vehicle is not being dispatched. + [b in bases, v in vehicles], + sum(shift[b, :, v]) + dispatch[b, v] <= 1 + ## Can't shift to same base. + [b in bases, v in vehicles], shift[b, b, v] == 0 + ## Update states for non-home/non-hospital bases. + [b in bases[2:end], v in vehicles], + location[b, v].out == base_balance[b, v] + ## Update states for home/hospital bases. + [v in vehicles], + location[hospital_location, v].out == + base_balance[hospital_location, v] + sum(dispatch[:, v]) + end + ) + SDDP.parameterize(sp, requests) do request + @stageobjective( + sp, + sum( + ## Distance to travel from base to emergency and then to hospital. + dispatch[b, v] * dispatch_cost(b, request) + + ## Distance travelled by vehicles relocating bases. + sum( + shift_cost(b, dest) * shift[b, dest, v] for + dest in bases + ) for b in bases, v in vehicles + ) + ) + end + end + if get(ARGS, 1, "") == "--write" + ## Run `$ julia vehicle_location.jl --write` to update the benchmark + ## model directory + model_dir = joinpath(@__DIR__, "..", "..", "..", "benchmarks", "models") + SDDP.write_to_file( + model, + joinpath(model_dir, "vehicle_location.sof.json.gz"); + test_scenarios = 100, + ) + exit(0) + end + SDDP.train( + model; + iteration_limit = 20, + log_frequency = 10, + cut_deletion_minimum = 100, + duality_handler = duality_handler, + ) + Test.@test SDDP.calculate_bound(model) >= 1000 + return +end + +## TODO(odow): find out why this fails +## vehicle_location_model(SDDP.ContinuousConicDuality()) diff --git a/previews/PR826/examples/vehicle_location/index.html b/previews/PR826/examples/vehicle_location/index.html new file mode 100644 index 0000000000..9cd968aaf0 --- /dev/null +++ b/previews/PR826/examples/vehicle_location/index.html @@ -0,0 +1,111 @@ + +Vehicle location · SDDP.jl

Vehicle location

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This problem is a version of the Ambulance dispatch problem. A hospital is located at 0 on the number line that stretches from 0 to 100. Ambulance bases are located at points 20, 40, 60, 80, and 100. When not responding to a call, Ambulances must be located at a base, or the hospital. In this example there are three ambulances.

Example location:

H       B       B       B       B       B
+0 ---- 20 ---- 40 ---- 60 ---- 80 ---- 100

Each stage, a call comes in from somewhere on the number line. The agent must decide which ambulance to dispatch. They pay the cost of twice the driving distance. If an ambulance is not dispatched in a stage, the ambulance can be relocated to a different base in preparation for future calls. This incurs a cost of the driving distance.

using SDDP
+import HiGHS
+import Test
+
+function vehicle_location_model(duality_handler)
+    hospital_location = 0
+    bases = vcat(hospital_location, [20, 40, 60, 80, 100])
+    vehicles = [1, 2, 3]
+    requests = 0:10:100
+    shift_cost(src, dest) = abs(src - dest)
+    function dispatch_cost(base, request)
+        return 2 * (abs(request - hospital_location) + abs(request - base))
+    end
+    # Initial state of emergency vehicles at bases. All ambulances start at the
+    # hospital.
+    initial_state(b, v) = b == hospital_location ? 1.0 : 0.0
+    model = SDDP.LinearPolicyGraph(;
+        stages = 10,
+        lower_bound = 0.0,
+        optimizer = HiGHS.Optimizer,
+    ) do sp, t
+        # Current location of each vehicle at each base.
+        @variable(
+            sp,
+            0 <= location[b = bases, v = vehicles] <= 1,
+            SDDP.State,
+            initial_value = initial_state(b, v)
+        )
+        @variables(sp, begin
+            # Which vehicle is dispatched?
+            0 <= dispatch[bases, vehicles] <= 1, Bin
+            # Shifting vehicles between bases: [src, dest, vehicle]
+            0 <= shift[bases, bases, vehicles] <= 1, Bin
+        end)
+        # Flow of vehicles in and out of bases:
+        @expression(
+            sp,
+            base_balance[b in bases, v in vehicles],
+            location[b, v].in - dispatch[b, v] - sum(shift[b, :, v]) +
+            sum(shift[:, b, v])
+        )
+        @constraints(
+            sp,
+            begin
+                # Only one vehicle dispatched to call.
+                sum(dispatch) == 1
+                # Can only dispatch vehicle from base if vehicle is at that base.
+                [b in bases, v in vehicles],
+                dispatch[b, v] <= location[b, v].in
+                # Can only shift vehicle if vehicle is at that src base.
+                [b in bases, v in vehicles],
+                sum(shift[b, :, v]) <= location[b, v].in
+                # Can only shift vehicle if vehicle is not being dispatched.
+                [b in bases, v in vehicles],
+                sum(shift[b, :, v]) + dispatch[b, v] <= 1
+                # Can't shift to same base.
+                [b in bases, v in vehicles], shift[b, b, v] == 0
+                # Update states for non-home/non-hospital bases.
+                [b in bases[2:end], v in vehicles],
+                location[b, v].out == base_balance[b, v]
+                # Update states for home/hospital bases.
+                [v in vehicles],
+                location[hospital_location, v].out ==
+                base_balance[hospital_location, v] + sum(dispatch[:, v])
+            end
+        )
+        SDDP.parameterize(sp, requests) do request
+            @stageobjective(
+                sp,
+                sum(
+                    # Distance to travel from base to emergency and then to hospital.
+                    dispatch[b, v] * dispatch_cost(b, request) +
+                    # Distance travelled by vehicles relocating bases.
+                    sum(
+                        shift_cost(b, dest) * shift[b, dest, v] for
+                        dest in bases
+                    ) for b in bases, v in vehicles
+                )
+            )
+        end
+    end
+    if get(ARGS, 1, "") == "--write"
+        # Run `$ julia vehicle_location.jl --write` to update the benchmark
+        # model directory
+        model_dir = joinpath(@__DIR__, "..", "..", "..", "benchmarks", "models")
+        SDDP.write_to_file(
+            model,
+            joinpath(model_dir, "vehicle_location.sof.json.gz");
+            test_scenarios = 100,
+        )
+        exit(0)
+    end
+    SDDP.train(
+        model;
+        iteration_limit = 20,
+        log_frequency = 10,
+        cut_deletion_minimum = 100,
+        duality_handler = duality_handler,
+    )
+    Test.@test SDDP.calculate_bound(model) >= 1000
+    return
+end
+
+# TODO(odow): find out why this fails
+# vehicle_location_model(SDDP.ContinuousConicDuality())
vehicle_location_model (generic function with 1 method)
diff --git a/previews/PR826/explanation/risk.ipynb b/previews/PR826/explanation/risk.ipynb new file mode 100644 index 0000000000..053921ff8c --- /dev/null +++ b/previews/PR826/explanation/risk.ipynb @@ -0,0 +1,1752 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Risk aversion" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In Introductory theory, we implemented a basic version of the\n", + "SDDP algorithm. This tutorial extends that implementation to add\n", + "**risk-aversion**." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "**Packages**\n", + "\n", + "This tutorial uses the following packages. For clarity, we call\n", + "`import PackageName` so that we must prefix `PackageName.` to all functions\n", + "and structs provided by that package. Everything not prefixed is either part\n", + "of base Julia, or we wrote it." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "import ForwardDiff\n", + "import HiGHS\n", + "import Ipopt\n", + "import JuMP\n", + "import Statistics" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Risk aversion: what and why?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Often, the agents making decisions in complex systems are **risk-averse**,\n", + "that is, they care more about avoiding very bad outcomes, than they do about\n", + "having a good average outcome." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As an example, consumers in a hydro-thermal problem may be willing to pay a\n", + "slightly higher electricity price on average, if it means that there is a\n", + "lower probability of blackouts." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Risk aversion in multistage stochastic programming has been well studied in\n", + "the academic literature, and is widely used in production implementations\n", + "around the world." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Risk measures" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One way to add risk aversion to models is to use a **risk measure**. A risk\n", + "measure is a function that maps a random variable to a real number.\n", + "\n", + "You are probably already familiar with lots of different risk measures. For\n", + "example, the mean, median, mode, and maximum are all risk measures.\n", + "\n", + "We call the act of applying a risk measure to a random variable \"computing the\n", + "risk\" of a random variable.\n", + "\n", + "To keep things simple, and because we need it for SDDP, we restrict our\n", + "attention to random variables $Z$ with a finite sample space $\\Omega$\n", + "and positive probabilities $p_{\\omega}$ for all $\\omega \\in \\Omega$. We denote\n", + "the realizations of $Z$ by $Z(\\omega) = z_{\\omega}$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A risk measure, $\\mathbb{F}[Z]$, is a **convex risk measure** if it satisfies\n", + "the following axioms:\n", + "\n", + "**Axiom 1: monotonicity**\n", + "\n", + "Given two random variables $Z_1$ and $Z_2$, with $Z_1 \\le Z_2$ almost surely,\n", + "then $\\mathbb{F}[Z_1] \\le F[Z_2]$.\n", + "\n", + "**Axiom 2: translation equivariance**\n", + "\n", + "Given two random variables $Z_1$ and $Z_2$, then for all $a \\in \\mathbb{R}$,\n", + "$\\mathbb{F}[Z + a] = \\mathbb{F}[Z] + a$.\n", + "\n", + "**Axiom 3: convexity**\n", + "\n", + "Given two random variables $Z_1$ and $Z_2$, then for all $a \\in [0, 1]$,\n", + "$$\n", + "\\mathbb{F}[a Z_1 + (1 - a) Z_2] \\le a \\mathbb{F}[Z_1] + (1-a)\\mathbb{F}[Z_2].\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we know what a risk measure is, let's see how we can use them to form\n", + "risk-averse decision rules." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Risk-averse decision rules: Part I" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We started this tutorial by explaining that we are interested in risk aversion\n", + "because some agents are risk-averse. What that really means, is that they\n", + "want a policy that is also risk-averse. The question then becomes, how do we\n", + "create risk-averse decision rules and policies?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Recall from Introductory theory that we can form an optimal\n", + "decision rule using the recursive formulation:\n", + "$$\n", + "\\begin{aligned}\n", + "V_i(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x,\n", + "\\end{aligned}\n", + "$$\n", + "where our decision rule, $\\pi_i(x, \\omega)$, solves this optimization problem\n", + "and returns a $u^*$ corresponding to an optimal solution." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If we can replace the expectation operator $\\mathbb{E}$ with another (more\n", + "risk-averse) risk measure $\\mathbb{F}$, then our decision rule will attempt to\n", + "choose a control decision now that minimizes the risk of the future costs, as\n", + "opposed to the expectation of the future costs. This makes our decisions more\n", + "risk-averse, because we care more about the worst outcomes than we do about\n", + "the average." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Therefore, we can form a risk-averse decision rule using the formulation:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\begin{aligned}\n", + "V_i(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x.\n", + "\\end{aligned}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To convert this problem into a tractable equivalent, we apply Kelley's\n", + "algorithm to the risk-averse cost-to-go term\n", + "$\\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]$, to\n", + "obtain the approximated problem:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\begin{aligned}\n", + "V_i^K(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\theta\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x \\\\\n", + "& \\theta \\ge \\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right] + \\frac{d}{dx^\\prime}\\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right]^\\top (x^\\prime - x^\\prime_k)\\quad k=1,\\ldots,K.\n", + "\\end{aligned}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> Note how we need to explicitly compute a risk-averse subgradient! (We\n", + "> need a subgradient because the function might not be differentiable.) When\n", + "> constructing cuts with the expectation operator in Introductory theory,\n", + "> we implicitly used the law of total expectation to combine the two\n", + "> expectations; we can't do that for a general risk measure." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Homework challenge**\n", + ">\n", + "> If it's not obvious why we can use Kelley's here, try to use the axioms of\n", + "> a convex risk measure to show that\n", + "> $\\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]$\n", + "> is a convex function w.r.t. $x^\\prime$ if $V_j$ is also a convex function." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Our challenge is now to find a way to compute the risk-averse cost-to-go\n", + "function $\\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right]$,\n", + "and a way to compute a subgradient of the risk-averse cost-to-go function\n", + "with respect to $x^\\prime$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Primal risk measures" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we know what a risk measure is, and how we will use it, let's implement\n", + "some code to see how we can compute the risk of some random variables." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> We're going to start by implementing the **primal** version of each risk\n", + "> measure. We implement the **dual** version in the next section." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, we need some data:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Z = [1.0, 2.0, 3.0, 4.0]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "with probabilities:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "p = [0.1, 0.2, 0.4, 0.3]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We're going to implement a number of different risk measures, so to leverage\n", + "Julia's multiple dispatch, we create an abstract type:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "abstract type AbstractRiskMeasure end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "and function to overload:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "\"\"\"\n", + " primal_risk(F::AbstractRiskMeasure, Z::Vector{<:Real}, p::Vector{Float64})\n", + "\n", + "Use `F` to compute the risk of the random variable defined by a vector of costs\n", + "`Z` and non-zero probabilities `p`.\n", + "\"\"\"\n", + "function primal_risk end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> We want `Vector{<:Real}` instead of `Vector{Float64}` because we're going\n", + "> to automatically differentiate this function in the next section." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Expectation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The expectation, $\\mathbb{E}$, also called the mean or the average, is the\n", + "most widely used convex risk measure. The expectation of a random variable is\n", + "just the sum of $Z$ weighted by the probability:\n", + "$$\n", + "\\mathbb{F}[Z] = \\mathbb{E}_p[Z] = \\sum\\limits_{\\omega\\in\\Omega} p_{\\omega} z_{\\omega}.\n", + "$$" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct Expectation <: AbstractRiskMeasure end\n", + "\n", + "function primal_risk(::Expectation, Z::Vector{<:Real}, p::Vector{Float64})\n", + " return sum(p[i] * Z[i] for i in 1:length(p))\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's try it out:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "primal_risk(Expectation(), Z, p)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### WorstCase" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The worst-case risk measure, also called the maximum, is another widely used\n", + "convex risk measure. This risk measure doesn't care about the probability\n", + "vector `p`, only the cost vector `Z`:\n", + "$$\n", + "\\mathbb{F}[Z] = \\max[Z] = \\max\\limits_{\\omega\\in\\Omega} z_{\\omega}.\n", + "$$" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct WorstCase <: AbstractRiskMeasure end\n", + "\n", + "function primal_risk(::WorstCase, Z::Vector{<:Real}, ::Vector{Float64})\n", + " return maximum(Z)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's try it out:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "primal_risk(WorstCase(), Z, p)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Entropic" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A more interesting, and less widely used risk measure is the entropic risk\n", + "measure. The entropic risk measure is parameterized by a value $\\gamma > 0$,\n", + "and computes the risk of a random variable as:\n", + "$$\n", + "\\mathbb{F}_\\gamma[Z] = \\frac{1}{\\gamma}\\log\\left(\\mathbb{E}_p[e^{\\gamma Z}]\\right) = \\frac{1}{\\gamma}\\log\\left(\\sum\\limits_{\\omega\\in\\Omega}p_{\\omega} e^{\\gamma z_{\\omega}}\\right).\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Homework challenge**\n", + ">\n", + "> Prove that the entropic risk measure satisfies the three axioms of a\n", + "> convex risk measure." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct Entropic <: AbstractRiskMeasure\n", + " γ::Float64\n", + " function Entropic(γ)\n", + " if !(γ > 0)\n", + " throw(DomainError(γ, \"Entropic risk measure must have γ > 0.\"))\n", + " end\n", + " return new(γ)\n", + " end\n", + "end\n", + "\n", + "function primal_risk(F::Entropic, Z::Vector{<:Real}, p::Vector{Float64})\n", + " return 1 / F.γ * log(sum(p[i] * exp(F.γ * Z[i]) for i in 1:length(p)))\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> `exp(x)` overflows when $x > 709$. Therefore, if we are passed a vector of\n", + "> `Float64`, use arbitrary precision arithmetic with `big.(Z)`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function primal_risk(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})\n", + " return Float64(primal_risk(F, big.(Z), p))\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's try it out for different values of $\\gamma$:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1_000.0]\n", + " println(\"γ = $(γ), F[Z] = \", primal_risk(Entropic(γ), Z, p))\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> The entropic has two extremes. As $\\gamma \\rightarrow 0$, the entropic\n", + "> acts like the expectation risk measure, and as $\\gamma \\rightarrow \\infty$,\n", + "> the entropic acts like the worst-case risk measure." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Computing risk measures this way works well for computing the primal value.\n", + "However, there isn't an obvious way to compute a subgradient of the\n", + "risk-averse cost-to-go function, which we need for our cut calculation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There is a nice solution to this problem, and that is to use the dual\n", + "representation of a risk measure, instead of the primal." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Dual risk measures" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Convex risk measures have a dual representation as follows:\n", + "$$\n", + "\\mathbb{F}[Z] = \\sup\\limits_{q \\in\\mathcal{M}(p)} \\mathbb{E}_q[Z] - \\alpha(p, q),\n", + "$$\n", + "where $\\alpha$ is a concave function that maps the probability vectors $p$ and\n", + "$q$ to a real number, and $\\mathcal{M}(p) \\subseteq \\mathcal{P}$ is a convex\n", + "subset of the probability simplex:\n", + "$$\n", + "\\mathcal{P} = \\{p \\ge 0\\;|\\;\\sum\\limits_{\\omega\\in\\Omega}p_{\\omega} = 1\\}.\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The dual of a convex risk measure can be interpreted as taking the expectation\n", + "of the random variable $Z$ with respect to the worst probability vector $q$\n", + "that lies within the set $\\mathcal{M}$, less some concave penalty term\n", + "$\\alpha(p, q)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If we define a function `dual_risk_inner` that computes `q` and `α`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "\"\"\"\n", + " dual_risk_inner(\n", + " F::AbstractRiskMeasure, Z::Vector{Float64}, p::Vector{Float64}\n", + " )::Tuple{Vector{Float64},Float64}\n", + "\n", + "Return a tuple formed by the worst-case probability vector `q` and the\n", + "corresponding evaluation `α(p, q)`.\n", + "\"\"\"\n", + "function dual_risk_inner end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "then we can write a generic `dual_risk` function as:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function dual_risk(\n", + " F::AbstractRiskMeasure,\n", + " Z::Vector{Float64},\n", + " p::Vector{Float64},\n", + ")\n", + " q, α = dual_risk_inner(F, Z, p)\n", + " return sum(q[i] * Z[i] for i in 1:length(q)) - α\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Expectation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For the expectation risk measure, $\\mathcal{M}(p) = \\{p\\}$, and\n", + "$\\alpha(\\cdot, \\cdot) = 0$. Therefore:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function dual_risk_inner(::Expectation, ::Vector{Float64}, p::Vector{Float64})\n", + " return p, 0.0\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can check we get the same result as the primal version:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dual_risk(Expectation(), Z, p) == primal_risk(Expectation(), Z, p)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Worst-case" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For the worst-case risk measure, $\\mathcal{M}(p) = \\mathcal{P}$, and\n", + "$\\alpha(\\cdot, \\cdot) = 0$. Therefore, the dual representation just puts\n", + "all of the probability weight on the maximum outcome:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function dual_risk_inner(::WorstCase, Z::Vector{Float64}, ::Vector{Float64})\n", + " q = zeros(length(Z))\n", + " _, index = findmax(Z)\n", + " q[index] = 1.0\n", + " return q, 0.0\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can check we get the same result as the primal version:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dual_risk(WorstCase(), Z, p) == primal_risk(WorstCase(), Z, p)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Entropic" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For the entropic risk measure, $\\mathcal{M}(p) = \\mathcal{P}$, and:\n", + "$$\n", + "\\alpha(p, q) = \\frac{1}{\\gamma}\\sum\\limits_{\\omega\\in\\Omega} q_\\omega \\log\\left(\\frac{q_\\omega}{p_{\\omega}}\\right).\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One way to solve the dual problem is to explicitly solve a nonlinear\n", + "optimization problem:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})\n", + " N = length(p)\n", + " model = JuMP.Model(Ipopt.Optimizer)\n", + " JuMP.set_silent(model)\n", + " # For this problem, the solve is more accurate if we turn off problem\n", + " # scaling.\n", + " JuMP.set_optimizer_attribute(model, \"nlp_scaling_method\", \"none\")\n", + " JuMP.@variable(model, 0 <= q[1:N] <= 1)\n", + " JuMP.@constraint(model, sum(q) == 1)\n", + " JuMP.@NLexpression(\n", + " model,\n", + " α,\n", + " 1 / F.γ * sum(q[i] * log(q[i] / p[i]) for i in 1:N),\n", + " )\n", + " JuMP.@NLobjective(model, Max, sum(q[i] * Z[i] for i in 1:N) - α)\n", + " JuMP.optimize!(model)\n", + " return JuMP.value.(q), JuMP.value(α)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can check we get the same result as the primal version:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]\n", + " primal = primal_risk(Entropic(γ), Z, p)\n", + " dual = dual_risk(Entropic(γ), Z, p)\n", + " success = primal ≈ dual ? \"✓\" : \"×\"\n", + " println(\"$(success) γ = $(γ), primal = $(primal), dual = $(dual)\")\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> This method of solving the dual problem \"on-the-side\" is used by SDDP.jl\n", + "> for a number of risk measures, including a distributionally robust risk\n", + "> measure with the Wasserstein distance. Check out all the risk measures\n", + "> that SDDP.jl supports in Add a risk measure." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The \"on-the-side\" method is very general, and it lets us incorporate any\n", + "convex risk measure into SDDP. However, this comes at an increased\n", + "computational cost and potential numerical issues (e.g., not converging to the\n", + "exact solution)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "However, for the entropic risk measure, [Dowson, Morton, and Pagnoncelli (2020)](http://www.optimization-online.org/DB_HTML/2020/08/7984.html)\n", + "derive the following closed form solution for $q^*$:\n", + "$$\n", + "q_\\omega^* = \\frac{p_{\\omega} e^{\\gamma z_{\\omega}}}{\\sum\\limits_{\\varphi \\in \\Omega} p_{\\varphi} e^{\\gamma z_{\\varphi}}}.\n", + "$$\n", + "This is faster because we don't need to use Ipopt, and it avoids some of the\n", + "numerical issues associated with solving a nonlinear program." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})\n", + " q, α = zeros(length(p)), big(0.0)\n", + " peγz = p .* exp.(F.γ .* big.(Z))\n", + " sum_peγz = sum(peγz)\n", + " for i in 1:length(q)\n", + " big_q = peγz[i] / sum_peγz\n", + " α += big_q * log(big_q / p[i])\n", + " q[i] = Float64(big_q)\n", + " end\n", + " return q, Float64(α / F.γ)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> Again, note that we use `big` to avoid introducing overflow errors, before\n", + "> explicitly casting back to `Float64` for the values we return." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can check we get the same result as the primal version:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]\n", + " primal = primal_risk(Entropic(γ), Z, p)\n", + " dual = dual_risk(Entropic(γ), Z, p)\n", + " success = primal ≈ dual ? \"✓\" : \"×\"\n", + " println(\"$(success) γ = $(γ), primal = $(primal), dual = $(dual)\")\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Risk-averse subgradients" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We ended the section on primal risk measures by explaining how we couldn't\n", + "use the primal risk measure in the cut calculation because we needed some way\n", + "of computing a risk-averse subgradient:\n", + "$$\n", + "\\theta \\ge \\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right] + \\frac{d}{dx^\\prime}\\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right]^\\top (x^\\prime - x^\\prime_k).\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The reason we use the dual representation is because of the following theorem,\n", + "which explains how to compute a risk-averse gradient." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **The risk-averse subgradient theorem**\n", + ">\n", + "> Let $\\omega \\in \\Omega$ index a random vector with finite support and with\n", + "> nominal probability mass function, $p \\in \\mathcal{P}$, which satisfies\n", + "> $p > 0$.\n", + "\n", + " Consider a convex risk measure, $\\mathbb{F}$, with a convex risk set,\n", + " $\\mathcal{M}(p)$, so that $\\mathbb{F}$ can be expressed as the dual form.\n", + "\n", + " Let $V(x,\\omega)$ be convex with respect to $x$ for all fixed\n", + " $\\omega\\in\\Omega$, and let $\\lambda(\\tilde{x}, \\omega)$ be a subgradient\n", + " of $V(x,\\omega)$ with respect to $x$ at $x = \\tilde{x}$ for each\n", + " $\\omega \\in \\Omega$.\n", + "\n", + " Then, $\\sum_{\\omega\\in\\Omega}q^*_{\\omega} \\lambda(\\tilde{x},\\omega)$ is a\n", + " subgradient of $\\mathbb{F}[V(x,\\omega)]$ at $\\tilde{x}$, where\n", + " $$\n", + " q^* \\in \\argmax_{q \\in \\mathcal{M}(p)}\\left\\{{\\mathbb{E}}_q[V(\\tilde{x},\\omega)] - \\alpha(p, q)\\right\\}.\n", + " $$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This theorem can be a little hard to unpack, so let's see an example:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function dual_risk_averse_subgradient(\n", + " V::Function,\n", + " # Use automatic differentiation to compute the gradient of V w.r.t. x,\n", + " # given a fixed ω.\n", + " λ::Function = (x, ω) -> ForwardDiff.gradient(x -> V(x, ω), x);\n", + " F::AbstractRiskMeasure,\n", + " Ω::Vector,\n", + " p::Vector{Float64},\n", + " x̃::Vector{Float64},\n", + ")\n", + " # Evaluate the function at x=x̃ for all ω ∈ Ω.\n", + " V_ω = [V(x̃, ω) for ω in Ω]\n", + " # Solve the dual problem to obtain an optimal q^*.\n", + " q, α = dual_risk_inner(F, V_ω, p)\n", + " # Compute the risk-averse subgradient by taking the expectation of the\n", + " # subgradients w.r.t. q^*.\n", + " dVdx = sum(q[i] * λ(x̃, ω) for (i, ω) in enumerate(Ω))\n", + " return dVdx\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can compare the subgradient obtained with the dual form against the\n", + "automatic differentiation of the `primal_risk` function." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function primal_risk_averse_subgradient(\n", + " V::Function;\n", + " F::AbstractRiskMeasure,\n", + " Ω::Vector,\n", + " p::Vector{Float64},\n", + " x̃::Vector{Float64},\n", + ")\n", + " inner(x) = primal_risk(F, [V(x, ω) for ω in Ω], p)\n", + " return ForwardDiff.gradient(inner, x̃)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "As our example function, we use:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "V(x, ω) = ω * x[1]^2" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "with:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Ω = [1.0, 2.0, 3.0]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + " and:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "p = [0.3, 0.4, 0.3]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "at the point:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "x̃ = [3.0]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "If $\\mathbb{F}$ is the expectation risk-measure, then:\n", + "$$\n", + "\\mathbb{F}[V(x, \\omega)] = 2 x^2.\n", + "$$\n", + "The function evaluation $x=3$ is $18$ and the subgradient is $12$. Let's check\n", + "we get it right with the dual form:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dual_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "and the primal form:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "primal_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "If $\\mathbb{F}$ is the worst-case risk measure, then:\n", + "$$\n", + "\\mathbb{F}[V(x, \\omega)] = 3 x^2.\n", + "$$\n", + "The function evaluation at $x=3$ is $27$, and the subgradient is $18$. Let's\n", + "check we get it right with the dual form:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dual_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "and the primal form:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "primal_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "If $\\mathbb{F}$ is the entropic risk measure, the math is a little more\n", + "difficult to derive analytically. However, we can check against our primal\n", + "version:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]\n", + " dual =\n", + " dual_risk_averse_subgradient(V; F = Entropic(γ), Ω = Ω, p = p, x̃ = x̃)\n", + " primal = primal_risk_averse_subgradient(\n", + " V;\n", + " F = Entropic(γ),\n", + " Ω = Ω,\n", + " p = p,\n", + " x̃ = x̃,\n", + " )\n", + " success = primal ≈ dual ? \"✓\" : \"×\"\n", + " println(\"$(success) γ = $(γ), primal = $(primal), dual = $(dual)\")\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Uh oh! What happened with the last line? It looks our `primal_risk_averse_subgradient`\n", + "encountered an error and returned a subgradient of `NaN`. This is because of\n", + "the overflow issue with `exp(x)`. However, we can be confident that our dual\n", + "method of computing the risk-averse subgradient is both correct and more\n", + "numerically robust than the primal version." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> As another sanity check, notice how as $\\gamma \\rightarrow 0$, we tend\n", + "> toward the solution of the expectation risk-measure `[12]`, and as\n", + "> $\\gamma \\rightarrow \\infty$, we tend toward the solution of the worse-case\n", + "> risk measure `[18]`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "# Risk-averse decision rules: Part II" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Why is the risk-averse subgradient theorem helpful? Using the dual\n", + "representation of a convex risk measure, we can re-write the cut:\n", + "$$\n", + "\\theta \\ge \\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right] + \\frac{d}{dx^\\prime}\\mathbb{F}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi)\\right]^\\top (x^\\prime - x^\\prime_k),\\quad k=1,\\ldots,K\n", + "$$\n", + "as:\n", + "$$\n", + "\\theta \\ge \\mathbb{E}_{q_k}\\left[V_j^k(x^\\prime_k, \\varphi) + \\frac{d}{dx^\\prime}V_j^k(x^\\prime_k, \\varphi)^\\top (x^\\prime - x^\\prime_k)\\right] - \\alpha(p, q_k),\\quad k=1,\\ldots,K,\n", + "$$\n", + "where $q_k = \\mathrm{arg}\\sup\\limits_{q \\in\\mathcal{M}(p)} \\mathbb{E}_q[V_j^k(x_k^\\prime, \\varphi)] - \\alpha(p, q)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Therefore, we can formulate a risk-averse decision rule as:\n", + "$$\n", + "\\begin{aligned}\n", + "V_i^K(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\theta\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x \\\\\n", + "& \\theta \\ge \\mathbb{E}_{q_k}\\left[V_j^k(x^\\prime_k, \\varphi) + \\frac{d}{dx^\\prime}V_j^k(x^\\prime_k, \\varphi)^\\top (x^\\prime - x^\\prime_k)\\right] - \\alpha(p, q_k),\\quad k=1,\\ldots,K \\\\\n", + "& \\theta \\ge M.\n", + "\\end{aligned}\n", + "$$\n", + "where $q_k = \\mathrm{arg}\\sup\\limits_{q \\in\\mathcal{M}(p)} \\mathbb{E}_q[V_j^k(x_k^\\prime, \\varphi)] - \\alpha(p, q)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Thus, to implement risk-averse SDDP, all we need to do is modify the backward\n", + "pass to include this calculation of $q_k$, form the cut using $q_k$ instead of\n", + "$p$, and subtract the penalty term $\\alpha(p, q_k)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we're ready to implement our risk-averse version of SDDP." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As a prerequisite, we need most of the code from Introductory theory." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "

\n", + "Click to view code from the tutorial \"Introductory theory\"." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct State\n", + " in::JuMP.VariableRef\n", + " out::JuMP.VariableRef\n", + "end\n", + "\n", + "struct Uncertainty\n", + " parameterize::Function\n", + " Ω::Vector{Any}\n", + " P::Vector{Float64}\n", + "end\n", + "\n", + "struct Node\n", + " subproblem::JuMP.Model\n", + " states::Dict{Symbol,State}\n", + " uncertainty::Uncertainty\n", + " cost_to_go::JuMP.VariableRef\n", + "end\n", + "\n", + "struct PolicyGraph\n", + " nodes::Vector{Node}\n", + " arcs::Vector{Dict{Int,Float64}}\n", + "end\n", + "\n", + "function Base.show(io::IO, model::PolicyGraph)\n", + " println(io, \"A policy graph with $(length(model.nodes)) nodes\")\n", + " println(io, \"Arcs:\")\n", + " for (from, arcs) in enumerate(model.arcs)\n", + " for (to, probability) in arcs\n", + " println(io, \" $(from) => $(to) w.p. $(probability)\")\n", + " end\n", + " end\n", + " return\n", + "end\n", + "\n", + "function PolicyGraph(\n", + " subproblem_builder::Function;\n", + " graph::Vector{Dict{Int,Float64}},\n", + " lower_bound::Float64,\n", + " optimizer,\n", + ")\n", + " nodes = Node[]\n", + " for t in 1:length(graph)\n", + " model = JuMP.Model(optimizer)\n", + " states, uncertainty = subproblem_builder(model, t)\n", + " JuMP.@variable(model, cost_to_go >= lower_bound)\n", + " obj = JuMP.objective_function(model)\n", + " JuMP.@objective(model, Min, obj + cost_to_go)\n", + " if length(graph[t]) == 0\n", + " JuMP.fix(cost_to_go, 0.0; force = true)\n", + " end\n", + " push!(nodes, Node(model, states, uncertainty, cost_to_go))\n", + " end\n", + " return PolicyGraph(nodes, graph)\n", + "end\n", + "\n", + "function sample_uncertainty(uncertainty::Uncertainty)\n", + " r = rand()\n", + " for (p, ω) in zip(uncertainty.P, uncertainty.Ω)\n", + " r -= p\n", + " if r < 0.0\n", + " return ω\n", + " end\n", + " end\n", + " return error(\"We should never get here because P should sum to 1.0.\")\n", + "end\n", + "\n", + "function sample_next_node(model::PolicyGraph, current::Int)\n", + " if length(model.arcs[current]) == 0\n", + " return nothing\n", + " else\n", + " r = rand()\n", + " for (to, probability) in model.arcs[current]\n", + " r -= probability\n", + " if r < 0.0\n", + " return to\n", + " end\n", + " end\n", + " return nothing\n", + " end\n", + "end\n", + "\n", + "function forward_pass(model::PolicyGraph, io::IO = stdout)\n", + " incoming_state =\n", + " Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states)\n", + " simulation_cost = 0.0\n", + " trajectory = Tuple{Int,Dict{Symbol,Float64}}[]\n", + " t = 1\n", + " while t !== nothing\n", + " node = model.nodes[t]\n", + " ω = sample_uncertainty(node.uncertainty)\n", + " node.uncertainty.parameterize(ω)\n", + " for (k, v) in incoming_state\n", + " JuMP.fix(node.states[k].in, v; force = true)\n", + " end\n", + " JuMP.optimize!(node.subproblem)\n", + " if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL\n", + " error(\"Something went terribly wrong!\")\n", + " end\n", + " outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states)\n", + " stage_cost =\n", + " JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go)\n", + " simulation_cost += stage_cost\n", + " incoming_state = outgoing_state\n", + " push!(trajectory, (t, outgoing_state))\n", + " t = sample_next_node(model, t)\n", + " end\n", + " return trajectory, simulation_cost\n", + "end\n", + "\n", + "function upper_bound(model::PolicyGraph; replications::Int)\n", + " simulations = [forward_pass(model, devnull) for i in 1:replications]\n", + " z = [s[2] for s in simulations]\n", + " μ = Statistics.mean(z)\n", + " tσ = 1.96 * Statistics.std(z) / sqrt(replications)\n", + " return μ, tσ\n", + "end\n", + "\n", + "function lower_bound(model::PolicyGraph)\n", + " node = model.nodes[1]\n", + " bound = 0.0\n", + " for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω)\n", + " node.uncertainty.parameterize(ω)\n", + " JuMP.optimize!(node.subproblem)\n", + " bound += p * JuMP.objective_value(node.subproblem)\n", + " end\n", + " return bound\n", + "end\n", + "\n", + "function evaluate_policy(\n", + " model::PolicyGraph;\n", + " node::Int,\n", + " incoming_state::Dict{Symbol,Float64},\n", + " random_variable,\n", + ")\n", + " the_node = model.nodes[node]\n", + " the_node.uncertainty.parameterize(random_variable)\n", + " for (k, v) in incoming_state\n", + " JuMP.fix(the_node.states[k].in, v; force = true)\n", + " end\n", + " JuMP.optimize!(the_node.subproblem)\n", + " return Dict(\n", + " k => JuMP.value.(v) for\n", + " (k, v) in JuMP.object_dictionary(the_node.subproblem)\n", + " )\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "

" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, we need to modify the backward pass to compute the cuts using the\n", + "risk-averse subgradient theorem:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function backward_pass(\n", + " model::PolicyGraph,\n", + " trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}},\n", + " io::IO = stdout;\n", + " risk_measure::AbstractRiskMeasure,\n", + ")\n", + " println(io, \"| Backward pass\")\n", + " for i in reverse(1:length(trajectory))\n", + " index, outgoing_states = trajectory[i]\n", + " node = model.nodes[index]\n", + " println(io, \"| | Visiting node $(index)\")\n", + " if length(model.arcs[index]) == 0\n", + " continue\n", + " end\n", + " # =====================================================================\n", + " # New! Create vectors to store the cut expressions, V(x,ω) and p:\n", + " cut_expressions, V_ω, p = JuMP.AffExpr[], Float64[], Float64[]\n", + " # =====================================================================\n", + " for (j, P_ij) in model.arcs[index]\n", + " next_node = model.nodes[j]\n", + " for (k, v) in outgoing_states\n", + " JuMP.fix(next_node.states[k].in, v; force = true)\n", + " end\n", + " for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω)\n", + " next_node.uncertainty.parameterize(φ)\n", + " JuMP.optimize!(next_node.subproblem)\n", + " V = JuMP.objective_value(next_node.subproblem)\n", + " dVdx = Dict(\n", + " k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states\n", + " )\n", + " # =============================================================\n", + " # New! Construct and append the expression\n", + " # `V_j^K(x_k, φ) + dVdx_j^K(x'_k, φ)ᵀ(x - x_k)` to the list of\n", + " # cut expressions.\n", + " push!(\n", + " cut_expressions,\n", + " JuMP.@expression(\n", + " node.subproblem,\n", + " V + sum(\n", + " dVdx[k] * (x.out - outgoing_states[k]) for\n", + " (k, x) in node.states\n", + " ),\n", + " )\n", + " )\n", + " # Add the objective value to Z:\n", + " push!(V_ω, V)\n", + " # Add the probability to p:\n", + " push!(p, P_ij * pφ)\n", + " # =============================================================\n", + " end\n", + " end\n", + " # =====================================================================\n", + " # New! Using the solutions in V_ω, compute q and α:\n", + " q, α = dual_risk_inner(risk_measure, V_ω, p)\n", + " println(io, \"| | | Z = \", Z)\n", + " println(io, \"| | | p = \", p)\n", + " println(io, \"| | | q = \", q)\n", + " println(io, \"| | | α = \", α)\n", + " # Then add the cut:\n", + " c = JuMP.@constraint(\n", + " node.subproblem,\n", + " node.cost_to_go >=\n", + " sum(q[i] * cut_expressions[i] for i in 1:length(q)) - α\n", + " )\n", + " # =====================================================================\n", + " println(io, \"| | | Adding cut : \", c)\n", + " end\n", + " return nothing\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We also need to update the `train` loop of SDDP to pass a risk measure to the\n", + "backward pass:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function train(\n", + " model::PolicyGraph;\n", + " iteration_limit::Int,\n", + " replications::Int,\n", + " # =========================================================================\n", + " # New! Add a risk_measure argument\n", + " risk_measure::AbstractRiskMeasure,\n", + " # =========================================================================\n", + " io::IO = stdout,\n", + ")\n", + " for i in 1:iteration_limit\n", + " println(io, \"Starting iteration $(i)\")\n", + " outgoing_states, _ = forward_pass(model, io)\n", + " # =====================================================================\n", + " # New! Pass the risk measure to the backward pass.\n", + " backward_pass(model, outgoing_states, io; risk_measure = risk_measure)\n", + " # =====================================================================\n", + " println(io, \"| Finished iteration\")\n", + " println(io, \"| | lower_bound = \", lower_bound(model))\n", + " end\n", + " μ, tσ = upper_bound(model; replications = replications)\n", + " println(io, \"Upper bound = $(μ) ± $(tσ)\")\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Risk-averse bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> This section is important." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "When we had a risk-neutral policy (i.e., we only used the expectation risk\n", + "measure), we discussed how we could form valid lower and upper bounds." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The upper bound is still valid as a Monte Carlo simulation of the expected\n", + "cost of the policy. (Although this upper bound doesn't capture the change in\n", + "the policy we wanted to achieve, namely that the impact of the worst outcomes\n", + "were reduced.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "However, if we use a different risk measure, the lower bound is no longer\n", + "valid!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can still calculate a \"lower bound\" as the objective of the first-stage\n", + "approximated subproblem, and this will converge to a finite value. However,\n", + "we can't meaningfully interpret it as a bound with respect to the optimal\n", + "policy. Therefore, it's best to just ignore the lower bound when training a\n", + "risk-averse policy." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Example: risk-averse hydro-thermal scheduling" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now it's time for an example. We create the same problem as\n", + "Introductory theory:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = PolicyGraph(;\n", + " graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()],\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, t\n", + " JuMP.set_silent(subproblem)\n", + " JuMP.@variable(subproblem, volume_in == 200)\n", + " JuMP.@variable(subproblem, 0 <= volume_out <= 200)\n", + " states = Dict(:volume => State(volume_in, volume_out))\n", + " JuMP.@variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " JuMP.@constraints(\n", + " subproblem,\n", + " begin\n", + " volume_out == volume_in + inflow - hydro_generation - hydro_spill\n", + " demand_constraint, thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + " fuel_cost = [50.0, 100.0, 150.0]\n", + " JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation)\n", + " uncertainty =\n", + " Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " return states, uncertainty\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Then we train a risk-averse policy, passing a risk measure to `train`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "train(\n", + " model;\n", + " iteration_limit = 3,\n", + " replications = 100,\n", + " risk_measure = Entropic(1.0),\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Finally, evaluate the decision rule:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "evaluate_policy(\n", + " model;\n", + " node = 1,\n", + " incoming_state = Dict(:volume => 150.0),\n", + " random_variable = 75,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> For this trivial example, the risk-averse policy isn't very different from\n", + "> the policy obtained using the expectation risk-measure. If you try it on\n", + "> some bigger/more interesting problems, you should see the expected cost\n", + "> increase, and the upper tail of the policy decrease." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/explanation/risk.jl b/previews/PR826/explanation/risk.jl new file mode 100644 index 0000000000..2b6ede5186 --- /dev/null +++ b/previews/PR826/explanation/risk.jl @@ -0,0 +1,940 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Risk aversion + +# In [Introductory theory](@ref), we implemented a basic version of the +# SDDP algorithm. This tutorial extends that implementation to add +# **risk-aversion**. + +# **Packages** +# +# This tutorial uses the following packages. For clarity, we call +# `import PackageName` so that we must prefix `PackageName.` to all functions +# and structs provided by that package. Everything not prefixed is either part +# of base Julia, or we wrote it. + +import ForwardDiff +import HiGHS +import Ipopt +import JuMP +import Statistics + +# ## Risk aversion: what and why? + +# Often, the agents making decisions in complex systems are **risk-averse**, +# that is, they care more about avoiding very bad outcomes, than they do about +# having a good average outcome. + +# As an example, consumers in a hydro-thermal problem may be willing to pay a +# slightly higher electricity price on average, if it means that there is a +# lower probability of blackouts. + +# Risk aversion in multistage stochastic programming has been well studied in +# the academic literature, and is widely used in production implementations +# around the world. + +# ## Risk measures + +# One way to add risk aversion to models is to use a **risk measure**. A risk +# measure is a function that maps a random variable to a real number. +# +# You are probably already familiar with lots of different risk measures. For +# example, the mean, median, mode, and maximum are all risk measures. +# +# We call the act of applying a risk measure to a random variable "computing the +# risk" of a random variable. +# +# To keep things simple, and because we need it for SDDP, we restrict our +# attention to random variables $Z$ with a finite sample space $\Omega$ +# and positive probabilities $p_{\omega}$ for all $\omega \in \Omega$. We denote +# the realizations of $Z$ by $Z(\omega) = z_{\omega}$. + +# A risk measure, $\mathbb{F}[Z]$, is a **convex risk measure** if it satisfies +# the following axioms: +# +# **Axiom 1: monotonicity** +# +# Given two random variables $Z_1$ and $Z_2$, with $Z_1 \le Z_2$ almost surely, +# then $\mathbb{F}[Z_1] \le F[Z_2]$. +# +# **Axiom 2: translation equivariance** +# +# Given two random variables $Z_1$ and $Z_2$, then for all $a \in \mathbb{R}$, +# $\mathbb{F}[Z + a] = \mathbb{F}[Z] + a$. +# +# **Axiom 3: convexity** +# +# Given two random variables $Z_1$ and $Z_2$, then for all $a \in [0, 1]$, +# ```math +# \mathbb{F}[a Z_1 + (1 - a) Z_2] \le a \mathbb{F}[Z_1] + (1-a)\mathbb{F}[Z_2]. +# ``` + +# Now we know what a risk measure is, let's see how we can use them to form +# risk-averse decision rules. + +# ## Risk-averse decision rules: Part I + +# We started this tutorial by explaining that we are interested in risk aversion +# because some agents are risk-averse. What that really means, is that they +# want a policy that is also risk-averse. The question then becomes, how do we +# create risk-averse decision rules and policies? + +# Recall from [Introductory theory](@ref) that we can form an optimal +# decision rule using the recursive formulation: +# ```math +# \begin{aligned} +# V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x, +# \end{aligned} +# ``` +# where our decision rule, $\pi_i(x, \omega)$, solves this optimization problem +# and returns a $u^*$ corresponding to an optimal solution. + +# If we can replace the expectation operator $\mathbb{E}$ with another (more +# risk-averse) risk measure $\mathbb{F}$, then our decision rule will attempt to +# choose a control decision now that minimizes the risk of the future costs, as +# opposed to the expectation of the future costs. This makes our decisions more +# risk-averse, because we care more about the worst outcomes than we do about +# the average. + +# Therefore, we can form a risk-averse decision rule using the formulation: + +# ```math +# \begin{aligned} +# V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x. +# \end{aligned} +# ``` + +# To convert this problem into a tractable equivalent, we apply Kelley's +# algorithm to the risk-averse cost-to-go term +# $\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]$, to +# obtain the approximated problem: + +# ```math +# \begin{aligned} +# V_i^K(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \theta\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x \\ +# & \theta \ge \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right] + \frac{d}{dx^\prime}\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]^\top (x^\prime - x^\prime_k)\quad k=1,\ldots,K. +# \end{aligned} +# ``` + +# !!! warning +# Note how we need to explicitly compute a risk-averse subgradient! (We +# need a subgradient because the function might not be differentiable.) When +# constructing cuts with the expectation operator in [Introductory theory](@ref), +# we implicitly used the law of total expectation to combine the two +# expectations; we can't do that for a general risk measure. + +# !!! tip "Homework challenge" +# If it's not obvious why we can use Kelley's here, try to use the axioms of +# a convex risk measure to show that +# $\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]$ +# is a convex function w.r.t. $x^\prime$ if $V_j$ is also a convex function. + +# Our challenge is now to find a way to compute the risk-averse cost-to-go +# function $\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]$, +# and a way to compute a subgradient of the risk-averse cost-to-go function +# with respect to $x^\prime$. + +# ## Primal risk measures + +# Now we know what a risk measure is, and how we will use it, let's implement +# some code to see how we can compute the risk of some random variables. + +# !!! note +# We're going to start by implementing the **primal** version of each risk +# measure. We implement the **dual** version in the next section. + +# First, we need some data: + +Z = [1.0, 2.0, 3.0, 4.0] + +# with probabilities: + +p = [0.1, 0.2, 0.4, 0.3] + +# We're going to implement a number of different risk measures, so to leverage +# Julia's multiple dispatch, we create an abstract type: + +abstract type AbstractRiskMeasure end + +# and function to overload: + +""" + primal_risk(F::AbstractRiskMeasure, Z::Vector{<:Real}, p::Vector{Float64}) + +Use `F` to compute the risk of the random variable defined by a vector of costs +`Z` and non-zero probabilities `p`. +""" +function primal_risk end + +# !!! note +# We want `Vector{<:Real}` instead of `Vector{Float64}` because we're going +# to automatically differentiate this function in the next section. + +# ### Expectation + +# The expectation, $\mathbb{E}$, also called the mean or the average, is the +# most widely used convex risk measure. The expectation of a random variable is +# just the sum of $Z$ weighted by the probability: +# ```math +# \mathbb{F}[Z] = \mathbb{E}_p[Z] = \sum\limits_{\omega\in\Omega} p_{\omega} z_{\omega}. +# ``` + +struct Expectation <: AbstractRiskMeasure end + +function primal_risk(::Expectation, Z::Vector{<:Real}, p::Vector{Float64}) + return sum(p[i] * Z[i] for i in 1:length(p)) +end + +# Let's try it out: + +primal_risk(Expectation(), Z, p) + +# ### WorstCase + +# The worst-case risk measure, also called the maximum, is another widely used +# convex risk measure. This risk measure doesn't care about the probability +# vector `p`, only the cost vector `Z`: +# ```math +# \mathbb{F}[Z] = \max[Z] = \max\limits_{\omega\in\Omega} z_{\omega}. +# ``` + +struct WorstCase <: AbstractRiskMeasure end + +function primal_risk(::WorstCase, Z::Vector{<:Real}, ::Vector{Float64}) + return maximum(Z) +end + +# Let's try it out: + +primal_risk(WorstCase(), Z, p) + +# ### Entropic + +# A more interesting, and less widely used risk measure is the entropic risk +# measure. The entropic risk measure is parameterized by a value $\gamma > 0$, +# and computes the risk of a random variable as: +# ```math +# \mathbb{F}_\gamma[Z] = \frac{1}{\gamma}\log\left(\mathbb{E}_p[e^{\gamma Z}]\right) = \frac{1}{\gamma}\log\left(\sum\limits_{\omega\in\Omega}p_{\omega} e^{\gamma z_{\omega}}\right). +# ``` + +# !!! tip "Homework challenge" +# Prove that the entropic risk measure satisfies the three axioms of a +# convex risk measure. + +struct Entropic <: AbstractRiskMeasure + γ::Float64 + function Entropic(γ) + if !(γ > 0) + throw(DomainError(γ, "Entropic risk measure must have γ > 0.")) + end + return new(γ) + end +end + +function primal_risk(F::Entropic, Z::Vector{<:Real}, p::Vector{Float64}) + return 1 / F.γ * log(sum(p[i] * exp(F.γ * Z[i]) for i in 1:length(p))) +end + +# !!! warning +# `exp(x)` overflows when $x > 709$. Therefore, if we are passed a vector of +# `Float64`, use arbitrary precision arithmetic with `big.(Z)`. + +function primal_risk(F::Entropic, Z::Vector{Float64}, p::Vector{Float64}) + return Float64(primal_risk(F, big.(Z), p)) +end + +# Let's try it out for different values of $\gamma$: + +for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1_000.0] + println("γ = $(γ), F[Z] = ", primal_risk(Entropic(γ), Z, p)) +end + +# !!! info +# The entropic has two extremes. As $\gamma \rightarrow 0$, the entropic +# acts like the expectation risk measure, and as $\gamma \rightarrow \infty$, +# the entropic acts like the worst-case risk measure. + +# Computing risk measures this way works well for computing the primal value. +# However, there isn't an obvious way to compute a subgradient of the +# risk-averse cost-to-go function, which we need for our cut calculation. + +# There is a nice solution to this problem, and that is to use the dual +# representation of a risk measure, instead of the primal. + +# ## Dual risk measures + +# Convex risk measures have a dual representation as follows: +# ```math +# \mathbb{F}[Z] = \sup\limits_{q \in\mathcal{M}(p)} \mathbb{E}_q[Z] - \alpha(p, q), +# ``` +# where $\alpha$ is a concave function that maps the probability vectors $p$ and +# $q$ to a real number, and $\mathcal{M}(p) \subseteq \mathcal{P}$ is a convex +# subset of the probability simplex: +# ```math +# \mathcal{P} = \{p \ge 0\;|\;\sum\limits_{\omega\in\Omega}p_{\omega} = 1\}. +# ``` + +# The dual of a convex risk measure can be interpreted as taking the expectation +# of the random variable $Z$ with respect to the worst probability vector $q$ +# that lies within the set $\mathcal{M}$, less some concave penalty term +# $\alpha(p, q)$. + +# If we define a function `dual_risk_inner` that computes `q` and `α`: + +""" + dual_risk_inner( + F::AbstractRiskMeasure, Z::Vector{Float64}, p::Vector{Float64} + )::Tuple{Vector{Float64},Float64} + +Return a tuple formed by the worst-case probability vector `q` and the +corresponding evaluation `α(p, q)`. +""" +function dual_risk_inner end + +# then we can write a generic `dual_risk` function as: + +function dual_risk( + F::AbstractRiskMeasure, + Z::Vector{Float64}, + p::Vector{Float64}, +) + q, α = dual_risk_inner(F, Z, p) + return sum(q[i] * Z[i] for i in 1:length(q)) - α +end + +# ### Expectation + +# For the expectation risk measure, $\mathcal{M}(p) = \{p\}$, and +# $\alpha(\cdot, \cdot) = 0$. Therefore: + +function dual_risk_inner(::Expectation, ::Vector{Float64}, p::Vector{Float64}) + return p, 0.0 +end + +# We can check we get the same result as the primal version: + +dual_risk(Expectation(), Z, p) == primal_risk(Expectation(), Z, p) + +# ### Worst-case + +# For the worst-case risk measure, $\mathcal{M}(p) = \mathcal{P}$, and +# $\alpha(\cdot, \cdot) = 0$. Therefore, the dual representation just puts +# all of the probability weight on the maximum outcome: + +function dual_risk_inner(::WorstCase, Z::Vector{Float64}, ::Vector{Float64}) + q = zeros(length(Z)) + _, index = findmax(Z) + q[index] = 1.0 + return q, 0.0 +end + +# We can check we get the same result as the primal version: + +dual_risk(WorstCase(), Z, p) == primal_risk(WorstCase(), Z, p) + +# ### Entropic + +# For the entropic risk measure, $\mathcal{M}(p) = \mathcal{P}$, and: +# ```math +# \alpha(p, q) = \frac{1}{\gamma}\sum\limits_{\omega\in\Omega} q_\omega \log\left(\frac{q_\omega}{p_{\omega}}\right). +# ``` + +# One way to solve the dual problem is to explicitly solve a nonlinear +# optimization problem: + +function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64}) + N = length(p) + model = JuMP.Model(Ipopt.Optimizer) + JuMP.set_silent(model) + ## For this problem, the solve is more accurate if we turn off problem + ## scaling. + JuMP.set_optimizer_attribute(model, "nlp_scaling_method", "none") + JuMP.@variable(model, 0 <= q[1:N] <= 1) + JuMP.@constraint(model, sum(q) == 1) + JuMP.@NLexpression( + model, + α, + 1 / F.γ * sum(q[i] * log(q[i] / p[i]) for i in 1:N), + ) + JuMP.@NLobjective(model, Max, sum(q[i] * Z[i] for i in 1:N) - α) + JuMP.optimize!(model) + return JuMP.value.(q), JuMP.value(α) +end + +# We can check we get the same result as the primal version: + +for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0] + primal = primal_risk(Entropic(γ), Z, p) + dual = dual_risk(Entropic(γ), Z, p) + success = primal ≈ dual ? "✓" : "×" + println("$(success) γ = $(γ), primal = $(primal), dual = $(dual)") +end + +# !!! info +# This method of solving the dual problem "on-the-side" is used by SDDP.jl +# for a number of risk measures, including a distributionally robust risk +# measure with the Wasserstein distance. Check out all the risk measures +# that SDDP.jl supports in [Add a risk measure](@ref). + +# The "on-the-side" method is very general, and it lets us incorporate any +# convex risk measure into SDDP. However, this comes at an increased +# computational cost and potential numerical issues (e.g., not converging to the +# exact solution). + +# However, for the entropic risk measure, [Dowson, Morton, and Pagnoncelli (2020)](http://www.optimization-online.org/DB_HTML/2020/08/7984.html) +# derive the following closed form solution for $q^*$: +# ```math +# q_\omega^* = \frac{p_{\omega} e^{\gamma z_{\omega}}}{\sum\limits_{\varphi \in \Omega} p_{\varphi} e^{\gamma z_{\varphi}}}. +# ``` +# This is faster because we don't need to use Ipopt, and it avoids some of the +# numerical issues associated with solving a nonlinear program. + +function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64}) + q, α = zeros(length(p)), big(0.0) + peγz = p .* exp.(F.γ .* big.(Z)) + sum_peγz = sum(peγz) + for i in 1:length(q) + big_q = peγz[i] / sum_peγz + α += big_q * log(big_q / p[i]) + q[i] = Float64(big_q) + end + return q, Float64(α / F.γ) +end + +# !!! warning +# Again, note that we use `big` to avoid introducing overflow errors, before +# explicitly casting back to `Float64` for the values we return. + +# We can check we get the same result as the primal version: + +for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0] + primal = primal_risk(Entropic(γ), Z, p) + dual = dual_risk(Entropic(γ), Z, p) + success = primal ≈ dual ? "✓" : "×" + println("$(success) γ = $(γ), primal = $(primal), dual = $(dual)") +end + +# ## Risk-averse subgradients + +# We ended the section on primal risk measures by explaining how we couldn't +# use the primal risk measure in the cut calculation because we needed some way +# of computing a risk-averse subgradient: +# ```math +# \theta \ge \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right] + \frac{d}{dx^\prime}\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]^\top (x^\prime - x^\prime_k). +# ``` + +# The reason we use the dual representation is because of the following theorem, +# which explains how to compute a risk-averse gradient. + +# !!! info "The risk-averse subgradient theorem" +# Let $\omega \in \Omega$ index a random vector with finite support and with +# nominal probability mass function, $p \in \mathcal{P}$, which satisfies +# $p > 0$. +# +# Consider a convex risk measure, $\mathbb{F}$, with a convex risk set, +# $\mathcal{M}(p)$, so that $\mathbb{F}$ can be expressed as the dual form. +# +# Let $V(x,\omega)$ be convex with respect to $x$ for all fixed +# $\omega\in\Omega$, and let $\lambda(\tilde{x}, \omega)$ be a subgradient +# of $V(x,\omega)$ with respect to $x$ at $x = \tilde{x}$ for each +# $\omega \in \Omega$. +# +# Then, $\sum_{\omega\in\Omega}q^*_{\omega} \lambda(\tilde{x},\omega)$ is a +# subgradient of $\mathbb{F}[V(x,\omega)]$ at $\tilde{x}$, where +# ```math +# q^* \in \argmax_{q \in \mathcal{M}(p)}\left\{{\mathbb{E}}_q[V(\tilde{x},\omega)] - \alpha(p, q)\right\}. +# ``` + +# This theorem can be a little hard to unpack, so let's see an example: + +function dual_risk_averse_subgradient( + V::Function, + ## Use automatic differentiation to compute the gradient of V w.r.t. x, + ## given a fixed ω. + λ::Function = (x, ω) -> ForwardDiff.gradient(x -> V(x, ω), x); + F::AbstractRiskMeasure, + Ω::Vector, + p::Vector{Float64}, + x̃::Vector{Float64}, +) + ## Evaluate the function at x=x̃ for all ω ∈ Ω. + V_ω = [V(x̃, ω) for ω in Ω] + ## Solve the dual problem to obtain an optimal q^*. + q, α = dual_risk_inner(F, V_ω, p) + ## Compute the risk-averse subgradient by taking the expectation of the + ## subgradients w.r.t. q^*. + dVdx = sum(q[i] * λ(x̃, ω) for (i, ω) in enumerate(Ω)) + return dVdx +end + +# We can compare the subgradient obtained with the dual form against the +# automatic differentiation of the `primal_risk` function. + +function primal_risk_averse_subgradient( + V::Function; + F::AbstractRiskMeasure, + Ω::Vector, + p::Vector{Float64}, + x̃::Vector{Float64}, +) + inner(x) = primal_risk(F, [V(x, ω) for ω in Ω], p) + return ForwardDiff.gradient(inner, x̃) +end + +# As our example function, we use: + +V(x, ω) = ω * x[1]^2 + +# with: + +Ω = [1.0, 2.0, 3.0] + +# and: + +p = [0.3, 0.4, 0.3] + +# at the point: + +x̃ = [3.0] + +# If $\mathbb{F}$ is the expectation risk-measure, then: +# ```math +# \mathbb{F}[V(x, \omega)] = 2 x^2. +# ``` +# The function evaluation $x=3$ is $18$ and the subgradient is $12$. Let's check +# we get it right with the dual form: + +dual_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃) + +# and the primal form: + +primal_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃) + +# If $\mathbb{F}$ is the worst-case risk measure, then: +# ```math +# \mathbb{F}[V(x, \omega)] = 3 x^2. +# ``` +# The function evaluation at $x=3$ is $27$, and the subgradient is $18$. Let's +# check we get it right with the dual form: + +dual_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃) + +# and the primal form: + +primal_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃) + +# If $\mathbb{F}$ is the entropic risk measure, the math is a little more +# difficult to derive analytically. However, we can check against our primal +# version: + +for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0] + dual = + dual_risk_averse_subgradient(V; F = Entropic(γ), Ω = Ω, p = p, x̃ = x̃) + primal = primal_risk_averse_subgradient( + V; + F = Entropic(γ), + Ω = Ω, + p = p, + x̃ = x̃, + ) + success = primal ≈ dual ? "✓" : "×" + println("$(success) γ = $(γ), primal = $(primal), dual = $(dual)") +end + +# Uh oh! What happened with the last line? It looks our `primal_risk_averse_subgradient` +# encountered an error and returned a subgradient of `NaN`. This is because of +# the overflow issue with `exp(x)`. However, we can be confident that our dual +# method of computing the risk-averse subgradient is both correct and more +# numerically robust than the primal version. + +# !!! info +# As another sanity check, notice how as $\gamma \rightarrow 0$, we tend +# toward the solution of the expectation risk-measure `[12]`, and as +# $\gamma \rightarrow \infty$, we tend toward the solution of the worse-case +# risk measure `[18]`. + +# # Risk-averse decision rules: Part II + +# Why is the risk-averse subgradient theorem helpful? Using the dual +# representation of a convex risk measure, we can re-write the cut: +# ```math +# \theta \ge \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right] + \frac{d}{dx^\prime}\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]^\top (x^\prime - x^\prime_k),\quad k=1,\ldots,K +# ``` +# as: +# ```math +# \theta \ge \mathbb{E}_{q_k}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right] - \alpha(p, q_k),\quad k=1,\ldots,K, +# ``` +# where $q_k = \mathrm{arg}\sup\limits_{q \in\mathcal{M}(p)} \mathbb{E}_q[V_j^k(x_k^\prime, \varphi)] - \alpha(p, q)$. + +# Therefore, we can formulate a risk-averse decision rule as: +# ```math +# \begin{aligned} +# V_i^K(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \theta\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x \\ +# & \theta \ge \mathbb{E}_{q_k}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right] - \alpha(p, q_k),\quad k=1,\ldots,K \\ +# & \theta \ge M. +# \end{aligned} +# ``` +# where $q_k = \mathrm{arg}\sup\limits_{q \in\mathcal{M}(p)} \mathbb{E}_q[V_j^k(x_k^\prime, \varphi)] - \alpha(p, q)$. + +# Thus, to implement risk-averse SDDP, all we need to do is modify the backward +# pass to include this calculation of $q_k$, form the cut using $q_k$ instead of +# $p$, and subtract the penalty term $\alpha(p, q_k)$. + +# ## Implementation + +# Now we're ready to implement our risk-averse version of SDDP. + +# As a prerequisite, we need most of the code from [Introductory theory](@ref). + +# ```@raw html +#

+# Click to view code from the tutorial "Introductory theory". +# ``` + +struct State + in::JuMP.VariableRef + out::JuMP.VariableRef +end + +struct Uncertainty + parameterize::Function + Ω::Vector{Any} + P::Vector{Float64} +end + +struct Node + subproblem::JuMP.Model + states::Dict{Symbol,State} + uncertainty::Uncertainty + cost_to_go::JuMP.VariableRef +end + +struct PolicyGraph + nodes::Vector{Node} + arcs::Vector{Dict{Int,Float64}} +end + +function Base.show(io::IO, model::PolicyGraph) + println(io, "A policy graph with $(length(model.nodes)) nodes") + println(io, "Arcs:") + for (from, arcs) in enumerate(model.arcs) + for (to, probability) in arcs + println(io, " $(from) => $(to) w.p. $(probability)") + end + end + return +end + +function PolicyGraph( + subproblem_builder::Function; + graph::Vector{Dict{Int,Float64}}, + lower_bound::Float64, + optimizer, +) + nodes = Node[] + for t in 1:length(graph) + model = JuMP.Model(optimizer) + states, uncertainty = subproblem_builder(model, t) + JuMP.@variable(model, cost_to_go >= lower_bound) + obj = JuMP.objective_function(model) + JuMP.@objective(model, Min, obj + cost_to_go) + if length(graph[t]) == 0 + JuMP.fix(cost_to_go, 0.0; force = true) + end + push!(nodes, Node(model, states, uncertainty, cost_to_go)) + end + return PolicyGraph(nodes, graph) +end + +function sample_uncertainty(uncertainty::Uncertainty) + r = rand() + for (p, ω) in zip(uncertainty.P, uncertainty.Ω) + r -= p + if r < 0.0 + return ω + end + end + return error("We should never get here because P should sum to 1.0.") +end + +function sample_next_node(model::PolicyGraph, current::Int) + if length(model.arcs[current]) == 0 + return nothing + else + r = rand() + for (to, probability) in model.arcs[current] + r -= probability + if r < 0.0 + return to + end + end + return nothing + end +end + +function forward_pass(model::PolicyGraph, io::IO = stdout) + incoming_state = + Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states) + simulation_cost = 0.0 + trajectory = Tuple{Int,Dict{Symbol,Float64}}[] + t = 1 + while t !== nothing + node = model.nodes[t] + ω = sample_uncertainty(node.uncertainty) + node.uncertainty.parameterize(ω) + for (k, v) in incoming_state + JuMP.fix(node.states[k].in, v; force = true) + end + JuMP.optimize!(node.subproblem) + if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL + error("Something went terribly wrong!") + end + outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states) + stage_cost = + JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go) + simulation_cost += stage_cost + incoming_state = outgoing_state + push!(trajectory, (t, outgoing_state)) + t = sample_next_node(model, t) + end + return trajectory, simulation_cost +end + +function upper_bound(model::PolicyGraph; replications::Int) + simulations = [forward_pass(model, devnull) for i in 1:replications] + z = [s[2] for s in simulations] + μ = Statistics.mean(z) + tσ = 1.96 * Statistics.std(z) / sqrt(replications) + return μ, tσ +end + +function lower_bound(model::PolicyGraph) + node = model.nodes[1] + bound = 0.0 + for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω) + node.uncertainty.parameterize(ω) + JuMP.optimize!(node.subproblem) + bound += p * JuMP.objective_value(node.subproblem) + end + return bound +end + +function evaluate_policy( + model::PolicyGraph; + node::Int, + incoming_state::Dict{Symbol,Float64}, + random_variable, +) + the_node = model.nodes[node] + the_node.uncertainty.parameterize(random_variable) + for (k, v) in incoming_state + JuMP.fix(the_node.states[k].in, v; force = true) + end + JuMP.optimize!(the_node.subproblem) + return Dict( + k => JuMP.value.(v) for + (k, v) in JuMP.object_dictionary(the_node.subproblem) + ) +end + +# ```@raw html +#

+# ``` + +# First, we need to modify the backward pass to compute the cuts using the +# risk-averse subgradient theorem: + +function backward_pass( + model::PolicyGraph, + trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}}, + io::IO = stdout; + risk_measure::AbstractRiskMeasure, +) + println(io, "| Backward pass") + for i in reverse(1:length(trajectory)) + index, outgoing_states = trajectory[i] + node = model.nodes[index] + println(io, "| | Visiting node $(index)") + if length(model.arcs[index]) == 0 + continue + end + ## ===================================================================== + ## New! Create vectors to store the cut expressions, V(x,ω) and p: + cut_expressions, V_ω, p = JuMP.AffExpr[], Float64[], Float64[] + ## ===================================================================== + for (j, P_ij) in model.arcs[index] + next_node = model.nodes[j] + for (k, v) in outgoing_states + JuMP.fix(next_node.states[k].in, v; force = true) + end + for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω) + next_node.uncertainty.parameterize(φ) + JuMP.optimize!(next_node.subproblem) + V = JuMP.objective_value(next_node.subproblem) + dVdx = Dict( + k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states + ) + ## ============================================================= + ## New! Construct and append the expression + ## `V_j^K(x_k, φ) + dVdx_j^K(x'_k, φ)ᵀ(x - x_k)` to the list of + ## cut expressions. + push!( + cut_expressions, + JuMP.@expression( + node.subproblem, + V + sum( + dVdx[k] * (x.out - outgoing_states[k]) for + (k, x) in node.states + ), + ) + ) + ## Add the objective value to Z: + push!(V_ω, V) + ## Add the probability to p: + push!(p, P_ij * pφ) + ## ============================================================= + end + end + ## ===================================================================== + ## New! Using the solutions in V_ω, compute q and α: + q, α = dual_risk_inner(risk_measure, V_ω, p) + println(io, "| | | Z = ", Z) + println(io, "| | | p = ", p) + println(io, "| | | q = ", q) + println(io, "| | | α = ", α) + ## Then add the cut: + c = JuMP.@constraint( + node.subproblem, + node.cost_to_go >= + sum(q[i] * cut_expressions[i] for i in 1:length(q)) - α + ) + ## ===================================================================== + println(io, "| | | Adding cut : ", c) + end + return nothing +end + +# We also need to update the `train` loop of SDDP to pass a risk measure to the +# backward pass: + +function train( + model::PolicyGraph; + iteration_limit::Int, + replications::Int, + ## ========================================================================= + ## New! Add a risk_measure argument + risk_measure::AbstractRiskMeasure, + ## ========================================================================= + io::IO = stdout, +) + for i in 1:iteration_limit + println(io, "Starting iteration $(i)") + outgoing_states, _ = forward_pass(model, io) + ## ===================================================================== + ## New! Pass the risk measure to the backward pass. + backward_pass(model, outgoing_states, io; risk_measure = risk_measure) + ## ===================================================================== + println(io, "| Finished iteration") + println(io, "| | lower_bound = ", lower_bound(model)) + end + μ, tσ = upper_bound(model; replications = replications) + println(io, "Upper bound = $(μ) ± $(tσ)") + return +end + +# ### Risk-averse bounds + +# !!! warning +# This section is important. + +# When we had a risk-neutral policy (i.e., we only used the expectation risk +# measure), we discussed how we could form valid lower and upper bounds. + +# The upper bound is still valid as a Monte Carlo simulation of the expected +# cost of the policy. (Although this upper bound doesn't capture the change in +# the policy we wanted to achieve, namely that the impact of the worst outcomes +# were reduced.) + +# However, if we use a different risk measure, the lower bound is no longer +# valid! + +# We can still calculate a "lower bound" as the objective of the first-stage +# approximated subproblem, and this will converge to a finite value. However, +# we can't meaningfully interpret it as a bound with respect to the optimal +# policy. Therefore, it's best to just ignore the lower bound when training a +# risk-averse policy. + +# ## Example: risk-averse hydro-thermal scheduling + +# Now it's time for an example. We create the same problem as +# [Introductory theory](@ref): + +model = PolicyGraph(; + graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()], + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, t + JuMP.set_silent(subproblem) + JuMP.@variable(subproblem, volume_in == 200) + JuMP.@variable(subproblem, 0 <= volume_out <= 200) + states = Dict(:volume => State(volume_in, volume_out)) + JuMP.@variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + JuMP.@constraints( + subproblem, + begin + volume_out == volume_in + inflow - hydro_generation - hydro_spill + demand_constraint, thermal_generation + hydro_generation == 150.0 + end + ) + fuel_cost = [50.0, 100.0, 150.0] + JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation) + uncertainty = + Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω + return JuMP.fix(inflow, ω) + end + return states, uncertainty +end + +# Then we train a risk-averse policy, passing a risk measure to `train`: + +train( + model; + iteration_limit = 3, + replications = 100, + risk_measure = Entropic(1.0), +) + +# Finally, evaluate the decision rule: + +evaluate_policy( + model; + node = 1, + incoming_state = Dict(:volume => 150.0), + random_variable = 75, +) + +# !!! info +# For this trivial example, the risk-averse policy isn't very different from +# the policy obtained using the expectation risk-measure. If you try it on +# some bigger/more interesting problems, you should see the expected cost +# increase, and the upper tail of the policy decrease. diff --git a/previews/PR826/explanation/risk/index.html b/previews/PR826/explanation/risk/index.html new file mode 100644 index 0000000000..703d8a1089 --- /dev/null +++ b/previews/PR826/explanation/risk/index.html @@ -0,0 +1,539 @@ + +Risk aversion · SDDP.jl

Risk aversion

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

In Introductory theory, we implemented a basic version of the SDDP algorithm. This tutorial extends that implementation to add risk-aversion.

Packages

This tutorial uses the following packages. For clarity, we call import PackageName so that we must prefix PackageName. to all functions and structs provided by that package. Everything not prefixed is either part of base Julia, or we wrote it.

import ForwardDiff
+import HiGHS
+import Ipopt
+import JuMP
+import Statistics

Risk aversion: what and why?

Often, the agents making decisions in complex systems are risk-averse, that is, they care more about avoiding very bad outcomes, than they do about having a good average outcome.

As an example, consumers in a hydro-thermal problem may be willing to pay a slightly higher electricity price on average, if it means that there is a lower probability of blackouts.

Risk aversion in multistage stochastic programming has been well studied in the academic literature, and is widely used in production implementations around the world.

Risk measures

One way to add risk aversion to models is to use a risk measure. A risk measure is a function that maps a random variable to a real number.

You are probably already familiar with lots of different risk measures. For example, the mean, median, mode, and maximum are all risk measures.

We call the act of applying a risk measure to a random variable "computing the risk" of a random variable.

To keep things simple, and because we need it for SDDP, we restrict our attention to random variables $Z$ with a finite sample space $\Omega$ and positive probabilities $p_{\omega}$ for all $\omega \in \Omega$. We denote the realizations of $Z$ by $Z(\omega) = z_{\omega}$.

A risk measure, $\mathbb{F}[Z]$, is a convex risk measure if it satisfies the following axioms:

Axiom 1: monotonicity

Given two random variables $Z_1$ and $Z_2$, with $Z_1 \le Z_2$ almost surely, then $\mathbb{F}[Z_1] \le F[Z_2]$.

Axiom 2: translation equivariance

Given two random variables $Z_1$ and $Z_2$, then for all $a \in \mathbb{R}$, $\mathbb{F}[Z + a] = \mathbb{F}[Z] + a$.

Axiom 3: convexity

Given two random variables $Z_1$ and $Z_2$, then for all $a \in [0, 1]$,

\[\mathbb{F}[a Z_1 + (1 - a) Z_2] \le a \mathbb{F}[Z_1] + (1-a)\mathbb{F}[Z_2].\]

Now we know what a risk measure is, let's see how we can use them to form risk-averse decision rules.

Risk-averse decision rules: Part I

We started this tutorial by explaining that we are interested in risk aversion because some agents are risk-averse. What that really means, is that they want a policy that is also risk-averse. The question then becomes, how do we create risk-averse decision rules and policies?

Recall from Introductory theory that we can form an optimal decision rule using the recursive formulation:

\[\begin{aligned} +V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x, +\end{aligned}\]

where our decision rule, $\pi_i(x, \omega)$, solves this optimization problem and returns a $u^*$ corresponding to an optimal solution.

If we can replace the expectation operator $\mathbb{E}$ with another (more risk-averse) risk measure $\mathbb{F}$, then our decision rule will attempt to choose a control decision now that minimizes the risk of the future costs, as opposed to the expectation of the future costs. This makes our decisions more risk-averse, because we care more about the worst outcomes than we do about the average.

Therefore, we can form a risk-averse decision rule using the formulation:

\[\begin{aligned} +V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x. +\end{aligned}\]

To convert this problem into a tractable equivalent, we apply Kelley's algorithm to the risk-averse cost-to-go term $\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]$, to obtain the approximated problem:

\[\begin{aligned} +V_i^K(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \theta\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x \\ +& \theta \ge \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right] + \frac{d}{dx^\prime}\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]^\top (x^\prime - x^\prime_k)\quad k=1,\ldots,K. +\end{aligned}\]

Warning

Note how we need to explicitly compute a risk-averse subgradient! (We need a subgradient because the function might not be differentiable.) When constructing cuts with the expectation operator in Introductory theory, we implicitly used the law of total expectation to combine the two expectations; we can't do that for a general risk measure.

Homework challenge

If it's not obvious why we can use Kelley's here, try to use the axioms of a convex risk measure to show that $\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]$ is a convex function w.r.t. $x^\prime$ if $V_j$ is also a convex function.

Our challenge is now to find a way to compute the risk-averse cost-to-go function $\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]$, and a way to compute a subgradient of the risk-averse cost-to-go function with respect to $x^\prime$.

Primal risk measures

Now we know what a risk measure is, and how we will use it, let's implement some code to see how we can compute the risk of some random variables.

Note

We're going to start by implementing the primal version of each risk measure. We implement the dual version in the next section.

First, we need some data:

Z = [1.0, 2.0, 3.0, 4.0]
4-element Vector{Float64}:
+ 1.0
+ 2.0
+ 3.0
+ 4.0

with probabilities:

p = [0.1, 0.2, 0.4, 0.3]
4-element Vector{Float64}:
+ 0.1
+ 0.2
+ 0.4
+ 0.3

We're going to implement a number of different risk measures, so to leverage Julia's multiple dispatch, we create an abstract type:

abstract type AbstractRiskMeasure end

and function to overload:

"""
+    primal_risk(F::AbstractRiskMeasure, Z::Vector{<:Real}, p::Vector{Float64})
+
+Use `F` to compute the risk of the random variable defined by a vector of costs
+`Z` and non-zero probabilities `p`.
+"""
+function primal_risk end
Main.primal_risk
Note

We want Vector{<:Real} instead of Vector{Float64} because we're going to automatically differentiate this function in the next section.

Expectation

The expectation, $\mathbb{E}$, also called the mean or the average, is the most widely used convex risk measure. The expectation of a random variable is just the sum of $Z$ weighted by the probability:

\[\mathbb{F}[Z] = \mathbb{E}_p[Z] = \sum\limits_{\omega\in\Omega} p_{\omega} z_{\omega}.\]

struct Expectation <: AbstractRiskMeasure end
+
+function primal_risk(::Expectation, Z::Vector{<:Real}, p::Vector{Float64})
+    return sum(p[i] * Z[i] for i in 1:length(p))
+end
primal_risk (generic function with 1 method)

Let's try it out:

primal_risk(Expectation(), Z, p)
2.9000000000000004

WorstCase

The worst-case risk measure, also called the maximum, is another widely used convex risk measure. This risk measure doesn't care about the probability vector p, only the cost vector Z:

\[\mathbb{F}[Z] = \max[Z] = \max\limits_{\omega\in\Omega} z_{\omega}.\]

struct WorstCase <: AbstractRiskMeasure end
+
+function primal_risk(::WorstCase, Z::Vector{<:Real}, ::Vector{Float64})
+    return maximum(Z)
+end
primal_risk (generic function with 2 methods)

Let's try it out:

primal_risk(WorstCase(), Z, p)
4.0

Entropic

A more interesting, and less widely used risk measure is the entropic risk measure. The entropic risk measure is parameterized by a value $\gamma > 0$, and computes the risk of a random variable as:

\[\mathbb{F}_\gamma[Z] = \frac{1}{\gamma}\log\left(\mathbb{E}_p[e^{\gamma Z}]\right) = \frac{1}{\gamma}\log\left(\sum\limits_{\omega\in\Omega}p_{\omega} e^{\gamma z_{\omega}}\right).\]

Homework challenge

Prove that the entropic risk measure satisfies the three axioms of a convex risk measure.

struct Entropic <: AbstractRiskMeasure
+    γ::Float64
+    function Entropic(γ)
+        if !(γ > 0)
+            throw(DomainError(γ, "Entropic risk measure must have γ > 0."))
+        end
+        return new(γ)
+    end
+end
+
+function primal_risk(F::Entropic, Z::Vector{<:Real}, p::Vector{Float64})
+    return 1 / F.γ * log(sum(p[i] * exp(F.γ * Z[i]) for i in 1:length(p)))
+end
primal_risk (generic function with 3 methods)
Warning

exp(x) overflows when $x > 709$. Therefore, if we are passed a vector of Float64, use arbitrary precision arithmetic with big.(Z).

function primal_risk(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})
+    return Float64(primal_risk(F, big.(Z), p))
+end
primal_risk (generic function with 4 methods)

Let's try it out for different values of $\gamma$:

for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1_000.0]
+    println("γ = $(γ), F[Z] = ", primal_risk(Entropic(γ), Z, p))
+end
γ = 0.001, F[Z] = 2.9004449279791005
+γ = 0.01, F[Z] = 2.9044427792027596
+γ = 0.1, F[Z] = 2.9437604953310674
+γ = 1.0, F[Z] = 3.264357634151263
+γ = 10.0, F[Z] = 3.879608772845574
+γ = 100.0, F[Z] = 3.987960271956741
+γ = 1000.0, F[Z] = 3.998796027195674
Info

The entropic has two extremes. As $\gamma \rightarrow 0$, the entropic acts like the expectation risk measure, and as $\gamma \rightarrow \infty$, the entropic acts like the worst-case risk measure.

Computing risk measures this way works well for computing the primal value. However, there isn't an obvious way to compute a subgradient of the risk-averse cost-to-go function, which we need for our cut calculation.

There is a nice solution to this problem, and that is to use the dual representation of a risk measure, instead of the primal.

Dual risk measures

Convex risk measures have a dual representation as follows:

\[\mathbb{F}[Z] = \sup\limits_{q \in\mathcal{M}(p)} \mathbb{E}_q[Z] - \alpha(p, q),\]

where $\alpha$ is a concave function that maps the probability vectors $p$ and $q$ to a real number, and $\mathcal{M}(p) \subseteq \mathcal{P}$ is a convex subset of the probability simplex:

\[\mathcal{P} = \{p \ge 0\;|\;\sum\limits_{\omega\in\Omega}p_{\omega} = 1\}.\]

The dual of a convex risk measure can be interpreted as taking the expectation of the random variable $Z$ with respect to the worst probability vector $q$ that lies within the set $\mathcal{M}$, less some concave penalty term $\alpha(p, q)$.

If we define a function dual_risk_inner that computes q and α:

"""
+    dual_risk_inner(
+        F::AbstractRiskMeasure, Z::Vector{Float64}, p::Vector{Float64}
+    )::Tuple{Vector{Float64},Float64}
+
+Return a tuple formed by the worst-case probability vector `q` and the
+corresponding evaluation `α(p, q)`.
+"""
+function dual_risk_inner end
Main.dual_risk_inner

then we can write a generic dual_risk function as:

function dual_risk(
+    F::AbstractRiskMeasure,
+    Z::Vector{Float64},
+    p::Vector{Float64},
+)
+    q, α = dual_risk_inner(F, Z, p)
+    return sum(q[i] * Z[i] for i in 1:length(q)) - α
+end
dual_risk (generic function with 1 method)

Expectation

For the expectation risk measure, $\mathcal{M}(p) = \{p\}$, and $\alpha(\cdot, \cdot) = 0$. Therefore:

function dual_risk_inner(::Expectation, ::Vector{Float64}, p::Vector{Float64})
+    return p, 0.0
+end
dual_risk_inner (generic function with 1 method)

We can check we get the same result as the primal version:

dual_risk(Expectation(), Z, p) == primal_risk(Expectation(), Z, p)
true

Worst-case

For the worst-case risk measure, $\mathcal{M}(p) = \mathcal{P}$, and $\alpha(\cdot, \cdot) = 0$. Therefore, the dual representation just puts all of the probability weight on the maximum outcome:

function dual_risk_inner(::WorstCase, Z::Vector{Float64}, ::Vector{Float64})
+    q = zeros(length(Z))
+    _, index = findmax(Z)
+    q[index] = 1.0
+    return q, 0.0
+end
dual_risk_inner (generic function with 2 methods)

We can check we get the same result as the primal version:

dual_risk(WorstCase(), Z, p) == primal_risk(WorstCase(), Z, p)
true

Entropic

For the entropic risk measure, $\mathcal{M}(p) = \mathcal{P}$, and:

\[\alpha(p, q) = \frac{1}{\gamma}\sum\limits_{\omega\in\Omega} q_\omega \log\left(\frac{q_\omega}{p_{\omega}}\right).\]

One way to solve the dual problem is to explicitly solve a nonlinear optimization problem:

function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})
+    N = length(p)
+    model = JuMP.Model(Ipopt.Optimizer)
+    JuMP.set_silent(model)
+    # For this problem, the solve is more accurate if we turn off problem
+    # scaling.
+    JuMP.set_optimizer_attribute(model, "nlp_scaling_method", "none")
+    JuMP.@variable(model, 0 <= q[1:N] <= 1)
+    JuMP.@constraint(model, sum(q) == 1)
+    JuMP.@NLexpression(
+        model,
+        α,
+        1 / F.γ * sum(q[i] * log(q[i] / p[i]) for i in 1:N),
+    )
+    JuMP.@NLobjective(model, Max, sum(q[i] * Z[i] for i in 1:N) - α)
+    JuMP.optimize!(model)
+    return JuMP.value.(q), JuMP.value(α)
+end
dual_risk_inner (generic function with 3 methods)

We can check we get the same result as the primal version:

for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
+    primal = primal_risk(Entropic(γ), Z, p)
+    dual = dual_risk(Entropic(γ), Z, p)
+    success = primal ≈ dual ? "✓" : "×"
+    println("$(success) γ = $(γ), primal = $(primal), dual = $(dual)")
+end
✓ γ = 0.001, primal = 2.9004449279791005, dual = 2.900444927979128
+✓ γ = 0.01, primal = 2.9044427792027596, dual = 2.9044427792027667
+✓ γ = 0.1, primal = 2.9437604953310674, dual = 2.9437604953310674
+✓ γ = 1.0, primal = 3.264357634151263, dual = 3.264357634151263
+✓ γ = 10.0, primal = 3.879608772845574, dual = 3.8796087723675954
+✓ γ = 100.0, primal = 3.987960271956741, dual = 3.987960271956741
Info

This method of solving the dual problem "on-the-side" is used by SDDP.jl for a number of risk measures, including a distributionally robust risk measure with the Wasserstein distance. Check out all the risk measures that SDDP.jl supports in Add a risk measure.

The "on-the-side" method is very general, and it lets us incorporate any convex risk measure into SDDP. However, this comes at an increased computational cost and potential numerical issues (e.g., not converging to the exact solution).

However, for the entropic risk measure, Dowson, Morton, and Pagnoncelli (2020) derive the following closed form solution for $q^*$:

\[q_\omega^* = \frac{p_{\omega} e^{\gamma z_{\omega}}}{\sum\limits_{\varphi \in \Omega} p_{\varphi} e^{\gamma z_{\varphi}}}.\]

This is faster because we don't need to use Ipopt, and it avoids some of the numerical issues associated with solving a nonlinear program.

function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})
+    q, α = zeros(length(p)), big(0.0)
+    peγz = p .* exp.(F.γ .* big.(Z))
+    sum_peγz = sum(peγz)
+    for i in 1:length(q)
+        big_q = peγz[i] / sum_peγz
+        α += big_q * log(big_q / p[i])
+        q[i] = Float64(big_q)
+    end
+    return q, Float64(α / F.γ)
+end
dual_risk_inner (generic function with 3 methods)
Warning

Again, note that we use big to avoid introducing overflow errors, before explicitly casting back to Float64 for the values we return.

We can check we get the same result as the primal version:

for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
+    primal = primal_risk(Entropic(γ), Z, p)
+    dual = dual_risk(Entropic(γ), Z, p)
+    success = primal ≈ dual ? "✓" : "×"
+    println("$(success) γ = $(γ), primal = $(primal), dual = $(dual)")
+end
✓ γ = 0.001, primal = 2.9004449279791005, dual = 2.9004449279791005
+✓ γ = 0.01, primal = 2.9044427792027596, dual = 2.9044427792027596
+✓ γ = 0.1, primal = 2.9437604953310674, dual = 2.943760495331067
+✓ γ = 1.0, primal = 3.264357634151263, dual = 3.264357634151263
+✓ γ = 10.0, primal = 3.879608772845574, dual = 3.879608772845574
+✓ γ = 100.0, primal = 3.987960271956741, dual = 3.987960271956741

Risk-averse subgradients

We ended the section on primal risk measures by explaining how we couldn't use the primal risk measure in the cut calculation because we needed some way of computing a risk-averse subgradient:

\[\theta \ge \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right] + \frac{d}{dx^\prime}\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]^\top (x^\prime - x^\prime_k).\]

The reason we use the dual representation is because of the following theorem, which explains how to compute a risk-averse gradient.

The risk-averse subgradient theorem

Let $\omega \in \Omega$ index a random vector with finite support and with nominal probability mass function, $p \in \mathcal{P}$, which satisfies $p > 0$.

Consider a convex risk measure, $\mathbb{F}$, with a convex risk set, $\mathcal{M}(p)$, so that $\mathbb{F}$ can be expressed as the dual form.

Let $V(x,\omega)$ be convex with respect to $x$ for all fixed $\omega\in\Omega$, and let $\lambda(\tilde{x}, \omega)$ be a subgradient of $V(x,\omega)$ with respect to $x$ at $x = \tilde{x}$ for each $\omega \in \Omega$.

Then, $\sum_{\omega\in\Omega}q^*_{\omega} \lambda(\tilde{x},\omega)$ is a subgradient of $\mathbb{F}[V(x,\omega)]$ at $\tilde{x}$, where

\[q^* \in \argmax_{q \in \mathcal{M}(p)}\left\{{\mathbb{E}}_q[V(\tilde{x},\omega)] - \alpha(p, q)\right\}.\]

This theorem can be a little hard to unpack, so let's see an example:

function dual_risk_averse_subgradient(
+    V::Function,
+    # Use automatic differentiation to compute the gradient of V w.r.t. x,
+    # given a fixed ω.
+    λ::Function = (x, ω) -> ForwardDiff.gradient(x -> V(x, ω), x);
+    F::AbstractRiskMeasure,
+    Ω::Vector,
+    p::Vector{Float64},
+    x̃::Vector{Float64},
+)
+    # Evaluate the function at x=x̃ for all ω ∈ Ω.
+    V_ω = [V(x̃, ω) for ω in Ω]
+    # Solve the dual problem to obtain an optimal q^*.
+    q, α = dual_risk_inner(F, V_ω, p)
+    # Compute the risk-averse subgradient by taking the expectation of the
+    # subgradients w.r.t. q^*.
+    dVdx = sum(q[i] * λ(x̃, ω) for (i, ω) in enumerate(Ω))
+    return dVdx
+end
dual_risk_averse_subgradient (generic function with 2 methods)

We can compare the subgradient obtained with the dual form against the automatic differentiation of the primal_risk function.

function primal_risk_averse_subgradient(
+    V::Function;
+    F::AbstractRiskMeasure,
+    Ω::Vector,
+    p::Vector{Float64},
+    x̃::Vector{Float64},
+)
+    inner(x) = primal_risk(F, [V(x, ω) for ω in Ω], p)
+    return ForwardDiff.gradient(inner, x̃)
+end
primal_risk_averse_subgradient (generic function with 1 method)

As our example function, we use:

V(x, ω) = ω * x[1]^2
V (generic function with 1 method)

with:

Ω = [1.0, 2.0, 3.0]
3-element Vector{Float64}:
+ 1.0
+ 2.0
+ 3.0

and:

p = [0.3, 0.4, 0.3]
3-element Vector{Float64}:
+ 0.3
+ 0.4
+ 0.3

at the point:

x̃ = [3.0]
1-element Vector{Float64}:
+ 3.0

If $\mathbb{F}$ is the expectation risk-measure, then:

\[\mathbb{F}[V(x, \omega)] = 2 x^2.\]

The function evaluation $x=3$ is $18$ and the subgradient is $12$. Let's check we get it right with the dual form:

dual_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃)
1-element Vector{Float64}:
+ 12.0

and the primal form:

primal_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃)
1-element Vector{Float64}:
+ 12.0

If $\mathbb{F}$ is the worst-case risk measure, then:

\[\mathbb{F}[V(x, \omega)] = 3 x^2.\]

The function evaluation at $x=3$ is $27$, and the subgradient is $18$. Let's check we get it right with the dual form:

dual_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃)
1-element Vector{Float64}:
+ 18.0

and the primal form:

primal_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃)
1-element Vector{Float64}:
+ 18.0

If $\mathbb{F}$ is the entropic risk measure, the math is a little more difficult to derive analytically. However, we can check against our primal version:

for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]
+    dual =
+        dual_risk_averse_subgradient(V; F = Entropic(γ), Ω = Ω, p = p, x̃ = x̃)
+    primal = primal_risk_averse_subgradient(
+        V;
+        F = Entropic(γ),
+        Ω = Ω,
+        p = p,
+        x̃ = x̃,
+    )
+    success = primal ≈ dual ? "✓" : "×"
+    println("$(success) γ = $(γ), primal = $(primal), dual = $(dual)")
+end
✓ γ = 0.001, primal = [12.03239965008496], dual = [12.03239965008496]
+✓ γ = 0.01, primal = [12.323650575272044], dual = [12.323650575272044]
+✓ γ = 0.1, primal = [14.9332498638561], dual = [14.933249863856101]
+✓ γ = 1.0, primal = [17.999012701279042], dual = [17.999012701279042]
+✓ γ = 10.0, primal = [17.999999999999996], dual = [18.0]
+× γ = 100.0, primal = [NaN], dual = [18.0]

Uh oh! What happened with the last line? It looks our primal_risk_averse_subgradient encountered an error and returned a subgradient of NaN. This is because of the overflow issue with exp(x). However, we can be confident that our dual method of computing the risk-averse subgradient is both correct and more numerically robust than the primal version.

Info

As another sanity check, notice how as $\gamma \rightarrow 0$, we tend toward the solution of the expectation risk-measure [12], and as $\gamma \rightarrow \infty$, we tend toward the solution of the worse-case risk measure [18].

Risk-averse decision rules: Part II

Why is the risk-averse subgradient theorem helpful? Using the dual representation of a convex risk measure, we can re-write the cut:

\[\theta \ge \mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right] + \frac{d}{dx^\prime}\mathbb{F}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi)\right]^\top (x^\prime - x^\prime_k),\quad k=1,\ldots,K\]

as:

\[\theta \ge \mathbb{E}_{q_k}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right] - \alpha(p, q_k),\quad k=1,\ldots,K,\]

where $q_k = \mathrm{arg}\sup\limits_{q \in\mathcal{M}(p)} \mathbb{E}_q[V_j^k(x_k^\prime, \varphi)] - \alpha(p, q)$.

Therefore, we can formulate a risk-averse decision rule as:

\[\begin{aligned} +V_i^K(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \theta\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x \\ +& \theta \ge \mathbb{E}_{q_k}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right] - \alpha(p, q_k),\quad k=1,\ldots,K \\ +& \theta \ge M. +\end{aligned}\]

where $q_k = \mathrm{arg}\sup\limits_{q \in\mathcal{M}(p)} \mathbb{E}_q[V_j^k(x_k^\prime, \varphi)] - \alpha(p, q)$.

Thus, to implement risk-averse SDDP, all we need to do is modify the backward pass to include this calculation of $q_k$, form the cut using $q_k$ instead of $p$, and subtract the penalty term $\alpha(p, q_k)$.

Implementation

Now we're ready to implement our risk-averse version of SDDP.

As a prerequisite, we need most of the code from Introductory theory.

+Click to view code from the tutorial "Introductory theory".
struct State
+    in::JuMP.VariableRef
+    out::JuMP.VariableRef
+end
+
+struct Uncertainty
+    parameterize::Function
+    Ω::Vector{Any}
+    P::Vector{Float64}
+end
+
+struct Node
+    subproblem::JuMP.Model
+    states::Dict{Symbol,State}
+    uncertainty::Uncertainty
+    cost_to_go::JuMP.VariableRef
+end
+
+struct PolicyGraph
+    nodes::Vector{Node}
+    arcs::Vector{Dict{Int,Float64}}
+end
+
+function Base.show(io::IO, model::PolicyGraph)
+    println(io, "A policy graph with $(length(model.nodes)) nodes")
+    println(io, "Arcs:")
+    for (from, arcs) in enumerate(model.arcs)
+        for (to, probability) in arcs
+            println(io, "  $(from) => $(to) w.p. $(probability)")
+        end
+    end
+    return
+end
+
+function PolicyGraph(
+    subproblem_builder::Function;
+    graph::Vector{Dict{Int,Float64}},
+    lower_bound::Float64,
+    optimizer,
+)
+    nodes = Node[]
+    for t in 1:length(graph)
+        model = JuMP.Model(optimizer)
+        states, uncertainty = subproblem_builder(model, t)
+        JuMP.@variable(model, cost_to_go >= lower_bound)
+        obj = JuMP.objective_function(model)
+        JuMP.@objective(model, Min, obj + cost_to_go)
+        if length(graph[t]) == 0
+            JuMP.fix(cost_to_go, 0.0; force = true)
+        end
+        push!(nodes, Node(model, states, uncertainty, cost_to_go))
+    end
+    return PolicyGraph(nodes, graph)
+end
+
+function sample_uncertainty(uncertainty::Uncertainty)
+    r = rand()
+    for (p, ω) in zip(uncertainty.P, uncertainty.Ω)
+        r -= p
+        if r < 0.0
+            return ω
+        end
+    end
+    return error("We should never get here because P should sum to 1.0.")
+end
+
+function sample_next_node(model::PolicyGraph, current::Int)
+    if length(model.arcs[current]) == 0
+        return nothing
+    else
+        r = rand()
+        for (to, probability) in model.arcs[current]
+            r -= probability
+            if r < 0.0
+                return to
+            end
+        end
+        return nothing
+    end
+end
+
+function forward_pass(model::PolicyGraph, io::IO = stdout)
+    incoming_state =
+        Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states)
+    simulation_cost = 0.0
+    trajectory = Tuple{Int,Dict{Symbol,Float64}}[]
+    t = 1
+    while t !== nothing
+        node = model.nodes[t]
+        ω = sample_uncertainty(node.uncertainty)
+        node.uncertainty.parameterize(ω)
+        for (k, v) in incoming_state
+            JuMP.fix(node.states[k].in, v; force = true)
+        end
+        JuMP.optimize!(node.subproblem)
+        if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL
+            error("Something went terribly wrong!")
+        end
+        outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states)
+        stage_cost =
+            JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go)
+        simulation_cost += stage_cost
+        incoming_state = outgoing_state
+        push!(trajectory, (t, outgoing_state))
+        t = sample_next_node(model, t)
+    end
+    return trajectory, simulation_cost
+end
+
+function upper_bound(model::PolicyGraph; replications::Int)
+    simulations = [forward_pass(model, devnull) for i in 1:replications]
+    z = [s[2] for s in simulations]
+    μ = Statistics.mean(z)
+    tσ = 1.96 * Statistics.std(z) / sqrt(replications)
+    return μ, tσ
+end
+
+function lower_bound(model::PolicyGraph)
+    node = model.nodes[1]
+    bound = 0.0
+    for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω)
+        node.uncertainty.parameterize(ω)
+        JuMP.optimize!(node.subproblem)
+        bound += p * JuMP.objective_value(node.subproblem)
+    end
+    return bound
+end
+
+function evaluate_policy(
+    model::PolicyGraph;
+    node::Int,
+    incoming_state::Dict{Symbol,Float64},
+    random_variable,
+)
+    the_node = model.nodes[node]
+    the_node.uncertainty.parameterize(random_variable)
+    for (k, v) in incoming_state
+        JuMP.fix(the_node.states[k].in, v; force = true)
+    end
+    JuMP.optimize!(the_node.subproblem)
+    return Dict(
+        k => JuMP.value.(v) for
+        (k, v) in JuMP.object_dictionary(the_node.subproblem)
+    )
+end
evaluate_policy (generic function with 1 method)

First, we need to modify the backward pass to compute the cuts using the risk-averse subgradient theorem:

function backward_pass(
+    model::PolicyGraph,
+    trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}},
+    io::IO = stdout;
+    risk_measure::AbstractRiskMeasure,
+)
+    println(io, "| Backward pass")
+    for i in reverse(1:length(trajectory))
+        index, outgoing_states = trajectory[i]
+        node = model.nodes[index]
+        println(io, "| | Visiting node $(index)")
+        if length(model.arcs[index]) == 0
+            continue
+        end
+        # =====================================================================
+        # New! Create vectors to store the cut expressions, V(x,ω) and p:
+        cut_expressions, V_ω, p = JuMP.AffExpr[], Float64[], Float64[]
+        # =====================================================================
+        for (j, P_ij) in model.arcs[index]
+            next_node = model.nodes[j]
+            for (k, v) in outgoing_states
+                JuMP.fix(next_node.states[k].in, v; force = true)
+            end
+            for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω)
+                next_node.uncertainty.parameterize(φ)
+                JuMP.optimize!(next_node.subproblem)
+                V = JuMP.objective_value(next_node.subproblem)
+                dVdx = Dict(
+                    k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states
+                )
+                # =============================================================
+                # New! Construct and append the expression
+                # `V_j^K(x_k, φ) + dVdx_j^K(x'_k, φ)ᵀ(x - x_k)` to the list of
+                # cut expressions.
+                push!(
+                    cut_expressions,
+                    JuMP.@expression(
+                        node.subproblem,
+                        V + sum(
+                            dVdx[k] * (x.out - outgoing_states[k]) for
+                            (k, x) in node.states
+                        ),
+                    )
+                )
+                # Add the objective value to Z:
+                push!(V_ω, V)
+                # Add the probability to p:
+                push!(p, P_ij * pφ)
+                # =============================================================
+            end
+        end
+        # =====================================================================
+        # New! Using the solutions in V_ω, compute q and α:
+        q, α = dual_risk_inner(risk_measure, V_ω, p)
+        println(io, "| | | Z = ", Z)
+        println(io, "| | | p = ", p)
+        println(io, "| | | q = ", q)
+        println(io, "| | | α = ", α)
+        # Then add the cut:
+        c = JuMP.@constraint(
+            node.subproblem,
+            node.cost_to_go >=
+            sum(q[i] * cut_expressions[i] for i in 1:length(q)) - α
+        )
+        # =====================================================================
+        println(io, "| | | Adding cut : ", c)
+    end
+    return nothing
+end
backward_pass (generic function with 2 methods)

We also need to update the train loop of SDDP to pass a risk measure to the backward pass:

function train(
+    model::PolicyGraph;
+    iteration_limit::Int,
+    replications::Int,
+    # =========================================================================
+    # New! Add a risk_measure argument
+    risk_measure::AbstractRiskMeasure,
+    # =========================================================================
+    io::IO = stdout,
+)
+    for i in 1:iteration_limit
+        println(io, "Starting iteration $(i)")
+        outgoing_states, _ = forward_pass(model, io)
+        # =====================================================================
+        # New! Pass the risk measure to the backward pass.
+        backward_pass(model, outgoing_states, io; risk_measure = risk_measure)
+        # =====================================================================
+        println(io, "| Finished iteration")
+        println(io, "| | lower_bound = ", lower_bound(model))
+    end
+    μ, tσ = upper_bound(model; replications = replications)
+    println(io, "Upper bound = $(μ) ± $(tσ)")
+    return
+end
train (generic function with 1 method)

Risk-averse bounds

Warning

This section is important.

When we had a risk-neutral policy (i.e., we only used the expectation risk measure), we discussed how we could form valid lower and upper bounds.

The upper bound is still valid as a Monte Carlo simulation of the expected cost of the policy. (Although this upper bound doesn't capture the change in the policy we wanted to achieve, namely that the impact of the worst outcomes were reduced.)

However, if we use a different risk measure, the lower bound is no longer valid!

We can still calculate a "lower bound" as the objective of the first-stage approximated subproblem, and this will converge to a finite value. However, we can't meaningfully interpret it as a bound with respect to the optimal policy. Therefore, it's best to just ignore the lower bound when training a risk-averse policy.

Example: risk-averse hydro-thermal scheduling

Now it's time for an example. We create the same problem as Introductory theory:

model = PolicyGraph(;
+    graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()],
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, t
+    JuMP.set_silent(subproblem)
+    JuMP.@variable(subproblem, volume_in == 200)
+    JuMP.@variable(subproblem, 0 <= volume_out <= 200)
+    states = Dict(:volume => State(volume_in, volume_out))
+    JuMP.@variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    JuMP.@constraints(
+        subproblem,
+        begin
+            volume_out == volume_in + inflow - hydro_generation - hydro_spill
+            demand_constraint, thermal_generation + hydro_generation == 150.0
+        end
+    )
+    fuel_cost = [50.0, 100.0, 150.0]
+    JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation)
+    uncertainty =
+        Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω
+            return JuMP.fix(inflow, ω)
+        end
+    return states, uncertainty
+end
A policy graph with 3 nodes
+Arcs:
+  1 => 2 w.p. 1.0
+  2 => 3 w.p. 1.0
+

Then we train a risk-averse policy, passing a risk measure to train:

train(
+    model;
+    iteration_limit = 3,
+    replications = 100,
+    risk_measure = Entropic(1.0),
+)
Starting iteration 1
+| Backward pass
+| | Visiting node 3
+| | Visiting node 2
+| | | Z = [1.0, 2.0, 3.0, 4.0]
+| | | p = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
+| | | q = [1.0, 0.0, 0.0]
+| | | α = 1.0986122886681098
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 22498.901387711332
+| | Visiting node 1
+| | | Z = [1.0, 2.0, 3.0, 4.0]
+| | | p = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
+| | | q = [1.0, 0.0, 0.0]
+| | | α = 1.0986122886681098
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 37497.802775422664
+| Finished iteration
+| | lower_bound = 12497.802775422664
+Starting iteration 2
+| Backward pass
+| | Visiting node 3
+| | Visiting node 2
+| | | Z = [1.0, 2.0, 3.0, 4.0]
+| | | p = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
+| | | q = [0.5999999999999176, 0.2000000000000412, 0.2000000000000412]
+| | | α = 0.14834174943478465
+| | | Adding cut : 89.99999999998764 volume_out + cost_to_go ≥ 13499.851658248712
+| | Visiting node 1
+| | | Z = [1.0, 2.0, 3.0, 4.0]
+| | | p = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
+| | | q = [1.0, 0.0, 0.0]
+| | | α = 1.0986122886681098
+| | | Adding cut : 100 volume_out + cost_to_go ≥ 29998.594667538695
+| Finished iteration
+| | lower_bound = 14998.594667538693
+Starting iteration 3
+| Backward pass
+| | Visiting node 3
+| | Visiting node 2
+| | | Z = [1.0, 2.0, 3.0, 4.0]
+| | | p = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
+| | | q = [0.8432391442060109, 0.07838042789699455, 0.07838042789699455]
+| | | α = 0.5556945523744657
+| | | Adding cut : 126.48587163090163 volume_out + cost_to_go ≥ 18972.32505008287
+| | Visiting node 1
+| | | Z = [1.0, 2.0, 3.0, 4.0]
+| | | p = [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
+| | | q = [1.0, 0.0, 0.0]
+| | | α = 1.0986122886681098
+| | | Adding cut : 100 volume_out + cost_to_go ≥ 29998.641399238186
+| Finished iteration
+| | lower_bound = 14998.641399238184
+Upper bound = 9899.735079754455 ± 869.6148610654054

Finally, evaluate the decision rule:

evaluate_policy(
+    model;
+    node = 1,
+    incoming_state = Dict(:volume => 150.0),
+    random_variable = 75,
+)
Dict{Symbol, Float64} with 8 entries:
+  :volume_out         => 200.0
+  :demand_constraint  => 150.0
+  :hydro_spill        => 0.0
+  :inflow             => 75.0
+  :volume_in          => 150.0
+  :thermal_generation => 125.0
+  :hydro_generation   => 25.0
+  :cost_to_go         => 9998.64
Info

For this trivial example, the risk-averse policy isn't very different from the policy obtained using the expectation risk-measure. If you try it on some bigger/more interesting problems, you should see the expected cost increase, and the upper tail of the policy decrease.

diff --git a/previews/PR826/explanation/theory_intro.ipynb b/previews/PR826/explanation/theory_intro.ipynb new file mode 100644 index 0000000000..65dd769e86 --- /dev/null +++ b/previews/PR826/explanation/theory_intro.ipynb @@ -0,0 +1,1509 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Introductory theory" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> This tutorial is aimed at advanced undergraduates or early-stage graduate\n", + "> students. You don't need prior exposure to stochastic programming!\n", + "> (Indeed, it may be better if you don't, because our approach is\n", + "> non-standard in the literature.)\n", + "\n", + " This tutorial is also a living document. If parts are unclear, please\n", + " [open an issue](https://github.com/odow/SDDP.jl/issues/new) so it can be\n", + " improved!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial will teach you how the stochastic dual dynamic programming\n", + "algorithm works by implementing a simplified version of the algorithm." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Our implementation is very much a \"vanilla\" version of SDDP; it doesn't have\n", + "(m)any fancy computational tricks (e.g., the ones included in SDDP.jl) that\n", + "you need to code a performant or stable version that will work on realistic\n", + "instances. However, our simplified implementation will work on arbitrary\n", + "policy graphs, including those with cycles such as infinite horizon problems!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "**Packages**\n", + "\n", + "This tutorial uses the following packages. For clarity, we call\n", + "`import PackageName` so that we must prefix `PackageName.` to all functions\n", + "and structs provided by that package. Everything not prefixed is either part\n", + "of base Julia, or we wrote it." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "import ForwardDiff\n", + "import HiGHS\n", + "import JuMP\n", + "import Statistics" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> You can follow along by installing the above packages, and copy-pasting\n", + "> the code we will write into a Julia REPL. Alternatively, you can download\n", + "> the Julia `.jl` file which created this tutorial [from GitHub](https://github.com/odow/SDDP.jl/blob/master/docs/src/tutorial/21_theory_intro.jl)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Preliminaries: background theory" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Start this tutorial by reading An introduction to SDDP.jl, which\n", + "introduces the necessary notation and vocabulary that we need for this\n", + "tutorial." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Preliminaries: Kelley's cutting plane algorithm" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Kelley's cutting plane algorithm is an iterative method for minimizing convex\n", + "functions. Given a convex function $f(x)$, Kelley's constructs an\n", + "under-approximation of the function at the minimum by a set of first-order\n", + "Taylor series approximations (called **cuts**) constructed at a set of points\n", + "$k = 1,\\ldots,K$:\n", + "$$\n", + "\\begin{aligned}\n", + "f^K = \\min\\limits_{\\theta \\in \\mathbb{R}, x \\in \\mathbb{R}^N} \\;\\; & \\theta\\\\\n", + "& \\theta \\ge f(x_k) + \\frac{d}{dx}f(x_k)^\\top (x - x_k),\\quad k=1,\\ldots,K\\\\\n", + "& \\theta \\ge M,\n", + "\\end{aligned}\n", + "$$\n", + "where $M$ is a sufficiently large negative number that is a lower bound for\n", + "$f$ over the domain of $x$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Kelley's cutting plane algorithm is a structured way of choosing points $x_k$\n", + "to visit, so that as more cuts are added:\n", + "$$\n", + "\\lim_{K \\rightarrow \\infty} f^K = \\min\\limits_{x \\in \\mathbb{R}^N} f(x)\n", + "$$\n", + "However, before we introduce the algorithm, we need to introduce some bounds." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "By convexity, $f^K \\le f(x)$ for all $x$. Thus, if $x^*$ is a minimizer of\n", + "$f$, then at any point in time we can construct a lower bound for $f(x^*)$ by\n", + "solving $f^K$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Moreover, we can use the primal solutions $x_k^*$ returned by solving $f^k$ to\n", + "evaluate $f(x_k^*)$ to generate an upper bound." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Therefore, $f^K \\le f(x^*) \\le \\min\\limits_{k=1,\\ldots,K} f(x_k^*)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "When the lower bound is sufficiently close to the upper bound, we can\n", + "terminate the algorithm and declare that we have found an solution that is\n", + "close to optimal." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Implementation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is pseudo-code fo the Kelley algorithm:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "1. Take as input a convex function $f(x)$ and a iteration limit $K_{max}$.\n", + " Set $K = 0$, and initialize $f^K$. Set $lb = -\\infty$ and $ub = \\infty$.\n", + "2. Solve $f^K$ to obtain a candidate solution $x_{K+1}$.\n", + "3. Update $lb = f^K$ and $ub = \\min\\{ub, f(x_{K+1})\\}$.\n", + "4. Add a cut $\\theta \\ge f(x_{K+1}) + \\frac{d}{dx}f\\left(x_{K+1}\\right)^\\top (x - x_{K+1})$ to form $f^{K+1}$.\n", + "5. Increment $K$.\n", + "6. If $K = K_{max}$ or $|ub - lb| < \\epsilon$, STOP, otherwise, go to step 2." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "And here's a complete implementation:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function kelleys_cutting_plane(\n", + " # The function to be minimized.\n", + " f::Function,\n", + " # The gradient of `f`. By default, we use automatic differentiation to\n", + " # compute the gradient of f so the user doesn't have to!\n", + " dfdx::Function = x -> ForwardDiff.gradient(f, x);\n", + " # The number of arguments to `f`.\n", + " input_dimension::Int,\n", + " # A lower bound for the function `f` over its domain.\n", + " lower_bound::Float64,\n", + " # The number of iterations to run Kelley's algorithm for before stopping.\n", + " iteration_limit::Int,\n", + " # The absolute tolerance ϵ to use for convergence.\n", + " tolerance::Float64 = 1e-6,\n", + ")\n", + " # Step (1):\n", + " K = 0\n", + " model = JuMP.Model(HiGHS.Optimizer)\n", + " JuMP.set_silent(model)\n", + " JuMP.@variable(model, θ >= lower_bound)\n", + " JuMP.@variable(model, x[1:input_dimension])\n", + " JuMP.@objective(model, Min, θ)\n", + " x_k = fill(NaN, input_dimension)\n", + " lower_bound, upper_bound = -Inf, Inf\n", + " while true\n", + " # Step (2):\n", + " JuMP.optimize!(model)\n", + " x_k .= JuMP.value.(x)\n", + " # Step (3):\n", + " lower_bound = JuMP.objective_value(model)\n", + " upper_bound = min(upper_bound, f(x_k))\n", + " println(\"K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)\")\n", + " # Step (4):\n", + " JuMP.@constraint(model, θ >= f(x_k) + dfdx(x_k)' * (x .- x_k))\n", + " # Step (5):\n", + " K = K + 1\n", + " # Step (6):\n", + " if K == iteration_limit\n", + " println(\"-- Termination status: iteration limit --\")\n", + " break\n", + " elseif abs(upper_bound - lower_bound) < tolerance\n", + " println(\"-- Termination status: converged --\")\n", + " break\n", + " end\n", + " end\n", + " println(\"Found solution: x_K = \", x_k)\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's run our algorithm to see what happens:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "kelleys_cutting_plane(;\n", + " input_dimension = 2,\n", + " lower_bound = 0.0,\n", + " iteration_limit = 20,\n", + ") do x\n", + " return (x[1] - 1)^2 + (x[2] + 2)^2 + 1.0\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> It's hard to choose a valid lower bound! If you choose one too loose, the\n", + "> algorithm can take a long time to converge. However, if you choose one so\n", + "> tight that $M > f(x^*)$, then you can obtain a suboptimal solution. For a\n", + "> deeper discussion of the implications for SDDP.jl, see\n", + "> Choosing an initial bound." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Preliminaries: approximating the cost-to-go term" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the background theory section, we discussed how you could formulate an\n", + "optimal policy to a multistage stochastic program using the dynamic\n", + "programming recursion:\n", + "$$\n", + "\\begin{aligned}\n", + "V_i(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x,\n", + "\\end{aligned}\n", + "$$\n", + "where our decision rule, $\\pi_i(x, \\omega)$, solves this optimization problem\n", + "and returns a $u^*$ corresponding to an optimal solution. Moreover, we alluded\n", + "to the fact that the cost-to-go term (the nasty recursive expectation) makes\n", + "this problem intractable to solve." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "However, if, excluding the cost-to-go term (i.e., the `SP` formulation),\n", + "$V_i(x, \\omega)$ can be formulated as a linear program (this also works for\n", + "convex programs, but the math is more involved), then we can make some\n", + "progress by noticing that $x$ only appears as a right-hand side term of the\n", + "fishing constraint $\\bar{x} = x$. Therefore, $V_i(x, \\cdot)$ is convex with\n", + "respect to $x$ for fixed $\\omega$. (If you have not seen this result before,\n", + "try to prove it.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The fishing constraint $\\bar{x} = x$ has an associated dual variable. The\n", + "economic interpretation of this dual variable is that it represents the change\n", + "in the objective function if the right-hand side $x$ is increased on the scale\n", + "of one unit. In other words, and with a slight abuse of notation, it is the\n", + "value $\\frac{d}{dx} V_i(x, \\omega)$. (Because $V_i$ is not differentiable, it\n", + "is a [subgradient](https://en.wikipedia.org/wiki/Subderivative) instead of a\n", + "derivative.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If we implement the constraint $\\bar{x} = x$ by setting the lower- and upper\n", + "bounds of $\\bar{x}$ to $x$, then the [reduced cost](https://en.wikipedia.org/wiki/Reduced_cost)\n", + "of the decision variable $\\bar{x}$ is the subgradient, and we do not need to\n", + "explicitly add the fishing constraint as a row to the constraint matrix." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> The subproblem can have binary and integer variables, but you'll need to\n", + "> use Lagrangian duality to compute a subgradient!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Stochastic dual dynamic programming converts this problem into a tractable\n", + "form by applying Kelley's cutting plane algorithm to the $V_j$ functions in\n", + "the cost-to-go term:\n", + "$$\n", + "\\begin{aligned}\n", + "V_i^K(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\theta\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x \\\\\n", + "& \\theta \\ge \\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi) + \\frac{d}{dx^\\prime}V_j^k(x^\\prime_k, \\varphi)^\\top (x^\\prime - x^\\prime_k)\\right],\\quad k=1,\\ldots,K \\\\\n", + "& \\theta \\ge M.\n", + "\\end{aligned}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "All we need now is a way of generating these cutting planes in an iterative\n", + "manner. Before we get to that though, let's start writing some code." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: modeling" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Let's make a start by defining the problem structure. Like SDDP.jl, we need a\n", + "few things:\n", + "\n", + "1. A description of the structure of the policy graph: how many nodes there\n", + " are, and the arcs linking the nodes together with their corresponding\n", + " probabilities.\n", + "2. A JuMP model for each node in the policy graph.\n", + "3. A way to identify the incoming and outgoing state variables of each node.\n", + "4. A description of the random variable, as well as a function that we can\n", + " call that will modify the JuMP model to reflect the realization of the\n", + " random variable.\n", + "5. A decision variable to act as the approximated cost-to-go term." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> In the interests of brevity, there is minimal error checking. Think about\n", + "> all the different ways you could break the code!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Structs" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The first struct we are going to use is a `State` struct that will wrap an\n", + "incoming and outgoing state variable:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct State\n", + " in::JuMP.VariableRef\n", + " out::JuMP.VariableRef\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Next, we need a struct to wrap all of the uncertainty within a node:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct Uncertainty\n", + " parameterize::Function\n", + " Ω::Vector{Any}\n", + " P::Vector{Float64}\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`parameterize` is a function which takes a realization of the random variable\n", + "$\\omega\\in\\Omega$ and updates the subproblem accordingly. The finite discrete\n", + "random variable is defined by the vectors `Ω` and `P`, so that the random\n", + "variable takes the value `Ω[i]` with probability `P[i]`. As such, `P` should\n", + "sum to 1. (We don't check this here, but we should; we do in SDDP.jl.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we have two building blocks, we can declare the structure of each node:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct Node\n", + " subproblem::JuMP.Model\n", + " states::Dict{Symbol,State}\n", + " uncertainty::Uncertainty\n", + " cost_to_go::JuMP.VariableRef\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "* `subproblem` is going to be the JuMP model that we build at each node.\n", + "* `states` is a dictionary that maps a symbolic name of a state variable to a\n", + " `State` object wrapping the incoming and outgoing state variables in\n", + " `subproblem`.\n", + "* `uncertainty` is an `Uncertainty` object described above.\n", + "* `cost_to_go` is a JuMP variable that approximates the cost-to-go term." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we define a simplified policy graph as follows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "struct PolicyGraph\n", + " nodes::Vector{Node}\n", + " arcs::Vector{Dict{Int,Float64}}\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "There is a vector of nodes, as well as a data structure for the arcs. `arcs`\n", + "is a vector of dictionaries, where `arcs[i][j]` gives the probability of\n", + "transitioning from node `i` to node `j`, if an arc exists." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To simplify things, we will assume that the root node transitions to node `1`\n", + "with probability 1, and there are no other incoming arcs to node 1. Notably,\n", + "we can still define cyclic graphs though!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We also define a nice `show` method so that we don't accidentally print a\n", + "large amount of information to the screen when creating a model:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function Base.show(io::IO, model::PolicyGraph)\n", + " println(io, \"A policy graph with $(length(model.nodes)) nodes\")\n", + " println(io, \"Arcs:\")\n", + " for (from, arcs) in enumerate(model.arcs)\n", + " for (to, probability) in arcs\n", + " println(io, \" $(from) => $(to) w.p. $(probability)\")\n", + " end\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Functions" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we have some basic types, let's implement some functions so that the user\n", + "can create a model." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, we need an example of a function that the user will provide. Like\n", + "SDDP.jl, this takes an empty `subproblem`, and a node index, in this case\n", + "`t::Int`. You could change this function to change the model, or define a new\n", + "one later in the code." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We're going to copy the example from An introduction to SDDP.jl,\n", + "with some minor adjustments for the fact we don't have many of the bells and\n", + "whistles of SDDP.jl. You can probably see how some of the SDDP.jl\n", + "functionality like `@stageobjective` and `SDDP.parameterize`\n", + "help smooth some of the usability issues like needing to construct both the\n", + "incoming and outgoing state variables, or needing to explicitly declare\n", + "`return states, uncertainty`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::JuMP.Model, t::Int)\n", + " # Define the state variables. Note how we fix the incoming state to the\n", + " # initial state variable regardless of `t`! This isn't strictly necessary;\n", + " # it only matters that we do it for the first node.\n", + " JuMP.@variable(subproblem, volume_in == 200)\n", + " JuMP.@variable(subproblem, 0 <= volume_out <= 200)\n", + " states = Dict(:volume => State(volume_in, volume_out))\n", + " # Define the control variables.\n", + " JuMP.@variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " # Define the constraints\n", + " JuMP.@constraints(\n", + " subproblem,\n", + " begin\n", + " volume_out == volume_in + inflow - hydro_generation - hydro_spill\n", + " demand_constraint, thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + " # Define the objective for each stage `t`. Note that we can use `t` as an\n", + " # index for t = 1, 2, 3.\n", + " fuel_cost = [50.0, 100.0, 150.0]\n", + " JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation)\n", + " # Finally, we define the uncertainty object. Because this is a simplified\n", + " # implementation of SDDP, we shall politely ask the user to only modify the\n", + " # constraints, and not the objective function! (Not that it changes the\n", + " # algorithm, we just have to add more information to keep track of things.)\n", + " uncertainty = Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " return states, uncertainty\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The next function we need to define is the analog of\n", + "`SDDP.PolicyGraph`. It should be pretty readable." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function PolicyGraph(\n", + " subproblem_builder::Function;\n", + " graph::Vector{Dict{Int,Float64}},\n", + " lower_bound::Float64,\n", + " optimizer,\n", + ")\n", + " nodes = Node[]\n", + " for t in 1:length(graph)\n", + " # Create a model.\n", + " model = JuMP.Model(optimizer)\n", + " JuMP.set_silent(model)\n", + " # Use the provided function to build out each subproblem. The user's\n", + " # function returns a dictionary mapping `Symbol`s to `State` objects,\n", + " # and an `Uncertainty` object.\n", + " states, uncertainty = subproblem_builder(model, t)\n", + " # Now add the cost-to-go terms:\n", + " JuMP.@variable(model, cost_to_go >= lower_bound)\n", + " obj = JuMP.objective_function(model)\n", + " JuMP.@objective(model, Min, obj + cost_to_go)\n", + " # If there are no outgoing arcs, the cost-to-go is 0.0.\n", + " if length(graph[t]) == 0\n", + " JuMP.fix(cost_to_go, 0.0; force = true)\n", + " end\n", + " push!(nodes, Node(model, states, uncertainty, cost_to_go))\n", + " end\n", + " return PolicyGraph(nodes, graph)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Then, we can create a model using the `subproblem_builder` function we defined\n", + "earlier:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = PolicyGraph(\n", + " subproblem_builder;\n", + " graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()],\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: helpful samplers" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Before we get properly coding the solution algorithm, it's also going to be\n", + "useful to have a function that samples a realization of the random variable\n", + "defined by `Ω` and `P`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function sample_uncertainty(uncertainty::Uncertainty)\n", + " r = rand()\n", + " for (p, ω) in zip(uncertainty.P, uncertainty.Ω)\n", + " r -= p\n", + " if r < 0.0\n", + " return ω\n", + " end\n", + " end\n", + " return error(\"We should never get here because P should sum to 1.0.\")\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> `rand()` samples a uniform random variable in `[0, 1)`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For example:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for i in 1:3\n", + " println(\"ω = \", sample_uncertainty(model.nodes[1].uncertainty))\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "It's also going to be useful to define a function that generates a random walk\n", + "through the nodes of the graph:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function sample_next_node(model::PolicyGraph, current::Int)\n", + " if length(model.arcs[current]) == 0\n", + " # No outgoing arcs!\n", + " return nothing\n", + " else\n", + " r = rand()\n", + " for (to, probability) in model.arcs[current]\n", + " r -= probability\n", + " if r < 0.0\n", + " return to\n", + " end\n", + " end\n", + " # We looped through the outgoing arcs and still have probability left\n", + " # over! This means we've hit an implicit \"zero\" node.\n", + " return nothing\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "For example:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for i in 1:3\n", + " # We use `repr` to print the next node, because `sample_next_node` can\n", + " # return `nothing`.\n", + " println(\"Next node from $(i) = \", repr(sample_next_node(model, i)))\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This is a little boring, because our graph is simple. However, more\n", + "complicated graphs will generate more interesting trajectories!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: the forward pass" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Recall that, after approximating the cost-to-go term, we need a way of\n", + "generating the cuts. As the first step, we need a way of generating candidate\n", + "solutions $x_k^\\prime$. However, unlike the Kelley's example, our functions\n", + "$V_j^k(x^\\prime, \\varphi)$ need two inputs: an outgoing state variable and a\n", + "realization of the random variable." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One way of getting these inputs is just to pick a random (feasible) value.\n", + "However, in doing so, we might pick outgoing state variables that we will\n", + "never see in practice, or we might infrequently pick outgoing state variables\n", + "that we will often see in practice. Therefore, a better way of generating the\n", + "inputs is to use a simulation of the policy, which we call the **forward**\n", + "**pass**." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The forward pass walks the policy graph from start to end, transitioning\n", + "randomly along the arcs. At each node, it observes a realization of the random\n", + "variable and solves the approximated subproblem to generate a candidate\n", + "outgoing state variable $x_k^\\prime$. The outgoing state variable is passed as\n", + "the incoming state variable to the next node in the trajectory." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function forward_pass(model::PolicyGraph, io::IO = stdout)\n", + " println(io, \"| Forward Pass\")\n", + " # First, get the value of the state at the root node (e.g., x_R).\n", + " incoming_state =\n", + " Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states)\n", + " # `simulation_cost` is an accumlator that is going to sum the stage-costs\n", + " # incurred over the forward pass.\n", + " simulation_cost = 0.0\n", + " # We also need to record the nodes visited and resultant outgoing state\n", + " # variables so we can pass them to the backward pass.\n", + " trajectory = Tuple{Int,Dict{Symbol,Float64}}[]\n", + " # Now's the meat of the forward pass: beginning at the first node:\n", + " t = 1\n", + " while t !== nothing\n", + " node = model.nodes[t]\n", + " println(io, \"| | Visiting node $(t)\")\n", + " # Sample the uncertainty:\n", + " ω = sample_uncertainty(node.uncertainty)\n", + " println(io, \"| | | ω = \", ω)\n", + " # Parameterizing the subproblem using the user-provided function:\n", + " node.uncertainty.parameterize(ω)\n", + " println(io, \"| | | x = \", incoming_state)\n", + " # Update the incoming state variable:\n", + " for (k, v) in incoming_state\n", + " JuMP.fix(node.states[k].in, v; force = true)\n", + " end\n", + " # Now solve the subproblem and check we found an optimal solution:\n", + " JuMP.optimize!(node.subproblem)\n", + " if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL\n", + " error(\"Something went terribly wrong!\")\n", + " end\n", + " # Compute the outgoing state variables:\n", + " outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states)\n", + " println(io, \"| | | x′ = \", outgoing_state)\n", + " # We also need to compute the stage cost to add to our\n", + " # `simulation_cost` accumulator:\n", + " stage_cost =\n", + " JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go)\n", + " simulation_cost += stage_cost\n", + " println(io, \"| | | C(x, u, ω) = \", stage_cost)\n", + " # As a penultimate step, set the outgoing state of stage t and the\n", + " # incoming state of stage t + 1, and add the node to the trajectory.\n", + " incoming_state = outgoing_state\n", + " push!(trajectory, (t, outgoing_state))\n", + " # Finally, sample a new node to step to. If `t === nothing`, the\n", + " # `while` loop will break.\n", + " t = sample_next_node(model, t)\n", + " end\n", + " return trajectory, simulation_cost\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's take a look at one forward pass:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "trajectory, simulation_cost = forward_pass(model);" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: the backward pass" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "From the forward pass, we obtained a vector of nodes visited and their\n", + "corresponding outgoing state variables. Now we need to refine the\n", + "approximation for each node at the candidate solution for the outgoing state\n", + "variable. That is, we need to add a new cut:\n", + "$$\n", + "\\theta \\ge \\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}\\left[V_j^k(x^\\prime_k, \\varphi) + \\frac{d}{dx^\\prime}V_j^k(x^\\prime_k, \\varphi)^\\top (x^\\prime - x^\\prime_k)\\right]\n", + "$$\n", + "or alternatively:\n", + "$$\n", + "\\theta \\ge \\sum\\limits_{j \\in i^+} \\sum\\limits_{\\varphi \\in \\Omega_j} p_{ij} p_{\\varphi}\\left[V_j^k(x^\\prime_k, \\varphi) + \\frac{d}{dx^\\prime}V_j^k(x^\\prime_k, \\varphi)^\\top (x^\\prime - x^\\prime_k)\\right]\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "It doesn't matter what order we visit the nodes to generate these cuts for.\n", + "For example, we could compute them all in parallel, using the current\n", + "approximations of $V^K_i$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "However, we can be smarter than that." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If we traverse the list of nodes visited in the forward pass in reverse, then\n", + "we come to refine the $i^{th}$ node in the trajectory, we will already have\n", + "improved the approximation of the $(i+1)^{th}$ node in the trajectory as well!\n", + "Therefore, our refinement of the $i^{th}$ node will be better than if we\n", + "improved node $i$ first, and then refined node $(i+1)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Because we walk the nodes in reverse, we call this the **backward pass**." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> If you're into deep learning, you could view this as the equivalent of\n", + "> back-propagation: the forward pass pushes primal information through the\n", + "> graph (outgoing state variables), and the backward pass pulls dual\n", + "> information (cuts) back through the graph to improve our decisions on the\n", + "> next forward pass." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function backward_pass(\n", + " model::PolicyGraph,\n", + " trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}},\n", + " io::IO = stdout,\n", + ")\n", + " println(io, \"| Backward pass\")\n", + " # For the backward pass, we walk back up the nodes.\n", + " for i in reverse(1:length(trajectory))\n", + " index, outgoing_states = trajectory[i]\n", + " node = model.nodes[index]\n", + " println(io, \"| | Visiting node $(index)\")\n", + " if length(model.arcs[index]) == 0\n", + " # If there are no children, the cost-to-go is 0.\n", + " println(io, \"| | | Skipping node because the cost-to-go is 0\")\n", + " continue\n", + " end\n", + " # Create an empty affine expression that we will use to build up the\n", + " # right-hand side of the cut expression.\n", + " cut_expression = JuMP.AffExpr(0.0)\n", + " # For each node j ∈ i⁺\n", + " for (j, P_ij) in model.arcs[index]\n", + " next_node = model.nodes[j]\n", + " # Set the incoming state variables of node j to the outgoing state\n", + " # variables of node i\n", + " for (k, v) in outgoing_states\n", + " JuMP.fix(next_node.states[k].in, v; force = true)\n", + " end\n", + " # Then for each realization of φ ∈ Ωⱼ\n", + " for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω)\n", + " # Setup and solve for the realization of φ\n", + " println(io, \"| | | Solving φ = \", φ)\n", + " next_node.uncertainty.parameterize(φ)\n", + " JuMP.optimize!(next_node.subproblem)\n", + " # Then prepare the cut `P_ij * pφ * [V + dVdxᵀ(x - x_k)]``\n", + " V = JuMP.objective_value(next_node.subproblem)\n", + " println(io, \"| | | | V = \", V)\n", + " dVdx = Dict(\n", + " k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states\n", + " )\n", + " println(io, \"| | | | dVdx′ = \", dVdx)\n", + " cut_expression += JuMP.@expression(\n", + " node.subproblem,\n", + " P_ij *\n", + " pφ *\n", + " (\n", + " V + sum(\n", + " dVdx[k] * (x.out - outgoing_states[k]) for\n", + " (k, x) in node.states\n", + " )\n", + " ),\n", + " )\n", + " end\n", + " end\n", + " # And then refine the cost-to-go variable by adding the cut:\n", + " c = JuMP.@constraint(node.subproblem, node.cost_to_go >= cut_expression)\n", + " println(io, \"| | | Adding cut : \", c)\n", + " end\n", + " return nothing\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Lower bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Recall from Kelley's that we can obtain a lower bound for $f(x^*)$ be\n", + "evaluating $f^K$. The analogous lower bound for a multistage stochastic\n", + "program is:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\mathbb{E}_{i \\in R^+, \\omega \\in \\Omega_i}[V_i^K(x_R, \\omega)] \\le \\min_{\\pi} \\mathbb{E}_{i \\in R^+, \\omega \\in \\Omega_i}[V_i^\\pi(x_R, \\omega)]\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here's how we compute the lower bound:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function lower_bound(model::PolicyGraph)\n", + " node = model.nodes[1]\n", + " bound = 0.0\n", + " for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω)\n", + " node.uncertainty.parameterize(ω)\n", + " JuMP.optimize!(node.subproblem)\n", + " bound += p * JuMP.objective_value(node.subproblem)\n", + " end\n", + " return bound\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> The implementation is simplified because we assumed that there is only one\n", + "> arc from the root node, and that it pointed to the first node in the\n", + "> vector." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Because we haven't trained a policy yet, the lower bound is going to be very\n", + "bad:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "lower_bound(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Upper bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "With Kelley's algorithm, we could easily construct an upper bound by\n", + "evaluating $f(x_K)$. However, it is almost always intractable to evaluate an\n", + "upper bound for multistage stochastic programs due to the large number of\n", + "nodes and the nested expectations. Instead, we can perform a Monte Carlo\n", + "simulation of the policy to build a statistical estimate for the value of\n", + "$\\mathbb{E}_{i \\in R^+, \\omega \\in \\Omega_i}[V_i^\\pi(x_R, \\omega)]$, where\n", + "$\\pi$ is the policy defined by the current approximations $V^K_i$." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function upper_bound(model::PolicyGraph; replications::Int)\n", + " # Pipe the output to `devnull` so we don't print too much!\n", + " simulations = [forward_pass(model, devnull) for i in 1:replications]\n", + " z = [s[2] for s in simulations]\n", + " μ = Statistics.mean(z)\n", + " tσ = 1.96 * Statistics.std(z) / sqrt(replications)\n", + " return μ, tσ\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> The width of the confidence interval is incorrect if there are cycles in\n", + "> the graph, because the distribution of simulation costs `z` is not\n", + "> symmetric. The mean is correct, however." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Termination criteria" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In Kelley's algorithm, the upper bound was deterministic. Therefore, we could\n", + "terminate the algorithm when the lower bound was sufficiently close to the\n", + "upper bound. However, our upper bound for SDDP is not deterministic; it is a\n", + "confidence interval!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Some people suggest terminating SDDP when the lower bound is contained within\n", + "the confidence interval. However, this is a poor choice because it is too easy\n", + "to generate a false positive. For example, if we use a small number of\n", + "replications then the width of the confidence will be large, and we are more\n", + "likely to terminate!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In a future tutorial (not yet written...) we will discuss termination criteria\n", + "in more depth. For now, pick a large number of iterations and train for as\n", + "long as possible." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> For a rule of thumb, pick a large number of iterations to train the\n", + "> policy for (e.g.,\n", + "> $10 \\times |\\mathcal{N}| \\times \\max\\limits_{i\\in\\mathcal{N}} |\\Omega_i|$)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: the training loop" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The `train` loop of SDDP just applies the forward and backward passes\n", + "iteratively, followed by a final simulation to compute the upper bound\n", + "confidence interval:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function train(\n", + " model::PolicyGraph;\n", + " iteration_limit::Int,\n", + " replications::Int,\n", + " io::IO = stdout,\n", + ")\n", + " for i in 1:iteration_limit\n", + " println(io, \"Starting iteration $(i)\")\n", + " outgoing_states, _ = forward_pass(model, io)\n", + " backward_pass(model, outgoing_states, io)\n", + " println(io, \"| Finished iteration\")\n", + " println(io, \"| | lower_bound = \", lower_bound(model))\n", + " end\n", + " println(io, \"Termination status: iteration limit\")\n", + " μ, tσ = upper_bound(model; replications = replications)\n", + " println(io, \"Upper bound = $(μ) ± $(tσ)\")\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Using our `model` we defined earlier, we can go:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "train(model; iteration_limit = 3, replications = 100)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Success! We trained a policy for a finite horizon multistage stochastic\n", + "program using stochastic dual dynamic programming." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Implementation: evaluating the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A final step is the ability to evaluate the policy at a given point." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function evaluate_policy(\n", + " model::PolicyGraph;\n", + " node::Int,\n", + " incoming_state::Dict{Symbol,Float64},\n", + " random_variable,\n", + ")\n", + " the_node = model.nodes[node]\n", + " the_node.uncertainty.parameterize(random_variable)\n", + " for (k, v) in incoming_state\n", + " JuMP.fix(the_node.states[k].in, v; force = true)\n", + " end\n", + " JuMP.optimize!(the_node.subproblem)\n", + " return Dict(\n", + " k => JuMP.value.(v) for\n", + " (k, v) in JuMP.object_dictionary(the_node.subproblem)\n", + " )\n", + "end\n", + "\n", + "evaluate_policy(\n", + " model;\n", + " node = 1,\n", + " incoming_state = Dict(:volume => 150.0),\n", + " random_variable = 75,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> The random variable can be **out-of-sample**, i.e., it doesn't have to be\n", + "> in the vector $\\Omega$ we created when defining the model! This is a\n", + "> notable difference to other multistage stochastic solution methods like\n", + "> progressive hedging or using the deterministic equivalent." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Example: infinite horizon" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As promised earlier, our implementation is actually pretty general. It can\n", + "solve any multistage stochastic (linear) program defined by a policy graph,\n", + "including infinite horizon problems!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here's an example, where we have extended our earlier problem with an arc from\n", + "node 3 to node 2 with probability 0.5. You can interpret the 0.5 as a discount\n", + "factor." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = PolicyGraph(\n", + " subproblem_builder;\n", + " graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict(2 => 0.5)],\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Then, train a policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "train(model; iteration_limit = 3, replications = 100)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Success! We trained a policy for an infinite horizon multistage stochastic\n", + "program using stochastic dual dynamic programming. Note how some of the\n", + "forward passes are different lengths!" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "evaluate_policy(\n", + " model;\n", + " node = 3,\n", + " incoming_state = Dict(:volume => 100.0),\n", + " random_variable = 10.0,\n", + ")" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/explanation/theory_intro.jl b/previews/PR826/explanation/theory_intro.jl new file mode 100644 index 0000000000..251edacf38 --- /dev/null +++ b/previews/PR826/explanation/theory_intro.jl @@ -0,0 +1,818 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Introductory theory + +# !!! note +# This tutorial is aimed at advanced undergraduates or early-stage graduate +# students. You don't need prior exposure to stochastic programming! +# (Indeed, it may be better if you don't, because our approach is +# non-standard in the literature.) +# +# This tutorial is also a living document. If parts are unclear, please +# [open an issue](https://github.com/odow/SDDP.jl/issues/new) so it can be +# improved! + +# This tutorial will teach you how the stochastic dual dynamic programming +# algorithm works by implementing a simplified version of the algorithm. + +# Our implementation is very much a "vanilla" version of SDDP; it doesn't have +# (m)any fancy computational tricks (e.g., the ones included in SDDP.jl) that +# you need to code a performant or stable version that will work on realistic +# instances. However, our simplified implementation will work on arbitrary +# policy graphs, including those with cycles such as infinite horizon problems! + +# **Packages** +# +# This tutorial uses the following packages. For clarity, we call +# `import PackageName` so that we must prefix `PackageName.` to all functions +# and structs provided by that package. Everything not prefixed is either part +# of base Julia, or we wrote it. + +import ForwardDiff +import HiGHS +import JuMP +import Statistics + +# !!! tip +# You can follow along by installing the above packages, and copy-pasting +# the code we will write into a Julia REPL. Alternatively, you can download +# the Julia `.jl` file which created this tutorial [from GitHub](https://github.com/odow/SDDP.jl/blob/master/docs/src/tutorial/21_theory_intro.jl). + +# ## Preliminaries: background theory + +# Start this tutorial by reading [An introduction to SDDP.jl](@ref), which +# introduces the necessary notation and vocabulary that we need for this +# tutorial. + +# ## Preliminaries: Kelley's cutting plane algorithm + +# Kelley's cutting plane algorithm is an iterative method for minimizing convex +# functions. Given a convex function $f(x)$, Kelley's constructs an +# under-approximation of the function at the minimum by a set of first-order +# Taylor series approximations (called **cuts**) constructed at a set of points +# $k = 1,\ldots,K$: +# ```math +# \begin{aligned} +# f^K = \min\limits_{\theta \in \mathbb{R}, x \in \mathbb{R}^N} \;\; & \theta\\ +# & \theta \ge f(x_k) + \frac{d}{dx}f(x_k)^\top (x - x_k),\quad k=1,\ldots,K\\ +# & \theta \ge M, +# \end{aligned} +# ``` +# where $M$ is a sufficiently large negative number that is a lower bound for +# $f$ over the domain of $x$. + +# Kelley's cutting plane algorithm is a structured way of choosing points $x_k$ +# to visit, so that as more cuts are added: +# ```math +# \lim_{K \rightarrow \infty} f^K = \min\limits_{x \in \mathbb{R}^N} f(x) +# ``` +# However, before we introduce the algorithm, we need to introduce some bounds. + +# ### Bounds + +# By convexity, $f^K \le f(x)$ for all $x$. Thus, if $x^*$ is a minimizer of +# $f$, then at any point in time we can construct a lower bound for $f(x^*)$ by +# solving $f^K$. + +# Moreover, we can use the primal solutions $x_k^*$ returned by solving $f^k$ to +# evaluate $f(x_k^*)$ to generate an upper bound. + +# Therefore, $f^K \le f(x^*) \le \min\limits_{k=1,\ldots,K} f(x_k^*)$. + +# When the lower bound is sufficiently close to the upper bound, we can +# terminate the algorithm and declare that we have found an solution that is +# close to optimal. + +# ### Implementation + +# Here is pseudo-code fo the Kelley algorithm: + +# 1. Take as input a convex function $f(x)$ and a iteration limit $K_{max}$. +# Set $K = 0$, and initialize $f^K$. Set $lb = -\infty$ and $ub = \infty$. +# 2. Solve $f^K$ to obtain a candidate solution $x_{K+1}$. +# 3. Update $lb = f^K$ and $ub = \min\{ub, f(x_{K+1})\}$. +# 4. Add a cut $\theta \ge f(x_{K+1}) + \frac{d}{dx}f\left(x_{K+1}\right)^\top (x - x_{K+1})$ to form $f^{K+1}$. +# 5. Increment $K$. +# 6. If $K = K_{max}$ or $|ub - lb| < \epsilon$, STOP, otherwise, go to step 2. + +# And here's a complete implementation: + +function kelleys_cutting_plane( + ## The function to be minimized. + f::Function, + ## The gradient of `f`. By default, we use automatic differentiation to + ## compute the gradient of f so the user doesn't have to! + dfdx::Function = x -> ForwardDiff.gradient(f, x); + ## The number of arguments to `f`. + input_dimension::Int, + ## A lower bound for the function `f` over its domain. + lower_bound::Float64, + ## The number of iterations to run Kelley's algorithm for before stopping. + iteration_limit::Int, + ## The absolute tolerance ϵ to use for convergence. + tolerance::Float64 = 1e-6, +) + ## Step (1): + K = 0 + model = JuMP.Model(HiGHS.Optimizer) + JuMP.set_silent(model) + JuMP.@variable(model, θ >= lower_bound) + JuMP.@variable(model, x[1:input_dimension]) + JuMP.@objective(model, Min, θ) + x_k = fill(NaN, input_dimension) + lower_bound, upper_bound = -Inf, Inf + while true + ## Step (2): + JuMP.optimize!(model) + x_k .= JuMP.value.(x) + ## Step (3): + lower_bound = JuMP.objective_value(model) + upper_bound = min(upper_bound, f(x_k)) + println("K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)") + ## Step (4): + JuMP.@constraint(model, θ >= f(x_k) + dfdx(x_k)' * (x .- x_k)) + ## Step (5): + K = K + 1 + ## Step (6): + if K == iteration_limit + println("-- Termination status: iteration limit --") + break + elseif abs(upper_bound - lower_bound) < tolerance + println("-- Termination status: converged --") + break + end + end + println("Found solution: x_K = ", x_k) + return +end + +# Let's run our algorithm to see what happens: + +kelleys_cutting_plane(; + input_dimension = 2, + lower_bound = 0.0, + iteration_limit = 20, +) do x + return (x[1] - 1)^2 + (x[2] + 2)^2 + 1.0 +end + +# !!! warning +# It's hard to choose a valid lower bound! If you choose one too loose, the +# algorithm can take a long time to converge. However, if you choose one so +# tight that $M > f(x^*)$, then you can obtain a suboptimal solution. For a +# deeper discussion of the implications for SDDP.jl, see +# [Choosing an initial bound](@ref). + +# ## Preliminaries: approximating the cost-to-go term + +# In the background theory section, we discussed how you could formulate an +# optimal policy to a multistage stochastic program using the dynamic +# programming recursion: +# ```math +# \begin{aligned} +# V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x, +# \end{aligned} +# ``` +# where our decision rule, $\pi_i(x, \omega)$, solves this optimization problem +# and returns a $u^*$ corresponding to an optimal solution. Moreover, we alluded +# to the fact that the cost-to-go term (the nasty recursive expectation) makes +# this problem intractable to solve. + +# However, if, excluding the cost-to-go term (i.e., the `SP` formulation), +# $V_i(x, \omega)$ can be formulated as a linear program (this also works for +# convex programs, but the math is more involved), then we can make some +# progress by noticing that $x$ only appears as a right-hand side term of the +# fishing constraint $\bar{x} = x$. Therefore, $V_i(x, \cdot)$ is convex with +# respect to $x$ for fixed $\omega$. (If you have not seen this result before, +# try to prove it.) + +# The fishing constraint $\bar{x} = x$ has an associated dual variable. The +# economic interpretation of this dual variable is that it represents the change +# in the objective function if the right-hand side $x$ is increased on the scale +# of one unit. In other words, and with a slight abuse of notation, it is the +# value $\frac{d}{dx} V_i(x, \omega)$. (Because $V_i$ is not differentiable, it +# is a [subgradient](https://en.wikipedia.org/wiki/Subderivative) instead of a +# derivative.) + +# If we implement the constraint $\bar{x} = x$ by setting the lower- and upper +# bounds of $\bar{x}$ to $x$, then the [reduced cost](https://en.wikipedia.org/wiki/Reduced_cost) +# of the decision variable $\bar{x}$ is the subgradient, and we do not need to +# explicitly add the fishing constraint as a row to the constraint matrix. + +# !!! tip +# The subproblem can have binary and integer variables, but you'll need to +# use Lagrangian duality to compute a subgradient! + +# Stochastic dual dynamic programming converts this problem into a tractable +# form by applying Kelley's cutting plane algorithm to the $V_j$ functions in +# the cost-to-go term: +# ```math +# \begin{aligned} +# V_i^K(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \theta\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x \\ +# & \theta \ge \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right],\quad k=1,\ldots,K \\ +# & \theta \ge M. +# \end{aligned} +# ``` + +# All we need now is a way of generating these cutting planes in an iterative +# manner. Before we get to that though, let's start writing some code. + +# ## Implementation: modeling + +# Let's make a start by defining the problem structure. Like SDDP.jl, we need a +# few things: +# +# 1. A description of the structure of the policy graph: how many nodes there +# are, and the arcs linking the nodes together with their corresponding +# probabilities. +# 2. A JuMP model for each node in the policy graph. +# 3. A way to identify the incoming and outgoing state variables of each node. +# 4. A description of the random variable, as well as a function that we can +# call that will modify the JuMP model to reflect the realization of the +# random variable. +# 5. A decision variable to act as the approximated cost-to-go term. + +# !!! warning +# In the interests of brevity, there is minimal error checking. Think about +# all the different ways you could break the code! + +# ### Structs + +# The first struct we are going to use is a `State` struct that will wrap an +# incoming and outgoing state variable: + +struct State + in::JuMP.VariableRef + out::JuMP.VariableRef +end + +# Next, we need a struct to wrap all of the uncertainty within a node: + +struct Uncertainty + parameterize::Function + Ω::Vector{Any} + P::Vector{Float64} +end + +# `parameterize` is a function which takes a realization of the random variable +# $\omega\in\Omega$ and updates the subproblem accordingly. The finite discrete +# random variable is defined by the vectors `Ω` and `P`, so that the random +# variable takes the value `Ω[i]` with probability `P[i]`. As such, `P` should +# sum to 1. (We don't check this here, but we should; we do in SDDP.jl.) + +# Now we have two building blocks, we can declare the structure of each node: + +struct Node + subproblem::JuMP.Model + states::Dict{Symbol,State} + uncertainty::Uncertainty + cost_to_go::JuMP.VariableRef +end + +# * `subproblem` is going to be the JuMP model that we build at each node. +# * `states` is a dictionary that maps a symbolic name of a state variable to a +# `State` object wrapping the incoming and outgoing state variables in +# `subproblem`. +# * `uncertainty` is an `Uncertainty` object described above. +# * `cost_to_go` is a JuMP variable that approximates the cost-to-go term. + +# Finally, we define a simplified policy graph as follows: +struct PolicyGraph + nodes::Vector{Node} + arcs::Vector{Dict{Int,Float64}} +end + +# There is a vector of nodes, as well as a data structure for the arcs. `arcs` +# is a vector of dictionaries, where `arcs[i][j]` gives the probability of +# transitioning from node `i` to node `j`, if an arc exists. + +# To simplify things, we will assume that the root node transitions to node `1` +# with probability 1, and there are no other incoming arcs to node 1. Notably, +# we can still define cyclic graphs though! + +# We also define a nice `show` method so that we don't accidentally print a +# large amount of information to the screen when creating a model: + +function Base.show(io::IO, model::PolicyGraph) + println(io, "A policy graph with $(length(model.nodes)) nodes") + println(io, "Arcs:") + for (from, arcs) in enumerate(model.arcs) + for (to, probability) in arcs + println(io, " $(from) => $(to) w.p. $(probability)") + end + end + return +end + +# ### Functions + +# Now we have some basic types, let's implement some functions so that the user +# can create a model. + +# First, we need an example of a function that the user will provide. Like +# SDDP.jl, this takes an empty `subproblem`, and a node index, in this case +# `t::Int`. You could change this function to change the model, or define a new +# one later in the code. + +# We're going to copy the example from [An introduction to SDDP.jl](@ref), +# with some minor adjustments for the fact we don't have many of the bells and +# whistles of SDDP.jl. You can probably see how some of the SDDP.jl +# functionality like [`@stageobjective`](@ref) and [`SDDP.parameterize`](@ref) +# help smooth some of the usability issues like needing to construct both the +# incoming and outgoing state variables, or needing to explicitly declare +# `return states, uncertainty`. + +function subproblem_builder(subproblem::JuMP.Model, t::Int) + ## Define the state variables. Note how we fix the incoming state to the + ## initial state variable regardless of `t`! This isn't strictly necessary; + ## it only matters that we do it for the first node. + JuMP.@variable(subproblem, volume_in == 200) + JuMP.@variable(subproblem, 0 <= volume_out <= 200) + states = Dict(:volume => State(volume_in, volume_out)) + ## Define the control variables. + JuMP.@variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + ## Define the constraints + JuMP.@constraints( + subproblem, + begin + volume_out == volume_in + inflow - hydro_generation - hydro_spill + demand_constraint, thermal_generation + hydro_generation == 150.0 + end + ) + ## Define the objective for each stage `t`. Note that we can use `t` as an + ## index for t = 1, 2, 3. + fuel_cost = [50.0, 100.0, 150.0] + JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation) + ## Finally, we define the uncertainty object. Because this is a simplified + ## implementation of SDDP, we shall politely ask the user to only modify the + ## constraints, and not the objective function! (Not that it changes the + ## algorithm, we just have to add more information to keep track of things.) + uncertainty = Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω + return JuMP.fix(inflow, ω) + end + return states, uncertainty +end + +# The next function we need to define is the analog of +# [`SDDP.PolicyGraph`](@ref). It should be pretty readable. + +function PolicyGraph( + subproblem_builder::Function; + graph::Vector{Dict{Int,Float64}}, + lower_bound::Float64, + optimizer, +) + nodes = Node[] + for t in 1:length(graph) + ## Create a model. + model = JuMP.Model(optimizer) + JuMP.set_silent(model) + ## Use the provided function to build out each subproblem. The user's + ## function returns a dictionary mapping `Symbol`s to `State` objects, + ## and an `Uncertainty` object. + states, uncertainty = subproblem_builder(model, t) + ## Now add the cost-to-go terms: + JuMP.@variable(model, cost_to_go >= lower_bound) + obj = JuMP.objective_function(model) + JuMP.@objective(model, Min, obj + cost_to_go) + ## If there are no outgoing arcs, the cost-to-go is 0.0. + if length(graph[t]) == 0 + JuMP.fix(cost_to_go, 0.0; force = true) + end + push!(nodes, Node(model, states, uncertainty, cost_to_go)) + end + return PolicyGraph(nodes, graph) +end + +# Then, we can create a model using the `subproblem_builder` function we defined +# earlier: + +model = PolicyGraph( + subproblem_builder; + graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()], + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) + +# ## Implementation: helpful samplers + +# Before we get properly coding the solution algorithm, it's also going to be +# useful to have a function that samples a realization of the random variable +# defined by `Ω` and `P`. + +function sample_uncertainty(uncertainty::Uncertainty) + r = rand() + for (p, ω) in zip(uncertainty.P, uncertainty.Ω) + r -= p + if r < 0.0 + return ω + end + end + return error("We should never get here because P should sum to 1.0.") +end + +# !!! note +# `rand()` samples a uniform random variable in `[0, 1)`. + +# For example: + +for i in 1:3 + println("ω = ", sample_uncertainty(model.nodes[1].uncertainty)) +end + +# It's also going to be useful to define a function that generates a random walk +# through the nodes of the graph: + +function sample_next_node(model::PolicyGraph, current::Int) + if length(model.arcs[current]) == 0 + ## No outgoing arcs! + return nothing + else + r = rand() + for (to, probability) in model.arcs[current] + r -= probability + if r < 0.0 + return to + end + end + ## We looped through the outgoing arcs and still have probability left + ## over! This means we've hit an implicit "zero" node. + return nothing + end +end + +# For example: + +for i in 1:3 + ## We use `repr` to print the next node, because `sample_next_node` can + ## return `nothing`. + println("Next node from $(i) = ", repr(sample_next_node(model, i))) +end + +# This is a little boring, because our graph is simple. However, more +# complicated graphs will generate more interesting trajectories! + +# ## Implementation: the forward pass + +# Recall that, after approximating the cost-to-go term, we need a way of +# generating the cuts. As the first step, we need a way of generating candidate +# solutions $x_k^\prime$. However, unlike the Kelley's example, our functions +# $V_j^k(x^\prime, \varphi)$ need two inputs: an outgoing state variable and a +# realization of the random variable. + +# One way of getting these inputs is just to pick a random (feasible) value. +# However, in doing so, we might pick outgoing state variables that we will +# never see in practice, or we might infrequently pick outgoing state variables +# that we will often see in practice. Therefore, a better way of generating the +# inputs is to use a simulation of the policy, which we call the **forward** +# **pass**. + +# The forward pass walks the policy graph from start to end, transitioning +# randomly along the arcs. At each node, it observes a realization of the random +# variable and solves the approximated subproblem to generate a candidate +# outgoing state variable $x_k^\prime$. The outgoing state variable is passed as +# the incoming state variable to the next node in the trajectory. + +function forward_pass(model::PolicyGraph, io::IO = stdout) + println(io, "| Forward Pass") + ## First, get the value of the state at the root node (e.g., x_R). + incoming_state = + Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states) + ## `simulation_cost` is an accumlator that is going to sum the stage-costs + ## incurred over the forward pass. + simulation_cost = 0.0 + ## We also need to record the nodes visited and resultant outgoing state + ## variables so we can pass them to the backward pass. + trajectory = Tuple{Int,Dict{Symbol,Float64}}[] + ## Now's the meat of the forward pass: beginning at the first node: + t = 1 + while t !== nothing + node = model.nodes[t] + println(io, "| | Visiting node $(t)") + ## Sample the uncertainty: + ω = sample_uncertainty(node.uncertainty) + println(io, "| | | ω = ", ω) + ## Parameterizing the subproblem using the user-provided function: + node.uncertainty.parameterize(ω) + println(io, "| | | x = ", incoming_state) + ## Update the incoming state variable: + for (k, v) in incoming_state + JuMP.fix(node.states[k].in, v; force = true) + end + ## Now solve the subproblem and check we found an optimal solution: + JuMP.optimize!(node.subproblem) + if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL + error("Something went terribly wrong!") + end + ## Compute the outgoing state variables: + outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states) + println(io, "| | | x′ = ", outgoing_state) + ## We also need to compute the stage cost to add to our + ## `simulation_cost` accumulator: + stage_cost = + JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go) + simulation_cost += stage_cost + println(io, "| | | C(x, u, ω) = ", stage_cost) + ## As a penultimate step, set the outgoing state of stage t and the + ## incoming state of stage t + 1, and add the node to the trajectory. + incoming_state = outgoing_state + push!(trajectory, (t, outgoing_state)) + ## Finally, sample a new node to step to. If `t === nothing`, the + ## `while` loop will break. + t = sample_next_node(model, t) + end + return trajectory, simulation_cost +end + +# Let's take a look at one forward pass: + +trajectory, simulation_cost = forward_pass(model); + +# ## Implementation: the backward pass + +# From the forward pass, we obtained a vector of nodes visited and their +# corresponding outgoing state variables. Now we need to refine the +# approximation for each node at the candidate solution for the outgoing state +# variable. That is, we need to add a new cut: +# ```math +# \theta \ge \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right] +# ``` +# or alternatively: +# ```math +# \theta \ge \sum\limits_{j \in i^+} \sum\limits_{\varphi \in \Omega_j} p_{ij} p_{\varphi}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right] +# ``` + +# It doesn't matter what order we visit the nodes to generate these cuts for. +# For example, we could compute them all in parallel, using the current +# approximations of $V^K_i$. + +# However, we can be smarter than that. + +# If we traverse the list of nodes visited in the forward pass in reverse, then +# we come to refine the $i^{th}$ node in the trajectory, we will already have +# improved the approximation of the $(i+1)^{th}$ node in the trajectory as well! +# Therefore, our refinement of the $i^{th}$ node will be better than if we +# improved node $i$ first, and then refined node $(i+1)$. + +# Because we walk the nodes in reverse, we call this the **backward pass**. + +# !!! info +# If you're into deep learning, you could view this as the equivalent of +# back-propagation: the forward pass pushes primal information through the +# graph (outgoing state variables), and the backward pass pulls dual +# information (cuts) back through the graph to improve our decisions on the +# next forward pass. + +function backward_pass( + model::PolicyGraph, + trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}}, + io::IO = stdout, +) + println(io, "| Backward pass") + ## For the backward pass, we walk back up the nodes. + for i in reverse(1:length(trajectory)) + index, outgoing_states = trajectory[i] + node = model.nodes[index] + println(io, "| | Visiting node $(index)") + if length(model.arcs[index]) == 0 + ## If there are no children, the cost-to-go is 0. + println(io, "| | | Skipping node because the cost-to-go is 0") + continue + end + ## Create an empty affine expression that we will use to build up the + ## right-hand side of the cut expression. + cut_expression = JuMP.AffExpr(0.0) + ## For each node j ∈ i⁺ + for (j, P_ij) in model.arcs[index] + next_node = model.nodes[j] + ## Set the incoming state variables of node j to the outgoing state + ## variables of node i + for (k, v) in outgoing_states + JuMP.fix(next_node.states[k].in, v; force = true) + end + ## Then for each realization of φ ∈ Ωⱼ + for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω) + ## Setup and solve for the realization of φ + println(io, "| | | Solving φ = ", φ) + next_node.uncertainty.parameterize(φ) + JuMP.optimize!(next_node.subproblem) + ## Then prepare the cut `P_ij * pφ * [V + dVdxᵀ(x - x_k)]`` + V = JuMP.objective_value(next_node.subproblem) + println(io, "| | | | V = ", V) + dVdx = Dict( + k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states + ) + println(io, "| | | | dVdx′ = ", dVdx) + cut_expression += JuMP.@expression( + node.subproblem, + P_ij * + pφ * + ( + V + sum( + dVdx[k] * (x.out - outgoing_states[k]) for + (k, x) in node.states + ) + ), + ) + end + end + ## And then refine the cost-to-go variable by adding the cut: + c = JuMP.@constraint(node.subproblem, node.cost_to_go >= cut_expression) + println(io, "| | | Adding cut : ", c) + end + return nothing +end + +# ## Implementation: bounds + +# ### Lower bounds + +# Recall from Kelley's that we can obtain a lower bound for $f(x^*)$ be +# evaluating $f^K$. The analogous lower bound for a multistage stochastic +# program is: + +# ```math +# \mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^K(x_R, \omega)] \le \min_{\pi} \mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^\pi(x_R, \omega)] +# ``` + +# Here's how we compute the lower bound: + +function lower_bound(model::PolicyGraph) + node = model.nodes[1] + bound = 0.0 + for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω) + node.uncertainty.parameterize(ω) + JuMP.optimize!(node.subproblem) + bound += p * JuMP.objective_value(node.subproblem) + end + return bound +end + +# !!! note +# The implementation is simplified because we assumed that there is only one +# arc from the root node, and that it pointed to the first node in the +# vector. + +# Because we haven't trained a policy yet, the lower bound is going to be very +# bad: + +lower_bound(model) + +# ### Upper bounds + +# With Kelley's algorithm, we could easily construct an upper bound by +# evaluating $f(x_K)$. However, it is almost always intractable to evaluate an +# upper bound for multistage stochastic programs due to the large number of +# nodes and the nested expectations. Instead, we can perform a Monte Carlo +# simulation of the policy to build a statistical estimate for the value of +# $\mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^\pi(x_R, \omega)]$, where +# $\pi$ is the policy defined by the current approximations $V^K_i$. + +function upper_bound(model::PolicyGraph; replications::Int) + ## Pipe the output to `devnull` so we don't print too much! + simulations = [forward_pass(model, devnull) for i in 1:replications] + z = [s[2] for s in simulations] + μ = Statistics.mean(z) + tσ = 1.96 * Statistics.std(z) / sqrt(replications) + return μ, tσ +end + +# !!! note +# The width of the confidence interval is incorrect if there are cycles in +# the graph, because the distribution of simulation costs `z` is not +# symmetric. The mean is correct, however. + +# ### Termination criteria + +# In Kelley's algorithm, the upper bound was deterministic. Therefore, we could +# terminate the algorithm when the lower bound was sufficiently close to the +# upper bound. However, our upper bound for SDDP is not deterministic; it is a +# confidence interval! + +# Some people suggest terminating SDDP when the lower bound is contained within +# the confidence interval. However, this is a poor choice because it is too easy +# to generate a false positive. For example, if we use a small number of +# replications then the width of the confidence will be large, and we are more +# likely to terminate! + +# In a future tutorial (not yet written...) we will discuss termination criteria +# in more depth. For now, pick a large number of iterations and train for as +# long as possible. + +# !!! tip +# For a rule of thumb, pick a large number of iterations to train the +# policy for (e.g., +# $10 \times |\mathcal{N}| \times \max\limits_{i\in\mathcal{N}} |\Omega_i|$) + +# ## Implementation: the training loop + +# The `train` loop of SDDP just applies the forward and backward passes +# iteratively, followed by a final simulation to compute the upper bound +# confidence interval: + +function train( + model::PolicyGraph; + iteration_limit::Int, + replications::Int, + io::IO = stdout, +) + for i in 1:iteration_limit + println(io, "Starting iteration $(i)") + outgoing_states, _ = forward_pass(model, io) + backward_pass(model, outgoing_states, io) + println(io, "| Finished iteration") + println(io, "| | lower_bound = ", lower_bound(model)) + end + println(io, "Termination status: iteration limit") + μ, tσ = upper_bound(model; replications = replications) + println(io, "Upper bound = $(μ) ± $(tσ)") + return +end + +# Using our `model` we defined earlier, we can go: + +train(model; iteration_limit = 3, replications = 100) + +# Success! We trained a policy for a finite horizon multistage stochastic +# program using stochastic dual dynamic programming. + +# ## Implementation: evaluating the policy + +# A final step is the ability to evaluate the policy at a given point. + +function evaluate_policy( + model::PolicyGraph; + node::Int, + incoming_state::Dict{Symbol,Float64}, + random_variable, +) + the_node = model.nodes[node] + the_node.uncertainty.parameterize(random_variable) + for (k, v) in incoming_state + JuMP.fix(the_node.states[k].in, v; force = true) + end + JuMP.optimize!(the_node.subproblem) + return Dict( + k => JuMP.value.(v) for + (k, v) in JuMP.object_dictionary(the_node.subproblem) + ) +end + +evaluate_policy( + model; + node = 1, + incoming_state = Dict(:volume => 150.0), + random_variable = 75, +) + +# !!! note +# The random variable can be **out-of-sample**, i.e., it doesn't have to be +# in the vector $\Omega$ we created when defining the model! This is a +# notable difference to other multistage stochastic solution methods like +# progressive hedging or using the deterministic equivalent. + +# ## Example: infinite horizon + +# As promised earlier, our implementation is actually pretty general. It can +# solve any multistage stochastic (linear) program defined by a policy graph, +# including infinite horizon problems! + +# Here's an example, where we have extended our earlier problem with an arc from +# node 3 to node 2 with probability 0.5. You can interpret the 0.5 as a discount +# factor. + +model = PolicyGraph( + subproblem_builder; + graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict(2 => 0.5)], + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) + +# Then, train a policy: + +train(model; iteration_limit = 3, replications = 100) + +# Success! We trained a policy for an infinite horizon multistage stochastic +# program using stochastic dual dynamic programming. Note how some of the +# forward passes are different lengths! + +evaluate_policy( + model; + node = 3, + incoming_state = Dict(:volume => 100.0), + random_variable = 10.0, +) diff --git a/previews/PR826/explanation/theory_intro/index.html b/previews/PR826/explanation/theory_intro/index.html new file mode 100644 index 0000000000..7d7ccfccb4 --- /dev/null +++ b/previews/PR826/explanation/theory_intro/index.html @@ -0,0 +1,921 @@ + +Introductory theory · SDDP.jl

Introductory theory

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

Note

This tutorial is aimed at advanced undergraduates or early-stage graduate students. You don't need prior exposure to stochastic programming! (Indeed, it may be better if you don't, because our approach is non-standard in the literature.)

This tutorial is also a living document. If parts are unclear, please open an issue so it can be improved!

This tutorial will teach you how the stochastic dual dynamic programming algorithm works by implementing a simplified version of the algorithm.

Our implementation is very much a "vanilla" version of SDDP; it doesn't have (m)any fancy computational tricks (e.g., the ones included in SDDP.jl) that you need to code a performant or stable version that will work on realistic instances. However, our simplified implementation will work on arbitrary policy graphs, including those with cycles such as infinite horizon problems!

Packages

This tutorial uses the following packages. For clarity, we call import PackageName so that we must prefix PackageName. to all functions and structs provided by that package. Everything not prefixed is either part of base Julia, or we wrote it.

import ForwardDiff
+import HiGHS
+import JuMP
+import Statistics
Tip

You can follow along by installing the above packages, and copy-pasting the code we will write into a Julia REPL. Alternatively, you can download the Julia .jl file which created this tutorial from GitHub.

Preliminaries: background theory

Start this tutorial by reading An introduction to SDDP.jl, which introduces the necessary notation and vocabulary that we need for this tutorial.

Preliminaries: Kelley's cutting plane algorithm

Kelley's cutting plane algorithm is an iterative method for minimizing convex functions. Given a convex function $f(x)$, Kelley's constructs an under-approximation of the function at the minimum by a set of first-order Taylor series approximations (called cuts) constructed at a set of points $k = 1,\ldots,K$:

\[\begin{aligned} +f^K = \min\limits_{\theta \in \mathbb{R}, x \in \mathbb{R}^N} \;\; & \theta\\ +& \theta \ge f(x_k) + \frac{d}{dx}f(x_k)^\top (x - x_k),\quad k=1,\ldots,K\\ +& \theta \ge M, +\end{aligned}\]

where $M$ is a sufficiently large negative number that is a lower bound for $f$ over the domain of $x$.

Kelley's cutting plane algorithm is a structured way of choosing points $x_k$ to visit, so that as more cuts are added:

\[\lim_{K \rightarrow \infty} f^K = \min\limits_{x \in \mathbb{R}^N} f(x)\]

However, before we introduce the algorithm, we need to introduce some bounds.

Bounds

By convexity, $f^K \le f(x)$ for all $x$. Thus, if $x^*$ is a minimizer of $f$, then at any point in time we can construct a lower bound for $f(x^*)$ by solving $f^K$.

Moreover, we can use the primal solutions $x_k^*$ returned by solving $f^k$ to evaluate $f(x_k^*)$ to generate an upper bound.

Therefore, $f^K \le f(x^*) \le \min\limits_{k=1,\ldots,K} f(x_k^*)$.

When the lower bound is sufficiently close to the upper bound, we can terminate the algorithm and declare that we have found an solution that is close to optimal.

Implementation

Here is pseudo-code fo the Kelley algorithm:

  1. Take as input a convex function $f(x)$ and a iteration limit $K_{max}$. Set $K = 0$, and initialize $f^K$. Set $lb = -\infty$ and $ub = \infty$.
  2. Solve $f^K$ to obtain a candidate solution $x_{K+1}$.
  3. Update $lb = f^K$ and $ub = \min\{ub, f(x_{K+1})\}$.
  4. Add a cut $\theta \ge f(x_{K+1}) + \frac{d}{dx}f\left(x_{K+1}\right)^\top (x - x_{K+1})$ to form $f^{K+1}$.
  5. Increment $K$.
  6. If $K = K_{max}$ or $|ub - lb| < \epsilon$, STOP, otherwise, go to step 2.

And here's a complete implementation:

function kelleys_cutting_plane(
+    # The function to be minimized.
+    f::Function,
+    # The gradient of `f`. By default, we use automatic differentiation to
+    # compute the gradient of f so the user doesn't have to!
+    dfdx::Function = x -> ForwardDiff.gradient(f, x);
+    # The number of arguments to `f`.
+    input_dimension::Int,
+    # A lower bound for the function `f` over its domain.
+    lower_bound::Float64,
+    # The number of iterations to run Kelley's algorithm for before stopping.
+    iteration_limit::Int,
+    # The absolute tolerance ϵ to use for convergence.
+    tolerance::Float64 = 1e-6,
+)
+    # Step (1):
+    K = 0
+    model = JuMP.Model(HiGHS.Optimizer)
+    JuMP.set_silent(model)
+    JuMP.@variable(model, θ >= lower_bound)
+    JuMP.@variable(model, x[1:input_dimension])
+    JuMP.@objective(model, Min, θ)
+    x_k = fill(NaN, input_dimension)
+    lower_bound, upper_bound = -Inf, Inf
+    while true
+        # Step (2):
+        JuMP.optimize!(model)
+        x_k .= JuMP.value.(x)
+        # Step (3):
+        lower_bound = JuMP.objective_value(model)
+        upper_bound = min(upper_bound, f(x_k))
+        println("K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)")
+        # Step (4):
+        JuMP.@constraint(model, θ >= f(x_k) + dfdx(x_k)' * (x .- x_k))
+        # Step (5):
+        K = K + 1
+        # Step (6):
+        if K == iteration_limit
+            println("-- Termination status: iteration limit --")
+            break
+        elseif abs(upper_bound - lower_bound) < tolerance
+            println("-- Termination status: converged --")
+            break
+        end
+    end
+    println("Found solution: x_K = ", x_k)
+    return
+end
kelleys_cutting_plane (generic function with 2 methods)

Let's run our algorithm to see what happens:

kelleys_cutting_plane(;
+    input_dimension = 2,
+    lower_bound = 0.0,
+    iteration_limit = 20,
+) do x
+    return (x[1] - 1)^2 + (x[2] + 2)^2 + 1.0
+end
K = 0 : 0.0 <= f(x*) <= 6.0
+K = 1 : 0.0 <= f(x*) <= 2.25
+K = 2 : 0.0 <= f(x*) <= 2.25
+K = 3 : 0.0 <= f(x*) <= 1.0986328124999998
+K = 4 : 0.0 <= f(x*) <= 1.0986328124999998
+K = 5 : 0.0 <= f(x*) <= 1.0986328124999998
+K = 6 : 0.31451789700255117 <= f(x*) <= 1.0986328124999998
+K = 7 : 0.5251698041461698 <= f(x*) <= 1.0986328124999998
+K = 8 : 0.730061698203828 <= f(x*) <= 1.0986328124999998
+K = 9 : 0.8034849495130735 <= f(x*) <= 1.028310187109376
+K = 10 : 0.9287004555830413 <= f(x*) <= 1.028310187109376
+K = 11 : 0.9487595478289129 <= f(x*) <= 1.005714217279734
+K = 12 : 0.9823564684073585 <= f(x*) <= 1.005714217279734
+K = 13 : 0.9878994829249161 <= f(x*) <= 1.0023277313143473
+K = 14 : 0.9948297447399875 <= f(x*) <= 1.0023277313143473
+K = 15 : 0.9966035252317597 <= f(x*) <= 1.0002586136309097
+K = 16 : 0.9988344662887892 <= f(x*) <= 1.0002586136309097
+K = 17 : 0.9994007197804659 <= f(x*) <= 1.0002586136309097
+K = 18 : 0.9995654191110326 <= f(x*) <= 1.0000883284156266
+K = 19 : 0.9998024915692466 <= f(x*) <= 1.0000370583103833
+-- Termination status: iteration limit --
+Found solution: x_K = [0.993919804093335, -1.9997007875004429]
Warning

It's hard to choose a valid lower bound! If you choose one too loose, the algorithm can take a long time to converge. However, if you choose one so tight that $M > f(x^*)$, then you can obtain a suboptimal solution. For a deeper discussion of the implications for SDDP.jl, see Choosing an initial bound.

Preliminaries: approximating the cost-to-go term

In the background theory section, we discussed how you could formulate an optimal policy to a multistage stochastic program using the dynamic programming recursion:

\[\begin{aligned} +V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x, +\end{aligned}\]

where our decision rule, $\pi_i(x, \omega)$, solves this optimization problem and returns a $u^*$ corresponding to an optimal solution. Moreover, we alluded to the fact that the cost-to-go term (the nasty recursive expectation) makes this problem intractable to solve.

However, if, excluding the cost-to-go term (i.e., the SP formulation), $V_i(x, \omega)$ can be formulated as a linear program (this also works for convex programs, but the math is more involved), then we can make some progress by noticing that $x$ only appears as a right-hand side term of the fishing constraint $\bar{x} = x$. Therefore, $V_i(x, \cdot)$ is convex with respect to $x$ for fixed $\omega$. (If you have not seen this result before, try to prove it.)

The fishing constraint $\bar{x} = x$ has an associated dual variable. The economic interpretation of this dual variable is that it represents the change in the objective function if the right-hand side $x$ is increased on the scale of one unit. In other words, and with a slight abuse of notation, it is the value $\frac{d}{dx} V_i(x, \omega)$. (Because $V_i$ is not differentiable, it is a subgradient instead of a derivative.)

If we implement the constraint $\bar{x} = x$ by setting the lower- and upper bounds of $\bar{x}$ to $x$, then the reduced cost of the decision variable $\bar{x}$ is the subgradient, and we do not need to explicitly add the fishing constraint as a row to the constraint matrix.

Tip

The subproblem can have binary and integer variables, but you'll need to use Lagrangian duality to compute a subgradient!

Stochastic dual dynamic programming converts this problem into a tractable form by applying Kelley's cutting plane algorithm to the $V_j$ functions in the cost-to-go term:

\[\begin{aligned} +V_i^K(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \theta\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x \\ +& \theta \ge \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right],\quad k=1,\ldots,K \\ +& \theta \ge M. +\end{aligned}\]

All we need now is a way of generating these cutting planes in an iterative manner. Before we get to that though, let's start writing some code.

Implementation: modeling

Let's make a start by defining the problem structure. Like SDDP.jl, we need a few things:

  1. A description of the structure of the policy graph: how many nodes there are, and the arcs linking the nodes together with their corresponding probabilities.
  2. A JuMP model for each node in the policy graph.
  3. A way to identify the incoming and outgoing state variables of each node.
  4. A description of the random variable, as well as a function that we can call that will modify the JuMP model to reflect the realization of the random variable.
  5. A decision variable to act as the approximated cost-to-go term.
Warning

In the interests of brevity, there is minimal error checking. Think about all the different ways you could break the code!

Structs

The first struct we are going to use is a State struct that will wrap an incoming and outgoing state variable:

struct State
+    in::JuMP.VariableRef
+    out::JuMP.VariableRef
+end

Next, we need a struct to wrap all of the uncertainty within a node:

struct Uncertainty
+    parameterize::Function
+    Ω::Vector{Any}
+    P::Vector{Float64}
+end

parameterize is a function which takes a realization of the random variable $\omega\in\Omega$ and updates the subproblem accordingly. The finite discrete random variable is defined by the vectors Ω and P, so that the random variable takes the value Ω[i] with probability P[i]. As such, P should sum to 1. (We don't check this here, but we should; we do in SDDP.jl.)

Now we have two building blocks, we can declare the structure of each node:

struct Node
+    subproblem::JuMP.Model
+    states::Dict{Symbol,State}
+    uncertainty::Uncertainty
+    cost_to_go::JuMP.VariableRef
+end
  • subproblem is going to be the JuMP model that we build at each node.
  • states is a dictionary that maps a symbolic name of a state variable to a State object wrapping the incoming and outgoing state variables in subproblem.
  • uncertainty is an Uncertainty object described above.
  • cost_to_go is a JuMP variable that approximates the cost-to-go term.

Finally, we define a simplified policy graph as follows:

struct PolicyGraph
+    nodes::Vector{Node}
+    arcs::Vector{Dict{Int,Float64}}
+end

There is a vector of nodes, as well as a data structure for the arcs. arcs is a vector of dictionaries, where arcs[i][j] gives the probability of transitioning from node i to node j, if an arc exists.

To simplify things, we will assume that the root node transitions to node 1 with probability 1, and there are no other incoming arcs to node 1. Notably, we can still define cyclic graphs though!

We also define a nice show method so that we don't accidentally print a large amount of information to the screen when creating a model:

function Base.show(io::IO, model::PolicyGraph)
+    println(io, "A policy graph with $(length(model.nodes)) nodes")
+    println(io, "Arcs:")
+    for (from, arcs) in enumerate(model.arcs)
+        for (to, probability) in arcs
+            println(io, "  $(from) => $(to) w.p. $(probability)")
+        end
+    end
+    return
+end

Functions

Now we have some basic types, let's implement some functions so that the user can create a model.

First, we need an example of a function that the user will provide. Like SDDP.jl, this takes an empty subproblem, and a node index, in this case t::Int. You could change this function to change the model, or define a new one later in the code.

We're going to copy the example from An introduction to SDDP.jl, with some minor adjustments for the fact we don't have many of the bells and whistles of SDDP.jl. You can probably see how some of the SDDP.jl functionality like @stageobjective and SDDP.parameterize help smooth some of the usability issues like needing to construct both the incoming and outgoing state variables, or needing to explicitly declare return states, uncertainty.

function subproblem_builder(subproblem::JuMP.Model, t::Int)
+    # Define the state variables. Note how we fix the incoming state to the
+    # initial state variable regardless of `t`! This isn't strictly necessary;
+    # it only matters that we do it for the first node.
+    JuMP.@variable(subproblem, volume_in == 200)
+    JuMP.@variable(subproblem, 0 <= volume_out <= 200)
+    states = Dict(:volume => State(volume_in, volume_out))
+    # Define the control variables.
+    JuMP.@variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    # Define the constraints
+    JuMP.@constraints(
+        subproblem,
+        begin
+            volume_out == volume_in + inflow - hydro_generation - hydro_spill
+            demand_constraint, thermal_generation + hydro_generation == 150.0
+        end
+    )
+    # Define the objective for each stage `t`. Note that we can use `t` as an
+    # index for t = 1, 2, 3.
+    fuel_cost = [50.0, 100.0, 150.0]
+    JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation)
+    # Finally, we define the uncertainty object. Because this is a simplified
+    # implementation of SDDP, we shall politely ask the user to only modify the
+    # constraints, and not the objective function! (Not that it changes the
+    # algorithm, we just have to add more information to keep track of things.)
+    uncertainty = Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω
+        return JuMP.fix(inflow, ω)
+    end
+    return states, uncertainty
+end
subproblem_builder (generic function with 1 method)

The next function we need to define is the analog of SDDP.PolicyGraph. It should be pretty readable.

function PolicyGraph(
+    subproblem_builder::Function;
+    graph::Vector{Dict{Int,Float64}},
+    lower_bound::Float64,
+    optimizer,
+)
+    nodes = Node[]
+    for t in 1:length(graph)
+        # Create a model.
+        model = JuMP.Model(optimizer)
+        JuMP.set_silent(model)
+        # Use the provided function to build out each subproblem. The user's
+        # function returns a dictionary mapping `Symbol`s to `State` objects,
+        # and an `Uncertainty` object.
+        states, uncertainty = subproblem_builder(model, t)
+        # Now add the cost-to-go terms:
+        JuMP.@variable(model, cost_to_go >= lower_bound)
+        obj = JuMP.objective_function(model)
+        JuMP.@objective(model, Min, obj + cost_to_go)
+        # If there are no outgoing arcs, the cost-to-go is 0.0.
+        if length(graph[t]) == 0
+            JuMP.fix(cost_to_go, 0.0; force = true)
+        end
+        push!(nodes, Node(model, states, uncertainty, cost_to_go))
+    end
+    return PolicyGraph(nodes, graph)
+end
Main.PolicyGraph

Then, we can create a model using the subproblem_builder function we defined earlier:

model = PolicyGraph(
+    subproblem_builder;
+    graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()],
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+)
A policy graph with 3 nodes
+Arcs:
+  1 => 2 w.p. 1.0
+  2 => 3 w.p. 1.0
+

Implementation: helpful samplers

Before we get properly coding the solution algorithm, it's also going to be useful to have a function that samples a realization of the random variable defined by Ω and P.

function sample_uncertainty(uncertainty::Uncertainty)
+    r = rand()
+    for (p, ω) in zip(uncertainty.P, uncertainty.Ω)
+        r -= p
+        if r < 0.0
+            return ω
+        end
+    end
+    return error("We should never get here because P should sum to 1.0.")
+end
sample_uncertainty (generic function with 1 method)
Note

rand() samples a uniform random variable in [0, 1).

For example:

for i in 1:3
+    println("ω = ", sample_uncertainty(model.nodes[1].uncertainty))
+end
ω = 50.0
+ω = 50.0
+ω = 100.0

It's also going to be useful to define a function that generates a random walk through the nodes of the graph:

function sample_next_node(model::PolicyGraph, current::Int)
+    if length(model.arcs[current]) == 0
+        # No outgoing arcs!
+        return nothing
+    else
+        r = rand()
+        for (to, probability) in model.arcs[current]
+            r -= probability
+            if r < 0.0
+                return to
+            end
+        end
+        # We looped through the outgoing arcs and still have probability left
+        # over! This means we've hit an implicit "zero" node.
+        return nothing
+    end
+end
sample_next_node (generic function with 1 method)

For example:

for i in 1:3
+    # We use `repr` to print the next node, because `sample_next_node` can
+    # return `nothing`.
+    println("Next node from $(i) = ", repr(sample_next_node(model, i)))
+end
Next node from 1 = 2
+Next node from 2 = 3
+Next node from 3 = nothing

This is a little boring, because our graph is simple. However, more complicated graphs will generate more interesting trajectories!

Implementation: the forward pass

Recall that, after approximating the cost-to-go term, we need a way of generating the cuts. As the first step, we need a way of generating candidate solutions $x_k^\prime$. However, unlike the Kelley's example, our functions $V_j^k(x^\prime, \varphi)$ need two inputs: an outgoing state variable and a realization of the random variable.

One way of getting these inputs is just to pick a random (feasible) value. However, in doing so, we might pick outgoing state variables that we will never see in practice, or we might infrequently pick outgoing state variables that we will often see in practice. Therefore, a better way of generating the inputs is to use a simulation of the policy, which we call the forward pass.

The forward pass walks the policy graph from start to end, transitioning randomly along the arcs. At each node, it observes a realization of the random variable and solves the approximated subproblem to generate a candidate outgoing state variable $x_k^\prime$. The outgoing state variable is passed as the incoming state variable to the next node in the trajectory.

function forward_pass(model::PolicyGraph, io::IO = stdout)
+    println(io, "| Forward Pass")
+    # First, get the value of the state at the root node (e.g., x_R).
+    incoming_state =
+        Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states)
+    # `simulation_cost` is an accumlator that is going to sum the stage-costs
+    # incurred over the forward pass.
+    simulation_cost = 0.0
+    # We also need to record the nodes visited and resultant outgoing state
+    # variables so we can pass them to the backward pass.
+    trajectory = Tuple{Int,Dict{Symbol,Float64}}[]
+    # Now's the meat of the forward pass: beginning at the first node:
+    t = 1
+    while t !== nothing
+        node = model.nodes[t]
+        println(io, "| | Visiting node $(t)")
+        # Sample the uncertainty:
+        ω = sample_uncertainty(node.uncertainty)
+        println(io, "| | | ω = ", ω)
+        # Parameterizing the subproblem using the user-provided function:
+        node.uncertainty.parameterize(ω)
+        println(io, "| | | x = ", incoming_state)
+        # Update the incoming state variable:
+        for (k, v) in incoming_state
+            JuMP.fix(node.states[k].in, v; force = true)
+        end
+        # Now solve the subproblem and check we found an optimal solution:
+        JuMP.optimize!(node.subproblem)
+        if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL
+            error("Something went terribly wrong!")
+        end
+        # Compute the outgoing state variables:
+        outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states)
+        println(io, "| | | x′ = ", outgoing_state)
+        # We also need to compute the stage cost to add to our
+        # `simulation_cost` accumulator:
+        stage_cost =
+            JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go)
+        simulation_cost += stage_cost
+        println(io, "| | | C(x, u, ω) = ", stage_cost)
+        # As a penultimate step, set the outgoing state of stage t and the
+        # incoming state of stage t + 1, and add the node to the trajectory.
+        incoming_state = outgoing_state
+        push!(trajectory, (t, outgoing_state))
+        # Finally, sample a new node to step to. If `t === nothing`, the
+        # `while` loop will break.
+        t = sample_next_node(model, t)
+    end
+    return trajectory, simulation_cost
+end
forward_pass (generic function with 2 methods)

Let's take a look at one forward pass:

trajectory, simulation_cost = forward_pass(model);
| Forward Pass
+| | Visiting node 1
+| | | ω = 50.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 0.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 10000.0
+| | Visiting node 3
+| | | ω = 0.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 22500.0

Implementation: the backward pass

From the forward pass, we obtained a vector of nodes visited and their corresponding outgoing state variables. Now we need to refine the approximation for each node at the candidate solution for the outgoing state variable. That is, we need to add a new cut:

\[\theta \ge \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right]\]

or alternatively:

\[\theta \ge \sum\limits_{j \in i^+} \sum\limits_{\varphi \in \Omega_j} p_{ij} p_{\varphi}\left[V_j^k(x^\prime_k, \varphi) + \frac{d}{dx^\prime}V_j^k(x^\prime_k, \varphi)^\top (x^\prime - x^\prime_k)\right]\]

It doesn't matter what order we visit the nodes to generate these cuts for. For example, we could compute them all in parallel, using the current approximations of $V^K_i$.

However, we can be smarter than that.

If we traverse the list of nodes visited in the forward pass in reverse, then we come to refine the $i^{th}$ node in the trajectory, we will already have improved the approximation of the $(i+1)^{th}$ node in the trajectory as well! Therefore, our refinement of the $i^{th}$ node will be better than if we improved node $i$ first, and then refined node $(i+1)$.

Because we walk the nodes in reverse, we call this the backward pass.

Info

If you're into deep learning, you could view this as the equivalent of back-propagation: the forward pass pushes primal information through the graph (outgoing state variables), and the backward pass pulls dual information (cuts) back through the graph to improve our decisions on the next forward pass.

function backward_pass(
+    model::PolicyGraph,
+    trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}},
+    io::IO = stdout,
+)
+    println(io, "| Backward pass")
+    # For the backward pass, we walk back up the nodes.
+    for i in reverse(1:length(trajectory))
+        index, outgoing_states = trajectory[i]
+        node = model.nodes[index]
+        println(io, "| | Visiting node $(index)")
+        if length(model.arcs[index]) == 0
+            # If there are no children, the cost-to-go is 0.
+            println(io, "| | | Skipping node because the cost-to-go is 0")
+            continue
+        end
+        # Create an empty affine expression that we will use to build up the
+        # right-hand side of the cut expression.
+        cut_expression = JuMP.AffExpr(0.0)
+        # For each node j ∈ i⁺
+        for (j, P_ij) in model.arcs[index]
+            next_node = model.nodes[j]
+            # Set the incoming state variables of node j to the outgoing state
+            # variables of node i
+            for (k, v) in outgoing_states
+                JuMP.fix(next_node.states[k].in, v; force = true)
+            end
+            # Then for each realization of φ ∈ Ωⱼ
+            for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω)
+                # Setup and solve for the realization of φ
+                println(io, "| | | Solving φ = ", φ)
+                next_node.uncertainty.parameterize(φ)
+                JuMP.optimize!(next_node.subproblem)
+                # Then prepare the cut `P_ij * pφ * [V + dVdxᵀ(x - x_k)]``
+                V = JuMP.objective_value(next_node.subproblem)
+                println(io, "| | | | V = ", V)
+                dVdx = Dict(
+                    k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states
+                )
+                println(io, "| | | | dVdx′ = ", dVdx)
+                cut_expression += JuMP.@expression(
+                    node.subproblem,
+                    P_ij *
+                    pφ *
+                    (
+                        V + sum(
+                            dVdx[k] * (x.out - outgoing_states[k]) for
+                            (k, x) in node.states
+                        )
+                    ),
+                )
+            end
+        end
+        # And then refine the cost-to-go variable by adding the cut:
+        c = JuMP.@constraint(node.subproblem, node.cost_to_go >= cut_expression)
+        println(io, "| | | Adding cut : ", c)
+    end
+    return nothing
+end
backward_pass (generic function with 2 methods)

Implementation: bounds

Lower bounds

Recall from Kelley's that we can obtain a lower bound for $f(x^*)$ be evaluating $f^K$. The analogous lower bound for a multistage stochastic program is:

\[\mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^K(x_R, \omega)] \le \min_{\pi} \mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^\pi(x_R, \omega)]\]

Here's how we compute the lower bound:

function lower_bound(model::PolicyGraph)
+    node = model.nodes[1]
+    bound = 0.0
+    for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω)
+        node.uncertainty.parameterize(ω)
+        JuMP.optimize!(node.subproblem)
+        bound += p * JuMP.objective_value(node.subproblem)
+    end
+    return bound
+end
lower_bound (generic function with 1 method)
Note

The implementation is simplified because we assumed that there is only one arc from the root node, and that it pointed to the first node in the vector.

Because we haven't trained a policy yet, the lower bound is going to be very bad:

lower_bound(model)
0.0

Upper bounds

With Kelley's algorithm, we could easily construct an upper bound by evaluating $f(x_K)$. However, it is almost always intractable to evaluate an upper bound for multistage stochastic programs due to the large number of nodes and the nested expectations. Instead, we can perform a Monte Carlo simulation of the policy to build a statistical estimate for the value of $\mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^\pi(x_R, \omega)]$, where $\pi$ is the policy defined by the current approximations $V^K_i$.

function upper_bound(model::PolicyGraph; replications::Int)
+    # Pipe the output to `devnull` so we don't print too much!
+    simulations = [forward_pass(model, devnull) for i in 1:replications]
+    z = [s[2] for s in simulations]
+    μ = Statistics.mean(z)
+    tσ = 1.96 * Statistics.std(z) / sqrt(replications)
+    return μ, tσ
+end
upper_bound (generic function with 1 method)
Note

The width of the confidence interval is incorrect if there are cycles in the graph, because the distribution of simulation costs z is not symmetric. The mean is correct, however.

Termination criteria

In Kelley's algorithm, the upper bound was deterministic. Therefore, we could terminate the algorithm when the lower bound was sufficiently close to the upper bound. However, our upper bound for SDDP is not deterministic; it is a confidence interval!

Some people suggest terminating SDDP when the lower bound is contained within the confidence interval. However, this is a poor choice because it is too easy to generate a false positive. For example, if we use a small number of replications then the width of the confidence will be large, and we are more likely to terminate!

In a future tutorial (not yet written...) we will discuss termination criteria in more depth. For now, pick a large number of iterations and train for as long as possible.

Tip

For a rule of thumb, pick a large number of iterations to train the policy for (e.g., $10 \times |\mathcal{N}| \times \max\limits_{i\in\mathcal{N}} |\Omega_i|$)

Implementation: the training loop

The train loop of SDDP just applies the forward and backward passes iteratively, followed by a final simulation to compute the upper bound confidence interval:

function train(
+    model::PolicyGraph;
+    iteration_limit::Int,
+    replications::Int,
+    io::IO = stdout,
+)
+    for i in 1:iteration_limit
+        println(io, "Starting iteration $(i)")
+        outgoing_states, _ = forward_pass(model, io)
+        backward_pass(model, outgoing_states, io)
+        println(io, "| Finished iteration")
+        println(io, "| | lower_bound = ", lower_bound(model))
+    end
+    println(io, "Termination status: iteration limit")
+    μ, tσ = upper_bound(model; replications = replications)
+    println(io, "Upper bound = $(μ) ± $(tσ)")
+    return
+end
train (generic function with 1 method)

Using our model we defined earlier, we can go:

train(model; iteration_limit = 3, replications = 100)
Starting iteration 1
+| Forward Pass
+| | Visiting node 1
+| | | ω = 100.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 0.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 10000.0
+| | Visiting node 3
+| | | ω = 0.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 22500.0
+| Backward pass
+| | Visiting node 3
+| | | Skipping node because the cost-to-go is 0
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 22500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 15000.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 7500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 15000
+| | Visiting node 1
+| | | Solving φ = 0.0
+| | | | V = 30000.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 22500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 15000.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 22500
+| Finished iteration
+| | lower_bound = 2500.0
+Starting iteration 2
+| Forward Pass
+| | Visiting node 1
+| | | ω = 50.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 150.0)
+| | | C(x, u, ω) = 2500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 150.0)
+| | | x′ = Dict(:volume => 100.0)
+| | | C(x, u, ω) = 5000.0
+| | Visiting node 3
+| | | ω = 0.0
+| | | x = Dict(:volume => 100.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 7500.0
+| Backward pass
+| | Visiting node 3
+| | | Skipping node because the cost-to-go is 0
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 7500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 0.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 0.0
+| | | | dVdx′ = Dict(:volume => 0.0)
+| | | Adding cut : 100 volume_out + cost_to_go ≥ 12500
+| | Visiting node 1
+| | | Solving φ = 0.0
+| | | | V = 12499.999999999998
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 50.0
+| | | | V = 7499.999999999998
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 100.0
+| | | | V = 2499.9999999999986
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Adding cut : 99.99999999999999 volume_out + cost_to_go ≥ 22499.999999999996
+| Finished iteration
+| | lower_bound = 7499.999999999998
+Starting iteration 3
+| Forward Pass
+| | Visiting node 1
+| | | ω = 0.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 200.0)
+| | | C(x, u, ω) = 7500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 124.99999999999997)
+| | | C(x, u, ω) = 2499.9999999999986
+| | Visiting node 3
+| | | ω = 50.0
+| | | x = Dict(:volume => 124.99999999999997)
+| | | x′ = Dict(:volume => 24.99999999999997)
+| | | C(x, u, ω) = 0.0
+| Backward pass
+| | Visiting node 3
+| | | Skipping node because the cost-to-go is 0
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 3750.000000000004
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 0.0
+| | | | dVdx′ = Dict(:volume => 0.0)
+| | | Solving φ = 100.0
+| | | | V = 0.0
+| | | | dVdx′ = Dict(:volume => 0.0)
+| | | Adding cut : 50 volume_out + cost_to_go ≥ 7500
+| | Visiting node 1
+| | | Solving φ = 0.0
+| | | | V = 7500.0
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 50.0
+| | | | V = 2500.0
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 100.0
+| | | | V = 0.0
+| | | | dVdx′ = Dict(:volume => -50.0)
+| | | Adding cut : 83.33333333333331 volume_out + cost_to_go ≥ 19999.999999999996
+| Finished iteration
+| | lower_bound = 8333.333333333332
+Termination status: iteration limit
+Upper bound = 8500.0 ± 897.3203655238403

Success! We trained a policy for a finite horizon multistage stochastic program using stochastic dual dynamic programming.

Implementation: evaluating the policy

A final step is the ability to evaluate the policy at a given point.

function evaluate_policy(
+    model::PolicyGraph;
+    node::Int,
+    incoming_state::Dict{Symbol,Float64},
+    random_variable,
+)
+    the_node = model.nodes[node]
+    the_node.uncertainty.parameterize(random_variable)
+    for (k, v) in incoming_state
+        JuMP.fix(the_node.states[k].in, v; force = true)
+    end
+    JuMP.optimize!(the_node.subproblem)
+    return Dict(
+        k => JuMP.value.(v) for
+        (k, v) in JuMP.object_dictionary(the_node.subproblem)
+    )
+end
+
+evaluate_policy(
+    model;
+    node = 1,
+    incoming_state = Dict(:volume => 150.0),
+    random_variable = 75,
+)
Dict{Symbol, Float64} with 8 entries:
+  :volume_out         => 200.0
+  :demand_constraint  => 150.0
+  :hydro_spill        => 0.0
+  :inflow             => 75.0
+  :volume_in          => 150.0
+  :thermal_generation => 125.0
+  :hydro_generation   => 25.0
+  :cost_to_go         => 3333.33
Note

The random variable can be out-of-sample, i.e., it doesn't have to be in the vector $\Omega$ we created when defining the model! This is a notable difference to other multistage stochastic solution methods like progressive hedging or using the deterministic equivalent.

Example: infinite horizon

As promised earlier, our implementation is actually pretty general. It can solve any multistage stochastic (linear) program defined by a policy graph, including infinite horizon problems!

Here's an example, where we have extended our earlier problem with an arc from node 3 to node 2 with probability 0.5. You can interpret the 0.5 as a discount factor.

model = PolicyGraph(
+    subproblem_builder;
+    graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict(2 => 0.5)],
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+)
A policy graph with 3 nodes
+Arcs:
+  1 => 2 w.p. 1.0
+  2 => 3 w.p. 1.0
+  3 => 2 w.p. 0.5
+

Then, train a policy:

train(model; iteration_limit = 3, replications = 100)
Starting iteration 1
+| Forward Pass
+| | Visiting node 1
+| | | ω = 0.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 50.0)
+| | | C(x, u, ω) = 0.0
+| | Visiting node 2
+| | | ω = 100.0
+| | | x = Dict(:volume => 50.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 0.0
+| | Visiting node 3
+| | | ω = 0.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 22500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 10000.0
+| | Visiting node 3
+| | | ω = 100.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 7500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 10000.0
+| | Visiting node 3
+| | | ω = 0.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 22500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 10000.0
+| | Visiting node 3
+| | | ω = 100.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 7500.0
+| | Visiting node 2
+| | | ω = 0.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 15000.0
+| | Visiting node 3
+| | | ω = 100.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 7500.0
+| Backward pass
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 15000.0
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 50.0
+| | | | V = 10000.0
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 100.0
+| | | | V = 5000.0
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Adding cut : 49.99999999999999 volume_out + cost_to_go ≥ 4999.999999999999
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 27500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 20000.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 12500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 20000
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 35000.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 27500.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 20000.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 13750
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 36250.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 28750.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 21250.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 28749.999999999996
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 43750.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 36250.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 28749.999999999996
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 18125
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 40625.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 33125.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 25625.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 33125
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 48125.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 40625.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 33125.0
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 20312.5
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 42812.5
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 35312.5
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 27812.5
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 35312.5
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 50312.5
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 42812.5
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 35312.5
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 21406.25
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 43906.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 36406.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 28906.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 36406.25
+| | Visiting node 1
+| | | Solving φ = 0.0
+| | | | V = 43906.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 36406.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 28906.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 43906.24999999999
+| Finished iteration
+| | lower_bound = 18906.249999999993
+Starting iteration 2
+| Forward Pass
+| | Visiting node 1
+| | | ω = 100.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 200.0)
+| | | C(x, u, ω) = 2500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 200.0)
+| | | C(x, u, ω) = 10000.0
+| | Visiting node 3
+| | | ω = 50.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 100.0)
+| | | C(x, u, ω) = 0.0
+| Backward pass
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 36406.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 28906.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 21406.25
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 21953.124999999996
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 18203.124999999996
+| | | | dVdx′ = Dict(:volume => -75.0)
+| | | Solving φ = 50.0
+| | | | V = 14453.124999999996
+| | | | dVdx′ = Dict(:volume => -75.0)
+| | | Solving φ = 100.0
+| | | | V = 10703.124999999996
+| | | | dVdx′ = Dict(:volume => -75.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 29453.124999999996
+| | Visiting node 1
+| | | Solving φ = 0.0
+| | | | V = 26770.833333333332
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 50.0
+| | | | V = 21953.125
+| | | | dVdx′ = Dict(:volume => -75.0)
+| | | Solving φ = 100.0
+| | | | V = 18203.125
+| | | | dVdx′ = Dict(:volume => -75.0)
+| | | Adding cut : 83.33333333333333 volume_out + cost_to_go ≥ 38975.69444444444
+| Finished iteration
+| | lower_bound = 27309.02777777777
+Starting iteration 3
+| Forward Pass
+| | Visiting node 1
+| | | ω = 100.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 200.0)
+| | | C(x, u, ω) = 2500.0
+| | Visiting node 2
+| | | ω = 50.0
+| | | x = Dict(:volume => 200.0)
+| | | x′ = Dict(:volume => 100.0)
+| | | C(x, u, ω) = 0.0
+| | Visiting node 3
+| | | ω = 50.0
+| | | x = Dict(:volume => 100.0)
+| | | x′ = Dict(:volume => -0.0)
+| | | C(x, u, ω) = 0.0
+| | Visiting node 2
+| | | ω = 100.0
+| | | x = Dict(:volume => -0.0)
+| | | x′ = Dict(:volume => 92.70833333333339)
+| | | C(x, u, ω) = 14270.833333333336
+| | Visiting node 3
+| | | ω = 50.0
+| | | x = Dict(:volume => 92.70833333333339)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 1093.7499999999927
+| | Visiting node 2
+| | | ω = 0.0
+| | | x = Dict(:volume => 0.0)
+| | | x′ = Dict(:volume => -0.0)
+| | | C(x, u, ω) = 15000.000000000007
+| | Visiting node 3
+| | | ω = 50.0
+| | | x = Dict(:volume => -0.0)
+| | | x′ = Dict(:volume => 0.0)
+| | | C(x, u, ω) = 15000.000000000004
+| Backward pass
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 51406.25000000001
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 43906.25000000001
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 36770.83333333333
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Adding cut : 66.66666666666666 volume_out + cost_to_go ≥ 22013.88888888889
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 44513.88888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 37013.88888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 29513.88888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 150 volume_out + cost_to_go ≥ 37013.88888888889
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 52013.88888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 44513.88888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 37013.8888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Adding cut : 75 volume_out + cost_to_go ≥ 22256.944444444445
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 30850.694444444438
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 23350.694444444438
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 19166.666666666664
+| | | | dVdx′ = Dict(:volume => -66.66666666666666)
+| | | Adding cut : 122.22222222222221 volume_out + cost_to_go ≥ 35787.03703703704
+| | Visiting node 3
+| | | Solving φ = 0.0
+| | | | V = 52013.88888888889
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 44675.92592592593
+| | | | dVdx′ = Dict(:volume => -122.22222222222221)
+| | | Solving φ = 100.0
+| | | | V = 38564.81481481482
+| | | | dVdx′ = Dict(:volume => -122.22222222222221)
+| | | Adding cut : 65.74074074074073 volume_out + cost_to_go ≥ 22542.438271604937
+| | Visiting node 2
+| | | Solving φ = 0.0
+| | | | V = 30042.438271604937
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 50.0
+| | | | V = 22542.438271604937
+| | | | dVdx′ = Dict(:volume => -150.0)
+| | | Solving φ = 100.0
+| | | | V = 19255.4012345679
+| | | | dVdx′ = Dict(:volume => -65.74074074074073)
+| | | Adding cut : 121.91358024691357 volume_out + cost_to_go ≥ 36138.11728395061
+| | Visiting node 1
+| | | Solving φ = 0.0
+| | | | V = 28015.522203947367
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 50.0
+| | | | V = 23015.522203947367
+| | | | dVdx′ = Dict(:volume => -100.0)
+| | | Solving φ = 100.0
+| | | | V = 18203.124999999993
+| | | | dVdx′ = Dict(:volume => -75.0)
+| | | Adding cut : 91.66666666666666 volume_out + cost_to_go ≥ 41411.38980263157
+| Finished iteration
+| | lower_bound = 28078.056469298237
+Termination status: iteration limit
+Upper bound = 30502.446546052633 ± 5782.780181478381

Success! We trained a policy for an infinite horizon multistage stochastic program using stochastic dual dynamic programming. Note how some of the forward passes are different lengths!

evaluate_policy(
+    model;
+    node = 3,
+    incoming_state = Dict(:volume => 100.0),
+    random_variable = 10.0,
+)
Dict{Symbol, Float64} with 8 entries:
+  :volume_out         => 0.0
+  :demand_constraint  => 150.0
+  :hydro_spill        => 0.0
+  :inflow             => 10.0
+  :volume_in          => 100.0
+  :thermal_generation => 40.0
+  :hydro_generation   => 110.0
+  :cost_to_go         => 22542.4
diff --git a/previews/PR826/guides/access_previous_variables/index.html b/previews/PR826/guides/access_previous_variables/index.html new file mode 100644 index 0000000000..41a3fdabbe --- /dev/null +++ b/previews/PR826/guides/access_previous_variables/index.html @@ -0,0 +1,101 @@ + +Access variables from a previous stage · SDDP.jl

Access variables from a previous stage

A common question is "how do I use a variable from a previous stage in a constraint?"

Info

If you want to use a variable from a previous stage, it must be a state variable.

Here are some examples:

Access a first-stage decision in a future stage

This is often useful if your first-stage decisions are capacity-expansion type decisions (e.g., you choose first how much capacity to add, but because it takes time to build, it only shows up in some future stage).

julia> using SDDP, HiGHS
julia> SDDP.LinearPolicyGraph( + stages = 10, + sense = :Max, + upper_bound = 100.0, + optimizer = HiGHS.Optimizer, + ) do sp, t + # Capacity of the generator. Decided in the first stage. + @variable(sp, capacity >= 0, SDDP.State, initial_value = 0) + # Quantity of water stored. + @variable(sp, reservoir >= 0, SDDP.State, initial_value = 0) + # Quantity of water to use for electricity generation in current stage. + @variable(sp, generation >= 0) + if t == 1 + # There are no constraints in the first stage, but we need to push the + # initial value of the reservoir to the next stage. + @constraint(sp, reservoir.out == reservoir.in) + # Since we're maximizing profit, subtract cost of capacity. + @stageobjective(sp, -capacity.out) + else + # Water balance constraint. + @constraint(sp, balance, reservoir.out - reservoir.in + generation == 0) + # Generation limit. + @constraint(sp, generation <= capacity.in) + # Push capacity to the next stage. + @constraint(sp, capacity.out == capacity.in) + # Maximize generation. + @stageobjective(sp, generation) + # Random inflow in balance constraint. + SDDP.parameterize(sp, rand(4)) do w + set_normalized_rhs(balance, w) + end + end + endA policy graph with 10 nodes. + Node indices: 1, ..., 10

Access a decision from N stages ago

This is often useful if you have some inventory problem with a lead time on orders. In the code below, we assume that the product has a lead time of 5 stages, and we use a state variable to track the decisions on the production for the last 5 stages. The decisions are passed to the next stage by shifting them by one stage.

julia> using SDDP, HiGHS
julia> SDDP.LinearPolicyGraph( + stages = 10, + sense = :Max, + upper_bound = 100, + optimizer = HiGHS.Optimizer, + ) do sp, t + # Current inventory on hand. + @variable(sp, inventory >= 0, SDDP.State, initial_value = 0) + # Inventory pipeline. + # pipeline[1].out are orders placed today. + # pipeline[5].in are orders that arrive today and can be added to the + # current inventory. + # Stock moves up one slot in the pipeline each stage. + @variable(sp, pipeline[1:5], SDDP.State, initial_value = 0) + # The number of units to order today. + @variable(sp, 0 <= buy <= 10) + # The number of units to sell today. + @variable(sp, sell >= 0) + # Buy orders get placed in the pipeline. + @constraint(sp, pipeline[1].out == buy) + # Stock moves up one slot in the pipeline each stage. + @constraint(sp, [i=2:5], pipeline[i].out == pipeline[i-1].in) + # Stock balance constraint. + @constraint(sp, inventory.out == inventory.in - sell + pipeline[5].in) + # Maximize quantity of sold items. + @stageobjective(sp, sell) + endA policy graph with 10 nodes. + Node indices: 1, ..., 10
Warning

You must initialize the same number of state variables in every stage, even if they are not used in that stage.

Stochastic lead times

Stochastic lead times can be modeled by adding stochasticity to the pipeline balance constraint.

The trick is to use the random variable $\omega$ to represent the lead time, together with JuMP.set_normalized_coefficient to add u_buy to the i pipeline balance constraint when $\omega$ is equal to i. For example, if $\omega = 2$ and T = 4, we would have constraints:

c_pipeline[1], x_pipeline[1].out == x_pipeline[2].in + 0 * u_buy
+c_pipeline[2], x_pipeline[2].out == x_pipeline[3].in + 1 * u_buy
+c_pipeline[3], x_pipeline[3].out == x_pipeline[4].in + 0 * u_buy
+c_pipeline[4], x_pipeline[4].out == x_pipeline[5].in + 0 * u_buy
julia> using SDDP
julia> import HiGHS
julia> T = 1010
julia> model = SDDP.LinearPolicyGraph( + stages = 20, + sense = :Max, + upper_bound = 1000, + optimizer = HiGHS.Optimizer, + ) do sp, t + @variables(sp, begin + x_inventory >= 0, SDDP.State, (initial_value = 0) + x_pipeline[1:T+1], SDDP.State, (initial_value = 0) + 0 <= u_buy <= 10 + u_sell >= 0 + end) + fix(x_pipeline[T+1].out, 0) + @stageobjective(sp, u_sell) + @constraints(sp, begin + # Shift the orders one stage + c_pipeline[i=1:T], x_pipeline[i].out == x_pipeline[i+1].in + 1 * u_buy + # x_pipeline[1].in are arriving on the inventory + x_inventory.out == x_inventory.in - u_sell + x_pipeline[1].in + end) + SDDP.parameterize(sp, 1:T) do ω + # Rewrite the constraint c_pipeline[i=1:T] indicating how many stages + # ahead the order will arrive (ω) + # if ω == i: + # x_pipeline[i+1].in + 1 * u_buy == x_pipeline[i].out + # else: + # x_pipeline[i+1].in + 0 * u_buy == x_pipeline[i].out + for i in 1:T + set_normalized_coefficient(c_pipeline[i], u_buy, ω == i ? 1 : 0) + end + end + endA policy graph with 20 nodes. + Node indices: 1, ..., 20
diff --git a/previews/PR826/guides/add_a_multidimensional_state_variable/index.html b/previews/PR826/guides/add_a_multidimensional_state_variable/index.html new file mode 100644 index 0000000000..59c4f36201 --- /dev/null +++ b/previews/PR826/guides/add_a_multidimensional_state_variable/index.html @@ -0,0 +1,22 @@ + +Add a multi-dimensional state variable · SDDP.jl

Add a multi-dimensional state variable

Just like normal JuMP variables, it is possible to create containers of state variables.

julia> model = SDDP.LinearPolicyGraph(
+           stages=1, lower_bound = 0, optimizer = HiGHS.Optimizer
+       ) do subproblem, t
+           # A scalar state variable.
+           @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)
+           println("Lower bound of outgoing x is: ", JuMP.lower_bound(x.out))
+           # A vector of state variables.
+           @variable(subproblem, y[i = 1:2] >= i, SDDP.State, initial_value = i)
+           println("Lower bound of outgoing y[1] is: ", JuMP.lower_bound(y[1].out))
+           # A JuMP.Containers.DenseAxisArray of state variables.
+           @variable(subproblem,
+               z[i = 3:4, j = [:A, :B]] >= i, SDDP.State, initial_value = i)
+           println("Lower bound of outgoing z[3, :B] is: ", JuMP.lower_bound(z[3, :B].out))
+       end;
+Lower bound of outgoing x is: 0.0
+Lower bound of outgoing y[1] is: 1.0
+Lower bound of outgoing z[3, :B] is: 3.0
diff --git a/previews/PR826/guides/add_a_risk_measure/index.html b/previews/PR826/guides/add_a_risk_measure/index.html new file mode 100644 index 0000000000..cf21fc0046 --- /dev/null +++ b/previews/PR826/guides/add_a_risk_measure/index.html @@ -0,0 +1,27 @@ + +Add a risk measure · SDDP.jl

Add a risk measure

Training a risk-averse model

SDDP.jl supports a variety of risk measures. Two common ones are SDDP.Expectation and SDDP.WorstCase. Let's see how to train a policy using them. There are three possible ways.

If the same risk measure is used at every node in the policy graph, we can just pass an instance of one of the risk measures to the risk_measure keyword argument of the SDDP.train function.

SDDP.train(
+    model,
+    risk_measure = SDDP.WorstCase(),
+    iteration_limit = 10
+)

However, if you want different risk measures at different nodes, there are two options. First, you can pass risk_measure a dictionary of risk measures, with one entry for each node. The keys of the dictionary are the indices of the nodes.

SDDP.train(
+    model,
+    risk_measure = Dict(
+        1 => SDDP.Expectation(),
+        2 => SDDP.WorstCase()
+    ),
+    iteration_limit = 10
+)

An alternative method is to pass risk_measure a function that takes one argument, the index of a node, and returns an instance of a risk measure:

SDDP.train(
+    model,
+    risk_measure = (node_index) -> begin
+        if node_index == 1
+            return SDDP.Expectation()
+        else
+            return SDDP.WorstCase()
+        end
+    end,
+    iteration_limit = 10
+)
Note

If you simulate the policy, the simulated value is the risk-neutral value of the policy.

Supported risk measures

The following risk measures are implemented in SDDP.jl:

diff --git a/previews/PR826/guides/add_integrality/index.html b/previews/PR826/guides/add_integrality/index.html new file mode 100644 index 0000000000..0c9f4660fb --- /dev/null +++ b/previews/PR826/guides/add_integrality/index.html @@ -0,0 +1,28 @@ + +Integrality · SDDP.jl

Integrality

There's nothing special about binary and integer variables in SDDP.jl. Your models may contain a mix of binary, integer, or continuous state and control variables. Use the standard JuMP syntax to add binary or integer variables.

For example:

using SDDP, HiGHS
+model = SDDP.LinearPolicyGraph(
+   stages = 3,
+   lower_bound = 0.0,
+   optimizer = HiGHS.Optimizer,
+) do sp, t
+   @variable(sp, 0 <= x <= 100, Int, SDDP.State, initial_value = 0)
+   @variable(sp, 0 <= u <= 200, integer = true)
+   @variable(sp, v >= 0)
+   @constraint(sp, x.out == x.in + u + v - 150)
+   @stageobjective(sp, 2u + 6v + x.out)
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+

If you want finer control over how SDDP.jl computes subgradients in the backward pass, you can pass an SDDP.AbstractDualityHandler to the duality_handler argument of SDDP.train.

The duality handlers implemented in SDDP.jl are:

Convergence

SDDP.jl cannot guarantee that it will find a globally optimal policy when some of the variables are discrete. However, in most cases we find that it can still find an integer feasible policy that performs well in simulation.

Moreover, when the number of nodes in the graph is large, or there is uncertainty, we are not aware of another algorithm that can claim to find a globally optimal policy.

Does SDDP.jl implement the SDDiP algorithm?

Most discussions of SDDiP in the literature confuse two unrelated things.

  • First, how to compute dual variables
  • Second, when the algorithm will converge to a globally optimal policy.

Computing dual variables

The stochastic dual dynamic programming algorithm requires a subgradient of the objective with respect to the incoming state variable.

One way to obtain a valid subgradient is to compute an optimal value of the dual variable $\lambda$ in the following subproblem:

\[\begin{aligned} +V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x \quad [\lambda] +\end{aligned}\]

The easiest option is to relax integrality of the discrete variables to form a linear program and then use linear programming duality to obtain the dual. But we could also use Lagrangian duality without needing to relax the integrality constraints.

To compute the Lagrangian dual $\lambda$, we penalize $\lambda^\top(\bar{x} - x)$ in the objective instead of enforcing the constraint:

\[\begin{aligned} +\max\limits_{\lambda}\min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)] - \lambda^\top(\bar{x} - x)\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) +\end{aligned}\]

You can use Lagrangian duality in SDDP.jl by passing SDDP.LagrangianDuality to the duality_handler argument of SDDP.train.

Compared with linear programming duality, the Lagrangian problem is difficult to solve because it requires the solution of many mixed-integer programs instead of a single linear program. This is one reason why "SDDiP" has poor performance.

Convergence

The second part to SDDiP is a very tightly scoped claim: if all of the state variables are binary and the algorithm uses Lagrangian duality to compute a subgradient, then it will converge to an optimal policy.

In many cases, papers claim to "do SDDiP," but they have state variables which are not binary. In these cases, the algorithm is not guaranteed to converge to a globally optimal policy.

One work-around that has been suggested is to discretize the state variables into a set of binary state variables. However, this leads to a large number of binary state variables, which is another reason why "SDDiP" has poor performance.

In general, we recommend that you introduce integer variables into your model without fear of the consequences, and that you treat the resulting policy as a good heuristic, rather than an attempt to find a globally optimal policy.

diff --git a/previews/PR826/guides/add_multidimensional_noise/index.html b/previews/PR826/guides/add_multidimensional_noise/index.html new file mode 100644 index 0000000000..d562769932 --- /dev/null +++ b/previews/PR826/guides/add_multidimensional_noise/index.html @@ -0,0 +1,87 @@ + +Add multi-dimensional noise terms · SDDP.jl

Add multi-dimensional noise terms

Multi-dimensional stagewise-independent random variables can be created by forming the Cartesian product of the random variables.

A simple example

If the sample space and probabilities are given as vectors for each marginal distribution, do:

julia> model = SDDP.LinearPolicyGraph(
+           stages = 3,
+           lower_bound = 0,
+           optimizer = HiGHS.Optimizer,
+       ) do subproblem, t
+           @variable(subproblem, x, SDDP.State, initial_value = 0.0)
+           Ω = [(value = v, coefficient = c) for v in [1, 2] for c in [3, 4, 5]]
+           P = [v * c for v in [0.5, 0.5] for c in [0.3, 0.5, 0.2]]
+           SDDP.parameterize(subproblem, Ω, P) do ω
+               JuMP.fix(x.out, ω.value)
+               @stageobjective(subproblem, ω.coefficient * x.out)
+           end
+       end;
+
+julia> for s in SDDP.simulate(model, 1)[1]
+           println("ω is: ", s[:noise_term])
+       end
+ω is: (value = 1, coefficient = 4)
+ω is: (value = 1, coefficient = 3)
+ω is: (value = 2, coefficient = 4)

Using Distributions.jl

For sampling multidimensional random variates, it can be useful to use the Product type from Distributions.jl.

Finite discrete distributions

There are several ways to go about this. If the sample space is finite, and small enough that it makes sense to enumerate each element, we can use Base.product and Distributions.support to generate the entire sample space Ω from each of the marginal distributions.

We can then evaluate the density function of the product distribution on each element of this space to get the vector of corresponding probabilities, P.

julia> import Distributions
+
+julia> distributions = [
+           Distributions.Binomial(10, 0.5),
+           Distributions.Bernoulli(0.5),
+           Distributions.truncated(Distributions.Poisson(5), 2, 8)
+       ];
+
+julia> supports = Distributions.support.(distributions);
+
+julia> Ω = vec([collect(ω) for ω in Base.product(supports...)]);
+
+julia> P = [Distributions.pdf(Distributions.Product(distributions), ω) for ω in Ω];
+
+julia> model = SDDP.LinearPolicyGraph(
+           stages = 3,
+           lower_bound = 0,
+           optimizer = HiGHS.Optimizer,
+       ) do subproblem, t
+           @variable(subproblem, x, SDDP.State, initial_value = 0.0)
+           SDDP.parameterize(subproblem, Ω, P) do ω
+               JuMP.fix(x.out, ω[1])
+               @stageobjective(subproblem, ω[2] * x.out + ω[3])
+           end
+       end;
+
+julia> for s in SDDP.simulate(model, 1)[1]
+           println("ω is: ", s[:noise_term])
+       end
+ω is: [10, 0, 3]
+ω is: [0, 1, 6]
+ω is: [6, 0, 5]

Sampling

For sample spaces that are too large to explicitly represent, we can instead approximate the distribution by a sample of N points. Now Ω is a sample from the full sample space, and P is the uniform distribution over those points. Points with higher density in the full sample space will appear more frequently in Ω.

julia> import Distributions
+
+julia> distributions = Distributions.Product([
+           Distributions.Binomial(100, 0.5),
+           Distributions.Geometric(1 / 20),
+           Distributions.Poisson(20),
+       ]);
+
+julia> N = 100;
+
+julia> Ω = [rand(distributions) for _ in 1:N];
+
+julia> P = fill(1 / N, N);
+
+julia> model = SDDP.LinearPolicyGraph(
+           stages = 3,
+           lower_bound = 0,
+           optimizer = HiGHS.Optimizer,
+       ) do subproblem, t
+           @variable(subproblem, x, SDDP.State, initial_value = 0.0)
+           SDDP.parameterize(subproblem, Ω, P) do ω
+               JuMP.fix(x.out, ω[1])
+               @stageobjective(subproblem, ω[2] * x.out + ω[3])
+           end
+       end;
+
+julia> for s in SDDP.simulate(model, 1)[1]
+           println("ω is: ", s[:noise_term])
+       end
+ω is: [54, 38, 19]
+ω is: [43, 3, 13]
+ω is: [43, 4, 17]
diff --git a/previews/PR826/guides/add_noise_in_the_constraint_matrix/index.html b/previews/PR826/guides/add_noise_in_the_constraint_matrix/index.html new file mode 100644 index 0000000000..95d1e3211f --- /dev/null +++ b/previews/PR826/guides/add_noise_in_the_constraint_matrix/index.html @@ -0,0 +1,18 @@ + +Add noise in the constraint matrix · SDDP.jl

Add noise in the constraint matrix

SDDP.jl supports coefficients in the constraint matrix through the JuMP.set_normalized_coefficient function.

julia> model = SDDP.LinearPolicyGraph(
+               stages=3, lower_bound = 0, optimizer = HiGHS.Optimizer
+               ) do subproblem, t
+           @variable(subproblem, x, SDDP.State, initial_value = 0.0)
+           @constraint(subproblem, emissions, 1 * x.out <= 1)
+           SDDP.parameterize(subproblem, [0.2, 0.5, 1.0]) do ω
+               ## rewrite 1 * x.out <= 1 to ω * x.out <= 1
+               JuMP.set_normalized_coefficient(emissions, x.out, ω)
+           end
+           @stageobjective(subproblem, -x.out)
+       end
+A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
Note

JuMP will normalize constraints by moving all variables to the left-hand side. Thus, @constraint(model, 0 <= 1 - x.out) becomes x.out <= 1. JuMP.set_normalized_coefficient sets the coefficient on the normalized constraint.

diff --git a/previews/PR826/guides/choose_a_stopping_rule/index.html b/previews/PR826/guides/choose_a_stopping_rule/index.html new file mode 100644 index 0000000000..73d85921c9 --- /dev/null +++ b/previews/PR826/guides/choose_a_stopping_rule/index.html @@ -0,0 +1,24 @@ + +Choose a stopping rule · SDDP.jl

Choose a stopping rule

The theory of SDDP tells us that the algorithm converges to an optimal policy almost surely in a finite number of iterations. In practice, this number is very large. Therefore, we need some way of pre-emptively terminating SDDP when the solution is “good enough.” We call heuristics for pre-emptively terminating SDDP stopping rules.

Basic limits

The training of an SDDP policy can be terminated after a fixed number of iterations using the iteration_limit keyword.

SDDP.train(model; iteration_limit = 10)

The training of an SDDP policy can be terminated after a fixed number of seconds using the time_limit keyword.

SDDP.train(model; time_limit = 2.0)

Stopping rules

In addition to the limits provided as keyword arguments, a variety of other stopping rules are available. These can be passed to SDDP.train as a vector to the stopping_rules keyword. Training stops if any of the rules becomes active. To stop when all of the rules become active, use SDDP.StoppingChain. For example:

# Terminate if BoundStalling becomes true
+SDDP.train(
+    model;
+    stopping_rules = [SDDP.BoundStalling(10, 1e-4)],
+)
+
+# Terminate if BoundStalling OR TimeLimit becomes true
+SDDP.train(
+    model;
+    stopping_rules = [SDDP.BoundStalling(10, 1e-4), SDDP.TimeLimit(100.0)],
+)
+
+# Terminate if BoundStalling AND TimeLimit becomes true
+SDDP.train(
+    model;
+    stopping_rules = [
+        SDDP.StoppingChain(SDDP.BoundStalling(10, 1e-4), SDDP.TimeLimit(100.0)),
+    ],
+)

Supported rules

The stopping rules implemented in SDDP.jl are:

diff --git a/previews/PR826/guides/create_a_belief_state/index.html b/previews/PR826/guides/create_a_belief_state/index.html new file mode 100644 index 0000000000..d6eacf6944 --- /dev/null +++ b/previews/PR826/guides/create_a_belief_state/index.html @@ -0,0 +1,37 @@ + +Create a belief state · SDDP.jl

Create a belief state

SDDP.jl includes an implementation of the algorithm described in Dowson, O., Morton, D.P., & Pagnoncelli, B.K. (2020). Partially observable multistage stochastic optimization. Operations Research Letters, 48(4), 505–512.

Given a SDDP.Graph object (see Create a general policy graph for details), we can define the ambiguity partition using SDDP.add_ambiguity_set.

For example, first we create a Markovian graph:

julia> using SDDP
julia> G = SDDP.MarkovianGraph([[0.5 0.5], [0.2 0.8; 0.8 0.2]])Root + (0, 1) +Nodes + (1, 1) + (1, 2) + (2, 1) + (2, 2) +Arcs + (0, 1) => (1, 1) w.p. 0.5 + (0, 1) => (1, 2) w.p. 0.5 + (1, 1) => (2, 1) w.p. 0.2 + (1, 1) => (2, 2) w.p. 0.8 + (1, 2) => (2, 1) w.p. 0.8 + (1, 2) => (2, 2) w.p. 0.2

Then we add an ambiguity set over the nodes in the each stage:

julia> for t in 1:2
+           SDDP.add_ambiguity_set(G, [(t, 1), (t, 2)])
+       end

This results in the graph:

julia> GRoot
+ (0, 1)
+Nodes
+ (1, 1)
+ (1, 2)
+ (2, 1)
+ (2, 2)
+Arcs
+ (0, 1) => (1, 1) w.p. 0.5
+ (0, 1) => (1, 2) w.p. 0.5
+ (1, 1) => (2, 1) w.p. 0.2
+ (1, 1) => (2, 2) w.p. 0.8
+ (1, 2) => (2, 1) w.p. 0.8
+ (1, 2) => (2, 2) w.p. 0.2
+Partitions
+ {(1, 1), (1, 2)}
+ {(2, 1), (2, 2)}
diff --git a/previews/PR826/guides/create_a_general_policy_graph/index.html b/previews/PR826/guides/create_a_general_policy_graph/index.html new file mode 100644 index 0000000000..12c278251d --- /dev/null +++ b/previews/PR826/guides/create_a_general_policy_graph/index.html @@ -0,0 +1,113 @@ + +Create a general policy graph · SDDP.jl

Create a general policy graph

SDDP.jl uses the concept of a policy graph to formulate multistage stochastic programming problems. For more details, read An introduction to SDDP.jl or the paper Dowson, O., (2020). The policy graph decomposition of multistage stochastic optimization problems. Networks, 76(1), 3-23. doi.

Creating a SDDP.Graph

Linear graphs

Linear policy graphs can be created using the SDDP.LinearGraph function.

julia> graph = SDDP.LinearGraph(3)
+Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0

We can add nodes to a graph using SDDP.add_node and edges using SDDP.add_edge.

julia> SDDP.add_node(graph, 4)
+
+julia> SDDP.add_edge(graph, 3 => 4, 1.0)
+
+julia> SDDP.add_edge(graph, 4 => 1, 0.9)
+
+julia> graph
+Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+ 4
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0
+ 3 => 4 w.p. 1.0
+ 4 => 1 w.p. 0.9

Look! We just made a cyclic graph! SDDP.jl can solve infinite horizon problems. The probability on the arc that completes a cycle should be interpreted as a discount factor.

Unicyclic policy graphs

Linear policy graphs with a single infinite-horizon cycle can be created using the SDDP.UnicyclicGraph function.

julia> SDDP.UnicyclicGraph(0.95; num_nodes = 2)
+Root
+ 0
+Nodes
+ 1
+ 2
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 1 w.p. 0.95

Markovian policy graphs

Markovian policy graphs can be created using the SDDP.MarkovianGraph function.

julia> SDDP.MarkovianGraph(Matrix{Float64}[[1.0]', [0.4 0.6]])
+Root
+ (0, 1)
+Nodes
+ (1, 1)
+ (2, 1)
+ (2, 2)
+Arcs
+ (0, 1) => (1, 1) w.p. 1.0
+ (1, 1) => (2, 1) w.p. 0.4
+ (1, 1) => (2, 2) w.p. 0.6

General graphs

Arbitrarily complicated graphs can be constructed using SDDP.Graph, SDDP.add_node and SDDP.add_edge. For example

julia> graph = SDDP.Graph(:root_node)
+Root
+ root_node
+Nodes
+ {}
+Arcs
+ {}
+
+julia> SDDP.add_node(graph, :decision_node)
+
+julia> SDDP.add_edge(graph, :root_node => :decision_node, 1.0)
+
+julia> SDDP.add_edge(graph, :decision_node => :decision_node, 0.9)
+
+julia> graph
+Root
+ root_node
+Nodes
+ decision_node
+Arcs
+ root_node => decision_node w.p. 1.0
+ decision_node => decision_node w.p. 0.9

Creating a policy graph

Once you have constructed an instance of SDDP.Graph, you can create a policy graph by passing the graph as the first argument.

julia> graph = SDDP.Graph(
+           :root_node,
+           [:decision_node],
+           [
+               (:root_node => :decision_node, 1.0),
+               (:decision_node => :decision_node, 0.9)
+           ]);
+
+julia> model = SDDP.PolicyGraph(
+               graph,
+               lower_bound = 0,
+               optimizer = HiGHS.Optimizer) do subproblem, node
+           println("Called from node: ", node)
+       end;
+Called from node: decision_node

Special cases

There are two special cases which cover the majority of models in the literature.

Note that the type of the names of all nodes (including the root node) must be the same. In this case, they are Symbols.

Simulating non-standard policy graphs

If you simulate a policy graph with a node that has outgoing arcs that sum to less than one, you will end up with simulations of different lengths. (The most common case is an infinite horizon stochastic program, aka a linear policy graph with a single cycle.)

To simulate a fixed number of stages, use:

simulations = SDDP.simulate(
+    model,
+    1,
+    sampling_scheme = SDDP.InSampleMonteCarlo(
+        max_depth = 10,
+        terminate_on_dummy_leaf = false
+    )
+)

Here, max_depth controls the number of stages, and terminate_on_dummy_leaf = false stops us from terminating early.

See also Simulate using a different sampling scheme.

Creating a Markovian graph automatically

SDDP.jl can create a Markovian graph by automatically discretizing a one-dimensional stochastic process and fitting a Markov chain.

To access this functionality, pass a function that takes no arguments and returns a Vector{Float64} to SDDP.MarkovianGraph. To keyword arguments also need to be provided: budget is the total number of nodes in the Markovian graph, and scenarios is the number of realizations of the simulator function used to approximate the graph.

In some cases, scenarios may be too small to provide a reasonable fit of the stochastic process. If so, SDDP.jl will automatically try to re-fit the Markov chain using more scenarios.

function simulator()
+    scenario = zeros(5)
+    for i = 2:5
+        scenario[i] = scenario[i - 1] + rand() - 0.5
+    end
+    return scenario
+end
+
+model = SDDP.PolicyGraph(
+    SDDP.MarkovianGraph(simulator; budget = 10, scenarios = 100),
+    sense = :Max,
+    upper_bound = 1e3
+) do subproblem, node
+    (stage, price) = node
+    @variable(subproblem, x >= 0, SDDP.State, initial_value = 1)
+    @constraint(subproblem, x.out <= x.in)
+    @stageobjective(subproblem, price * x.out)
+end
diff --git a/previews/PR826/guides/debug_a_model/index.html b/previews/PR826/guides/debug_a_model/index.html new file mode 100644 index 0000000000..da0329f09d --- /dev/null +++ b/previews/PR826/guides/debug_a_model/index.html @@ -0,0 +1,71 @@ + +Debug a model · SDDP.jl

Debug a model

Building multistage stochastic programming models is hard. There are a lot of different pieces that need to be put together, and we typically have no idea of the optimal policy, so it can be hard (impossible?) to validate the solution.

That said, here are a few tips to verify and validate models built using SDDP.jl.

Writing subproblems to file

The first step to debug a model is to write out the subproblems to a file in order to check that you are actually building what you think you are building.

This can be achieved with the help of two functions: SDDP.parameterize and SDDP.write_subproblem_to_file. The first lets you parameterize a node given a noise, and the second writes out the subproblem to a file.

Here is an example model:

using SDDP, HiGHS
+
+model = SDDP.LinearPolicyGraph(
+            stages = 2,
+            lower_bound = 0.0,
+            optimizer = HiGHS.Optimizer,
+        ) do subproblem, t
+    @variable(subproblem, x, SDDP.State, initial_value = 1)
+    @variable(subproblem, y)
+    @constraint(subproblem, balance, x.in == x.out + y)
+    SDDP.parameterize(subproblem, [1.1, 2.2]) do ω
+        @stageobjective(subproblem, ω * x.out)
+        JuMP.fix(y, ω)
+    end
+end
+
+# output
+
+A policy graph with 2 nodes.
+ Node indices: 1, 2

Initially, model hasn't been parameterized with a concrete realizations of ω. Let's do so now by parameterizing the first subproblem with ω=1.1.

julia> SDDP.parameterize(model[1], 1.1)

Easy! To parameterize the second stage problem, we would have used model[2].

Now to write out the problem to a file. We'll get a few warnings because some variables and constraints don't have names. They don't matter, so ignore them.

julia> SDDP.write_subproblem_to_file(model[1], "subproblem.lp")
+
+julia> read("subproblem.lp") |> String |> print
+minimize
+obj: 1.1 x_out + 1 x4
+subject to
+balance: 1 x_in - 1 x_out - 1 y = 0
+Bounds
+x_in free
+x_out free
+y = 1.1
+x4 >= 0
+End

It is easy to see that ω has been set in the objective, and as the fixed value for y.

It is also possible to parameterize the subproblems using values for ω that are not in the original problem formulation.

julia> SDDP.parameterize(model[1], 3.3)
+
+julia> SDDP.write_subproblem_to_file(model[1], "subproblem.lp")
+
+julia> read("subproblem.lp") |> String |> print
+minimize
+obj: 3.3 x_out + 1 x4
+subject to
+balance: 1 x_in - 1 x_out - 1 y = 0
+Bounds
+x_in free
+x_out free
+y = 3.3
+x4 >= 0
+End
+
+julia> rm("subproblem.lp")  # Clean up.

Solve the deterministic equivalent

Sometimes, it can be helpful to solve the deterministic equivalent of a problem in order to obtain an exact solution to the problem. To obtain a JuMP model that represents the deterministic equivalent, use SDDP.deterministic_equivalent. The returned model is just a normal JuMP model. Use JuMP to optimize it and query the solution.

julia> det_equiv = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)
+A JuMP Model
+├ solver: HiGHS
+├ objective_sense: MIN_SENSE
+│ └ objective_function_type: AffExpr
+├ num_variables: 24
+├ num_constraints: 28
+│ ├ AffExpr in MOI.EqualTo{Float64}: 10
+│ ├ VariableRef in MOI.EqualTo{Float64}: 8
+│ ├ VariableRef in MOI.GreaterThan{Float64}: 6
+│ └ VariableRef in MOI.LessThan{Float64}: 4
+└ Names registered in the model: none
+
+julia> set_silent(det_equiv)
+
+julia> optimize!(det_equiv)
+
+julia> objective_value(det_equiv)
+-5.472500000000001
Warning

The deterministic equivalent scales poorly with problem size. Only use this on small problems!

diff --git a/previews/PR826/guides/improve_computational_performance/index.html b/previews/PR826/guides/improve_computational_performance/index.html new file mode 100644 index 0000000000..5ecbd85af3 --- /dev/null +++ b/previews/PR826/guides/improve_computational_performance/index.html @@ -0,0 +1,15 @@ + +Improve computational performance · SDDP.jl

Improve computational performance

SDDP is a computationally intensive algorithm. Here are some suggestions for how the computational performance can be improved.

Numerical stability (again)

We've already discussed this in the Numerical stability section of Words of warning. But, it's so important that we're going to say it again: improving the problem scaling is one of the best ways to improve the numerical performance of your models.

Solver selection

The majority of the solution time is spent inside the low-level solvers. Choosing which solver (and the associated settings) correctly can lead to big speed-ups.

  • Choose a commercial solver.

    Options include CPLEX, Gurobi, and Xpress. Using free solvers such as CLP and HiGHS isn't a viable approach for large problems.

  • Try different solvers.

Even commercial solvers can have wildly different solution times. We've seen models on which CPLEX was 50% fast than Gurobi, and vice versa.

  • Experiment with different solver options.

    Using the default settings is usually a good option. However, sometimes it can pay to change these. In particular, forcing solvers to use the dual simplex algorithm (e.g., Method=1 in Gurobi ) is usually a performance win.

Single-cut vs. multi-cut

There are two competing ways that cuts can be created in SDDP: single-cut and multi-cut. By default, SDDP.jl uses the single-cut version of SDDP.

The performance of each method is problem-dependent. We recommend that you try both in order to see which one performs better. In general, the single-cut method works better when the number of realizations of the stagewise-independent random variable is large, whereas the multi-cut method works better on small problems. However, the multi-cut method can cause numerical stability problems, particularly if used in conjunction with objective or belief state variables.

You can switch between the methods by passing the relevant flag to cut_type in SDDP.train.

SDDP.train(model; cut_type = SDDP.SINGLE_CUT)
+SDDP.train(model; cut_type = SDDP.MULTI_CUT)

Parallelism

SDDP.jl can take advantage of the parallel nature of modern computers to solve problems across multiple threads.

Start Julia from a command line with N threads using the --threads flag:

julia --threads N

Then, pass an instance of SDDP.Threaded to the parallel_scheme argument of SDDP.train and SDDP.simulate.

using SDDP, HiGHS
+model = SDDP.LinearPolicyGraph(
+  stages = 2, lower_bound = 0, optimizer = HiGHS.Optimizer
+) do sp, t
+     @variable(sp, x >= 0, SDDP.State, initial_value = 1)
+     @stageobjective(sp, x.out)
+end
+SDDP.train(model; iteration_limit = 10, parallel_scheme = SDDP.Threaded())
+SDDP.simulate(model, 10; parallel_scheme = SDDP.Threaded())
diff --git a/previews/PR826/guides/simulate_using_a_different_sampling_scheme/index.html b/previews/PR826/guides/simulate_using_a_different_sampling_scheme/index.html new file mode 100644 index 0000000000..3656b27e8c --- /dev/null +++ b/previews/PR826/guides/simulate_using_a_different_sampling_scheme/index.html @@ -0,0 +1,168 @@ + +Simulate using a different sampling scheme · SDDP.jl

Simulate using a different sampling scheme

By default, SDDP.simulate will simulate the policy using the distributions of noise terms that were defined when the model was created. We call these in-sample simulations. However, in general the in-sample distributions are an approximation of some underlying probability model which we term the true process. Therefore, SDDP.jl makes it easy to simulate the policy using different probability distributions.

To demonstrate the different ways of simulating the policy, we're going to use the model from the tutorial Markovian policy graphs.

julia> using SDDP, HiGHS
+
+julia> Ω = [
+           (inflow = 0.0, fuel_multiplier = 1.5),
+           (inflow = 50.0, fuel_multiplier = 1.0),
+           (inflow = 100.0, fuel_multiplier = 0.75),
+       ]
+3-element Vector{@NamedTuple{inflow::Float64, fuel_multiplier::Float64}}:
+ (inflow = 0.0, fuel_multiplier = 1.5)
+ (inflow = 50.0, fuel_multiplier = 1.0)
+ (inflow = 100.0, fuel_multiplier = 0.75)
+
+julia> model = SDDP.MarkovianPolicyGraph(
+           transition_matrices = Array{Float64, 2}[
+               [1.0]',
+               [0.75 0.25],
+               [0.75 0.25; 0.25 0.75],
+           ],
+           sense = :Min,
+           lower_bound = 0.0,
+           optimizer = HiGHS.Optimizer,
+       ) do subproblem, node
+           # Unpack the stage and Markov index.
+           t, markov_state = node
+           # Define the state variable.
+           @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+           # Define the control variables.
+           @variables(subproblem, begin
+               thermal_generation >= 0
+               hydro_generation >= 0
+               hydro_spill >= 0
+               inflow
+           end)
+           # Define the constraints
+           @constraints(subproblem, begin
+               volume.out == volume.in + inflow - hydro_generation - hydro_spill
+               thermal_generation + hydro_generation == 150.0
+           end)
+           # Note how we can use `markov_state` to dispatch an `if` statement.
+           probability = if markov_state == 1  # wet climate state
+               [1 / 6, 1 / 3, 1 / 2]
+           else  # dry climate state
+               [1 / 2, 1 / 3, 1 / 6]
+           end
+           fuel_cost = [50.0, 100.0, 150.0]
+           SDDP.parameterize(subproblem, Ω, probability) do ω
+               JuMP.fix(inflow, ω.inflow)
+               @stageobjective(
+                   subproblem,
+                   ω.fuel_multiplier * fuel_cost[t] * thermal_generation,
+               )
+               return
+           end
+           return
+       end
+A policy graph with 5 nodes.
+ Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)
+
+
+julia> SDDP.train(model; iteration_limit = 10, print_level = 0);

In-sample Monte Carlo simulation

To simulate the policy using the data defined when model was created, use SDDP.InSampleMonteCarlo.

julia> simulations = SDDP.simulate(
+           model,
+           20;
+           sampling_scheme = SDDP.InSampleMonteCarlo(),
+       );
+
+julia> sort(unique([data[:noise_term] for sim in simulations for data in sim]))
+3-element Vector{@NamedTuple{inflow::Float64, fuel_multiplier::Float64}}:
+ (inflow = 0.0, fuel_multiplier = 1.5)
+ (inflow = 50.0, fuel_multiplier = 1.0)
+ (inflow = 100.0, fuel_multiplier = 0.75)

Out-of-sample Monte Carlo simulation

Instead of using the in-sample data, we can perform an out-of-sample simulation of the policy using the SDDP.OutOfSampleMonteCarlo sampling scheme.

For each node, the SDDP.OutOfSampleMonteCarlo needs to define a new distribution for the transition probabilities between nodes in the policy graph, and a new distribution for the stagewise independent noise terms.

Note

The support of the distribution for the stagewise independent noise terms does not have to be the same as the in-sample distributions.

julia> sampling_scheme = SDDP.OutOfSampleMonteCarlo(model) do node
+           stage, markov_state = node
+           if stage == 0
+               # Called from the root node. Transition to (1, 1) with probability 1.0.
+               # Only return the list of children, _not_ a list of noise terms.
+               return [SDDP.Noise((1, 1), 1.0)]
+           elseif stage == 3
+               # Called from the final node. Return an empty list for the children,
+               # and a single, deterministic realization for the noise terms.
+               children = SDDP.Noise[]
+               noise_terms = [SDDP.Noise((inflow = 75.0, fuel_multiplier = 1.2), 1.0)]
+               return children, noise_terms
+           else
+               # Called from a normal node. Return the in-sample distribution for the
+               # noise terms, but modify the transition probabilities so that the
+               # Markov switching probability is now 50%.
+               probability = markov_state == 1 ? [1/6, 1/3, 1/2] : [1/2, 1/3, 1/6]
+               # Note: `Ω` is defined at the top of this page of documentation
+               noise_terms = [SDDP.Noise(ω, p) for (ω, p) in zip(Ω, probability)]
+               children = [
+                   SDDP.Noise((stage + 1, 1), 0.5), SDDP.Noise((stage + 1, 2), 0.5)
+               ]
+               return children, noise_terms
+           end
+       end;
+
+julia> simulations = SDDP.simulate(model, 1; sampling_scheme = sampling_scheme);
+
+julia> simulations[1][3][:noise_term]
+(inflow = 75.0, fuel_multiplier = 1.2)

Alternatively, if you only want to modify the stagewise independent noise terms, pass use_insample_transition = true.

julia> sampling_scheme = SDDP.OutOfSampleMonteCarlo(
+           model;
+           use_insample_transition = true
+       ) do node
+       stage, markov_state = node
+           if stage == 3
+               # Called from the final node. Return a single, deterministic
+               # realization for the noise terms. Don't return the children because we
+               # use the in-sample data.
+               return [SDDP.Noise((inflow = 65.0, fuel_multiplier = 1.1), 1.0)]
+           else
+               # Called from a normal node. Return the in-sample distribution for the
+               # noise terms. Don't return the children because we use the in-sample
+               # data.
+               probability = markov_state == 1 ? [1/6, 1/3, 1/2] : [1/2, 1/3, 1/6]
+               # Note: `Ω` is defined at the top of this page of documentation
+               return [SDDP.Noise(ω, p) for (ω, p) in zip(Ω, probability)]
+           end
+       end;
+
+julia> simulations = SDDP.simulate(model, 1; sampling_scheme = sampling_scheme);
+
+julia> simulations[1][3][:noise_term]
+(inflow = 65.0, fuel_multiplier = 1.1)

Historical simulation

Instead of performing a Monte Carlo simulation like the previous tutorials, we may want to simulate one particular sequence of noise realizations. This historical simulation can also be conducted by passing a SDDP.Historical sampling scheme to the sampling_scheme keyword of the SDDP.simulate function.

We can confirm that the historical sequence of nodes was visited by querying the :node_index key of the simulation results.

julia> simulations = SDDP.simulate(
+           model;
+           sampling_scheme = SDDP.Historical(
+               # Note: `Ω` is defined at the top of this page of documentation
+               [((1, 1), Ω[1]), ((2, 2), Ω[3]), ((3, 1), Ω[2])],
+           ),
+       );
+
+julia> [stage[:node_index] for stage in simulations[1]]
+3-element Vector{Tuple{Int64, Int64}}:
+ (1, 1)
+ (2, 2)
+ (3, 1)

You can also pass a vector of scenarios, which are sampled sequentially:

julia> sampling_scheme = SDDP.Historical(
+           [
+               [
+                   ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),
+                   ((2,2), (inflow = 10.0, fuel_multiplier = 1.4)), # Can be out-of-sample
+                   ((3,1), (inflow = 65.0, fuel_multiplier = 1.1)),
+               ],
+               [
+                   ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),
+                   ((2,2), (inflow = 100.0, fuel_multiplier = 0.75)),
+                   ((3,1), (inflow = 0.0, fuel_multiplier = 1.5)),
+               ],
+           ],
+       )
+A Historical sampler with 2 scenarios sampled sequentially.

Or a vector of scenarios and a corresponding vector of probabilities so that the historical scenarios are sampled probabilistically:

julia> sampling_scheme = SDDP.Historical(
+           [
+               [
+                   ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),
+                   ((2,2), (inflow = 10.0, fuel_multiplier = 1.4)), # Can be out-of-sample
+                   ((3,1), (inflow = 65.0, fuel_multiplier = 1.1)),
+               ],
+               [
+                   ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),
+                   ((2,2), (inflow = 100.0, fuel_multiplier = 0.75)),
+                   ((3,1), (inflow = 0.0, fuel_multiplier = 1.5)),
+               ],
+           ],
+           [0.3, 0.7],
+       )
+A Historical sampler with 2 scenarios sampled probabilistically.
Tip

Your sample space doesn't have to be a NamedTuple. It an be any Julia type! Use a Vector if that is easier, or define your own struct.

diff --git a/previews/PR826/index.html b/previews/PR826/index.html new file mode 100644 index 0000000000..d7fd133d0d --- /dev/null +++ b/previews/PR826/index.html @@ -0,0 +1,50 @@ + +Home · SDDP.jl
logo

Introduction

Build Status code coverage

Welcome to SDDP.jl, a package for solving large convex multistage stochastic programming problems using stochastic dual dynamic programming.

SDDP.jl is built on JuMP, so it supports a number of open-source and commercial solvers, making it a powerful and flexible tool for stochastic optimization.

The implementation of the stochastic dual dynamic programming algorithm in SDDP.jl is state of the art, and it includes support for a number of advanced features not commonly found in other implementations. This includes support for:

  • infinite horizon problems
  • convex risk measures
  • mixed-integer state and control variables
  • partially observable stochastic processes.

Installation

Install SDDP.jl as follows:

julia> import Pkg
+
+julia> Pkg.add("SDDP")

License

SDDP.jl is licensed under the MPL 2.0 license.

Resources for getting started

There are a few ways to get started with SDDP.jl:

Getting help

If you need help, please open a GitHub issue.

How the documentation is structured

Having a high-level overview of how this documentation is structured will help you know where to look for certain things.

  • Tutorials contains step-by-step explanations of how to use SDDP.jl. Once you've got SDDP.jl installed, start by reading An introduction to SDDP.jl.

  • Guides contains "how-to" snippets that demonstrate specific topics within SDDP.jl. A good one to get started on is Debug a model.

  • Explanation contains step-by-step explanations of the theory and algorithms that underpin SDDP.jl. If you want a basic understanding of the algorithm behind SDDP.jl, start with Introductory theory.

  • Examples contain worked examples of various problems solved using SDDP.jl. A good one to get started on is the Hydro-thermal scheduling problem. In particular, it shows how to solve an infinite horizon problem.

  • The API Reference contains a complete list of the functions you can use in SDDP.jl. Look here if you want to know how to use a particular function.

Citing SDDP.jl

If you use SDDP.jl, we ask that you please cite the following:

@article{dowson_sddp.jl,
+	title = {{SDDP}.jl: a {Julia} package for stochastic dual dynamic programming},
+	journal = {INFORMS Journal on Computing},
+	author = {Dowson, O. and Kapelevich, L.},
+	doi = {https://doi.org/10.1287/ijoc.2020.0987},
+	year = {2021},
+	volume = {33},
+	issue = {1},
+	pages = {27-33},
+}

Here is an earlier preprint.

If you use the infinite horizon functionality, we ask that you please cite the following:

@article{dowson_policy_graph,
+	title = {The policy graph decomposition of multistage stochastic optimization problems},
+	doi = {https://doi.org/10.1002/net.21932},
+	journal = {Networks},
+	author = {Dowson, O.},
+	volume = {76},
+	issue = {1},
+	pages = {3-23},
+	year = {2020}
+}

Here is an earlier preprint.

If you use the partially observable functionality, we ask that you please cite the following:

@article{dowson_pomsp,
+	title = {Partially observable multistage stochastic programming},
+	doi = {https://doi.org/10.1016/j.orl.2020.06.005},
+	journal = {Operations Research Letters},
+	author = {Dowson, O. and Morton, D.P. and Pagnoncelli, B.K.},
+	volume = {48},
+	issue = {4},
+	pages = {505-512},
+	year = {2020}
+}

Here is an earlier preprint.

If you use the objective state functionality, we ask that you please cite the following:

@article{downward_objective,
+	title = {Stochastic dual dynamic programming with stagewise-dependent objective uncertainty},
+	doi = {https://doi.org/10.1016/j.orl.2019.11.002},
+	journal = {Operations Research Letters},
+	author = {Downward, A. and Dowson, O. and Baucke, R.},
+	volume = {48},
+	issue = {1},
+	pages = {33-39},
+	year = {2020}
+}

Here is an earlier preprint.

If you use the entropic risk measure, we ask that you please cite the following:

@article{dowson_entropic,
+	title = {Incorporating convex risk measures into multistage stochastic programming algorithms},
+	doi = {https://doi.org/10.1007/s10479-022-04977-w},
+	journal = {Annals of Operations Research},
+	author = {Dowson, O. and Morton, D.P. and Pagnoncelli, B.K.},
+	year = {2022},
+}

Here is an earlier preprint.

diff --git a/previews/PR826/objects.inv b/previews/PR826/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..18af1c088643937042f5c5cdcaf013dec13fbdbd GIT binary patch literal 10118 zcmV;1CwbT-AX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkYL_|<7 zYHSK4AXa5^b7^mGIv_DFF)%JO3L_v?Xk{RBWo=<;Ze(S0Aa78b#rNMXCQiPX<{x4c$~F8X>;2~mf!g+x+>$^s49>M@Q~y$8CigB3f zk|-wRO|i*F`@_B{^K^Z3mzULkQSGx`rH}GGKOq-el5h8Au{tdGd9fw?g4|qPT_69k zRtSsRKQegwm@N+5WmfJ}fOz(z_KrLuziMDP%<+&BIg;GLP@G!Lw@J&d#bfJa?ywW` zp8l4nTe2(G`SO|Em+9`IQn+{PV!zL~_g;+R;R(4G!}dk7*1(_AatlBEH.A-@;p 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 literal 0 HcmV?d00001 diff --git a/previews/PR826/release_notes/index.html b/previews/PR826/release_notes/index.html new file mode 100644 index 0000000000..eda01ec126 --- /dev/null +++ b/previews/PR826/release_notes/index.html @@ -0,0 +1,6 @@ + +Release notes · SDDP.jl

Release notes

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

v1.10.3 (January 22, 2025)

Other

v1.10.2 (January 13, 2025)

Fixed

Other

  • Improved test coverage (#810) (#811)
  • Improved documentation for risk measures (#813)
  • Fixed badges for GitHub actions (#816)
  • Updated API reference (#814)

v1.10.1 (November 28, 2024)

Fixed

Other

  • Documentation updates (#801)

v1.10.0 (November 19, 2024)

Added

  • Added root_node_risk_measure keyword to train (#804)

Fixed

  • Fixed a bug with cut sharing in a graph with zero-probability arcs (#797)

Other

v1.9.0 (October 17, 2024)

Added

Fixed

  • Fixed the tests to skip threading tests if running in serial (#770)
  • Fixed BanditDuality to handle the case where the standard deviation is NaN (#779)
  • Fixed an error when lagged state variables are encountered in MSPFormat (#786)
  • Fixed publication_plot with replications of different lengths (#788)
  • Fixed CTRL+C interrupting the code at unsafe points (#789)

Other

  • Documentation improvements (#771) (#772)
  • Updated printing because of changes in JuMP (#773)

v1.8.1 (August 5, 2024)

Fixed

  • Fixed various issues with SDDP.Threaded() (#761)
  • Fixed a deprecation warning for sorting a dictionary (#763)

Other

  • Updated copyright notices (#762)
  • Updated .JuliaFormatter.toml (#764)

v1.8.0 (July 24, 2024)

Added

  • Added SDDP.Threaded(), which is an experimental parallel scheme that supports solving problems using multiple threads. Some parts of SDDP.jl may not be thread-safe, and this can cause incorrect results, segfaults, or other errors. Please use with care and report any issues by opening a GitHub issue. (#758)

Other

  • Documentation improvements and fixes (#747) (#759)

v1.7.0 (June 4, 2024)

Added

  • Added sample_backward_noise_terms_with_state for creating backward pass sampling schemes that depend on the current primal state. (#742) (Thanks @arthur-brigatto)

Fixed

  • Fixed error message when publication_plot has non-finite data (#738)

Other

  • Updated the logo constructor (#730)

v1.6.7 (February 1, 2024)

Fixed

  • Fixed non-constant state dimension in the MSPFormat reader (#695)
  • Fixed SimulatorSamplingScheme for deterministic nodes (#710)
  • Fixed line search in BFGS (#711)
  • Fixed handling of NEARLY_FEASIBLE_POINT status (#726)

Other

  • Documentation improvements (#692) (#694) (#706) (#716) (#727)
  • Updated to StochOptFormat v1.0 (#705)
  • Added an experimental OuterApproximation algorithm (#709)
  • Updated .gitignore (#717)
  • Added code for MDP paper (#720) (#721)
  • Added Google analytics (#723)

v1.6.6 (September 29, 2023)

Other

v1.6.5 (September 25, 2023)

Fixed

Other

v1.6.4 (September 23, 2023)

Fixed

Other

  • Documentation updates (#658) (#666) (#671)
  • Switch to GitHub action for deploying docs (#668) (#670)
  • Update to Documenter@1 (#669)

v1.6.3 (September 8, 2023)

Fixed

  • Fixed default stopping rule with iteration_limit or time_limit set (#662)

Other

v1.6.2 (August 24, 2023)

Fixed

  • MSPFormat now detect and exploit stagewise independent lattices (#653)
  • Fixed set_optimizer for models read from file (#654)

Other

  • Fixed typo in pglib_opf.jl (#647)
  • Fixed documentation build and added color (#652)

v1.6.1 (July 20, 2023)

Fixed

  • Fixed bugs in MSPFormat reader (#638) (#639)

Other

  • Clarified OutOfSampleMonteCarlo docstring (#643)

v1.6.0 (July 3, 2023)

Added

Other

v1.5.1 (June 30, 2023)

This release contains a number of minor code changes, but it has a large impact on the content that is printed to screen. In particular, we now log periodically, instead of each iteration, and a "good" stopping rule is used as the default if none are specified. Try using SDDP.train(model) to see the difference.

Other

  • Fixed various typos in the documentation (#617)
  • Fixed printing test after changes in JuMP (#618)
  • Set SimulationStoppingRule as the default stopping rule (#619)
  • Changed the default logging frequency. Pass log_every_seconds = 0.0 to train to revert to the old behavior. (#620)
  • Added example usage with Distributions.jl (@slwu89) (#622)
  • Removed the numerical issue @warn (#627)
  • Improved the quality of docstrings (#630)

v1.5.0 (May 14, 2023)

Added

  • Added the ability to use a different model for the forward pass. This is a novel feature that lets you train better policies when the model is non-convex or does not have a well-defined dual. See the Alternative forward models tutorial in which we train convex and non-convex formulations of the optimal power flow problem. (#611)

Other

  • Updated missing changelog entries (#608)
  • Removed global variables (#610)
  • Converted the Options struct to keyword arguments. This struct was a private implementation detail, but the change is breaking if you developed an extension to SDDP that touched these internals. (#612)
  • Fixed some typos (#613)

v1.4.0 (May 8, 2023)

Added

Fixed

  • Fixed parsing of some MSPFormat files (#602) (#604)
  • Fixed printing in header (#605)

v1.3.0 (May 3, 2023)

Added

  • Added experimental support for SDDP.MSPFormat.read_from_file (#593)

Other

  • Updated to StochOptFormat v0.3 (#600)

v1.2.1 (May 1, 2023)

Fixed

  • Fixed log_every_seconds (#597)

v1.2.0 (May 1, 2023)

Added

Other

  • Tweaked how the log is printed (#588)
  • Updated to StochOptFormat v0.2 (#592)

v1.1.4 (April 10, 2023)

Fixed

  • Logs are now flushed every iteration (#584)

Other

  • Added docstrings to various functions (#581)
  • Minor documentation updates (#580)
  • Clarified integrality documentation (#582)
  • Updated the README (#585)
  • Number of numerical issues is now printed to the log (#586)

v1.1.3 (April 2, 2023)

Other

v1.1.2 (March 18, 2023)

Other

v1.1.1 (March 16, 2023)

Other

  • Fixed email in Project.toml
  • Added notebook to documentation tutorials (#571)

v1.1.0 (January 12, 2023)

Added

v1.0.0 (January 3, 2023)

Although we're bumping MAJOR version, this is a non-breaking release. Going forward:

  • New features will bump the MINOR version
  • Bug fixes, maintenance, and documentation updates will bump the PATCH version
  • We will support only the Long Term Support (currently v1.6.7) and the latest patch (currently v1.8.4) releases of Julia. Updates to the LTS version will bump the MINOR version
  • Updates to the compat bounds of package dependencies will bump the PATCH version.

We do not intend any breaking changes to the public API, which would require a new MAJOR release. The public API is everything defined in the documentation. Anything not in the documentation is considered private and may change in any PATCH release.

Added

Other

v0.4.9 (January 3, 2023)

Added

Other

  • Added tutorial on Markov Decision Processes (#556)
  • Added two-stage newsvendor tutorial (#557)
  • Refactored the layout of the documentation (#554) (#555)
  • Updated copyright to 2023 (#558)
  • Fixed errors in the documentation (#561)

v0.4.8 (December 19, 2022)

Added

Fixed

  • Reverted then fixed (#531) because it failed to account for problems with integer variables (#546) (#551)

v0.4.7 (December 17, 2022)

Added

  • Added initial_node support to InSampleMonteCarlo and OutOfSampleMonteCarlo (#535)

Fixed

  • Rethrow InterruptException when solver is interrupted (#534)
  • Fixed numerical recovery when we need dual solutions (#531) (Thanks @bfpc)
  • Fixed re-using the dashboard = true option between solves (#538)
  • Fixed bug when no @stageobjective is set (now defaults to 0.0) (#539)
  • Fixed errors thrown when invalid inputs are provided to add_objective_state (#540)

Other

  • Drop support for Julia versions prior to 1.6 (#533)
  • Updated versions of dependencies (#522) (#533)
  • Switched to HiGHS in the documentation and tests (#533)
  • Added license headers (#519)
  • Fixed link in air conditioning example (#521) (Thanks @conema)
  • Clarified variable naming in deterministic equivalent (#525) (Thanks @lucasprocessi)
  • Added this change log (#536)
  • Cuts are now written to model.cuts.json when numerical instability is discovered. This can aid debugging because it allows to you reload the cuts as of the iteration that caused the numerical issue (#537)

v0.4.6 (March 25, 2022)

Other

  • Updated to JuMP v1.0 (#517)

v0.4.5 (March 9, 2022)

Fixed

  • Fixed issue with set_silent in a subproblem (#510)

Other

v0.4.4 (December 11, 2021)

Added

  • Added BanditDuality (#471)
  • Added benchmark scripts (#475) (#476) (#490)
  • write_cuts_to_file now saves visited states (#468)

Fixed

  • Fixed BoundStalling in a deterministic policy (#470) (#474)
  • Fixed magnitude warning with zero coefficients (#483)

Other

  • Improvements to LagrangianDuality (#481) (#482) (#487)
  • Improvements to StrengthenedConicDuality (#486)
  • Switch to functional form for the tests (#478)
  • Fixed typos (#472) (Thanks @vfdev-5)
  • Update to JuMP v0.22 (#498)

v0.4.3 (August 31, 2021)

Added

  • Added biobjective solver (#462)
  • Added forward_pass_callback (#466)

Other

  • Update tutorials and documentation (#459) (#465)
  • Organize how paper materials are stored (#464)

v0.4.2 (August 24, 2021)

Fixed

  • Fixed a bug in Lagrangian duality (#457)

v0.4.1 (August 23, 2021)

Other

  • Minor changes to our implementation of LagrangianDuality (#454) (#455)

v0.4.0 (August 17, 2021)

Breaking

Other

v0.3.17 (July 6, 2021)

Added

Other

  • Display more model attributes (#438)
  • Documentation improvements (#433) (#437) (#439)

v0.3.16 (June 17, 2021)

Added

Other

  • Update risk measure docstrings (#418)

v0.3.15 (June 1, 2021)

Added

Fixed

Other

  • Add JuliaFormatter (#412)
  • Documentation improvements (#406) (#408)

v0.3.14 (March 30, 2021)

Fixed

  • Fixed O(N^2) behavior in get_same_children (#393)

v0.3.13 (March 27, 2021)

Fixed

  • Fixed bug in print.jl
  • Fixed compat of Reexport (#388)

v0.3.12 (March 22, 2021)

Added

  • Added problem statistics to header (#385) (#386)

Fixed

  • Fixed subtypes in visualization (#384)

v0.3.11 (March 22, 2021)

Fixed

  • Fixed constructor in direct mode (#383)

Other

  • Fix documentation (#379)

v0.3.10 (February 23, 2021)

Fixed

  • Fixed seriescolor in publication plot (#376)

v0.3.9 (February 20, 2021)

Added

  • Add option to simulate with different incoming state (#372)
  • Added warning for cuts with high dynamic range (#373)

Fixed

  • Fixed seriesalpha in publication plot (#375)

v0.3.8 (January 19, 2021)

Other

v0.3.7 (January 8, 2021)

Other

v0.3.6 (December 17, 2020)

Other

  • Fix typos (#358)
  • Collapse navigation bar in docs (#359)
  • Update TagBot.yml (#361)

v0.3.5 (November 18, 2020)

Other

  • Update citations (#348)
  • Switch to GitHub actions (#355)

v0.3.4 (August 25, 2020)

Added

  • Added non-uniform distributionally robust risk measure (#328)
  • Added numerical recovery functions (#330)
  • Added experimental StochOptFormat (#332) (#336) (#337) (#341) (#343) (#344)
  • Added entropic risk measure (#347)

Other

v0.3.3 (June 19, 2020)

Added

  • Added asynchronous support for price and belief states (#325)
  • Added ForwardPass plug-in system (#320)

Fixed

  • Fix check for probabilities in Markovian graph (#322)

v0.3.2 (April 6, 2020)

Added

Other

  • Improve error message in deterministic equivalent (#312)
  • Update to RecipesBase 1.0 (#313)

v0.3.1 (February 26, 2020)

Fixed

  • Fixed filename in integrality_handlers.jl (#304)

v0.3.0 (February 20, 2020)

Breaking

  • Breaking changes to update to JuMP v0.21 (#300).

v0.2.4 (February 7, 2020)

Added

  • Added a counter for the number of total subproblem solves (#301)

Other

  • Update formatter (#298)
  • Added tests (#299)

v0.2.3 (January 24, 2020)

Added

  • Added support for convex risk measures (#294)

Fixed

  • Fixed bug when subproblem is infeasible (#296)
  • Fixed bug in deterministic equivalent (#297)

Other

  • Added example from IJOC paper (#293)

v0.2.2 (January 10, 2020)

Fixed

  • Fixed flakey time limit in tests (#291)

Other

  • Removed MathOptFormat.jl (#289)
  • Update copyright (#290)

v0.2.1 (December 19, 2019)

Added

  • Added support for approximating a Markov lattice (#282) (#285)
  • Add tools for visualizing the value function (#272) (#286)
  • Write .mof.json files on error (#284)

Other

  • Improve documentation (#281) (#283)
  • Update tests for Julia 1.3 (#287)

v0.2.0 (December 16, 2019)

This version added the asynchronous parallel implementation with a few minor breaking changes in how we iterated internally. It didn't break basic user-facing models, only implementations that implemented some of the extension features. It probably could have been a v1.1 release.

Added

  • Added asynchronous parallel implementation (#277)
  • Added roll-out algorithm for cyclic graphs (#279)

Other

  • Improved error messages in PolicyGraph (#271)
  • Added JuliaFormatter (#273) (#276)
  • Fixed compat bounds (#274) (#278)
  • Added documentation for simulating non-standard graphs (#280)

v0.1.0 (October 17, 2019)

A complete rewrite of SDDP.jl based on the policy graph framework. This was essentially a new package. It has minimal code in common with the previous implementation.

Development started on September 28, 2018 in Kokako.jl, and the code was merged into SDDP.jl on March 14, 2019.

The pull request SDDP.jl#180 lists the 29 issues that the rewrite closed.

v0.0.1 (April 18, 2018)

Initial release. Development had been underway since January 22, 2016 in the StochDualDynamicProgram.jl repository. The last development commit there was April 5, 2017. Work then continued in this repository for a year before the first tagged release.

diff --git a/previews/PR826/search_index.js b/previews/PR826/search_index.js new file mode 100644 index 0000000000..f00278aa81 --- /dev/null +++ b/previews/PR826/search_index.js @@ -0,0 +1,3 @@ +var documenterSearchIndex = {"docs": +[{"location":"guides/create_a_general_policy_graph/#Create-a-general-policy-graph","page":"Create a general policy graph","title":"Create a general policy graph","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"DocTestSetup = quote\n using SDDP, HiGHS\nend","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"SDDP.jl uses the concept of a policy graph to formulate multistage stochastic programming problems. For more details, read An introduction to SDDP.jl or the paper Dowson, O., (2020). The policy graph decomposition of multistage stochastic optimization problems. Networks, 76(1), 3-23. doi.","category":"page"},{"location":"guides/create_a_general_policy_graph/#Creating-a-[SDDP.Graph](@ref)","page":"Create a general policy graph","title":"Creating a SDDP.Graph","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/#Linear-graphs","page":"Create a general policy graph","title":"Linear graphs","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Linear policy graphs can be created using the SDDP.LinearGraph function.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"julia> graph = SDDP.LinearGraph(3)\nRoot\n 0\nNodes\n 1\n 2\n 3\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 3 w.p. 1.0","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"We can add nodes to a graph using SDDP.add_node and edges using SDDP.add_edge.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"julia> SDDP.add_node(graph, 4)\n\njulia> SDDP.add_edge(graph, 3 => 4, 1.0)\n\njulia> SDDP.add_edge(graph, 4 => 1, 0.9)\n\njulia> graph\nRoot\n 0\nNodes\n 1\n 2\n 3\n 4\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 3 w.p. 1.0\n 3 => 4 w.p. 1.0\n 4 => 1 w.p. 0.9","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Look! We just made a cyclic graph! SDDP.jl can solve infinite horizon problems. The probability on the arc that completes a cycle should be interpreted as a discount factor.","category":"page"},{"location":"guides/create_a_general_policy_graph/#guide_unicyclic_policy_graph","page":"Create a general policy graph","title":"Unicyclic policy graphs","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Linear policy graphs with a single infinite-horizon cycle can be created using the SDDP.UnicyclicGraph function.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"julia> SDDP.UnicyclicGraph(0.95; num_nodes = 2)\nRoot\n 0\nNodes\n 1\n 2\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 1 w.p. 0.95","category":"page"},{"location":"guides/create_a_general_policy_graph/#guide_markovian_policy_graph","page":"Create a general policy graph","title":"Markovian policy graphs","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Markovian policy graphs can be created using the SDDP.MarkovianGraph function.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"julia> SDDP.MarkovianGraph(Matrix{Float64}[[1.0]', [0.4 0.6]])\nRoot\n (0, 1)\nNodes\n (1, 1)\n (2, 1)\n (2, 2)\nArcs\n (0, 1) => (1, 1) w.p. 1.0\n (1, 1) => (2, 1) w.p. 0.4\n (1, 1) => (2, 2) w.p. 0.6","category":"page"},{"location":"guides/create_a_general_policy_graph/#General-graphs","page":"Create a general policy graph","title":"General graphs","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Arbitrarily complicated graphs can be constructed using SDDP.Graph, SDDP.add_node and SDDP.add_edge. For example","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"julia> graph = SDDP.Graph(:root_node)\nRoot\n root_node\nNodes\n {}\nArcs\n {}\n\njulia> SDDP.add_node(graph, :decision_node)\n\njulia> SDDP.add_edge(graph, :root_node => :decision_node, 1.0)\n\njulia> SDDP.add_edge(graph, :decision_node => :decision_node, 0.9)\n\njulia> graph\nRoot\n root_node\nNodes\n decision_node\nArcs\n root_node => decision_node w.p. 1.0\n decision_node => decision_node w.p. 0.9","category":"page"},{"location":"guides/create_a_general_policy_graph/#Creating-a-policy-graph","page":"Create a general policy graph","title":"Creating a policy graph","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Once you have constructed an instance of SDDP.Graph, you can create a policy graph by passing the graph as the first argument.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"julia> graph = SDDP.Graph(\n :root_node,\n [:decision_node],\n [\n (:root_node => :decision_node, 1.0),\n (:decision_node => :decision_node, 0.9)\n ]);\n\njulia> model = SDDP.PolicyGraph(\n graph,\n lower_bound = 0,\n optimizer = HiGHS.Optimizer) do subproblem, node\n println(\"Called from node: \", node)\n end;\nCalled from node: decision_node","category":"page"},{"location":"guides/create_a_general_policy_graph/#Special-cases","page":"Create a general policy graph","title":"Special cases","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"There are two special cases which cover the majority of models in the literature.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"SDDP.LinearPolicyGraph is a special case where a SDDP.LinearGraph is passed as the first argument.\nSDDP.MarkovianPolicyGraph is a special case where a SDDP.MarkovianGraph is passed as the first argument.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Note that the type of the names of all nodes (including the root node) must be the same. In this case, they are Symbols.","category":"page"},{"location":"guides/create_a_general_policy_graph/#Simulating-non-standard-policy-graphs","page":"Create a general policy graph","title":"Simulating non-standard policy graphs","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"If you simulate a policy graph with a node that has outgoing arcs that sum to less than one, you will end up with simulations of different lengths. (The most common case is an infinite horizon stochastic program, aka a linear policy graph with a single cycle.)","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"To simulate a fixed number of stages, use:","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"simulations = SDDP.simulate(\n model,\n 1,\n sampling_scheme = SDDP.InSampleMonteCarlo(\n max_depth = 10,\n terminate_on_dummy_leaf = false\n )\n)","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"Here, max_depth controls the number of stages, and terminate_on_dummy_leaf = false stops us from terminating early.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"See also Simulate using a different sampling scheme.","category":"page"},{"location":"guides/create_a_general_policy_graph/#Creating-a-Markovian-graph-automatically","page":"Create a general policy graph","title":"Creating a Markovian graph automatically","text":"","category":"section"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"SDDP.jl can create a Markovian graph by automatically discretizing a one-dimensional stochastic process and fitting a Markov chain.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"To access this functionality, pass a function that takes no arguments and returns a Vector{Float64} to SDDP.MarkovianGraph. To keyword arguments also need to be provided: budget is the total number of nodes in the Markovian graph, and scenarios is the number of realizations of the simulator function used to approximate the graph.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"In some cases, scenarios may be too small to provide a reasonable fit of the stochastic process. If so, SDDP.jl will automatically try to re-fit the Markov chain using more scenarios.","category":"page"},{"location":"guides/create_a_general_policy_graph/","page":"Create a general policy graph","title":"Create a general policy graph","text":"function simulator()\n scenario = zeros(5)\n for i = 2:5\n scenario[i] = scenario[i - 1] + rand() - 0.5\n end\n return scenario\nend\n\nmodel = SDDP.PolicyGraph(\n SDDP.MarkovianGraph(simulator; budget = 10, scenarios = 100),\n sense = :Max,\n upper_bound = 1e3\n) do subproblem, node\n (stage, price) = node\n @variable(subproblem, x >= 0, SDDP.State, initial_value = 1)\n @constraint(subproblem, x.out <= x.in)\n @stageobjective(subproblem, price * x.out)\nend","category":"page"},{"location":"guides/debug_a_model/#Debug-a-model","page":"Debug a model","title":"Debug a model","text":"","category":"section"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"Building multistage stochastic programming models is hard. There are a lot of different pieces that need to be put together, and we typically have no idea of the optimal policy, so it can be hard (impossible?) to validate the solution.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"That said, here are a few tips to verify and validate models built using SDDP.jl.","category":"page"},{"location":"guides/debug_a_model/#Writing-subproblems-to-file","page":"Debug a model","title":"Writing subproblems to file","text":"","category":"section"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"The first step to debug a model is to write out the subproblems to a file in order to check that you are actually building what you think you are building.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"This can be achieved with the help of two functions: SDDP.parameterize and SDDP.write_subproblem_to_file. The first lets you parameterize a node given a noise, and the second writes out the subproblem to a file.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"Here is an example model:","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"using SDDP, HiGHS\n\nmodel = SDDP.LinearPolicyGraph(\n stages = 2,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, t\n @variable(subproblem, x, SDDP.State, initial_value = 1)\n @variable(subproblem, y)\n @constraint(subproblem, balance, x.in == x.out + y)\n SDDP.parameterize(subproblem, [1.1, 2.2]) do ω\n @stageobjective(subproblem, ω * x.out)\n JuMP.fix(y, ω)\n end\nend\n\n# output\n\nA policy graph with 2 nodes.\n Node indices: 1, 2","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"Initially, model hasn't been parameterized with a concrete realizations of ω. Let's do so now by parameterizing the first subproblem with ω=1.1.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"julia> SDDP.parameterize(model[1], 1.1)","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"Easy! To parameterize the second stage problem, we would have used model[2].","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"Now to write out the problem to a file. We'll get a few warnings because some variables and constraints don't have names. They don't matter, so ignore them.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"julia> SDDP.write_subproblem_to_file(model[1], \"subproblem.lp\")\n\njulia> read(\"subproblem.lp\") |> String |> print\nminimize\nobj: 1.1 x_out + 1 x4\nsubject to\nbalance: 1 x_in - 1 x_out - 1 y = 0\nBounds\nx_in free\nx_out free\ny = 1.1\nx4 >= 0\nEnd","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"It is easy to see that ω has been set in the objective, and as the fixed value for y.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"It is also possible to parameterize the subproblems using values for ω that are not in the original problem formulation.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"julia> SDDP.parameterize(model[1], 3.3)\n\njulia> SDDP.write_subproblem_to_file(model[1], \"subproblem.lp\")\n\njulia> read(\"subproblem.lp\") |> String |> print\nminimize\nobj: 3.3 x_out + 1 x4\nsubject to\nbalance: 1 x_in - 1 x_out - 1 y = 0\nBounds\nx_in free\nx_out free\ny = 3.3\nx4 >= 0\nEnd\n\njulia> rm(\"subproblem.lp\") # Clean up.","category":"page"},{"location":"guides/debug_a_model/#Solve-the-deterministic-equivalent","page":"Debug a model","title":"Solve the deterministic equivalent","text":"","category":"section"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"Sometimes, it can be helpful to solve the deterministic equivalent of a problem in order to obtain an exact solution to the problem. To obtain a JuMP model that represents the deterministic equivalent, use SDDP.deterministic_equivalent. The returned model is just a normal JuMP model. Use JuMP to optimize it and query the solution.","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"julia> det_equiv = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\nA JuMP Model\n├ solver: HiGHS\n├ objective_sense: MIN_SENSE\n│ └ objective_function_type: AffExpr\n├ num_variables: 24\n├ num_constraints: 28\n│ ├ AffExpr in MOI.EqualTo{Float64}: 10\n│ ├ VariableRef in MOI.EqualTo{Float64}: 8\n│ ├ VariableRef in MOI.GreaterThan{Float64}: 6\n│ └ VariableRef in MOI.LessThan{Float64}: 4\n└ Names registered in the model: none\n\njulia> set_silent(det_equiv)\n\njulia> optimize!(det_equiv)\n\njulia> objective_value(det_equiv)\n-5.472500000000001","category":"page"},{"location":"guides/debug_a_model/","page":"Debug a model","title":"Debug a model","text":"warning: Warning\nThe deterministic equivalent scales poorly with problem size. Only use this on small problems!","category":"page"},{"location":"guides/add_multidimensional_noise/#Add-multi-dimensional-noise-terms","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"","category":"section"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"DocTestSetup = quote\n using SDDP, HiGHS\nend","category":"page"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"Multi-dimensional stagewise-independent random variables can be created by forming the Cartesian product of the random variables.","category":"page"},{"location":"guides/add_multidimensional_noise/#A-simple-example","page":"Add multi-dimensional noise terms","title":"A simple example","text":"","category":"section"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"If the sample space and probabilities are given as vectors for each marginal distribution, do:","category":"page"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"julia> model = SDDP.LinearPolicyGraph(\n stages = 3,\n lower_bound = 0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, t\n @variable(subproblem, x, SDDP.State, initial_value = 0.0)\n Ω = [(value = v, coefficient = c) for v in [1, 2] for c in [3, 4, 5]]\n P = [v * c for v in [0.5, 0.5] for c in [0.3, 0.5, 0.2]]\n SDDP.parameterize(subproblem, Ω, P) do ω\n JuMP.fix(x.out, ω.value)\n @stageobjective(subproblem, ω.coefficient * x.out)\n end\n end;\n\njulia> for s in SDDP.simulate(model, 1)[1]\n println(\"ω is: \", s[:noise_term])\n end\nω is: (value = 1, coefficient = 4)\nω is: (value = 1, coefficient = 3)\nω is: (value = 2, coefficient = 4)","category":"page"},{"location":"guides/add_multidimensional_noise/#Using-Distributions.jl","page":"Add multi-dimensional noise terms","title":"Using Distributions.jl","text":"","category":"section"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"For sampling multidimensional random variates, it can be useful to use the Product type from Distributions.jl.","category":"page"},{"location":"guides/add_multidimensional_noise/#Finite-discrete-distributions","page":"Add multi-dimensional noise terms","title":"Finite discrete distributions","text":"","category":"section"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"There are several ways to go about this. If the sample space is finite, and small enough that it makes sense to enumerate each element, we can use Base.product and Distributions.support to generate the entire sample space Ω from each of the marginal distributions.","category":"page"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"We can then evaluate the density function of the product distribution on each element of this space to get the vector of corresponding probabilities, P.","category":"page"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"julia> import Distributions\n\njulia> distributions = [\n Distributions.Binomial(10, 0.5),\n Distributions.Bernoulli(0.5),\n Distributions.truncated(Distributions.Poisson(5), 2, 8)\n ];\n\njulia> supports = Distributions.support.(distributions);\n\njulia> Ω = vec([collect(ω) for ω in Base.product(supports...)]);\n\njulia> P = [Distributions.pdf(Distributions.Product(distributions), ω) for ω in Ω];\n\njulia> model = SDDP.LinearPolicyGraph(\n stages = 3,\n lower_bound = 0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, t\n @variable(subproblem, x, SDDP.State, initial_value = 0.0)\n SDDP.parameterize(subproblem, Ω, P) do ω\n JuMP.fix(x.out, ω[1])\n @stageobjective(subproblem, ω[2] * x.out + ω[3])\n end\n end;\n\njulia> for s in SDDP.simulate(model, 1)[1]\n println(\"ω is: \", s[:noise_term])\n end\nω is: [10, 0, 3]\nω is: [0, 1, 6]\nω is: [6, 0, 5]","category":"page"},{"location":"guides/add_multidimensional_noise/#Sampling","page":"Add multi-dimensional noise terms","title":"Sampling","text":"","category":"section"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"For sample spaces that are too large to explicitly represent, we can instead approximate the distribution by a sample of N points. Now Ω is a sample from the full sample space, and P is the uniform distribution over those points. Points with higher density in the full sample space will appear more frequently in Ω.","category":"page"},{"location":"guides/add_multidimensional_noise/","page":"Add multi-dimensional noise terms","title":"Add multi-dimensional noise terms","text":"julia> import Distributions\n\njulia> distributions = Distributions.Product([\n Distributions.Binomial(100, 0.5),\n Distributions.Geometric(1 / 20),\n Distributions.Poisson(20),\n ]);\n\njulia> N = 100;\n\njulia> Ω = [rand(distributions) for _ in 1:N];\n\njulia> P = fill(1 / N, N);\n\njulia> model = SDDP.LinearPolicyGraph(\n stages = 3,\n lower_bound = 0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, t\n @variable(subproblem, x, SDDP.State, initial_value = 0.0)\n SDDP.parameterize(subproblem, Ω, P) do ω\n JuMP.fix(x.out, ω[1])\n @stageobjective(subproblem, ω[2] * x.out + ω[3])\n end\n end;\n\njulia> for s in SDDP.simulate(model, 1)[1]\n println(\"ω is: \", s[:noise_term])\n end\nω is: [54, 38, 19]\nω is: [43, 3, 13]\nω is: [43, 4, 17]","category":"page"},{"location":"examples/booking_management/","page":"Booking management","title":"Booking management","text":"EditURL = \"booking_management.jl\"","category":"page"},{"location":"examples/booking_management/#Booking-management","page":"Booking management","title":"Booking management","text":"","category":"section"},{"location":"examples/booking_management/","page":"Booking management","title":"Booking management","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/booking_management/","page":"Booking management","title":"Booking management","text":"This example concerns the acceptance of booking requests for rooms in a hotel in the lead up to a large event.","category":"page"},{"location":"examples/booking_management/","page":"Booking management","title":"Booking management","text":"Each stage, we receive a booking request and can choose to accept or decline it. Once accepted, bookings cannot be terminated.","category":"page"},{"location":"examples/booking_management/","page":"Booking management","title":"Booking management","text":"using SDDP, HiGHS, Test\n\nfunction booking_management_model(num_days, num_rooms, num_requests)\n # maximum revenue that could be accrued.\n max_revenue = (num_rooms + num_requests) * num_days * num_rooms\n # booking_requests is a vector of {0,1} arrays of size\n # (num_days x num_rooms) if the room is requested.\n booking_requests = Array{Int,2}[]\n for room in 1:num_rooms\n for day in 1:num_days\n # note: length_of_stay is 0 indexed to avoid unnecessary +/- 1\n # on the indexing\n for length_of_stay in 0:(num_days-day)\n req = zeros(Int, (num_rooms, num_days))\n req[room:room, day.+(0:length_of_stay)] .= 1\n push!(booking_requests, req)\n end\n end\n end\n\n return model = SDDP.LinearPolicyGraph(;\n stages = num_requests,\n upper_bound = max_revenue,\n sense = :Max,\n optimizer = HiGHS.Optimizer,\n ) do sp, stage\n @variable(\n sp,\n 0 <= vacancy[room = 1:num_rooms, day = 1:num_days] <= 1,\n SDDP.State,\n Bin,\n initial_value = 1\n )\n @variables(\n sp,\n begin\n # Accept request for booking of room for length of time.\n 0 <= accept_request <= 1, Bin\n # Accept a booking for an individual room on an individual day.\n 0 <= room_request_accepted[1:num_rooms, 1:num_days] <= 1, Bin\n # Helper for JuMP.fix\n req[1:num_rooms, 1:num_days]\n end\n )\n for room in 1:num_rooms, day in 1:num_days\n @constraints(\n sp,\n begin\n # Update vacancy if we accept a room request\n vacancy[room, day].out ==\n vacancy[room, day].in - room_request_accepted[room, day]\n # Can't accept a request of a filled room\n room_request_accepted[room, day] <= vacancy[room, day].in\n # Can't accept invididual room request if entire request is declined\n room_request_accepted[room, day] <= accept_request\n # Can't accept request if room not requested\n room_request_accepted[room, day] <= req[room, day]\n # Accept all individual rooms is entire request is accepted\n room_request_accepted[room, day] + (1 - accept_request) >= req[room, day]\n end\n )\n end\n SDDP.parameterize(sp, booking_requests) do request\n return JuMP.fix.(req, request)\n end\n @stageobjective(\n sp,\n sum(\n (room + stage - 1) * room_request_accepted[room, day] for\n room in 1:num_rooms for day in 1:num_days\n )\n )\n end\nend\n\nfunction booking_management(duality_handler)\n m_1_2_5 = booking_management_model(1, 2, 5)\n SDDP.train(m_1_2_5; log_frequency = 5, duality_handler = duality_handler)\n if duality_handler == SDDP.ContinuousConicDuality()\n @test SDDP.calculate_bound(m_1_2_5) >= 7.25 - 1e-4\n else\n @test isapprox(SDDP.calculate_bound(m_1_2_5), 7.25, atol = 0.02)\n end\n\n m_2_2_3 = booking_management_model(2, 2, 3)\n SDDP.train(m_2_2_3; log_frequency = 10, duality_handler = duality_handler)\n if duality_handler == SDDP.ContinuousConicDuality()\n @test SDDP.calculate_bound(m_1_2_5) > 6.13\n else\n @test isapprox(SDDP.calculate_bound(m_2_2_3), 6.13, atol = 0.02)\n end\nend\n\nbooking_management(SDDP.ContinuousConicDuality())","category":"page"},{"location":"examples/booking_management/","page":"Booking management","title":"Booking management","text":"New version of HiGHS stalls booking_management(SDDP.LagrangianDuality())","category":"page"},{"location":"examples/no_strong_duality/","page":"No strong duality","title":"No strong duality","text":"EditURL = \"no_strong_duality.jl\"","category":"page"},{"location":"examples/no_strong_duality/#No-strong-duality","page":"No strong duality","title":"No strong duality","text":"","category":"section"},{"location":"examples/no_strong_duality/","page":"No strong duality","title":"No strong duality","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/no_strong_duality/","page":"No strong duality","title":"No strong duality","text":"This example is interesting, because strong duality doesn't hold for the extensive form (see if you can show why!), but we still converge.","category":"page"},{"location":"examples/no_strong_duality/","page":"No strong duality","title":"No strong duality","text":"using SDDP, HiGHS, Test\n\nfunction no_strong_duality()\n model = SDDP.PolicyGraph(\n SDDP.Graph(\n :root,\n [:node],\n [(:root => :node, 1.0), (:node => :node, 0.5)],\n );\n optimizer = HiGHS.Optimizer,\n lower_bound = 0.0,\n ) do sp, t\n @variable(sp, x, SDDP.State, initial_value = 1.0)\n @stageobjective(sp, x.out)\n @constraint(sp, x.in == x.out)\n end\n SDDP.train(model)\n @test SDDP.calculate_bound(model) ≈ 2.0 atol = 1e-5\n return\nend\n\nno_strong_duality()","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"CurrentModule = SDDP","category":"page"},{"location":"guides/add_integrality/#Integrality","page":"Integrality","title":"Integrality","text":"","category":"section"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"There's nothing special about binary and integer variables in SDDP.jl. Your models may contain a mix of binary, integer, or continuous state and control variables. Use the standard JuMP syntax to add binary or integer variables.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"For example:","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"using SDDP, HiGHS\nmodel = SDDP.LinearPolicyGraph(\n stages = 3,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, 0 <= x <= 100, Int, SDDP.State, initial_value = 0)\n @variable(sp, 0 <= u <= 200, integer = true)\n @variable(sp, v >= 0)\n @constraint(sp, x.out == x.in + u + v - 150)\n @stageobjective(sp, 2u + 6v + x.out)\nend","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"If you want finer control over how SDDP.jl computes subgradients in the backward pass, you can pass an SDDP.AbstractDualityHandler to the duality_handler argument of SDDP.train.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"The duality handlers implemented in SDDP.jl are:","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"SDDP.ContinuousConicDuality\nSDDP.LagrangianDuality\nSDDP.StrengthenedConicDuality\nSDDP.BanditDuality","category":"page"},{"location":"guides/add_integrality/#Convergence","page":"Integrality","title":"Convergence","text":"","category":"section"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"SDDP.jl cannot guarantee that it will find a globally optimal policy when some of the variables are discrete. However, in most cases we find that it can still find an integer feasible policy that performs well in simulation.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"Moreover, when the number of nodes in the graph is large, or there is uncertainty, we are not aware of another algorithm that can claim to find a globally optimal policy.","category":"page"},{"location":"guides/add_integrality/#Does-SDDP.jl-implement-the-SDDiP-algorithm?","page":"Integrality","title":"Does SDDP.jl implement the SDDiP algorithm?","text":"","category":"section"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"Most discussions of SDDiP in the literature confuse two unrelated things.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"First, how to compute dual variables\nSecond, when the algorithm will converge to a globally optimal policy.","category":"page"},{"location":"guides/add_integrality/#Computing-dual-variables","page":"Integrality","title":"Computing dual variables","text":"","category":"section"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"The stochastic dual dynamic programming algorithm requires a subgradient of the objective with respect to the incoming state variable.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"One way to obtain a valid subgradient is to compute an optimal value of the dual variable lambda in the following subproblem:","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"beginaligned\nV_i(x omega) = minlimits_barx x^prime u C_i(barx u omega) + mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi)\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x quad lambda\nendaligned","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"The easiest option is to relax integrality of the discrete variables to form a linear program and then use linear programming duality to obtain the dual. But we could also use Lagrangian duality without needing to relax the integrality constraints.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"To compute the Lagrangian dual lambda, we penalize lambda^top(barx - x) in the objective instead of enforcing the constraint:","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"beginaligned\nmaxlimits_lambdaminlimits_barx x^prime u C_i(barx u omega) + mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi) - lambda^top(barx - x)\n x^prime = T_i(barx u omega) \n u in U_i(barx omega)\nendaligned","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"You can use Lagrangian duality in SDDP.jl by passing SDDP.LagrangianDuality to the duality_handler argument of SDDP.train.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"Compared with linear programming duality, the Lagrangian problem is difficult to solve because it requires the solution of many mixed-integer programs instead of a single linear program. This is one reason why \"SDDiP\" has poor performance.","category":"page"},{"location":"guides/add_integrality/#Convergence-2","page":"Integrality","title":"Convergence","text":"","category":"section"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"The second part to SDDiP is a very tightly scoped claim: if all of the state variables are binary and the algorithm uses Lagrangian duality to compute a subgradient, then it will converge to an optimal policy.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"In many cases, papers claim to \"do SDDiP,\" but they have state variables which are not binary. In these cases, the algorithm is not guaranteed to converge to a globally optimal policy.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"One work-around that has been suggested is to discretize the state variables into a set of binary state variables. However, this leads to a large number of binary state variables, which is another reason why \"SDDiP\" has poor performance.","category":"page"},{"location":"guides/add_integrality/","page":"Integrality","title":"Integrality","text":"In general, we recommend that you introduce integer variables into your model without fear of the consequences, and that you treat the resulting policy as a good heuristic, rather than an attempt to find a globally optimal policy.","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_2stages/","page":"StructDualDynProg: Problem 5.2, 2 stages","title":"StructDualDynProg: Problem 5.2, 2 stages","text":"EditURL = \"StructDualDynProg.jl_prob5.2_2stages.jl\"","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_2stages/#StructDualDynProg:-Problem-5.2,-2-stages","page":"StructDualDynProg: Problem 5.2, 2 stages","title":"StructDualDynProg: Problem 5.2, 2 stages","text":"","category":"section"},{"location":"examples/StructDualDynProg.jl_prob5.2_2stages/","page":"StructDualDynProg: Problem 5.2, 2 stages","title":"StructDualDynProg: Problem 5.2, 2 stages","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_2stages/","page":"StructDualDynProg: Problem 5.2, 2 stages","title":"StructDualDynProg: Problem 5.2, 2 stages","text":"This example comes from StochasticDualDynamicProgramming.jl","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_2stages/","page":"StructDualDynProg: Problem 5.2, 2 stages","title":"StructDualDynProg: Problem 5.2, 2 stages","text":"using SDDP, HiGHS, Test\n\nfunction test_prob52_2stages()\n model = SDDP.LinearPolicyGraph(;\n stages = 2,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, stage\n # ========== Problem data ==========\n n = 4\n m = 3\n i_c = [16, 5, 32, 2]\n C = [25, 80, 6.5, 160]\n T = [8760, 7000, 1500] / 8760\n D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])]\n p2 = [0.9, 0.1]\n # ========== State Variables ==========\n @variable(subproblem, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0)\n # ========== Variables ==========\n @variables(subproblem, begin\n y[1:n, 1:m] >= 0\n v[1:n] >= 0\n penalty >= 0\n rhs_noise[1:m] # Dummy variable for RHS noise term.\n end)\n # ========== Constraints ==========\n @constraints(\n subproblem,\n begin\n [i = 1:n], x[i].out == x[i].in + v[i]\n [i = 1:n], sum(y[i, :]) <= x[i].in\n [j = 1:m], sum(y[:, j]) + penalty >= rhs_noise[j]\n end\n )\n if stage == 2\n # No investment in last stage.\n @constraint(subproblem, sum(v) == 0)\n end\n # ========== Uncertainty ==========\n if stage != 1 # no uncertainty in first stage\n SDDP.parameterize(subproblem, 1:size(D2, 2), p2) do ω\n for j in 1:m\n JuMP.fix(rhs_noise[j], D2[j, ω])\n end\n end\n end\n # ========== Stage objective ==========\n @stageobjective(subproblem, i_c' * v + C' * y * T + 1e6 * penalty)\n return\n end\n SDDP.train(model; log_frequency = 10)\n @test SDDP.calculate_bound(model) ≈ 340315.52 atol = 0.1\n return\nend\n\ntest_prob52_2stages()","category":"page"},{"location":"examples/stochastic_all_blacks/","page":"Stochastic All Blacks","title":"Stochastic All Blacks","text":"EditURL = \"stochastic_all_blacks.jl\"","category":"page"},{"location":"examples/stochastic_all_blacks/#Stochastic-All-Blacks","page":"Stochastic All Blacks","title":"Stochastic All Blacks","text":"","category":"section"},{"location":"examples/stochastic_all_blacks/","page":"Stochastic All Blacks","title":"Stochastic All Blacks","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/stochastic_all_blacks/","page":"Stochastic All Blacks","title":"Stochastic All Blacks","text":"using SDDP, HiGHS, Test\n\nfunction stochastic_all_blacks()\n # Number of time periods\n T = 3\n # Number of seats\n N = 2\n # R_ij = price of seat i at time j\n R = [3 3 6; 3 3 6]\n # Number of noises\n s = 3\n offers = [\n [[1, 1], [0, 0], [1, 1]],\n [[1, 0], [0, 0], [0, 0]],\n [[0, 1], [1, 0], [1, 1]],\n ]\n\n model = SDDP.LinearPolicyGraph(;\n stages = T,\n sense = :Max,\n upper_bound = 100.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, stage\n # Seat remaining?\n @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1)\n # Action: accept offer, or don't accept offer\n # We are allowed to accept some of the seats offered but not others\n @variable(sp, accept_offer[1:N], Bin)\n @variable(sp, offers_made[1:N])\n # Balance on seats\n @constraint(\n sp,\n balance[i in 1:N],\n x[i].in - x[i].out == accept_offer[i]\n )\n @stageobjective(sp, sum(R[i, stage] * accept_offer[i] for i in 1:N))\n SDDP.parameterize(sp, offers[stage]) do o\n return JuMP.fix.(offers_made, o)\n end\n @constraint(sp, accept_offer .<= offers_made)\n end\n\n SDDP.train(model; duality_handler = SDDP.LagrangianDuality())\n @test SDDP.calculate_bound(model) ≈ 8.0\n return\nend\n\nstochastic_all_blacks()","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"EditURL = \"example_milk_producer.jl\"","category":"page"},{"location":"tutorial/example_milk_producer/#Example:-the-milk-producer","page":"Example: the milk producer","title":"Example: the milk producer","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The purpose of this tutorial is to demonstrate how to fit a Markovian policy graph to a univariate stochastic process.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"This tutorial uses the following packages:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"using SDDP\nimport HiGHS\nimport Plots","category":"page"},{"location":"tutorial/example_milk_producer/#Background","page":"Example: the milk producer","title":"Background","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"A company produces milk for sale on a spot market each month. The quantity of milk they produce is uncertain, and so too is the price on the spot market. The company can store unsold milk in a stockpile of dried milk powder.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The spot price is determined by an auction system, and so varies from month to month, but demonstrates serial correlation. In each auction, there is sufficient demand that the milk producer finds a buyer for all their milk, regardless of the quantity they supply. Furthermore, the spot price is independent of the milk producer (they are a small player in the market).","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The spot price is highly volatile, and is the result of a process that is out of the control of the company. To counteract their price risk, the company engages in a forward contracting programme.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The forward contracting programme is a deal for physical milk four months in the future.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The futures price is the current spot price, plus some forward contango (the buyers gain certainty that they will receive the milk in the future).","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"In general, the milk company should forward contract (since they reduce their price risk), however they also have production risk. Therefore, it may be the case that they forward contract a fixed amount, but find that they do not produce enough milk to meet the fixed demand. They are then forced to buy additional milk on the spot market.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The goal of the milk company is to choose the extent to which they forward contract in order to maximise (risk-adjusted) revenues, whilst managing their production risk.","category":"page"},{"location":"tutorial/example_milk_producer/#A-stochastic-process-for-price","page":"Example: the milk producer","title":"A stochastic process for price","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"It is outside the scope of this tutorial, but assume that we have gone away and analysed historical data to fit a stochastic process to the sequence of monthly auction spot prices.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"One plausible model is a multiplicative auto-regressive model of order one, where the white noise term is modeled by a finite distribution of empirical residuals. We can simulate this stochastic process as follows:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"function simulator()\n residuals = [0.0987, 0.199, 0.303, 0.412, 0.530, 0.661, 0.814, 1.010, 1.290]\n residuals = 0.1 * vcat(-residuals, 0.0, residuals)\n scenario = zeros(12)\n y, μ, α = 4.5, 6.0, 0.05\n for t in 1:12\n y = exp((1 - α) * log(y) + α * log(μ) + rand(residuals))\n scenario[t] = clamp(y, 3.0, 9.0)\n end\n return scenario\nend\n\nsimulator()","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"It may be helpful to visualize a number of simulations of the price process:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"plot = Plots.plot(\n [simulator() for _ in 1:500];\n color = \"gray\",\n opacity = 0.2,\n legend = false,\n xlabel = \"Month\",\n ylabel = \"Price [\\$/kg]\",\n xlims = (1, 12),\n ylims = (3, 9),\n)","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The prices gradually revert to the mean of $6/kg, and there is high volatility.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"We can't incorporate this price process directly into SDDP.jl, but we can fit a SDDP.MarkovianGraph directly from the simulator:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"graph = SDDP.MarkovianGraph(simulator; budget = 30, scenarios = 10_000);\nnothing # hide","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"Here budget is the number of nodes in the policy graph, and scenarios is the number of simulations to use when estimating the transition probabilities.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"The graph contains too many nodes to be show, but we can plot it:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"for ((t, price), edges) in graph.nodes\n for ((t′, price′), probability) in edges\n Plots.plot!(\n plot,\n [t, t′],\n [price, price′];\n color = \"red\",\n width = 3 * probability,\n )\n end\nend\n\nplot","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"That looks okay. Try changing budget and scenarios to see how different Markovian policy graphs can be created.","category":"page"},{"location":"tutorial/example_milk_producer/#Model","page":"Example: the milk producer","title":"Model","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"Now that we have a Markovian graph, we can build the model. See if you can work out how we arrived at this formulation by reading the background description. Do all the variables and constraints make sense?","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"model = SDDP.PolicyGraph(\n graph;\n sense = :Max,\n upper_bound = 1e2,\n optimizer = HiGHS.Optimizer,\n) do sp, node\n # Decompose the node into the month (::Int) and spot price (::Float64)\n t, price = node::Tuple{Int,Float64}\n # Transactions on the futures market cost 0.01\n c_transaction = 0.01\n # It costs the company +50% to buy milk on the spot market and deliver to\n # their customers\n c_buy_premium = 1.5\n # Buyer is willing to pay +5% for certainty\n c_contango = 1.05\n # Distribution of production\n Ω_production = range(0.1, 0.2; length = 5)\n c_max_production = 12 * maximum(Ω_production)\n # x_stock: quantity of milk in stock pile\n @variable(sp, 0 <= x_stock, SDDP.State, initial_value = 0)\n # x_forward[i]: quantity of milk for delivery in i months\n @variable(sp, 0 <= x_forward[1:4], SDDP.State, initial_value = 0)\n # u_spot_sell: quantity of milk to sell on spot market\n @variable(sp, 0 <= u_spot_sell <= c_max_production)\n # u_spot_buy: quantity of milk to buy on spot market\n @variable(sp, 0 <= u_spot_buy <= c_max_production)\n # u_spot_buy: quantity of milk to sell on futures market\n c_max_futures = t <= 8 ? c_max_production : 0.0\n @variable(sp, 0 <= u_forward_sell <= c_max_futures)\n # ω_production: production random variable\n @variable(sp, ω_production)\n # Forward contracting constraints:\n @constraint(sp, [i in 1:3], x_forward[i].out == x_forward[i+1].in)\n @constraint(sp, x_forward[4].out == u_forward_sell)\n # Stockpile balance constraint\n @constraint(\n sp,\n x_stock.out ==\n x_stock.in + ω_production + u_spot_buy - x_forward[1].in - u_spot_sell\n )\n # The random variables. `price` comes from the Markov node\n #\n # !!! warning\n # The elements in Ω MUST be a tuple with 1 or 2 values, where the first\n # value is `price` and the second value is the random variable for the\n # current node. If the node is deterministic, use Ω = [(price,)].\n Ω = [(price, p) for p in Ω_production]\n SDDP.parameterize(sp, Ω) do ω\n # Fix the ω_production variable\n fix(ω_production, ω[2])\n @stageobjective(\n sp,\n # Sales on spot market\n ω[1] * (u_spot_sell - c_buy_premium * u_spot_buy) +\n # Sales on futures smarket\n (ω[1] * c_contango - c_transaction) * u_forward_sell\n )\n return\n end\n return\nend","category":"page"},{"location":"tutorial/example_milk_producer/#Training-a-policy","page":"Example: the milk producer","title":"Training a policy","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"Now we have a model, we train a policy. The SDDP.SimulatorSamplingScheme is used in the forward pass. It generates an out-of-sample sequence of prices using simulator and traverses the closest sequence of nodes in the policy graph. When calling SDDP.parameterize for each subproblem, it uses the new out-of-sample price instead of the price associated with the Markov node.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"SDDP.train(\n model;\n time_limit = 20,\n risk_measure = 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25),\n sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),\n)","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"warning: Warning\nWe're intentionally terminating the training early so that the documentation doesn't take too long to build. If you run this example locally, increase the time limit.","category":"page"},{"location":"tutorial/example_milk_producer/#Simulating-the-policy","page":"Example: the milk producer","title":"Simulating the policy","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"When simulating the policy, we can also use the SDDP.SimulatorSamplingScheme.","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"simulations = SDDP.simulate(\n model,\n 200,\n Symbol[:x_stock, :u_forward_sell, :u_spot_sell, :u_spot_buy];\n sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),\n);\nnothing # hide","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"To show how the sampling scheme uses the new out-of-sample price instead of the price associated with the Markov node, compare the index of the Markov state visited in stage 12 of the first simulation:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"simulations[1][12][:node_index]","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"to the realization of the noise (price, ω) passed to SDDP.parameterize:","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"simulations[1][12][:noise_term]","category":"page"},{"location":"tutorial/example_milk_producer/#Visualizing-the-policy","page":"Example: the milk producer","title":"Visualizing the policy","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"Finally, we can plot the policy to gain insight (although note that we terminated the training early, so we should run the re-train the policy for more iterations before making too many judgements).","category":"page"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"plot = Plots.plot(\n SDDP.publication_plot(simulations; title = \"x_stock.out\") do data\n return data[:x_stock].out\n end,\n SDDP.publication_plot(simulations; title = \"u_forward_sell\") do data\n return data[:u_forward_sell]\n end,\n SDDP.publication_plot(simulations; title = \"u_spot_buy\") do data\n return data[:u_spot_buy]\n end,\n SDDP.publication_plot(simulations; title = \"u_spot_sell\") do data\n return data[:u_spot_sell]\n end;\n layout = (2, 2),\n)","category":"page"},{"location":"tutorial/example_milk_producer/#Next-steps","page":"Example: the milk producer","title":"Next steps","text":"","category":"section"},{"location":"tutorial/example_milk_producer/","page":"Example: the milk producer","title":"Example: the milk producer","text":"Train the policy for longer. What do you observe?\nTry creating different Markovian graphs. What happens if you add more nodes?\nTry different risk measures","category":"page"},{"location":"examples/FAST_production_management/","page":"FAST: the production management problem","title":"FAST: the production management problem","text":"EditURL = \"FAST_production_management.jl\"","category":"page"},{"location":"examples/FAST_production_management/#FAST:-the-production-management-problem","page":"FAST: the production management problem","title":"FAST: the production management problem","text":"","category":"section"},{"location":"examples/FAST_production_management/","page":"FAST: the production management problem","title":"FAST: the production management problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/FAST_production_management/","page":"FAST: the production management problem","title":"FAST: the production management problem","text":"An implementation of the Production Management example from FAST","category":"page"},{"location":"examples/FAST_production_management/","page":"FAST: the production management problem","title":"FAST: the production management problem","text":"using SDDP, HiGHS, Test\n\nfunction fast_production_management(; cut_type)\n DEMAND = [2, 10]\n H = 3\n N = 2\n C = [0.2, 0.7]\n S = 2 .+ [0.33, 0.54]\n model = SDDP.LinearPolicyGraph(;\n stages = H,\n lower_bound = -50.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n @variable(sp, x[1:N] >= 0, SDDP.State, initial_value = 0.0)\n @variables(sp, begin\n s[i = 1:N] >= 0\n d\n end)\n @constraints(sp, begin\n [i = 1:N], s[i] <= x[i].in\n sum(s) <= d\n end)\n SDDP.parameterize(sp, t == 1 ? [0] : DEMAND) do ω\n return JuMP.fix(d, ω)\n end\n @stageobjective(sp, sum(C[i] * x[i].out for i in 1:N) - S's)\n end\n SDDP.train(model; cut_type = cut_type, print_level = 2, log_frequency = 5)\n @test SDDP.calculate_bound(model) ≈ -23.96 atol = 1e-2\nend\n\nfast_production_management(; cut_type = SDDP.SINGLE_CUT)\nfast_production_management(; cut_type = SDDP.MULTI_CUT)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"EditURL = \"example_reservoir.jl\"","category":"page"},{"location":"tutorial/example_reservoir/#Example:-deterministic-to-stochastic","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"The purpose of this tutorial is to explain how we can go from a deterministic time-staged optimal control model in JuMP to a multistage stochastic optimization model in SDDP.jl. As a motivating problem, we consider the hydro-thermal problem with a single reservoir.","category":"page"},{"location":"tutorial/example_reservoir/#Packages","page":"Example: deterministic to stochastic","title":"Packages","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"This tutorial requires the following packages:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"using JuMP\nusing SDDP\nimport CSV\nimport DataFrames\nimport HiGHS\nimport Plots","category":"page"},{"location":"tutorial/example_reservoir/#Data","page":"Example: deterministic to stochastic","title":"Data","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"First, we need some data for the problem. For this tutorial, we'll write CSV files to a temporary directory from Julia. If you have an existing file, you could change the filename to point to that instead.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"dir = mktempdir()\nfilename = joinpath(dir, \"example_reservoir.csv\")","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Here is the data","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"csv_data = \"\"\"\nweek,inflow,demand,cost\n1,3,7,10.2\\n2,2,7.1,10.4\\n3,3,7.2,10.6\\n4,2,7.3,10.9\\n5,3,7.4,11.2\\n\n6,2,7.6,11.5\\n7,3,7.8,11.9\\n8,2,8.1,12.3\\n9,3,8.3,12.7\\n10,2,8.6,13.1\\n\n11,3,8.9,13.6\\n12,2,9.2,14\\n13,3,9.5,14.5\\n14,2,9.8,14.9\\n15,3,10.1,15.3\\n\n16,2,10.4,15.8\\n17,3,10.7,16.2\\n18,2,10.9,16.6\\n19,3,11.2,17\\n20,3,11.4,17.4\\n\n21,3,11.6,17.7\\n22,2,11.7,18\\n23,3,11.8,18.3\\n24,2,11.9,18.5\\n25,3,12,18.7\\n\n26,2,12,18.9\\n27,3,12,19\\n28,2,11.9,19.1\\n29,3,11.8,19.2\\n30,2,11.7,19.2\\n\n31,3,11.6,19.2\\n32,2,11.4,19.2\\n33,3,11.2,19.1\\n34,2,10.9,19\\n35,3,10.7,18.9\\n\n36,2,10.4,18.8\\n37,3,10.1,18.6\\n38,2,9.8,18.5\\n39,3,9.5,18.4\\n40,3,9.2,18.2\\n\n41,2,8.9,18.1\\n42,3,8.6,17.9\\n43,2,8.3,17.8\\n44,3,8.1,17.7\\n45,2,7.8,17.6\\n\n46,3,7.6,17.5\\n47,2,7.4,17.5\\n48,3,7.3,17.5\\n49,2,7.2,17.5\\n50,3,7.1,17.6\\n\n51,3,7,17.7\\n52,3,7,17.8\\n\n\"\"\"\nwrite(filename, csv_data);\nnothing #hide","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"And here we read it into a DataFrame:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"data = CSV.read(filename, DataFrames.DataFrame)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"It's easier to visualize the data if we plot it:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Plots.plot(\n Plots.plot(data[!, :inflow]; ylabel = \"Inflow\"),\n Plots.plot(data[!, :demand]; ylabel = \"Demand\"),\n Plots.plot(data[!, :cost]; ylabel = \"Cost\", xlabel = \"Week\");\n layout = (3, 1),\n legend = false,\n)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"The number of weeks will be useful later:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"T = size(data, 1)","category":"page"},{"location":"tutorial/example_reservoir/#Deterministic-JuMP-model","page":"Example: deterministic to stochastic","title":"Deterministic JuMP model","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"To start, we construct a deterministic model in pure JuMP.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Create a JuMP model, using HiGHS as the optimizer:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"model = Model(HiGHS.Optimizer)\nset_silent(model)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"x_storage[t]: the amount of water in the reservoir at the start of stage t:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"reservoir_max = 320.0\n@variable(model, 0 <= x_storage[1:T+1] <= reservoir_max)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"We need an initial condition for x_storage[1]. Fix it to 300 units:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"reservoir_initial = 300\nfix(x_storage[1], reservoir_initial; force = true)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"u_flow[t]: the amount of water to flow through the turbine in stage t:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"flow_max = 12\n@variable(model, 0 <= u_flow[1:T] <= flow_max)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"u_spill[t]: the amount of water to spill from the reservoir in stage t, bypassing the turbine:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"@variable(model, 0 <= u_spill[1:T])","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"u_thermal[t]: the amount of thermal generation in stage t:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"@variable(model, 0 <= u_thermal[1:T])","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"ω_inflow[t]: the amount of inflow to the reservoir in stage t:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"@variable(model, ω_inflow[1:T])","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"For this model, our inflow is fixed, so we fix it to the data we have:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"for t in 1:T\n fix(ω_inflow[t], data[t, :inflow])\nend","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"The water balance constraint says that the water in the reservoir at the start of stage t+1 is the water in the reservoir at the start of stage t, less the amount flowed through the turbine, u_flow[t], less the amount spilled, u_spill[t], plus the amount of inflow, ω_inflow[t], into the reservoir:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"@constraint(\n model,\n [t in 1:T],\n x_storage[t+1] == x_storage[t] - u_flow[t] - u_spill[t] + ω_inflow[t],\n)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"We also need a supply = demand constraint. In practice, the units of this would be in MWh, and there would be a conversion factor between the amount of water flowing through the turbine and the power output. To simplify, we assume that power and water have the same units, so that one \"unit\" of demand is equal to one \"unit\" of the reservoir x_storage[t]:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"@constraint(model, [t in 1:T], u_flow[t] + u_thermal[t] == data[t, :demand])","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Our objective is to minimize the cost of thermal generation:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"@objective(model, Min, sum(data[t, :cost] * u_thermal[t] for t in 1:T))","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Let's optimize and check the solution","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"optimize!(model)\nsolution_summary(model)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"The total cost is:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"objective_value(model)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Here's a plot of demand and generation:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Plots.plot(data[!, :demand]; label = \"Demand\", xlabel = \"Week\")\nPlots.plot!(value.(u_thermal); label = \"Thermal\")\nPlots.plot!(value.(u_flow); label = \"Hydro\")","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"And here's the storage over time:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Plots.plot(value.(x_storage); label = \"Storage\", xlabel = \"Week\")","category":"page"},{"location":"tutorial/example_reservoir/#Deterministic-SDDP-model","page":"Example: deterministic to stochastic","title":"Deterministic SDDP model","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"For the next step, we show how to decompose our JuMP model into SDDP.jl. It should obtain the same solution.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"model = SDDP.LinearPolicyGraph(;\n stages = T,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(\n sp,\n 0 <= x_storage <= reservoir_max,\n SDDP.State,\n initial_value = reservoir_initial,\n )\n @variable(sp, 0 <= u_flow <= flow_max)\n @variable(sp, 0 <= u_thermal)\n @variable(sp, 0 <= u_spill)\n @variable(sp, ω_inflow)\n fix(ω_inflow, data[t, :inflow])\n @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)\n @constraint(sp, u_flow + u_thermal == data[t, :demand])\n @stageobjective(sp, data[t, :cost] * u_thermal)\n return\nend","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Can you see how the JuMP model maps to this syntax? We have created a SDDP.LinearPolicyGraph with T stages, we're minimizing, and we're using HiGHS.Optimizer as the optimizer.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"A few bits might be non-obvious:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"We need to provide a lower bound for the objective function. Since our costs are always positive, a valid lower bound for the total cost is 0.0.\nWe define x_storage as a state variable using SDDP.State. A state variable is any variable that flows through time, and for which we need to know the value of it in stage t-1 to compute the best action in stage t. The state variable x_storage is actually two decision variables, x_storage.in and x_storage.out, which represent x_storage[t] and x_storage[t+1] respectively.\nWe need to use @stageobjective instead of @objective.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Instead of calling JuMP.optimize!, SDDP.jl uses a train method. With our machine learning hat on, you can think of SDDP.jl as training a function for each stage that accepts the current reservoir state as input and returns the optimal actions as output. It is also an iterative algorithm, so we need to specify when it should terminate:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"SDDP.train(model; iteration_limit = 10)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"As a quick sanity check, did we get the same cost as our JuMP model?","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"SDDP.calculate_bound(model)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"That's good. Next, to check the value of the decision variables. This isn't as straight forward as our JuMP model. Instead, we need to simulate the policy, and then extract the values of the decision variables from the results of the simulation.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Since our model is deterministic, we need only 1 replication of the simulation, and we want to record the values of the x_storage, u_flow, and u_thermal variables:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"simulations = SDDP.simulate(\n model,\n 1, # Number of replications\n [:x_storage, :u_flow, :u_thermal],\n);\nnothing #hide","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"The simulations vector is too big to show. But it contains one element for each replication, and each replication contains one dictionary for each stage.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"For example, the data corresponding to the tenth stage in the first replication is:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"simulations[1][10]","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Let's grab the trace of the u_thermal and u_flow variables in the first replication, and then plot them:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"r_sim = [sim[:u_thermal] for sim in simulations[1]]\nu_sim = [sim[:u_flow] for sim in simulations[1]]\n\nPlots.plot(data[!, :demand]; label = \"Demand\", xlabel = \"Week\")\nPlots.plot!(r_sim; label = \"Thermal\")\nPlots.plot!(u_sim; label = \"Hydro\")","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Perfect. That's the same as we got before.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Now let's look at x_storage. This is a little more complicated, because we need to grab the outgoing value of the state variable in each stage:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"x_sim = [sim[:x_storage].out for sim in simulations[1]]\n\nPlots.plot(x_sim; label = \"Storage\", xlabel = \"Week\")","category":"page"},{"location":"tutorial/example_reservoir/#Stochastic-SDDP-model","page":"Example: deterministic to stochastic","title":"Stochastic SDDP model","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Now we add some randomness to our model. In each stage, we assume that the inflow could be: 2 units lower, with 30% probability; the same as before, with 40% probability; or 5 units higher, with 30% probability.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"model = SDDP.LinearPolicyGraph(;\n stages = T,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(\n sp,\n 0 <= x_storage <= reservoir_max,\n SDDP.State,\n initial_value = reservoir_initial,\n )\n @variable(sp, 0 <= u_flow <= flow_max)\n @variable(sp, 0 <= u_thermal)\n @variable(sp, 0 <= u_spill)\n @variable(sp, ω_inflow)\n # <--- This bit is new\n Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3]\n SDDP.parameterize(sp, Ω, P) do ω\n fix(ω_inflow, data[t, :inflow] + ω)\n return\n end\n # --->\n @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)\n @constraint(sp, u_flow + u_thermal == data[t, :demand])\n @stageobjective(sp, data[t, :cost] * u_thermal)\n return\nend","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Can you see the differences?","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Let's train our new model. We need more iterations because of the stochasticity:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"SDDP.train(model; iteration_limit = 100)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Now simulate the policy. This time we do 100 replications because the policy is now stochastic instead of deterministic:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"simulations =\n SDDP.simulate(model, 100, [:x_storage, :u_flow, :u_thermal, :ω_inflow]);\nnothing #hide","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"And let's plot the use of thermal generation in each replication:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"plot = Plots.plot(data[!, :demand]; label = \"Demand\", xlabel = \"Week\")\nfor simulation in simulations\n Plots.plot!(plot, [sim[:u_thermal] for sim in simulation]; label = \"\")\nend\nplot","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Viewing an interpreting static plots like this is difficult, particularly as the number of simulations grows. SDDP.jl includes an interactive SpaghettiPlot that makes things easier:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"plot = SDDP.SpaghettiPlot(simulations)\nSDDP.add_spaghetti(plot; title = \"Storage\") do sim\n return sim[:x_storage].out\nend\nSDDP.add_spaghetti(plot; title = \"Hydro\") do sim\n return sim[:u_flow]\nend\nSDDP.add_spaghetti(plot; title = \"Inflow\") do sim\n return sim[:ω_inflow]\nend\nSDDP.plot(\n plot,\n \"spaghetti_plot.html\";\n # We need this to build the documentation. Set to true if running locally.\n open = false,\n)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"info: Info\nIf you have trouble viewing the plot, you can open it in a new window.","category":"page"},{"location":"tutorial/example_reservoir/#Cyclic-graphs","page":"Example: deterministic to stochastic","title":"Cyclic graphs","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"One major problem with our model is that the reservoir is empty at the end of the time horizon. This is because our model does not consider the cost of future years after the T weeks.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"We can fix this using a cyclic policy graph. One way to construct a graph is with the SDDP.UnicyclicGraph constructor:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"SDDP.UnicyclicGraph(0.7; num_nodes = 2)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"This graph has two nodes, and a loop from node 2 back to node 1 with probability 0.7.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"We can construct a cyclic policy graph as follows:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"graph = SDDP.UnicyclicGraph(0.95; num_nodes = T)\nmodel = SDDP.PolicyGraph(\n graph;\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(\n sp,\n 0 <= x_storage <= reservoir_max,\n SDDP.State,\n initial_value = reservoir_initial,\n )\n @variable(sp, 0 <= u_flow <= flow_max)\n @variable(sp, 0 <= u_thermal)\n @variable(sp, 0 <= u_spill)\n @variable(sp, ω_inflow)\n Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3]\n SDDP.parameterize(sp, Ω, P) do ω\n fix(ω_inflow, data[t, :inflow] + ω)\n return\n end\n @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)\n @constraint(sp, u_flow + u_thermal == data[t, :demand])\n @stageobjective(sp, data[t, :cost] * u_thermal)\n return\nend","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Notice how the only thing that has changed is our graph; the subproblems remain the same.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Let's train a policy:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"SDDP.train(model; iteration_limit = 100)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"When we simulate now, each trajectory will be a different length, because each cycle has a 95% probability of continuing and a 5% probability of stopping.","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"simulations = SDDP.simulate(model, 3);\nlength.(simulations)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"We can simulate a fixed number of cycles by passing a sampling_scheme:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"simulations = SDDP.simulate(\n model,\n 100,\n [:x_storage, :u_flow];\n sampling_scheme = SDDP.InSampleMonteCarlo(;\n max_depth = 5 * T,\n terminate_on_dummy_leaf = false,\n ),\n);\nlength.(simulations)","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Let's visualize the policy:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Plots.plot(\n SDDP.publication_plot(simulations; ylabel = \"Storage\") do sim\n return sim[:x_storage].out\n end,\n SDDP.publication_plot(simulations; ylabel = \"Hydro\") do sim\n return sim[:u_flow]\n end;\n layout = (2, 1),\n)","category":"page"},{"location":"tutorial/example_reservoir/#Next-steps","page":"Example: deterministic to stochastic","title":"Next steps","text":"","category":"section"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Our model is very basic. There are many aspects that we could improve:","category":"page"},{"location":"tutorial/example_reservoir/","page":"Example: deterministic to stochastic","title":"Example: deterministic to stochastic","text":"Can you add a second reservoir to make a river chain?\nCan you modify the problem and data to use proper units, including a conversion between the volume of water flowing through the turbine and the electrical power output?","category":"page"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"CurrentModule = SDDP","category":"page"},{"location":"changelog/#Release-notes","page":"Release notes","title":"Release notes","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"changelog/#v1.10.3-(January-22,-2025)","page":"Release notes","title":"v1.10.3 (January 22, 2025)","text":"","category":"section"},{"location":"changelog/#Other","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Add variable bounds to incoming states in LagrangianDuality (#819)\nDocument how to use the Threaded parallel scheme (#821)","category":"page"},{"location":"changelog/#v1.10.2-(January-13,-2025)","page":"Release notes","title":"v1.10.2 (January 13, 2025)","text":"","category":"section"},{"location":"changelog/#Fixed","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed ConvexCombination with intercept terms (#815)","category":"page"},{"location":"changelog/#Other-2","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Improved test coverage (#810) (#811)\nImproved documentation for risk measures (#813)\nFixed badges for GitHub actions (#816)\nUpdated API reference (#814)","category":"page"},{"location":"changelog/#v1.10.1-(November-28,-2024)","page":"Release notes","title":"v1.10.1 (November 28, 2024)","text":"","category":"section"},{"location":"changelog/#Fixed-2","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed thread safety of RegularizedForwardPass (#806)\nFixed thread safety of AlternativeForwardPass (#808)","category":"page"},{"location":"changelog/#Other-3","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation updates (#801)","category":"page"},{"location":"changelog/#v1.10.0-(November-19,-2024)","page":"Release notes","title":"v1.10.0 (November 19, 2024)","text":"","category":"section"},{"location":"changelog/#Added","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added root_node_risk_measure keyword to train (#804)","category":"page"},{"location":"changelog/#Fixed-3","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed a bug with cut sharing in a graph with zero-probability arcs (#797)","category":"page"},{"location":"changelog/#Other-4","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added a new tutorial Example: inventory management (#795)\nAdded a stochastic lead time example to docs (#800)","category":"page"},{"location":"changelog/#v1.9.0-(October-17,-2024)","page":"Release notes","title":"v1.9.0 (October 17, 2024)","text":"","category":"section"},{"location":"changelog/#Added-2","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added write_only_selected_cuts and cut_selection keyword arguments to write_cuts_to_file and read_cuts_from_file to skip potentially expensive operations (#781) (#784)\nAdded set_numerical_difficulty_callback to modify the subproblem on numerical difficulty (#790)","category":"page"},{"location":"changelog/#Fixed-4","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed the tests to skip threading tests if running in serial (#770)\nFixed BanditDuality to handle the case where the standard deviation is NaN (#779)\nFixed an error when lagged state variables are encountered in MSPFormat (#786)\nFixed publication_plot with replications of different lengths (#788)\nFixed CTRL+C interrupting the code at unsafe points (#789)","category":"page"},{"location":"changelog/#Other-5","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#771) (#772)\nUpdated printing because of changes in JuMP (#773)","category":"page"},{"location":"changelog/#v1.8.1-(August-5,-2024)","page":"Release notes","title":"v1.8.1 (August 5, 2024)","text":"","category":"section"},{"location":"changelog/#Fixed-5","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed various issues with SDDP.Threaded() (#761)\nFixed a deprecation warning for sorting a dictionary (#763)","category":"page"},{"location":"changelog/#Other-6","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated copyright notices (#762)\nUpdated .JuliaFormatter.toml (#764)","category":"page"},{"location":"changelog/#v1.8.0-(July-24,-2024)","page":"Release notes","title":"v1.8.0 (July 24, 2024)","text":"","category":"section"},{"location":"changelog/#Added-3","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.Threaded(), which is an experimental parallel scheme that supports solving problems using multiple threads. Some parts of SDDP.jl may not be thread-safe, and this can cause incorrect results, segfaults, or other errors. Please use with care and report any issues by opening a GitHub issue. (#758)","category":"page"},{"location":"changelog/#Other-7","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements and fixes (#747) (#759)","category":"page"},{"location":"changelog/#v1.7.0-(June-4,-2024)","page":"Release notes","title":"v1.7.0 (June 4, 2024)","text":"","category":"section"},{"location":"changelog/#Added-4","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added sample_backward_noise_terms_with_state for creating backward pass sampling schemes that depend on the current primal state. (#742) (Thanks @arthur-brigatto)","category":"page"},{"location":"changelog/#Fixed-6","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed error message when publication_plot has non-finite data (#738)","category":"page"},{"location":"changelog/#Other-8","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated the logo constructor (#730)","category":"page"},{"location":"changelog/#v1.6.7-(February-1,-2024)","page":"Release notes","title":"v1.6.7 (February 1, 2024)","text":"","category":"section"},{"location":"changelog/#Fixed-7","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed non-constant state dimension in the MSPFormat reader (#695)\nFixed SimulatorSamplingScheme for deterministic nodes (#710)\nFixed line search in BFGS (#711)\nFixed handling of NEARLY_FEASIBLE_POINT status (#726)","category":"page"},{"location":"changelog/#Other-9","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#692) (#694) (#706) (#716) (#727)\nUpdated to StochOptFormat v1.0 (#705)\nAdded an experimental OuterApproximation algorithm (#709)\nUpdated .gitignore (#717)\nAdded code for MDP paper (#720) (#721)\nAdded Google analytics (#723)","category":"page"},{"location":"changelog/#v1.6.6-(September-29,-2023)","page":"Release notes","title":"v1.6.6 (September 29, 2023)","text":"","category":"section"},{"location":"changelog/#Other-10","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated Example: two-stage newsvendor tutorial (#689)\nAdded a warning for people using SDDP.Statistical (#687)","category":"page"},{"location":"changelog/#v1.6.5-(September-25,-2023)","page":"Release notes","title":"v1.6.5 (September 25, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-8","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed duplicate nodes in MarkovianGraph (#681)","category":"page"},{"location":"changelog/#Other-11","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated tutorials (#677) (#678) (#682) (#683)\nFixed documentation preview (#679)","category":"page"},{"location":"changelog/#v1.6.4-(September-23,-2023)","page":"Release notes","title":"v1.6.4 (September 23, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-9","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed error for invalid log_frequency values (#665)\nFixed objective sense in deterministic_equivalent (#673)","category":"page"},{"location":"changelog/#Other-12","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation updates (#658) (#666) (#671)\nSwitch to GitHub action for deploying docs (#668) (#670)\nUpdate to Documenter@1 (#669)","category":"page"},{"location":"changelog/#v1.6.3-(September-8,-2023)","page":"Release notes","title":"v1.6.3 (September 8, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-10","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed default stopping rule with iteration_limit or time_limit set (#662)","category":"page"},{"location":"changelog/#Other-13","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Various documentation improvements (#651) (#657) (#659) (#660)","category":"page"},{"location":"changelog/#v1.6.2-(August-24,-2023)","page":"Release notes","title":"v1.6.2 (August 24, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-11","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"MSPFormat now detect and exploit stagewise independent lattices (#653)\nFixed set_optimizer for models read from file (#654)","category":"page"},{"location":"changelog/#Other-14","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed typo in pglib_opf.jl (#647)\nFixed documentation build and added color (#652)","category":"page"},{"location":"changelog/#v1.6.1-(July-20,-2023)","page":"Release notes","title":"v1.6.1 (July 20, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-12","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed bugs in MSPFormat reader (#638) (#639)","category":"page"},{"location":"changelog/#Other-15","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Clarified OutOfSampleMonteCarlo docstring (#643)","category":"page"},{"location":"changelog/#v1.6.0-(July-3,-2023)","page":"Release notes","title":"v1.6.0 (July 3, 2023)","text":"","category":"section"},{"location":"changelog/#Added-5","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added RegularizedForwardPass (#624)\nAdded FirstStageStoppingRule (#634)","category":"page"},{"location":"changelog/#Other-16","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Removed an unbound type parameter (#632)\nFixed typo in docstring (#633)\nAdded Here-and-now and hazard-decision tutorial (#635)","category":"page"},{"location":"changelog/#v1.5.1-(June-30,-2023)","page":"Release notes","title":"v1.5.1 (June 30, 2023)","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"This release contains a number of minor code changes, but it has a large impact on the content that is printed to screen. In particular, we now log periodically, instead of each iteration, and a \"good\" stopping rule is used as the default if none are specified. Try using SDDP.train(model) to see the difference.","category":"page"},{"location":"changelog/#Other-17","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed various typos in the documentation (#617)\nFixed printing test after changes in JuMP (#618)\nSet SimulationStoppingRule as the default stopping rule (#619)\nChanged the default logging frequency. Pass log_every_seconds = 0.0 to train to revert to the old behavior. (#620)\nAdded example usage with Distributions.jl (@slwu89) (#622)\nRemoved the numerical issue @warn (#627)\nImproved the quality of docstrings (#630)","category":"page"},{"location":"changelog/#v1.5.0-(May-14,-2023)","page":"Release notes","title":"v1.5.0 (May 14, 2023)","text":"","category":"section"},{"location":"changelog/#Added-6","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added the ability to use a different model for the forward pass. This is a novel feature that lets you train better policies when the model is non-convex or does not have a well-defined dual. See the Alternative forward models tutorial in which we train convex and non-convex formulations of the optimal power flow problem. (#611)","category":"page"},{"location":"changelog/#Other-18","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated missing changelog entries (#608)\nRemoved global variables (#610)\nConverted the Options struct to keyword arguments. This struct was a private implementation detail, but the change is breaking if you developed an extension to SDDP that touched these internals. (#612)\nFixed some typos (#613)","category":"page"},{"location":"changelog/#v1.4.0-(May-8,-2023)","page":"Release notes","title":"v1.4.0 (May 8, 2023)","text":"","category":"section"},{"location":"changelog/#Added-7","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.SimulationStoppingRule (#598)\nAdded sampling_scheme argument to SDDP.write_to_file (#607)","category":"page"},{"location":"changelog/#Fixed-13","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed parsing of some MSPFormat files (#602) (#604)\nFixed printing in header (#605)","category":"page"},{"location":"changelog/#v1.3.0-(May-3,-2023)","page":"Release notes","title":"v1.3.0 (May 3, 2023)","text":"","category":"section"},{"location":"changelog/#Added-8","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added experimental support for SDDP.MSPFormat.read_from_file (#593)","category":"page"},{"location":"changelog/#Other-19","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated to StochOptFormat v0.3 (#600)","category":"page"},{"location":"changelog/#v1.2.1-(May-1,-2023)","page":"Release notes","title":"v1.2.1 (May 1, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-14","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed log_every_seconds (#597)","category":"page"},{"location":"changelog/#v1.2.0-(May-1,-2023)","page":"Release notes","title":"v1.2.0 (May 1, 2023)","text":"","category":"section"},{"location":"changelog/#Added-9","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.SimulatorSamplingScheme (#594)\nAdded log_every_seconds argument to SDDP.train (#595)","category":"page"},{"location":"changelog/#Other-20","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Tweaked how the log is printed (#588)\nUpdated to StochOptFormat v0.2 (#592)","category":"page"},{"location":"changelog/#v1.1.4-(April-10,-2023)","page":"Release notes","title":"v1.1.4 (April 10, 2023)","text":"","category":"section"},{"location":"changelog/#Fixed-15","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Logs are now flushed every iteration (#584)","category":"page"},{"location":"changelog/#Other-21","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added docstrings to various functions (#581)\nMinor documentation updates (#580)\nClarified integrality documentation (#582)\nUpdated the README (#585)\nNumber of numerical issues is now printed to the log (#586)","category":"page"},{"location":"changelog/#v1.1.3-(April-2,-2023)","page":"Release notes","title":"v1.1.3 (April 2, 2023)","text":"","category":"section"},{"location":"changelog/#Other-22","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed typo in Example: deterministic to stochastic tutorial (#578)\nFixed typo in documentation of SDDP.simulate (#577)","category":"page"},{"location":"changelog/#v1.1.2-(March-18,-2023)","page":"Release notes","title":"v1.1.2 (March 18, 2023)","text":"","category":"section"},{"location":"changelog/#Other-23","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added Example: deterministic to stochastic tutorial (#572)","category":"page"},{"location":"changelog/#v1.1.1-(March-16,-2023)","page":"Release notes","title":"v1.1.1 (March 16, 2023)","text":"","category":"section"},{"location":"changelog/#Other-24","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed email in Project.toml\nAdded notebook to documentation tutorials (#571)","category":"page"},{"location":"changelog/#v1.1.0-(January-12,-2023)","page":"Release notes","title":"v1.1.0 (January 12, 2023)","text":"","category":"section"},{"location":"changelog/#Added-10","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added the node_name_parser argument to SDDP.write_cuts_to_file and added the option to skip nodes in SDDP.read_cuts_from_file (#565)","category":"page"},{"location":"changelog/#v1.0.0-(January-3,-2023)","page":"Release notes","title":"v1.0.0 (January 3, 2023)","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Although we're bumping MAJOR version, this is a non-breaking release. Going forward:","category":"page"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"New features will bump the MINOR version\nBug fixes, maintenance, and documentation updates will bump the PATCH version\nWe will support only the Long Term Support (currently v1.6.7) and the latest patch (currently v1.8.4) releases of Julia. Updates to the LTS version will bump the MINOR version\nUpdates to the compat bounds of package dependencies will bump the PATCH version.","category":"page"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"We do not intend any breaking changes to the public API, which would require a new MAJOR release. The public API is everything defined in the documentation. Anything not in the documentation is considered private and may change in any PATCH release.","category":"page"},{"location":"changelog/#Added-11","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added num_nodes argument to SDDP.UnicyclicGraph (#562)\nAdded support for passing an optimizer to SDDP.Asynchronous (#545)","category":"page"},{"location":"changelog/#Other-25","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated Plotting tools to use live plots (#563)\nAdded vale as a linter (#565)\nImproved documentation for initializing a parallel scheme (#566)","category":"page"},{"location":"changelog/#v0.4.9-(January-3,-2023)","page":"Release notes","title":"v0.4.9 (January 3, 2023)","text":"","category":"section"},{"location":"changelog/#Added-12","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.UnicyclicGraph (#556)","category":"page"},{"location":"changelog/#Other-26","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added tutorial on Markov Decision Processes (#556)\nAdded two-stage newsvendor tutorial (#557)\nRefactored the layout of the documentation (#554) (#555)\nUpdated copyright to 2023 (#558)\nFixed errors in the documentation (#561)","category":"page"},{"location":"changelog/#v0.4.8-(December-19,-2022)","page":"Release notes","title":"v0.4.8 (December 19, 2022)","text":"","category":"section"},{"location":"changelog/#Added-13","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added terminate_on_cycle option to SDDP.Historical (#549)\nAdded include_last_node option to SDDP.DefaultForwardPass (#547)","category":"page"},{"location":"changelog/#Fixed-16","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Reverted then fixed (#531) because it failed to account for problems with integer variables (#546) (#551)","category":"page"},{"location":"changelog/#v0.4.7-(December-17,-2022)","page":"Release notes","title":"v0.4.7 (December 17, 2022)","text":"","category":"section"},{"location":"changelog/#Added-14","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added initial_node support to InSampleMonteCarlo and OutOfSampleMonteCarlo (#535)","category":"page"},{"location":"changelog/#Fixed-17","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Rethrow InterruptException when solver is interrupted (#534)\nFixed numerical recovery when we need dual solutions (#531) (Thanks @bfpc)\nFixed re-using the dashboard = true option between solves (#538)\nFixed bug when no @stageobjective is set (now defaults to 0.0) (#539)\nFixed errors thrown when invalid inputs are provided to add_objective_state (#540)","category":"page"},{"location":"changelog/#Other-27","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Drop support for Julia versions prior to 1.6 (#533)\nUpdated versions of dependencies (#522) (#533)\nSwitched to HiGHS in the documentation and tests (#533)\nAdded license headers (#519)\nFixed link in air conditioning example (#521) (Thanks @conema)\nClarified variable naming in deterministic equivalent (#525) (Thanks @lucasprocessi)\nAdded this change log (#536)\nCuts are now written to model.cuts.json when numerical instability is discovered. This can aid debugging because it allows to you reload the cuts as of the iteration that caused the numerical issue (#537)","category":"page"},{"location":"changelog/#v0.4.6-(March-25,-2022)","page":"Release notes","title":"v0.4.6 (March 25, 2022)","text":"","category":"section"},{"location":"changelog/#Other-28","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Updated to JuMP v1.0 (#517)","category":"page"},{"location":"changelog/#v0.4.5-(March-9,-2022)","page":"Release notes","title":"v0.4.5 (March 9, 2022)","text":"","category":"section"},{"location":"changelog/#Fixed-18","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed issue with set_silent in a subproblem (#510)","category":"page"},{"location":"changelog/#Other-29","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed many typos (#500) (#501) (#506) (#511) (Thanks @bfpc)\nUpdate to JuMP v0.23 (#514)\nAdded auto-regressive tutorial (#507)","category":"page"},{"location":"changelog/#v0.4.4-(December-11,-2021)","page":"Release notes","title":"v0.4.4 (December 11, 2021)","text":"","category":"section"},{"location":"changelog/#Added-15","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added BanditDuality (#471)\nAdded benchmark scripts (#475) (#476) (#490)\nwrite_cuts_to_file now saves visited states (#468)","category":"page"},{"location":"changelog/#Fixed-19","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed BoundStalling in a deterministic policy (#470) (#474)\nFixed magnitude warning with zero coefficients (#483)","category":"page"},{"location":"changelog/#Other-30","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Improvements to LagrangianDuality (#481) (#482) (#487)\nImprovements to StrengthenedConicDuality (#486)\nSwitch to functional form for the tests (#478)\nFixed typos (#472) (Thanks @vfdev-5)\nUpdate to JuMP v0.22 (#498)","category":"page"},{"location":"changelog/#v0.4.3-(August-31,-2021)","page":"Release notes","title":"v0.4.3 (August 31, 2021)","text":"","category":"section"},{"location":"changelog/#Added-16","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added biobjective solver (#462)\nAdded forward_pass_callback (#466)","category":"page"},{"location":"changelog/#Other-31","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Update tutorials and documentation (#459) (#465)\nOrganize how paper materials are stored (#464)","category":"page"},{"location":"changelog/#v0.4.2-(August-24,-2021)","page":"Release notes","title":"v0.4.2 (August 24, 2021)","text":"","category":"section"},{"location":"changelog/#Fixed-20","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed a bug in Lagrangian duality (#457)","category":"page"},{"location":"changelog/#v0.4.1-(August-23,-2021)","page":"Release notes","title":"v0.4.1 (August 23, 2021)","text":"","category":"section"},{"location":"changelog/#Other-32","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Minor changes to our implementation of LagrangianDuality (#454) (#455)","category":"page"},{"location":"changelog/#v0.4.0-(August-17,-2021)","page":"Release notes","title":"v0.4.0 (August 17, 2021)","text":"","category":"section"},{"location":"changelog/#Breaking","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"A large refactoring for how we handle stochastic integer programs. This added support for things like SDDP.ContinuousConicDuality and SDDP.LagrangianDuality. It was breaking because we removed the integrality_handler argument to PolicyGraph. (#449) (#453)","category":"page"},{"location":"changelog/#Other-33","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#447) (#448) (#450)","category":"page"},{"location":"changelog/#v0.3.17-(July-6,-2021)","page":"Release notes","title":"v0.3.17 (July 6, 2021)","text":"","category":"section"},{"location":"changelog/#Added-17","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.PSRSamplingScheme (#426)","category":"page"},{"location":"changelog/#Other-34","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Display more model attributes (#438)\nDocumentation improvements (#433) (#437) (#439)","category":"page"},{"location":"changelog/#v0.3.16-(June-17,-2021)","page":"Release notes","title":"v0.3.16 (June 17, 2021)","text":"","category":"section"},{"location":"changelog/#Added-18","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.RiskAdjustedForwardPass (#413)\nAllow SDDP.Historical to sample sequentially (#420)","category":"page"},{"location":"changelog/#Other-35","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Update risk measure docstrings (#418)","category":"page"},{"location":"changelog/#v0.3.15-(June-1,-2021)","page":"Release notes","title":"v0.3.15 (June 1, 2021)","text":"","category":"section"},{"location":"changelog/#Added-19","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added SDDP.StoppingChain","category":"page"},{"location":"changelog/#Fixed-21","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed scoping bug in SDDP.@stageobjective (#407)\nFixed a bug when the initial point is infeasible (#411)\nSet subproblems to silent by default (#409)","category":"page"},{"location":"changelog/#Other-36","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Add JuliaFormatter (#412)\nDocumentation improvements (#406) (#408)","category":"page"},{"location":"changelog/#v0.3.14-(March-30,-2021)","page":"Release notes","title":"v0.3.14 (March 30, 2021)","text":"","category":"section"},{"location":"changelog/#Fixed-22","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed O(N^2) behavior in get_same_children (#393)","category":"page"},{"location":"changelog/#v0.3.13-(March-27,-2021)","page":"Release notes","title":"v0.3.13 (March 27, 2021)","text":"","category":"section"},{"location":"changelog/#Fixed-23","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed bug in print.jl\nFixed compat of Reexport (#388)","category":"page"},{"location":"changelog/#v0.3.12-(March-22,-2021)","page":"Release notes","title":"v0.3.12 (March 22, 2021)","text":"","category":"section"},{"location":"changelog/#Added-20","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added problem statistics to header (#385) (#386)","category":"page"},{"location":"changelog/#Fixed-24","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed subtypes in visualization (#384)","category":"page"},{"location":"changelog/#v0.3.11-(March-22,-2021)","page":"Release notes","title":"v0.3.11 (March 22, 2021)","text":"","category":"section"},{"location":"changelog/#Fixed-25","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed constructor in direct mode (#383)","category":"page"},{"location":"changelog/#Other-37","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fix documentation (#379)","category":"page"},{"location":"changelog/#v0.3.10-(February-23,-2021)","page":"Release notes","title":"v0.3.10 (February 23, 2021)","text":"","category":"section"},{"location":"changelog/#Fixed-26","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed seriescolor in publication plot (#376)","category":"page"},{"location":"changelog/#v0.3.9-(February-20,-2021)","page":"Release notes","title":"v0.3.9 (February 20, 2021)","text":"","category":"section"},{"location":"changelog/#Added-21","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Add option to simulate with different incoming state (#372)\nAdded warning for cuts with high dynamic range (#373)","category":"page"},{"location":"changelog/#Fixed-27","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed seriesalpha in publication plot (#375)","category":"page"},{"location":"changelog/#v0.3.8-(January-19,-2021)","page":"Release notes","title":"v0.3.8 (January 19, 2021)","text":"","category":"section"},{"location":"changelog/#Other-38","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#367) (#369) (#370)","category":"page"},{"location":"changelog/#v0.3.7-(January-8,-2021)","page":"Release notes","title":"v0.3.7 (January 8, 2021)","text":"","category":"section"},{"location":"changelog/#Other-39","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#362) (#363) (#365) (#366)\nBump copyright (#364)","category":"page"},{"location":"changelog/#v0.3.6-(December-17,-2020)","page":"Release notes","title":"v0.3.6 (December 17, 2020)","text":"","category":"section"},{"location":"changelog/#Other-40","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fix typos (#358)\nCollapse navigation bar in docs (#359)\nUpdate TagBot.yml (#361)","category":"page"},{"location":"changelog/#v0.3.5-(November-18,-2020)","page":"Release notes","title":"v0.3.5 (November 18, 2020)","text":"","category":"section"},{"location":"changelog/#Other-41","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Update citations (#348)\nSwitch to GitHub actions (#355)","category":"page"},{"location":"changelog/#v0.3.4-(August-25,-2020)","page":"Release notes","title":"v0.3.4 (August 25, 2020)","text":"","category":"section"},{"location":"changelog/#Added-22","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added non-uniform distributionally robust risk measure (#328)\nAdded numerical recovery functions (#330)\nAdded experimental StochOptFormat (#332) (#336) (#337) (#341) (#343) (#344)\nAdded entropic risk measure (#347)","category":"page"},{"location":"changelog/#Other-42","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#327) (#333) (#339) (#340)","category":"page"},{"location":"changelog/#v0.3.3-(June-19,-2020)","page":"Release notes","title":"v0.3.3 (June 19, 2020)","text":"","category":"section"},{"location":"changelog/#Added-23","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added asynchronous support for price and belief states (#325)\nAdded ForwardPass plug-in system (#320)","category":"page"},{"location":"changelog/#Fixed-28","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fix check for probabilities in Markovian graph (#322)","category":"page"},{"location":"changelog/#v0.3.2-(April-6,-2020)","page":"Release notes","title":"v0.3.2 (April 6, 2020)","text":"","category":"section"},{"location":"changelog/#Added-24","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added log_frequency argument to SDDP.train (#307)","category":"page"},{"location":"changelog/#Other-43","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Improve error message in deterministic equivalent (#312)\nUpdate to RecipesBase 1.0 (#313)","category":"page"},{"location":"changelog/#v0.3.1-(February-26,-2020)","page":"Release notes","title":"v0.3.1 (February 26, 2020)","text":"","category":"section"},{"location":"changelog/#Fixed-29","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed filename in integrality_handlers.jl (#304)","category":"page"},{"location":"changelog/#v0.3.0-(February-20,-2020)","page":"Release notes","title":"v0.3.0 (February 20, 2020)","text":"","category":"section"},{"location":"changelog/#Breaking-2","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Breaking changes to update to JuMP v0.21 (#300).","category":"page"},{"location":"changelog/#v0.2.4-(February-7,-2020)","page":"Release notes","title":"v0.2.4 (February 7, 2020)","text":"","category":"section"},{"location":"changelog/#Added-25","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added a counter for the number of total subproblem solves (#301)","category":"page"},{"location":"changelog/#Other-44","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Update formatter (#298)\nAdded tests (#299)","category":"page"},{"location":"changelog/#v0.2.3-(January-24,-2020)","page":"Release notes","title":"v0.2.3 (January 24, 2020)","text":"","category":"section"},{"location":"changelog/#Added-26","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added support for convex risk measures (#294)","category":"page"},{"location":"changelog/#Fixed-30","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed bug when subproblem is infeasible (#296)\nFixed bug in deterministic equivalent (#297)","category":"page"},{"location":"changelog/#Other-45","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added example from IJOC paper (#293)","category":"page"},{"location":"changelog/#v0.2.2-(January-10,-2020)","page":"Release notes","title":"v0.2.2 (January 10, 2020)","text":"","category":"section"},{"location":"changelog/#Fixed-31","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Fixed flakey time limit in tests (#291)","category":"page"},{"location":"changelog/#Other-46","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Removed MathOptFormat.jl (#289)\nUpdate copyright (#290)","category":"page"},{"location":"changelog/#v0.2.1-(December-19,-2019)","page":"Release notes","title":"v0.2.1 (December 19, 2019)","text":"","category":"section"},{"location":"changelog/#Added-27","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added support for approximating a Markov lattice (#282) (#285)\nAdd tools for visualizing the value function (#272) (#286)\nWrite .mof.json files on error (#284)","category":"page"},{"location":"changelog/#Other-47","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Improve documentation (#281) (#283)\nUpdate tests for Julia 1.3 (#287)","category":"page"},{"location":"changelog/#v0.2.0-(December-16,-2019)","page":"Release notes","title":"v0.2.0 (December 16, 2019)","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"This version added the asynchronous parallel implementation with a few minor breaking changes in how we iterated internally. It didn't break basic user-facing models, only implementations that implemented some of the extension features. It probably could have been a v1.1 release.","category":"page"},{"location":"changelog/#Added-28","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Added asynchronous parallel implementation (#277)\nAdded roll-out algorithm for cyclic graphs (#279)","category":"page"},{"location":"changelog/#Other-48","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Improved error messages in PolicyGraph (#271)\nAdded JuliaFormatter (#273) (#276)\nFixed compat bounds (#274) (#278)\nAdded documentation for simulating non-standard graphs (#280)","category":"page"},{"location":"changelog/#v0.1.0-(October-17,-2019)","page":"Release notes","title":"v0.1.0 (October 17, 2019)","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"A complete rewrite of SDDP.jl based on the policy graph framework. This was essentially a new package. It has minimal code in common with the previous implementation.","category":"page"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Development started on September 28, 2018 in Kokako.jl, and the code was merged into SDDP.jl on March 14, 2019.","category":"page"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"The pull request SDDP.jl#180 lists the 29 issues that the rewrite closed.","category":"page"},{"location":"changelog/#v0.0.1-(April-18,-2018)","page":"Release notes","title":"v0.0.1 (April 18, 2018)","text":"","category":"section"},{"location":"changelog/","page":"Release notes","title":"Release notes","text":"Initial release. Development had been underway since January 22, 2016 in the StochDualDynamicProgram.jl repository. The last development commit there was April 5, 2017. Work then continued in this repository for a year before the first tagged release.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"EditURL = \"example_newsvendor.jl\"","category":"page"},{"location":"tutorial/example_newsvendor/#Example:-two-stage-newsvendor","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"The purpose of this tutorial is to demonstrate how to model and solve a two-stage stochastic program.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"It is based on the Two stage stochastic programs tutorial in JuMP.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"This tutorial uses the following packages","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"using JuMP\nusing SDDP\nimport Distributions\nimport ForwardDiff\nimport HiGHS\nimport Plots\nimport StatsPlots\nimport Statistics","category":"page"},{"location":"tutorial/example_newsvendor/#Background","page":"Example: two-stage newsvendor","title":"Background","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"The data for this problem is:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"D = Distributions.TriangularDist(150.0, 250.0, 200.0)\nN = 100\nd = sort!(rand(D, N));\nΩ = 1:N\nP = fill(1 / N, N);\nStatsPlots.histogram(d; bins = 20, label = \"\", xlabel = \"Demand\")","category":"page"},{"location":"tutorial/example_newsvendor/#Kelley's-cutting-plane-algorithm","page":"Example: two-stage newsvendor","title":"Kelley's cutting plane algorithm","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Kelley's cutting plane algorithm is an iterative method for maximizing concave functions. Given a concave function f(x), Kelley's constructs an outer-approximation of the function at the minimum by a set of first-order Taylor series approximations (called cuts) constructed at a set of points k = 1ldotsK:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"beginaligned\nf^K = maxlimits_theta in mathbbR x in mathbbR^N theta\n theta le f(x_k) + nabla f(x_k)^top (x - x_k)quad k=1ldotsK\n theta le M\nendaligned","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"where M is a sufficiently large number that is an upper bound for f over the domain of x.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Kelley's cutting plane algorithm is a structured way of choosing points x_k to visit, so that as more cuts are added:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"lim_K rightarrow infty f^K = maxlimits_x in mathbbR^N f(x)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"However, before we introduce the algorithm, we need to introduce some bounds.","category":"page"},{"location":"tutorial/example_newsvendor/#Bounds","page":"Example: two-stage newsvendor","title":"Bounds","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"By convexity, f(x) le f^K for all x. Thus, if x^* is a maximizer of f, then at any point in time we can construct an upper bound for f(x^*) by solving f^K.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Moreover, we can use the primal solutions x_k^* returned by solving f^k to evaluate f(x_k^*) to generate a lower bound.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Therefore, maxlimits_k=1ldotsK f(x_k^*) le f(x^*) le f^K.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"When the lower bound is sufficiently close to the upper bound, we can terminate the algorithm and declare that we have found an solution that is close to optimal.","category":"page"},{"location":"tutorial/example_newsvendor/#Implementation","page":"Example: two-stage newsvendor","title":"Implementation","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Here is pseudo-code fo the Kelley algorithm:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Take as input a convex function f(x) and a iteration limit K_max. Set K = 1, and initialize f^K-1. Set lb = -infty and ub = infty.\nSolve f^K-1 to obtain a candidate solution x_K.\nUpdate ub = f^K-1 and lb = maxlb f(x_K).\nAdd a cut theta ge f(x_K) + nabla fleft(x_Kright)^top (x - x_K) to form f^K.\nIncrement K.\nIf K K_max or ub - lb epsilon, STOP, otherwise, go to step 2.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"And here's a complete implementation:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"function kelleys_cutting_plane(\n # The function to be minimized.\n f::Function,\n # The gradient of `f`. By default, we use automatic differentiation to\n # compute the gradient of f so the user doesn't have to!\n ∇f::Function = x -> ForwardDiff.gradient(f, x);\n # The number of arguments to `f`.\n input_dimension::Int,\n # An upper bound for the function `f` over its domain.\n upper_bound::Float64,\n # The number of iterations to run Kelley's algorithm for before stopping.\n iteration_limit::Int,\n # The absolute tolerance ϵ to use for convergence.\n tolerance::Float64 = 1e-6,\n)\n # Step (1):\n K = 1\n model = JuMP.Model(HiGHS.Optimizer)\n JuMP.set_silent(model)\n JuMP.@variable(model, θ <= upper_bound)\n JuMP.@variable(model, x[1:input_dimension])\n JuMP.@objective(model, Max, θ)\n x_k = fill(NaN, input_dimension)\n lower_bound, upper_bound = -Inf, Inf\n while true\n # Step (2):\n JuMP.optimize!(model)\n x_k .= JuMP.value.(x)\n # Step (3):\n upper_bound = JuMP.objective_value(model)\n lower_bound = min(upper_bound, f(x_k))\n println(\"K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)\")\n # Step (4):\n JuMP.@constraint(model, θ <= f(x_k) + ∇f(x_k)' * (x .- x_k))\n # Step (5):\n K = K + 1\n # Step (6):\n if K > iteration_limit\n println(\"-- Termination status: iteration limit --\")\n break\n elseif abs(upper_bound - lower_bound) < tolerance\n println(\"-- Termination status: converged --\")\n break\n end\n end\n println(\"Found solution: x_K = \", x_k)\n return\nend","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Let's run our algorithm to see what happens:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"kelleys_cutting_plane(;\n input_dimension = 2,\n upper_bound = 10.0,\n iteration_limit = 20,\n) do x\n return -(x[1] - 1)^2 + -(x[2] + 2)^2 + 1.0\nend","category":"page"},{"location":"tutorial/example_newsvendor/#L-Shaped-theory","page":"Example: two-stage newsvendor","title":"L-Shaped theory","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"The L-Shaped method is a way of solving two-stage stochastic programs by Benders' decomposition. It takes the problem:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"beginaligned\nV = maxlimits_xy_omega -2x + mathbbE_omega5y_omega - 01(x - y_omega) \n y_omega le x quad forall omega in Omega \n 0 le y_omega le d_omega quad forall omega in Omega \n x ge 0\nendaligned","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"and decomposes it into a second-stage problem:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"beginaligned\nV_2(barx d_omega) = maxlimits_xx^primey_omega 5y_omega - x^prime \n y_omega le x \n x^prime = x - y_omega \n 0 le y_omega le d_omega \n x = barx lambda\nendaligned","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"and a first-stage problem:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"beginaligned\nV = maxlimits_xtheta -2x + theta \n theta le mathbbE_omegaV_2(x omega) \n x ge 0\nendaligned","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Then, because V_2 is convex with respect to barx for fixed omega, we can use a set of feasible points x^k construct an outer approximation:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"beginaligned\nV^K = maxlimits_xtheta -2x + theta \n theta le mathbbE_omegaV_2(x^k omega) + nabla V_2(x^k omega)^top(x - x^k) quad k = 1ldotsK\n x ge 0 \n theta le M\nendaligned","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"where M is an upper bound on possible values of V_2 so that the problem has a bounded solution.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"It is also useful to see that because barx appears only on the right-hand side of a linear program, nabla V_2(x^k omega) = lambda^k.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Ignoring how we choose x^k for now, we can construct a lower and upper bound on the optimal solution:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"-2x^K + mathbbE_omegaV_2(x^K omega) = underbarV le V le overlineV = V^K","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Thus, we need some way of cleverly choosing a sequence of x^k so that the lower bound converges to the upper bound.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Start with K=1\nSolve V^K-1 to get x^K\nSet overlineV = V^k\nSolve V_2(x^K omega) for all omega and store the optimal objective value and dual solution lambda^K\nSet underbarV = -2x^K + mathbbE_omegaV_2(x^k omega)\nIf underbarV approx overlineV, STOP\nAdd new constraint theta le mathbbE_omegaV_2(x^K omega) +lambda^K (x - x^K)\nIncrement K, GOTO 2","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"The next section implements this algorithm in Julia.","category":"page"},{"location":"tutorial/example_newsvendor/#L-Shaped-implementation","page":"Example: two-stage newsvendor","title":"L-Shaped implementation","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Here's a function to compute the second-stage problem;","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"function solve_second_stage(x̅, d_ω)\n model = Model(HiGHS.Optimizer)\n set_silent(model)\n @variable(model, x_in)\n @variable(model, x_out >= 0)\n fix(x_in, x̅)\n @variable(model, 0 <= u_sell <= d_ω)\n @constraint(model, x_out == x_in - u_sell)\n @constraint(model, u_sell <= x_in)\n @objective(model, Max, 5 * u_sell - 0.1 * x_out)\n optimize!(model)\n return (\n V = objective_value(model),\n λ = reduced_cost(x_in),\n x = value(x_out),\n u = value(u_sell),\n )\nend\n\nsolve_second_stage(200, 170)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Here's the first-stage subproblem:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"model = Model(HiGHS.Optimizer)\nset_silent(model)\n@variable(model, x_in == 0)\n@variable(model, x_out >= 0)\n@variable(model, u_make >= 0)\n@constraint(model, x_out == x_in + u_make)\nM = 5 * maximum(d)\n@variable(model, θ <= M)\n@objective(model, Max, -2 * u_make + θ)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Importantly, to ensure we have a bounded solution, we need to add an upper bound to the variable θ.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"kIterationLimit = 100\nfor k in 1:kIterationLimit\n println(\"Solving iteration k = $k\")\n # Step 2\n optimize!(model)\n xᵏ = value(x_out)\n println(\" xᵏ = $xᵏ\")\n # Step 3\n ub = objective_value(model)\n println(\" V̅ = $ub\")\n # Step 4\n ret = [solve_second_stage(xᵏ, d[ω]) for ω in Ω]\n # Step 5\n lb = value(-2 * u_make) + sum(p * r.V for (p, r) in zip(P, ret))\n println(\" V̲ = $lb\")\n # Step 6\n if ub - lb < 1e-6\n println(\"Terminating with near-optimal solution\")\n break\n end\n # Step 7\n c = @constraint(\n model,\n θ <= sum(p * (r.V + r.λ * (x_out - xᵏ)) for (p, r) in zip(P, ret)),\n )\n println(\" Added cut: $c\")\nend","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"To get the first-stage solution, we do:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"optimize!(model)\nxᵏ = value(x_out)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"To compute a second-stage solution, we do:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"solve_second_stage(xᵏ, 170.0)","category":"page"},{"location":"tutorial/example_newsvendor/#Policy-Graph","page":"Example: two-stage newsvendor","title":"Policy Graph","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Now let's see how we can formulate and train a policy for the two-stage newsvendor problem using SDDP.jl. Under the hood, SDDP.jl implements the exact algorithm that we just wrote by hand.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"model = SDDP.LinearPolicyGraph(;\n stages = 2,\n sense = :Max,\n upper_bound = 5 * maximum(d), # The `M` in θ <= M\n optimizer = HiGHS.Optimizer,\n) do subproblem::JuMP.Model, stage::Int\n @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)\n if stage == 1\n @variable(subproblem, u_make >= 0)\n @constraint(subproblem, x.out == x.in + u_make)\n @stageobjective(subproblem, -2 * u_make)\n else\n @variable(subproblem, u_sell >= 0)\n @constraint(subproblem, u_sell <= x.in)\n @constraint(subproblem, x.out == x.in - u_sell)\n SDDP.parameterize(subproblem, d, P) do ω\n set_upper_bound(u_sell, ω)\n return\n end\n @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out)\n end\n return\nend\n\nSDDP.train(model; log_every_iteration = true)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"One way to query the optimal policy is with SDDP.DecisionRule:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"first_stage_rule = SDDP.DecisionRule(model; node = 1)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"solution_1 = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0))","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Here's the second stage:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"second_stage_rule = SDDP.DecisionRule(model; node = 2)\nsolution = SDDP.evaluate(\n second_stage_rule;\n incoming_state = Dict(:x => solution_1.outgoing_state[:x]),\n noise = 170.0, # A value of d[ω], can be out-of-sample.\n controls_to_record = [:u_sell],\n)","category":"page"},{"location":"tutorial/example_newsvendor/#Simulation","page":"Example: two-stage newsvendor","title":"Simulation","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Querying the decision rules is tedious. It's often more useful to simulate the policy:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"simulations = SDDP.simulate(\n model,\n 10, #= number of replications =#\n [:x, :u_sell, :u_make]; #= variables to record =#\n skip_undefined_variables = true,\n);\nnothing #hide","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"simulations is a vector with 10 elements","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"length(simulations)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"and each element is a vector with two elements (one for each stage)","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"length(simulations[1])","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"The first stage contains:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"simulations[1][1]","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"The second stage contains:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"simulations[1][2]","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"We can compute aggregated statistics across the simulations:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"objectives = map(simulations) do simulation\n return sum(data[:stage_objective] for data in simulation)\nend\nμ, t = SDDP.confidence_interval(objectives)\nprintln(\"Simulation ci : $μ ± $t\")","category":"page"},{"location":"tutorial/example_newsvendor/#Risk-aversion-revisited","page":"Example: two-stage newsvendor","title":"Risk aversion revisited","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"SDDP.jl contains a number of risk measures. One example is:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"0.5 * SDDP.Expectation() + 0.5 * SDDP.WorstCase()","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"You can construct a risk-averse policy by passing a risk measure to the risk_measure keyword argument of SDDP.train.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"We can explore how the optimal decision changes with risk by creating a function:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"function solve_newsvendor(risk_measure::SDDP.AbstractRiskMeasure)\n model = SDDP.LinearPolicyGraph(;\n stages = 2,\n sense = :Max,\n upper_bound = 5 * maximum(d),\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)\n if node == 1\n @stageobjective(subproblem, -2 * x.out)\n else\n @variable(subproblem, u_sell >= 0)\n @constraint(subproblem, u_sell <= x.in)\n @constraint(subproblem, x.out == x.in - u_sell)\n SDDP.parameterize(subproblem, d, P) do ω\n set_upper_bound(u_sell, ω)\n return\n end\n @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out)\n end\n return\n end\n SDDP.train(model; risk_measure = risk_measure, print_level = 0)\n first_stage_rule = SDDP.DecisionRule(model; node = 1)\n solution = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0))\n return solution.outgoing_state[:x]\nend","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Now we can see how many units a decision maker would order using CVaR:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"solve_newsvendor(SDDP.CVaR(0.4))","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"as well as a decision-maker who cares only about the worst-case outcome:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"solve_newsvendor(SDDP.WorstCase())","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"In general, the decision-maker will be somewhere between the two extremes. The SDDP.Entropic risk measure is a risk measure that has a single parameter that lets us explore the space of policies between the two extremes. When the parameter is small, the measure acts like SDDP.Expectation, and when it is large, it acts like SDDP.WorstCase.","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Here is what we get if we solve our problem multiple times for different values of the risk aversion parameter gamma:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Γ = [10^i for i in -4:0.5:1]\nbuy = [solve_newsvendor(SDDP.Entropic(γ)) for γ in Γ]\nPlots.plot(\n Γ,\n buy;\n xaxis = :log,\n xlabel = \"Risk aversion parameter γ\",\n ylabel = \"Number of pies to make\",\n legend = false,\n)","category":"page"},{"location":"tutorial/example_newsvendor/#Things-to-try","page":"Example: two-stage newsvendor","title":"Things to try","text":"","category":"section"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"There are a number of things you can try next:","category":"page"},{"location":"tutorial/example_newsvendor/","page":"Example: two-stage newsvendor","title":"Example: two-stage newsvendor","text":"Experiment with different buy and sales prices\nExperiment with different distributions of demand\nExplore how the optimal policy changes if you use a different risk measure\nWhat happens if you can only buy and sell integer numbers of newspapers? Try this by adding Int to the variable definitions: @variable(subproblem, buy >= 0, Int)\nWhat happens if you use a different upper bound? Try an invalid one like -100, and a very large one like 1e12.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"EditURL = \"theory_intro.jl\"","category":"page"},{"location":"explanation/theory_intro/#Introductory-theory","page":"Introductory theory","title":"Introductory theory","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"note: Note\nThis tutorial is aimed at advanced undergraduates or early-stage graduate students. You don't need prior exposure to stochastic programming! (Indeed, it may be better if you don't, because our approach is non-standard in the literature.)This tutorial is also a living document. If parts are unclear, please open an issue so it can be improved!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"This tutorial will teach you how the stochastic dual dynamic programming algorithm works by implementing a simplified version of the algorithm.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Our implementation is very much a \"vanilla\" version of SDDP; it doesn't have (m)any fancy computational tricks (e.g., the ones included in SDDP.jl) that you need to code a performant or stable version that will work on realistic instances. However, our simplified implementation will work on arbitrary policy graphs, including those with cycles such as infinite horizon problems!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Packages","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"This tutorial uses the following packages. For clarity, we call import PackageName so that we must prefix PackageName. to all functions and structs provided by that package. Everything not prefixed is either part of base Julia, or we wrote it.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"import ForwardDiff\nimport HiGHS\nimport JuMP\nimport Statistics","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"tip: Tip\nYou can follow along by installing the above packages, and copy-pasting the code we will write into a Julia REPL. Alternatively, you can download the Julia .jl file which created this tutorial from GitHub.","category":"page"},{"location":"explanation/theory_intro/#Preliminaries:-background-theory","page":"Introductory theory","title":"Preliminaries: background theory","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Start this tutorial by reading An introduction to SDDP.jl, which introduces the necessary notation and vocabulary that we need for this tutorial.","category":"page"},{"location":"explanation/theory_intro/#Preliminaries:-Kelley's-cutting-plane-algorithm","page":"Introductory theory","title":"Preliminaries: Kelley's cutting plane algorithm","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Kelley's cutting plane algorithm is an iterative method for minimizing convex functions. Given a convex function f(x), Kelley's constructs an under-approximation of the function at the minimum by a set of first-order Taylor series approximations (called cuts) constructed at a set of points k = 1ldotsK:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"beginaligned\nf^K = minlimits_theta in mathbbR x in mathbbR^N theta\n theta ge f(x_k) + fracddxf(x_k)^top (x - x_k)quad k=1ldotsK\n theta ge M\nendaligned","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"where M is a sufficiently large negative number that is a lower bound for f over the domain of x.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Kelley's cutting plane algorithm is a structured way of choosing points x_k to visit, so that as more cuts are added:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"lim_K rightarrow infty f^K = minlimits_x in mathbbR^N f(x)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"However, before we introduce the algorithm, we need to introduce some bounds.","category":"page"},{"location":"explanation/theory_intro/#Bounds","page":"Introductory theory","title":"Bounds","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"By convexity, f^K le f(x) for all x. Thus, if x^* is a minimizer of f, then at any point in time we can construct a lower bound for f(x^*) by solving f^K.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Moreover, we can use the primal solutions x_k^* returned by solving f^k to evaluate f(x_k^*) to generate an upper bound.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Therefore, f^K le f(x^*) le minlimits_k=1ldotsK f(x_k^*).","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"When the lower bound is sufficiently close to the upper bound, we can terminate the algorithm and declare that we have found an solution that is close to optimal.","category":"page"},{"location":"explanation/theory_intro/#Implementation","page":"Introductory theory","title":"Implementation","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Here is pseudo-code fo the Kelley algorithm:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Take as input a convex function f(x) and a iteration limit K_max. Set K = 0, and initialize f^K. Set lb = -infty and ub = infty.\nSolve f^K to obtain a candidate solution x_K+1.\nUpdate lb = f^K and ub = minub f(x_K+1).\nAdd a cut theta ge f(x_K+1) + fracddxfleft(x_K+1right)^top (x - x_K+1) to form f^K+1.\nIncrement K.\nIf K = K_max or ub - lb epsilon, STOP, otherwise, go to step 2.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"And here's a complete implementation:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function kelleys_cutting_plane(\n # The function to be minimized.\n f::Function,\n # The gradient of `f`. By default, we use automatic differentiation to\n # compute the gradient of f so the user doesn't have to!\n dfdx::Function = x -> ForwardDiff.gradient(f, x);\n # The number of arguments to `f`.\n input_dimension::Int,\n # A lower bound for the function `f` over its domain.\n lower_bound::Float64,\n # The number of iterations to run Kelley's algorithm for before stopping.\n iteration_limit::Int,\n # The absolute tolerance ϵ to use for convergence.\n tolerance::Float64 = 1e-6,\n)\n # Step (1):\n K = 0\n model = JuMP.Model(HiGHS.Optimizer)\n JuMP.set_silent(model)\n JuMP.@variable(model, θ >= lower_bound)\n JuMP.@variable(model, x[1:input_dimension])\n JuMP.@objective(model, Min, θ)\n x_k = fill(NaN, input_dimension)\n lower_bound, upper_bound = -Inf, Inf\n while true\n # Step (2):\n JuMP.optimize!(model)\n x_k .= JuMP.value.(x)\n # Step (3):\n lower_bound = JuMP.objective_value(model)\n upper_bound = min(upper_bound, f(x_k))\n println(\"K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)\")\n # Step (4):\n JuMP.@constraint(model, θ >= f(x_k) + dfdx(x_k)' * (x .- x_k))\n # Step (5):\n K = K + 1\n # Step (6):\n if K == iteration_limit\n println(\"-- Termination status: iteration limit --\")\n break\n elseif abs(upper_bound - lower_bound) < tolerance\n println(\"-- Termination status: converged --\")\n break\n end\n end\n println(\"Found solution: x_K = \", x_k)\n return\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Let's run our algorithm to see what happens:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"kelleys_cutting_plane(;\n input_dimension = 2,\n lower_bound = 0.0,\n iteration_limit = 20,\n) do x\n return (x[1] - 1)^2 + (x[2] + 2)^2 + 1.0\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"warning: Warning\nIt's hard to choose a valid lower bound! If you choose one too loose, the algorithm can take a long time to converge. However, if you choose one so tight that M f(x^*), then you can obtain a suboptimal solution. For a deeper discussion of the implications for SDDP.jl, see Choosing an initial bound.","category":"page"},{"location":"explanation/theory_intro/#Preliminaries:-approximating-the-cost-to-go-term","page":"Introductory theory","title":"Preliminaries: approximating the cost-to-go term","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"In the background theory section, we discussed how you could formulate an optimal policy to a multistage stochastic program using the dynamic programming recursion:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"beginaligned\nV_i(x omega) = minlimits_barx x^prime u C_i(barx u omega) + mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi)\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x\nendaligned","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"where our decision rule, pi_i(x omega), solves this optimization problem and returns a u^* corresponding to an optimal solution. Moreover, we alluded to the fact that the cost-to-go term (the nasty recursive expectation) makes this problem intractable to solve.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"However, if, excluding the cost-to-go term (i.e., the SP formulation), V_i(x omega) can be formulated as a linear program (this also works for convex programs, but the math is more involved), then we can make some progress by noticing that x only appears as a right-hand side term of the fishing constraint barx = x. Therefore, V_i(x cdot) is convex with respect to x for fixed omega. (If you have not seen this result before, try to prove it.)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"The fishing constraint barx = x has an associated dual variable. The economic interpretation of this dual variable is that it represents the change in the objective function if the right-hand side x is increased on the scale of one unit. In other words, and with a slight abuse of notation, it is the value fracddx V_i(x omega). (Because V_i is not differentiable, it is a subgradient instead of a derivative.)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"If we implement the constraint barx = x by setting the lower- and upper bounds of barx to x, then the reduced cost of the decision variable barx is the subgradient, and we do not need to explicitly add the fishing constraint as a row to the constraint matrix.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"tip: Tip\nThe subproblem can have binary and integer variables, but you'll need to use Lagrangian duality to compute a subgradient!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Stochastic dual dynamic programming converts this problem into a tractable form by applying Kelley's cutting plane algorithm to the V_j functions in the cost-to-go term:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"beginaligned\nV_i^K(x omega) = minlimits_barx x^prime u C_i(barx u omega) + theta\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x \n theta ge mathbbE_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi) + fracddx^primeV_j^k(x^prime_k varphi)^top (x^prime - x^prime_k)rightquad k=1ldotsK \n theta ge M\nendaligned","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"All we need now is a way of generating these cutting planes in an iterative manner. Before we get to that though, let's start writing some code.","category":"page"},{"location":"explanation/theory_intro/#Implementation:-modeling","page":"Introductory theory","title":"Implementation: modeling","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Let's make a start by defining the problem structure. Like SDDP.jl, we need a few things:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"A description of the structure of the policy graph: how many nodes there are, and the arcs linking the nodes together with their corresponding probabilities.\nA JuMP model for each node in the policy graph.\nA way to identify the incoming and outgoing state variables of each node.\nA description of the random variable, as well as a function that we can call that will modify the JuMP model to reflect the realization of the random variable.\nA decision variable to act as the approximated cost-to-go term.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"warning: Warning\nIn the interests of brevity, there is minimal error checking. Think about all the different ways you could break the code!","category":"page"},{"location":"explanation/theory_intro/#Structs","page":"Introductory theory","title":"Structs","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"The first struct we are going to use is a State struct that will wrap an incoming and outgoing state variable:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"struct State\n in::JuMP.VariableRef\n out::JuMP.VariableRef\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Next, we need a struct to wrap all of the uncertainty within a node:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"struct Uncertainty\n parameterize::Function\n Ω::Vector{Any}\n P::Vector{Float64}\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"parameterize is a function which takes a realization of the random variable omegainOmega and updates the subproblem accordingly. The finite discrete random variable is defined by the vectors Ω and P, so that the random variable takes the value Ω[i] with probability P[i]. As such, P should sum to 1. (We don't check this here, but we should; we do in SDDP.jl.)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Now we have two building blocks, we can declare the structure of each node:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"struct Node\n subproblem::JuMP.Model\n states::Dict{Symbol,State}\n uncertainty::Uncertainty\n cost_to_go::JuMP.VariableRef\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"subproblem is going to be the JuMP model that we build at each node.\nstates is a dictionary that maps a symbolic name of a state variable to a State object wrapping the incoming and outgoing state variables in subproblem.\nuncertainty is an Uncertainty object described above.\ncost_to_go is a JuMP variable that approximates the cost-to-go term.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Finally, we define a simplified policy graph as follows:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"struct PolicyGraph\n nodes::Vector{Node}\n arcs::Vector{Dict{Int,Float64}}\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"There is a vector of nodes, as well as a data structure for the arcs. arcs is a vector of dictionaries, where arcs[i][j] gives the probability of transitioning from node i to node j, if an arc exists.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"To simplify things, we will assume that the root node transitions to node 1 with probability 1, and there are no other incoming arcs to node 1. Notably, we can still define cyclic graphs though!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"We also define a nice show method so that we don't accidentally print a large amount of information to the screen when creating a model:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function Base.show(io::IO, model::PolicyGraph)\n println(io, \"A policy graph with $(length(model.nodes)) nodes\")\n println(io, \"Arcs:\")\n for (from, arcs) in enumerate(model.arcs)\n for (to, probability) in arcs\n println(io, \" $(from) => $(to) w.p. $(probability)\")\n end\n end\n return\nend","category":"page"},{"location":"explanation/theory_intro/#Functions","page":"Introductory theory","title":"Functions","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Now we have some basic types, let's implement some functions so that the user can create a model.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"First, we need an example of a function that the user will provide. Like SDDP.jl, this takes an empty subproblem, and a node index, in this case t::Int. You could change this function to change the model, or define a new one later in the code.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"We're going to copy the example from An introduction to SDDP.jl, with some minor adjustments for the fact we don't have many of the bells and whistles of SDDP.jl. You can probably see how some of the SDDP.jl functionality like @stageobjective and SDDP.parameterize help smooth some of the usability issues like needing to construct both the incoming and outgoing state variables, or needing to explicitly declare return states, uncertainty.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function subproblem_builder(subproblem::JuMP.Model, t::Int)\n # Define the state variables. Note how we fix the incoming state to the\n # initial state variable regardless of `t`! This isn't strictly necessary;\n # it only matters that we do it for the first node.\n JuMP.@variable(subproblem, volume_in == 200)\n JuMP.@variable(subproblem, 0 <= volume_out <= 200)\n states = Dict(:volume => State(volume_in, volume_out))\n # Define the control variables.\n JuMP.@variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n # Define the constraints\n JuMP.@constraints(\n subproblem,\n begin\n volume_out == volume_in + inflow - hydro_generation - hydro_spill\n demand_constraint, thermal_generation + hydro_generation == 150.0\n end\n )\n # Define the objective for each stage `t`. Note that we can use `t` as an\n # index for t = 1, 2, 3.\n fuel_cost = [50.0, 100.0, 150.0]\n JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation)\n # Finally, we define the uncertainty object. Because this is a simplified\n # implementation of SDDP, we shall politely ask the user to only modify the\n # constraints, and not the objective function! (Not that it changes the\n # algorithm, we just have to add more information to keep track of things.)\n uncertainty = Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω\n return JuMP.fix(inflow, ω)\n end\n return states, uncertainty\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"The next function we need to define is the analog of SDDP.PolicyGraph. It should be pretty readable.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function PolicyGraph(\n subproblem_builder::Function;\n graph::Vector{Dict{Int,Float64}},\n lower_bound::Float64,\n optimizer,\n)\n nodes = Node[]\n for t in 1:length(graph)\n # Create a model.\n model = JuMP.Model(optimizer)\n JuMP.set_silent(model)\n # Use the provided function to build out each subproblem. The user's\n # function returns a dictionary mapping `Symbol`s to `State` objects,\n # and an `Uncertainty` object.\n states, uncertainty = subproblem_builder(model, t)\n # Now add the cost-to-go terms:\n JuMP.@variable(model, cost_to_go >= lower_bound)\n obj = JuMP.objective_function(model)\n JuMP.@objective(model, Min, obj + cost_to_go)\n # If there are no outgoing arcs, the cost-to-go is 0.0.\n if length(graph[t]) == 0\n JuMP.fix(cost_to_go, 0.0; force = true)\n end\n push!(nodes, Node(model, states, uncertainty, cost_to_go))\n end\n return PolicyGraph(nodes, graph)\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Then, we can create a model using the subproblem_builder function we defined earlier:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"model = PolicyGraph(\n subproblem_builder;\n graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()],\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n)","category":"page"},{"location":"explanation/theory_intro/#Implementation:-helpful-samplers","page":"Introductory theory","title":"Implementation: helpful samplers","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Before we get properly coding the solution algorithm, it's also going to be useful to have a function that samples a realization of the random variable defined by Ω and P.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function sample_uncertainty(uncertainty::Uncertainty)\n r = rand()\n for (p, ω) in zip(uncertainty.P, uncertainty.Ω)\n r -= p\n if r < 0.0\n return ω\n end\n end\n return error(\"We should never get here because P should sum to 1.0.\")\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"note: Note\nrand() samples a uniform random variable in [0, 1).","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"For example:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"for i in 1:3\n println(\"ω = \", sample_uncertainty(model.nodes[1].uncertainty))\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"It's also going to be useful to define a function that generates a random walk through the nodes of the graph:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function sample_next_node(model::PolicyGraph, current::Int)\n if length(model.arcs[current]) == 0\n # No outgoing arcs!\n return nothing\n else\n r = rand()\n for (to, probability) in model.arcs[current]\n r -= probability\n if r < 0.0\n return to\n end\n end\n # We looped through the outgoing arcs and still have probability left\n # over! This means we've hit an implicit \"zero\" node.\n return nothing\n end\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"For example:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"for i in 1:3\n # We use `repr` to print the next node, because `sample_next_node` can\n # return `nothing`.\n println(\"Next node from $(i) = \", repr(sample_next_node(model, i)))\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"This is a little boring, because our graph is simple. However, more complicated graphs will generate more interesting trajectories!","category":"page"},{"location":"explanation/theory_intro/#Implementation:-the-forward-pass","page":"Introductory theory","title":"Implementation: the forward pass","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Recall that, after approximating the cost-to-go term, we need a way of generating the cuts. As the first step, we need a way of generating candidate solutions x_k^prime. However, unlike the Kelley's example, our functions V_j^k(x^prime varphi) need two inputs: an outgoing state variable and a realization of the random variable.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"One way of getting these inputs is just to pick a random (feasible) value. However, in doing so, we might pick outgoing state variables that we will never see in practice, or we might infrequently pick outgoing state variables that we will often see in practice. Therefore, a better way of generating the inputs is to use a simulation of the policy, which we call the forward pass.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"The forward pass walks the policy graph from start to end, transitioning randomly along the arcs. At each node, it observes a realization of the random variable and solves the approximated subproblem to generate a candidate outgoing state variable x_k^prime. The outgoing state variable is passed as the incoming state variable to the next node in the trajectory.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function forward_pass(model::PolicyGraph, io::IO = stdout)\n println(io, \"| Forward Pass\")\n # First, get the value of the state at the root node (e.g., x_R).\n incoming_state =\n Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states)\n # `simulation_cost` is an accumlator that is going to sum the stage-costs\n # incurred over the forward pass.\n simulation_cost = 0.0\n # We also need to record the nodes visited and resultant outgoing state\n # variables so we can pass them to the backward pass.\n trajectory = Tuple{Int,Dict{Symbol,Float64}}[]\n # Now's the meat of the forward pass: beginning at the first node:\n t = 1\n while t !== nothing\n node = model.nodes[t]\n println(io, \"| | Visiting node $(t)\")\n # Sample the uncertainty:\n ω = sample_uncertainty(node.uncertainty)\n println(io, \"| | | ω = \", ω)\n # Parameterizing the subproblem using the user-provided function:\n node.uncertainty.parameterize(ω)\n println(io, \"| | | x = \", incoming_state)\n # Update the incoming state variable:\n for (k, v) in incoming_state\n JuMP.fix(node.states[k].in, v; force = true)\n end\n # Now solve the subproblem and check we found an optimal solution:\n JuMP.optimize!(node.subproblem)\n if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL\n error(\"Something went terribly wrong!\")\n end\n # Compute the outgoing state variables:\n outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states)\n println(io, \"| | | x′ = \", outgoing_state)\n # We also need to compute the stage cost to add to our\n # `simulation_cost` accumulator:\n stage_cost =\n JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go)\n simulation_cost += stage_cost\n println(io, \"| | | C(x, u, ω) = \", stage_cost)\n # As a penultimate step, set the outgoing state of stage t and the\n # incoming state of stage t + 1, and add the node to the trajectory.\n incoming_state = outgoing_state\n push!(trajectory, (t, outgoing_state))\n # Finally, sample a new node to step to. If `t === nothing`, the\n # `while` loop will break.\n t = sample_next_node(model, t)\n end\n return trajectory, simulation_cost\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Let's take a look at one forward pass:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"trajectory, simulation_cost = forward_pass(model);\nnothing #hide","category":"page"},{"location":"explanation/theory_intro/#Implementation:-the-backward-pass","page":"Introductory theory","title":"Implementation: the backward pass","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"From the forward pass, we obtained a vector of nodes visited and their corresponding outgoing state variables. Now we need to refine the approximation for each node at the candidate solution for the outgoing state variable. That is, we need to add a new cut:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"theta ge mathbbE_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi) + fracddx^primeV_j^k(x^prime_k varphi)^top (x^prime - x^prime_k)right","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"or alternatively:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"theta ge sumlimits_j in i^+ sumlimits_varphi in Omega_j p_ij p_varphileftV_j^k(x^prime_k varphi) + fracddx^primeV_j^k(x^prime_k varphi)^top (x^prime - x^prime_k)right","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"It doesn't matter what order we visit the nodes to generate these cuts for. For example, we could compute them all in parallel, using the current approximations of V^K_i.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"However, we can be smarter than that.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"If we traverse the list of nodes visited in the forward pass in reverse, then we come to refine the i^th node in the trajectory, we will already have improved the approximation of the (i+1)^th node in the trajectory as well! Therefore, our refinement of the i^th node will be better than if we improved node i first, and then refined node (i+1).","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Because we walk the nodes in reverse, we call this the backward pass.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"info: Info\nIf you're into deep learning, you could view this as the equivalent of back-propagation: the forward pass pushes primal information through the graph (outgoing state variables), and the backward pass pulls dual information (cuts) back through the graph to improve our decisions on the next forward pass.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function backward_pass(\n model::PolicyGraph,\n trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}},\n io::IO = stdout,\n)\n println(io, \"| Backward pass\")\n # For the backward pass, we walk back up the nodes.\n for i in reverse(1:length(trajectory))\n index, outgoing_states = trajectory[i]\n node = model.nodes[index]\n println(io, \"| | Visiting node $(index)\")\n if length(model.arcs[index]) == 0\n # If there are no children, the cost-to-go is 0.\n println(io, \"| | | Skipping node because the cost-to-go is 0\")\n continue\n end\n # Create an empty affine expression that we will use to build up the\n # right-hand side of the cut expression.\n cut_expression = JuMP.AffExpr(0.0)\n # For each node j ∈ i⁺\n for (j, P_ij) in model.arcs[index]\n next_node = model.nodes[j]\n # Set the incoming state variables of node j to the outgoing state\n # variables of node i\n for (k, v) in outgoing_states\n JuMP.fix(next_node.states[k].in, v; force = true)\n end\n # Then for each realization of φ ∈ Ωⱼ\n for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω)\n # Setup and solve for the realization of φ\n println(io, \"| | | Solving φ = \", φ)\n next_node.uncertainty.parameterize(φ)\n JuMP.optimize!(next_node.subproblem)\n # Then prepare the cut `P_ij * pφ * [V + dVdxᵀ(x - x_k)]``\n V = JuMP.objective_value(next_node.subproblem)\n println(io, \"| | | | V = \", V)\n dVdx = Dict(\n k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states\n )\n println(io, \"| | | | dVdx′ = \", dVdx)\n cut_expression += JuMP.@expression(\n node.subproblem,\n P_ij *\n pφ *\n (\n V + sum(\n dVdx[k] * (x.out - outgoing_states[k]) for\n (k, x) in node.states\n )\n ),\n )\n end\n end\n # And then refine the cost-to-go variable by adding the cut:\n c = JuMP.@constraint(node.subproblem, node.cost_to_go >= cut_expression)\n println(io, \"| | | Adding cut : \", c)\n end\n return nothing\nend","category":"page"},{"location":"explanation/theory_intro/#Implementation:-bounds","page":"Introductory theory","title":"Implementation: bounds","text":"","category":"section"},{"location":"explanation/theory_intro/#Lower-bounds","page":"Introductory theory","title":"Lower bounds","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Recall from Kelley's that we can obtain a lower bound for f(x^*) be evaluating f^K. The analogous lower bound for a multistage stochastic program is:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"mathbbE_i in R^+ omega in Omega_iV_i^K(x_R omega) le min_pi mathbbE_i in R^+ omega in Omega_iV_i^pi(x_R omega)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Here's how we compute the lower bound:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function lower_bound(model::PolicyGraph)\n node = model.nodes[1]\n bound = 0.0\n for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω)\n node.uncertainty.parameterize(ω)\n JuMP.optimize!(node.subproblem)\n bound += p * JuMP.objective_value(node.subproblem)\n end\n return bound\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"note: Note\nThe implementation is simplified because we assumed that there is only one arc from the root node, and that it pointed to the first node in the vector.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Because we haven't trained a policy yet, the lower bound is going to be very bad:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"lower_bound(model)","category":"page"},{"location":"explanation/theory_intro/#Upper-bounds","page":"Introductory theory","title":"Upper bounds","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"With Kelley's algorithm, we could easily construct an upper bound by evaluating f(x_K). However, it is almost always intractable to evaluate an upper bound for multistage stochastic programs due to the large number of nodes and the nested expectations. Instead, we can perform a Monte Carlo simulation of the policy to build a statistical estimate for the value of mathbbE_i in R^+ omega in Omega_iV_i^pi(x_R omega), where pi is the policy defined by the current approximations V^K_i.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function upper_bound(model::PolicyGraph; replications::Int)\n # Pipe the output to `devnull` so we don't print too much!\n simulations = [forward_pass(model, devnull) for i in 1:replications]\n z = [s[2] for s in simulations]\n μ = Statistics.mean(z)\n tσ = 1.96 * Statistics.std(z) / sqrt(replications)\n return μ, tσ\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"note: Note\nThe width of the confidence interval is incorrect if there are cycles in the graph, because the distribution of simulation costs z is not symmetric. The mean is correct, however.","category":"page"},{"location":"explanation/theory_intro/#Termination-criteria","page":"Introductory theory","title":"Termination criteria","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"In Kelley's algorithm, the upper bound was deterministic. Therefore, we could terminate the algorithm when the lower bound was sufficiently close to the upper bound. However, our upper bound for SDDP is not deterministic; it is a confidence interval!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Some people suggest terminating SDDP when the lower bound is contained within the confidence interval. However, this is a poor choice because it is too easy to generate a false positive. For example, if we use a small number of replications then the width of the confidence will be large, and we are more likely to terminate!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"In a future tutorial (not yet written...) we will discuss termination criteria in more depth. For now, pick a large number of iterations and train for as long as possible.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"tip: Tip\nFor a rule of thumb, pick a large number of iterations to train the policy for (e.g., 10 times mathcalN times maxlimits_iinmathcalN Omega_i)","category":"page"},{"location":"explanation/theory_intro/#Implementation:-the-training-loop","page":"Introductory theory","title":"Implementation: the training loop","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"The train loop of SDDP just applies the forward and backward passes iteratively, followed by a final simulation to compute the upper bound confidence interval:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function train(\n model::PolicyGraph;\n iteration_limit::Int,\n replications::Int,\n io::IO = stdout,\n)\n for i in 1:iteration_limit\n println(io, \"Starting iteration $(i)\")\n outgoing_states, _ = forward_pass(model, io)\n backward_pass(model, outgoing_states, io)\n println(io, \"| Finished iteration\")\n println(io, \"| | lower_bound = \", lower_bound(model))\n end\n println(io, \"Termination status: iteration limit\")\n μ, tσ = upper_bound(model; replications = replications)\n println(io, \"Upper bound = $(μ) ± $(tσ)\")\n return\nend","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Using our model we defined earlier, we can go:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"train(model; iteration_limit = 3, replications = 100)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Success! We trained a policy for a finite horizon multistage stochastic program using stochastic dual dynamic programming.","category":"page"},{"location":"explanation/theory_intro/#Implementation:-evaluating-the-policy","page":"Introductory theory","title":"Implementation: evaluating the policy","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"A final step is the ability to evaluate the policy at a given point.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"function evaluate_policy(\n model::PolicyGraph;\n node::Int,\n incoming_state::Dict{Symbol,Float64},\n random_variable,\n)\n the_node = model.nodes[node]\n the_node.uncertainty.parameterize(random_variable)\n for (k, v) in incoming_state\n JuMP.fix(the_node.states[k].in, v; force = true)\n end\n JuMP.optimize!(the_node.subproblem)\n return Dict(\n k => JuMP.value.(v) for\n (k, v) in JuMP.object_dictionary(the_node.subproblem)\n )\nend\n\nevaluate_policy(\n model;\n node = 1,\n incoming_state = Dict(:volume => 150.0),\n random_variable = 75,\n)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"note: Note\nThe random variable can be out-of-sample, i.e., it doesn't have to be in the vector Omega we created when defining the model! This is a notable difference to other multistage stochastic solution methods like progressive hedging or using the deterministic equivalent.","category":"page"},{"location":"explanation/theory_intro/#Example:-infinite-horizon","page":"Introductory theory","title":"Example: infinite horizon","text":"","category":"section"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"As promised earlier, our implementation is actually pretty general. It can solve any multistage stochastic (linear) program defined by a policy graph, including infinite horizon problems!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Here's an example, where we have extended our earlier problem with an arc from node 3 to node 2 with probability 0.5. You can interpret the 0.5 as a discount factor.","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"model = PolicyGraph(\n subproblem_builder;\n graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict(2 => 0.5)],\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Then, train a policy:","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"train(model; iteration_limit = 3, replications = 100)","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"Success! We trained a policy for an infinite horizon multistage stochastic program using stochastic dual dynamic programming. Note how some of the forward passes are different lengths!","category":"page"},{"location":"explanation/theory_intro/","page":"Introductory theory","title":"Introductory theory","text":"evaluate_policy(\n model;\n node = 3,\n incoming_state = Dict(:volume => 100.0),\n random_variable = 10.0,\n)","category":"page"},{"location":"examples/generation_expansion/","page":"Generation expansion","title":"Generation expansion","text":"EditURL = \"generation_expansion.jl\"","category":"page"},{"location":"examples/generation_expansion/#Generation-expansion","page":"Generation expansion","title":"Generation expansion","text":"","category":"section"},{"location":"examples/generation_expansion/","page":"Generation expansion","title":"Generation expansion","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/generation_expansion/","page":"Generation expansion","title":"Generation expansion","text":"using SDDP\nimport HiGHS\nimport Test\n\nfunction generation_expansion(duality_handler)\n build_cost = 1e4\n use_cost = 4\n num_units = 5\n capacities = ones(num_units)\n demand_vals =\n 0.5 * [\n 5 5 5 5 5 5 5 5\n 4 3 1 3 0 9 8 17\n 0 9 4 2 19 19 13 7\n 25 11 4 14 4 6 15 12\n 6 7 5 3 8 4 17 13\n ]\n # Cost of unmet demand\n penalty = 5e5\n # Discounting rate\n rho = 0.99\n model = SDDP.LinearPolicyGraph(;\n stages = 5,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, stage\n @variable(\n sp,\n 0 <= invested[1:num_units] <= 1,\n SDDP.State,\n Int,\n initial_value = 0\n )\n @variables(sp, begin\n generation >= 0\n unmet >= 0\n demand\n end)\n\n @constraints(\n sp,\n begin\n # Can't un-invest\n investment[i in 1:num_units], invested[i].out >= invested[i].in\n # Generation capacity\n sum(capacities[i] * invested[i].out for i in 1:num_units) >=\n generation\n # Meet demand or pay a penalty\n unmet >= demand - sum(generation)\n # For fewer iterations order the units to break symmetry, units are identical (tougher numerically)\n [j in 1:(num_units-1)], invested[j].out <= invested[j+1].out\n end\n )\n # Demand is uncertain\n SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, demand_vals[stage, :])\n\n @expression(\n sp,\n investment_cost,\n build_cost *\n sum(invested[i].out - invested[i].in for i in 1:num_units)\n )\n @stageobjective(\n sp,\n (investment_cost + generation * use_cost) * rho^(stage - 1) +\n penalty * unmet\n )\n end\n if get(ARGS, 1, \"\") == \"--write\"\n # Run `$ julia generation_expansion.jl --write` to update the benchmark\n # model directory\n model_dir = joinpath(@__DIR__, \"..\", \"..\", \"..\", \"benchmarks\", \"models\")\n SDDP.write_to_file(\n model,\n joinpath(model_dir, \"generation_expansion.sof.json.gz\");\n test_scenarios = 100,\n )\n exit(0)\n end\n SDDP.train(model; log_frequency = 10, duality_handler = duality_handler)\n Test.@test SDDP.calculate_bound(model) ≈ 2.078860e6 atol = 1e3\n return\nend\n\ngeneration_expansion(SDDP.ContinuousConicDuality())\ngeneration_expansion(SDDP.LagrangianDuality())","category":"page"},{"location":"examples/biobjective_hydro/","page":"Biobjective hydro-thermal","title":"Biobjective hydro-thermal","text":"EditURL = \"biobjective_hydro.jl\"","category":"page"},{"location":"examples/biobjective_hydro/#Biobjective-hydro-thermal","page":"Biobjective hydro-thermal","title":"Biobjective hydro-thermal","text":"","category":"section"},{"location":"examples/biobjective_hydro/","page":"Biobjective hydro-thermal","title":"Biobjective hydro-thermal","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/biobjective_hydro/","page":"Biobjective hydro-thermal","title":"Biobjective hydro-thermal","text":"using SDDP, HiGHS, Statistics, Test\n\nfunction biobjective_example()\n model = SDDP.LinearPolicyGraph(;\n stages = 3,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, _\n @variable(subproblem, 0 <= v <= 200, SDDP.State, initial_value = 50)\n @variables(subproblem, begin\n 0 <= g[i = 1:2] <= 100\n 0 <= u <= 150\n s >= 0\n shortage_cost >= 0\n end)\n @expressions(subproblem, begin\n objective_1, g[1] + 10 * g[2]\n objective_2, shortage_cost\n end)\n @constraints(subproblem, begin\n inflow_constraint, v.out == v.in - u - s\n g[1] + g[2] + u == 150\n shortage_cost >= 40 - v.out\n shortage_cost >= 60 - 2 * v.out\n shortage_cost >= 80 - 4 * v.out\n end)\n # You must call this for a biobjective problem!\n SDDP.initialize_biobjective_subproblem(subproblem)\n SDDP.parameterize(subproblem, 0.0:5:50.0) do ω\n JuMP.set_normalized_rhs(inflow_constraint, ω)\n # You must call `set_biobjective_functions` from within\n # `SDDP.parameterize`.\n return SDDP.set_biobjective_functions(\n subproblem,\n objective_1,\n objective_2,\n )\n end\n end\n pareto_weights =\n SDDP.train_biobjective(model; solution_limit = 10, iteration_limit = 10)\n solutions = [(k, v) for (k, v) in pareto_weights]\n sort!(solutions; by = x -> x[1])\n @test length(solutions) == 10\n # Test for convexity! The gradient must be decreasing as we move from left\n # to right.\n gradient(a, b) = (b[2] - a[2]) / (b[1] - a[1])\n grad = Inf\n for i in 1:9\n new_grad = gradient(solutions[i], solutions[i+1])\n @test new_grad < grad\n grad = new_grad\n end\n return\nend\n\nbiobjective_example()","category":"page"},{"location":"examples/asset_management_simple/","page":"Asset management","title":"Asset management","text":"EditURL = \"asset_management_simple.jl\"","category":"page"},{"location":"examples/asset_management_simple/#Asset-management","page":"Asset management","title":"Asset management","text":"","category":"section"},{"location":"examples/asset_management_simple/","page":"Asset management","title":"Asset management","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/asset_management_simple/","page":"Asset management","title":"Asset management","text":"Taken from the book J.R. Birge, F. Louveaux, Introduction to Stochastic Programming, Springer Series in Operations Research and Financial Engineering, Springer New York, New York, NY, 2011","category":"page"},{"location":"examples/asset_management_simple/","page":"Asset management","title":"Asset management","text":"using SDDP, HiGHS, Test\n\nfunction asset_management_simple()\n model = SDDP.PolicyGraph(\n SDDP.MarkovianGraph(\n Array{Float64,2}[\n [1.0]',\n [0.5 0.5],\n [0.5 0.5; 0.5 0.5],\n [0.5 0.5; 0.5 0.5],\n ],\n );\n lower_bound = -1_000.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, index\n (stage, markov_state) = index\n r_stock = [1.25, 1.06]\n r_bonds = [1.14, 1.12]\n @variable(subproblem, stocks >= 0, SDDP.State, initial_value = 0.0)\n @variable(subproblem, bonds >= 0, SDDP.State, initial_value = 0.0)\n if stage == 1\n @constraint(subproblem, stocks.out + bonds.out == 55)\n @stageobjective(subproblem, 0)\n elseif 1 < stage < 4\n @constraint(\n subproblem,\n r_stock[markov_state] * stocks.in +\n r_bonds[markov_state] * bonds.in == stocks.out + bonds.out\n )\n @stageobjective(subproblem, 0)\n else\n @variable(subproblem, over >= 0)\n @variable(subproblem, short >= 0)\n @constraint(\n subproblem,\n r_stock[markov_state] * stocks.in +\n r_bonds[markov_state] * bonds.in - over + short == 80\n )\n @stageobjective(subproblem, -over + 4 * short)\n end\n end\n SDDP.train(model; log_frequency = 5)\n @test SDDP.calculate_bound(model) ≈ 1.514 atol = 1e-4\n return\nend\n\nasset_management_simple()","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"EditURL = \"inventory.jl\"","category":"page"},{"location":"tutorial/inventory/#Example:-inventory-management","page":"Example: inventory management","title":"Example: inventory management","text":"","category":"section"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"The purpose of this tutorial is to demonstrate a well-known inventory management problem with a finite- and infinite-horizon policy.","category":"page"},{"location":"tutorial/inventory/#Required-packages","page":"Example: inventory management","title":"Required packages","text":"","category":"section"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"This tutorial requires the following packages:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"using SDDP\nimport Distributions\nimport HiGHS\nimport Plots\nimport Statistics","category":"page"},{"location":"tutorial/inventory/#Background","page":"Example: inventory management","title":"Background","text":"","category":"section"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Consider a periodic review inventory problem involving a single product. The initial inventory is denoted by x_0 geq 0, and a decision-maker can place an order at the start of each stage. The objective is to minimize expected costs over the planning horizon. The following parameters define the cost structure:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"c is the unit cost for purchasing each unit\nh is the holding cost per unit remaining at the end of each stage\np is the shortage cost per unit of unsatisfied demand at the end of each stage","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"There are no fixed ordering costs, and the demand at each stage is assumed to follow an independent and identically distributed random variable with cumulative distribution function (CDF) Phi(cdot). Any unsatisfied demand is backlogged and carried forward to the next stage.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"At each stage, an agent must decide how many items to order. The per-stage costs are the sum of the order costs, shortage and holding costs incurred at the end of the stage, after demand is realized.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Following Chapter 19 of Introduction to Operations Research by Hillier and Lieberman (7th edition), we use the following parameters: c=15 h=1 p=15.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"x_0 = 10 # initial inventory\nc = 35 # unit inventory cost\nh = 1 # unit inventory holding cost\np = 15 # unit order cost","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Demand follows a continuous uniform distribution between 0 and 800. We construct a sample average approximation with 20 scenarios:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Ω = range(0, 800; length = 20);\nnothing #hide","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"This is a well-known inventory problem with a closed-form solution. The optimal policy is a simple order-up-to policy: if the inventory level is below a certain number of units, the decision-maker orders up to that number of units. Otherwise, no order is placed. For a detailed analysis, refer to Foundations of Stochastic Inventory Theory by Evan Porteus (Stanford Business Books, 2002).","category":"page"},{"location":"tutorial/inventory/#Finite-horizon","page":"Example: inventory management","title":"Finite horizon","text":"","category":"section"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"For a finite horizon of length T, the problem is to minimize the total expected cost over all stages.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"In the last stage, the decision-maker can recover the unit cost c for each leftover item, or buy out any remaining backlog, also at the unit cost c.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"T = 10 # number of stages\nmodel = SDDP.LinearPolicyGraph(;\n stages = T + 1,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0)\n @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0)\n # u_buy is a Decision-Hazard control variable. We decide u.out for use in\n # the next stage\n @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0)\n @variable(sp, u_sell >= 0)\n @variable(sp, w_demand == 0)\n @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell)\n @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell)\n if t == 1\n fix(u_sell, 0; force = true)\n @stageobjective(sp, c * u_buy.out)\n elseif t == T + 1\n fix(u_buy.out, 0; force = true)\n @stageobjective(sp, -c * x_inventory.out + c * x_demand.out)\n SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)\n else\n @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out)\n SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)\n end\n return\nend","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Train and simulate the policy:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"SDDP.train(model)\nsimulations = SDDP.simulate(model, 200, [:x_inventory, :u_buy])\nobjective_values = [sum(t[:stage_objective] for t in s) for s in simulations]\nμ, ci = round.(SDDP.confidence_interval(objective_values, 1.96); digits = 2)\nlower_bound = round(SDDP.calculate_bound(model); digits = 2)\nprintln(\"Confidence interval: \", μ, \" ± \", ci)\nprintln(\"Lower bound: \", lower_bound)","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Plot the optimal inventory levels:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"plt = SDDP.publication_plot(\n simulations;\n title = \"x_inventory.out + u_buy.out\",\n xlabel = \"Stage\",\n ylabel = \"Quantity\",\n ylims = (0, 1_000),\n) do data\n return data[:x_inventory].out + data[:u_buy].out\nend","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"In the early stages, we indeed recover an order-up-to policy. However, there are end-of-horizon effects as the agent tries to optimize their decision making knowing that they have 10 realizations of demand.","category":"page"},{"location":"tutorial/inventory/#Infinite-horizon","page":"Example: inventory management","title":"Infinite horizon","text":"","category":"section"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"We can remove the end-of-horizonn effects by considering an infinite horizon model. We assume a discount factor alpha=095:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"α = 0.95\ngraph = SDDP.LinearGraph(2)\nSDDP.add_edge(graph, 2 => 2, α)\ngraph","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"The objective in this case is to minimize the discounted expected costs over an infinite planning horizon.","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"model = SDDP.PolicyGraph(\n graph;\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0)\n @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0)\n # u_buy is a Decision-Hazard control variable. We decide u.out for use in\n # the next stage\n @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0)\n @variable(sp, u_sell >= 0)\n @variable(sp, w_demand == 0)\n @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell)\n @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell)\n if t == 1\n fix(u_sell, 0; force = true)\n @stageobjective(sp, c * u_buy.out)\n else\n @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out)\n SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)\n end\n return\nend\n\nSDDP.train(model; iteration_limit = 400)\nsimulations = SDDP.simulate(\n model,\n 200,\n [:x_inventory, :u_buy];\n sampling_scheme = SDDP.InSampleMonteCarlo(;\n max_depth = 50,\n terminate_on_dummy_leaf = false,\n ),\n);\nnothing #hide","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"Plot the optimal inventory levels:","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"plt = SDDP.publication_plot(\n simulations;\n title = \"x_inventory.out + u_buy.out\",\n xlabel = \"Stage\",\n ylabel = \"Quantity\",\n ylims = (0, 1_000),\n) do data\n return data[:x_inventory].out + data[:u_buy].out\nend\nPlots.hline!(plt, [662]; label = \"Analytic solution\")","category":"page"},{"location":"tutorial/inventory/","page":"Example: inventory management","title":"Example: inventory management","text":"We again recover an order-up-to policy. The analytic solution is to order-up-to 662 units. We do not precisely recover this solution because we used a sample average approximation of 20 elements. If we increased the number of samples, our solution would approach the analytic solution.","category":"page"},{"location":"guides/access_previous_variables/#Access-variables-from-a-previous-stage","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"","category":"section"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"A common question is \"how do I use a variable from a previous stage in a constraint?\"","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"info: Info\nIf you want to use a variable from a previous stage, it must be a state variable.","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"Here are some examples:","category":"page"},{"location":"guides/access_previous_variables/#Access-a-first-stage-decision-in-a-future-stage","page":"Access variables from a previous stage","title":"Access a first-stage decision in a future stage","text":"","category":"section"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"This is often useful if your first-stage decisions are capacity-expansion type decisions (e.g., you choose first how much capacity to add, but because it takes time to build, it only shows up in some future stage).","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"using SDDP, HiGHS\nSDDP.LinearPolicyGraph(\n stages = 10,\n sense = :Max,\n upper_bound = 100.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n # Capacity of the generator. Decided in the first stage.\n @variable(sp, capacity >= 0, SDDP.State, initial_value = 0)\n # Quantity of water stored.\n @variable(sp, reservoir >= 0, SDDP.State, initial_value = 0)\n # Quantity of water to use for electricity generation in current stage.\n @variable(sp, generation >= 0)\n if t == 1\n # There are no constraints in the first stage, but we need to push the\n # initial value of the reservoir to the next stage.\n @constraint(sp, reservoir.out == reservoir.in)\n # Since we're maximizing profit, subtract cost of capacity.\n @stageobjective(sp, -capacity.out)\n else\n # Water balance constraint.\n @constraint(sp, balance, reservoir.out - reservoir.in + generation == 0)\n # Generation limit.\n @constraint(sp, generation <= capacity.in)\n # Push capacity to the next stage.\n @constraint(sp, capacity.out == capacity.in)\n # Maximize generation.\n @stageobjective(sp, generation)\n # Random inflow in balance constraint.\n SDDP.parameterize(sp, rand(4)) do w\n set_normalized_rhs(balance, w)\n end\n end\nend","category":"page"},{"location":"guides/access_previous_variables/#Access-a-decision-from-N-stages-ago","page":"Access variables from a previous stage","title":"Access a decision from N stages ago","text":"","category":"section"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"This is often useful if you have some inventory problem with a lead time on orders. In the code below, we assume that the product has a lead time of 5 stages, and we use a state variable to track the decisions on the production for the last 5 stages. The decisions are passed to the next stage by shifting them by one stage.","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"using SDDP, HiGHS\nSDDP.LinearPolicyGraph(\n stages = 10,\n sense = :Max,\n upper_bound = 100,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n # Current inventory on hand.\n @variable(sp, inventory >= 0, SDDP.State, initial_value = 0)\n # Inventory pipeline.\n # pipeline[1].out are orders placed today.\n # pipeline[5].in are orders that arrive today and can be added to the\n # current inventory.\n # Stock moves up one slot in the pipeline each stage.\n @variable(sp, pipeline[1:5], SDDP.State, initial_value = 0)\n # The number of units to order today.\n @variable(sp, 0 <= buy <= 10)\n # The number of units to sell today.\n @variable(sp, sell >= 0)\n # Buy orders get placed in the pipeline.\n @constraint(sp, pipeline[1].out == buy)\n # Stock moves up one slot in the pipeline each stage.\n @constraint(sp, [i=2:5], pipeline[i].out == pipeline[i-1].in)\n # Stock balance constraint.\n @constraint(sp, inventory.out == inventory.in - sell + pipeline[5].in)\n # Maximize quantity of sold items.\n @stageobjective(sp, sell)\nend","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"warning: Warning\nYou must initialize the same number of state variables in every stage, even if they are not used in that stage.","category":"page"},{"location":"guides/access_previous_variables/#Stochastic-lead-times","page":"Access variables from a previous stage","title":"Stochastic lead times","text":"","category":"section"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"Stochastic lead times can be modeled by adding stochasticity to the pipeline balance constraint.","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"The trick is to use the random variable omega to represent the lead time, together with JuMP.set_normalized_coefficient to add u_buy to the i pipeline balance constraint when omega is equal to i. For example, if omega = 2 and T = 4, we would have constraints:","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"c_pipeline[1], x_pipeline[1].out == x_pipeline[2].in + 0 * u_buy\nc_pipeline[2], x_pipeline[2].out == x_pipeline[3].in + 1 * u_buy\nc_pipeline[3], x_pipeline[3].out == x_pipeline[4].in + 0 * u_buy\nc_pipeline[4], x_pipeline[4].out == x_pipeline[5].in + 0 * u_buy","category":"page"},{"location":"guides/access_previous_variables/","page":"Access variables from a previous stage","title":"Access variables from a previous stage","text":"using SDDP\nimport HiGHS\nT = 10\nmodel = SDDP.LinearPolicyGraph(\n stages = 20,\n sense = :Max,\n upper_bound = 1000,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variables(sp, begin\n x_inventory >= 0, SDDP.State, (initial_value = 0)\n x_pipeline[1:T+1], SDDP.State, (initial_value = 0)\n 0 <= u_buy <= 10\n u_sell >= 0\n end)\n fix(x_pipeline[T+1].out, 0)\n @stageobjective(sp, u_sell)\n @constraints(sp, begin\n # Shift the orders one stage \n c_pipeline[i=1:T], x_pipeline[i].out == x_pipeline[i+1].in + 1 * u_buy\n # x_pipeline[1].in are arriving on the inventory\n x_inventory.out == x_inventory.in - u_sell + x_pipeline[1].in\n end)\n SDDP.parameterize(sp, 1:T) do ω\n # Rewrite the constraint c_pipeline[i=1:T] indicating how many stages\n # ahead the order will arrive (ω)\n # if ω == i:\n # x_pipeline[i+1].in + 1 * u_buy == x_pipeline[i].out\n # else:\n # x_pipeline[i+1].in + 0 * u_buy == x_pipeline[i].out\n for i in 1:T\n set_normalized_coefficient(c_pipeline[i], u_buy, ω == i ? 1 : 0)\n end\n end\nend","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"DocTestSetup = quote\n using SDDP\nend","category":"page"},{"location":"guides/create_a_belief_state/#Create-a-belief-state","page":"Create a belief state","title":"Create a belief state","text":"","category":"section"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"SDDP.jl includes an implementation of the algorithm described in Dowson, O., Morton, D.P., & Pagnoncelli, B.K. (2020). Partially observable multistage stochastic optimization. Operations Research Letters, 48(4), 505–512.","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"Given a SDDP.Graph object (see Create a general policy graph for details), we can define the ambiguity partition using SDDP.add_ambiguity_set.","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"For example, first we create a Markovian graph:","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"using SDDP\nG = SDDP.MarkovianGraph([[0.5 0.5], [0.2 0.8; 0.8 0.2]])","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"Then we add an ambiguity set over the nodes in the each stage:","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"for t in 1:2\n SDDP.add_ambiguity_set(G, [(t, 1), (t, 2)])\nend","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"This results in the graph:","category":"page"},{"location":"guides/create_a_belief_state/","page":"Create a belief state","title":"Create a belief state","text":"G","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"CurrentModule = SDDP","category":"page"},{"location":"release_notes/#Release-notes","page":"Release notes","title":"Release notes","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.","category":"page"},{"location":"release_notes/#[v1.10.3](https://github.com/odow/SDDP.jl/releases/tag/v1.10.3)-(January-22,-2025)","page":"Release notes","title":"v1.10.3 (January 22, 2025)","text":"","category":"section"},{"location":"release_notes/#Other","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add variable bounds to incoming states in LagrangianDuality (#819)\nDocument how to use the Threaded parallel scheme (#821)","category":"page"},{"location":"release_notes/#[v1.10.2](https://github.com/odow/SDDP.jl/releases/tag/v1.10.2)-(January-13,-2025)","page":"Release notes","title":"v1.10.2 (January 13, 2025)","text":"","category":"section"},{"location":"release_notes/#Fixed","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed ConvexCombination with intercept terms (#815)","category":"page"},{"location":"release_notes/#Other-2","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved test coverage (#810) (#811)\nImproved documentation for risk measures (#813)\nFixed badges for GitHub actions (#816)\nUpdated API reference (#814)","category":"page"},{"location":"release_notes/#[v1.10.1](https://github.com/odow/SDDP.jl/releases/tag/v1.10.1)-(November-28,-2024)","page":"Release notes","title":"v1.10.1 (November 28, 2024)","text":"","category":"section"},{"location":"release_notes/#Fixed-2","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed thread safety of RegularizedForwardPass (#806)\nFixed thread safety of AlternativeForwardPass (#808)","category":"page"},{"location":"release_notes/#Other-3","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation updates (#801)","category":"page"},{"location":"release_notes/#[v1.10.0](https://github.com/odow/SDDP.jl/releases/tag/v1.10.0)-(November-19,-2024)","page":"Release notes","title":"v1.10.0 (November 19, 2024)","text":"","category":"section"},{"location":"release_notes/#Added","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added root_node_risk_measure keyword to train (#804)","category":"page"},{"location":"release_notes/#Fixed-3","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug with cut sharing in a graph with zero-probability arcs (#797)","category":"page"},{"location":"release_notes/#Other-4","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added a new tutorial Example: inventory management (#795)\nAdded a stochastic lead time example to docs (#800)","category":"page"},{"location":"release_notes/#[v1.9.0](https://github.com/odow/SDDP.jl/releases/tag/v1.9.0)-(October-17,-2024)","page":"Release notes","title":"v1.9.0 (October 17, 2024)","text":"","category":"section"},{"location":"release_notes/#Added-2","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added write_only_selected_cuts and cut_selection keyword arguments to write_cuts_to_file and read_cuts_from_file to skip potentially expensive operations (#781) (#784)\nAdded set_numerical_difficulty_callback to modify the subproblem on numerical difficulty (#790)","category":"page"},{"location":"release_notes/#Fixed-4","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed the tests to skip threading tests if running in serial (#770)\nFixed BanditDuality to handle the case where the standard deviation is NaN (#779)\nFixed an error when lagged state variables are encountered in MSPFormat (#786)\nFixed publication_plot with replications of different lengths (#788)\nFixed CTRL+C interrupting the code at unsafe points (#789)","category":"page"},{"location":"release_notes/#Other-5","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#771) (#772)\nUpdated printing because of changes in JuMP (#773)","category":"page"},{"location":"release_notes/#[v1.8.1](https://github.com/odow/SDDP.jl/releases/tag/v1.8.1)-(August-5,-2024)","page":"Release notes","title":"v1.8.1 (August 5, 2024)","text":"","category":"section"},{"location":"release_notes/#Fixed-5","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed various issues with SDDP.Threaded() (#761)\nFixed a deprecation warning for sorting a dictionary (#763)","category":"page"},{"location":"release_notes/#Other-6","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated copyright notices (#762)\nUpdated .JuliaFormatter.toml (#764)","category":"page"},{"location":"release_notes/#[v1.8.0](https://github.com/odow/SDDP.jl/releases/tag/v1.8.0)-(July-24,-2024)","page":"Release notes","title":"v1.8.0 (July 24, 2024)","text":"","category":"section"},{"location":"release_notes/#Added-3","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.Threaded(), which is an experimental parallel scheme that supports solving problems using multiple threads. Some parts of SDDP.jl may not be thread-safe, and this can cause incorrect results, segfaults, or other errors. Please use with care and report any issues by opening a GitHub issue. (#758)","category":"page"},{"location":"release_notes/#Other-7","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements and fixes (#747) (#759)","category":"page"},{"location":"release_notes/#[v1.7.0](https://github.com/odow/SDDP.jl/releases/tag/v1.7.0)-(June-4,-2024)","page":"Release notes","title":"v1.7.0 (June 4, 2024)","text":"","category":"section"},{"location":"release_notes/#Added-4","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added sample_backward_noise_terms_with_state for creating backward pass sampling schemes that depend on the current primal state. (#742) (Thanks @arthur-brigatto)","category":"page"},{"location":"release_notes/#Fixed-6","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed error message when publication_plot has non-finite data (#738)","category":"page"},{"location":"release_notes/#Other-8","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated the logo constructor (#730)","category":"page"},{"location":"release_notes/#[v1.6.7](https://github.com/odow/SDDP.jl/releases/tag/v1.6.7)-(February-1,-2024)","page":"Release notes","title":"v1.6.7 (February 1, 2024)","text":"","category":"section"},{"location":"release_notes/#Fixed-7","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed non-constant state dimension in the MSPFormat reader (#695)\nFixed SimulatorSamplingScheme for deterministic nodes (#710)\nFixed line search in BFGS (#711)\nFixed handling of NEARLY_FEASIBLE_POINT status (#726)","category":"page"},{"location":"release_notes/#Other-9","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#692) (#694) (#706) (#716) (#727)\nUpdated to StochOptFormat v1.0 (#705)\nAdded an experimental OuterApproximation algorithm (#709)\nUpdated .gitignore (#717)\nAdded code for MDP paper (#720) (#721)\nAdded Google analytics (#723)","category":"page"},{"location":"release_notes/#[v1.6.6](https://github.com/odow/SDDP.jl/releases/tag/v1.6.6)-(September-29,-2023)","page":"Release notes","title":"v1.6.6 (September 29, 2023)","text":"","category":"section"},{"location":"release_notes/#Other-10","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated Example: two-stage newsvendor tutorial (#689)\nAdded a warning for people using SDDP.Statistical (#687)","category":"page"},{"location":"release_notes/#[v1.6.5](https://github.com/odow/SDDP.jl/releases/tag/v1.6.5)-(September-25,-2023)","page":"Release notes","title":"v1.6.5 (September 25, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-8","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed duplicate nodes in MarkovianGraph (#681)","category":"page"},{"location":"release_notes/#Other-11","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated tutorials (#677) (#678) (#682) (#683)\nFixed documentation preview (#679)","category":"page"},{"location":"release_notes/#[v1.6.4](https://github.com/odow/SDDP.jl/releases/tag/v1.6.4)-(September-23,-2023)","page":"Release notes","title":"v1.6.4 (September 23, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-9","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed error for invalid log_frequency values (#665)\nFixed objective sense in deterministic_equivalent (#673)","category":"page"},{"location":"release_notes/#Other-12","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation updates (#658) (#666) (#671)\nSwitch to GitHub action for deploying docs (#668) (#670)\nUpdate to Documenter@1 (#669)","category":"page"},{"location":"release_notes/#[v1.6.3](https://github.com/odow/SDDP.jl/releases/tag/v1.6.3)-(September-8,-2023)","page":"Release notes","title":"v1.6.3 (September 8, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-10","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed default stopping rule with iteration_limit or time_limit set (#662)","category":"page"},{"location":"release_notes/#Other-13","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Various documentation improvements (#651) (#657) (#659) (#660)","category":"page"},{"location":"release_notes/#[v1.6.2](https://github.com/odow/SDDP.jl/releases/tag/v1.6.2)-(August-24,-2023)","page":"Release notes","title":"v1.6.2 (August 24, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-11","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"MSPFormat now detect and exploit stagewise independent lattices (#653)\nFixed set_optimizer for models read from file (#654)","category":"page"},{"location":"release_notes/#Other-14","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed typo in pglib_opf.jl (#647)\nFixed documentation build and added color (#652)","category":"page"},{"location":"release_notes/#[v1.6.1](https://github.com/odow/SDDP.jl/releases/tag/v1.6.1)-(July-20,-2023)","page":"Release notes","title":"v1.6.1 (July 20, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-12","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed bugs in MSPFormat reader (#638) (#639)","category":"page"},{"location":"release_notes/#Other-15","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Clarified OutOfSampleMonteCarlo docstring (#643)","category":"page"},{"location":"release_notes/#[v1.6.0](https://github.com/odow/SDDP.jl/releases/tag/v1.6.0)-(July-3,-2023)","page":"Release notes","title":"v1.6.0 (July 3, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-5","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added RegularizedForwardPass (#624)\nAdded FirstStageStoppingRule (#634)","category":"page"},{"location":"release_notes/#Other-16","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Removed an unbound type parameter (#632)\nFixed typo in docstring (#633)\nAdded Here-and-now and hazard-decision tutorial (#635)","category":"page"},{"location":"release_notes/#[v1.5.1](https://github.com/odow/SDDP.jl/releases/tag/v1.5.1)-(June-30,-2023)","page":"Release notes","title":"v1.5.1 (June 30, 2023)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"This release contains a number of minor code changes, but it has a large impact on the content that is printed to screen. In particular, we now log periodically, instead of each iteration, and a \"good\" stopping rule is used as the default if none are specified. Try using SDDP.train(model) to see the difference.","category":"page"},{"location":"release_notes/#Other-17","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed various typos in the documentation (#617)\nFixed printing test after changes in JuMP (#618)\nSet SimulationStoppingRule as the default stopping rule (#619)\nChanged the default logging frequency. Pass log_every_seconds = 0.0 to train to revert to the old behavior. (#620)\nAdded example usage with Distributions.jl (@slwu89) (#622)\nRemoved the numerical issue @warn (#627)\nImproved the quality of docstrings (#630)","category":"page"},{"location":"release_notes/#[v1.5.0](https://github.com/odow/SDDP.jl/releases/tag/v1.5.0)-(May-14,-2023)","page":"Release notes","title":"v1.5.0 (May 14, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-6","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added the ability to use a different model for the forward pass. This is a novel feature that lets you train better policies when the model is non-convex or does not have a well-defined dual. See the Alternative forward models tutorial in which we train convex and non-convex formulations of the optimal power flow problem. (#611)","category":"page"},{"location":"release_notes/#Other-18","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated missing changelog entries (#608)\nRemoved global variables (#610)\nConverted the Options struct to keyword arguments. This struct was a private implementation detail, but the change is breaking if you developed an extension to SDDP that touched these internals. (#612)\nFixed some typos (#613)","category":"page"},{"location":"release_notes/#[v1.4.0](https://github.com/odow/SDDP.jl/releases/tag/v1.4.0)-(May-8,-2023)","page":"Release notes","title":"v1.4.0 (May 8, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-7","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.SimulationStoppingRule (#598)\nAdded sampling_scheme argument to SDDP.write_to_file (#607)","category":"page"},{"location":"release_notes/#Fixed-13","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed parsing of some MSPFormat files (#602) (#604)\nFixed printing in header (#605)","category":"page"},{"location":"release_notes/#[v1.3.0](https://github.com/odow/SDDP.jl/releases/tag/v1.3.0)-(May-3,-2023)","page":"Release notes","title":"v1.3.0 (May 3, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-8","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added experimental support for SDDP.MSPFormat.read_from_file (#593)","category":"page"},{"location":"release_notes/#Other-19","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated to StochOptFormat v0.3 (#600)","category":"page"},{"location":"release_notes/#[v1.2.1](https://github.com/odow/SDDP.jl/releases/tag/v1.2.1)-(May-1,-2023)","page":"Release notes","title":"v1.2.1 (May 1, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-14","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed log_every_seconds (#597)","category":"page"},{"location":"release_notes/#[v1.2.0](https://github.com/odow/SDDP.jl/releases/tag/v1.2.0)-(May-1,-2023)","page":"Release notes","title":"v1.2.0 (May 1, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-9","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.SimulatorSamplingScheme (#594)\nAdded log_every_seconds argument to SDDP.train (#595)","category":"page"},{"location":"release_notes/#Other-20","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Tweaked how the log is printed (#588)\nUpdated to StochOptFormat v0.2 (#592)","category":"page"},{"location":"release_notes/#[v1.1.4](https://github.com/odow/SDDP.jl/releases/tag/v1.1.4)-(April-10,-2023)","page":"Release notes","title":"v1.1.4 (April 10, 2023)","text":"","category":"section"},{"location":"release_notes/#Fixed-15","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Logs are now flushed every iteration (#584)","category":"page"},{"location":"release_notes/#Other-21","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added docstrings to various functions (#581)\nMinor documentation updates (#580)\nClarified integrality documentation (#582)\nUpdated the README (#585)\nNumber of numerical issues is now printed to the log (#586)","category":"page"},{"location":"release_notes/#[v1.1.3](https://github.com/odow/SDDP.jl/releases/tag/v1.1.3)-(April-2,-2023)","page":"Release notes","title":"v1.1.3 (April 2, 2023)","text":"","category":"section"},{"location":"release_notes/#Other-22","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed typo in Example: deterministic to stochastic tutorial (#578)\nFixed typo in documentation of SDDP.simulate (#577)","category":"page"},{"location":"release_notes/#[v1.1.2](https://github.com/odow/SDDP.jl/releases/tag/v1.1.2)-(March-18,-2023)","page":"Release notes","title":"v1.1.2 (March 18, 2023)","text":"","category":"section"},{"location":"release_notes/#Other-23","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added Example: deterministic to stochastic tutorial (#572)","category":"page"},{"location":"release_notes/#[v1.1.1](https://github.com/odow/SDDP.jl/releases/tag/v1.1.1)-(March-16,-2023)","page":"Release notes","title":"v1.1.1 (March 16, 2023)","text":"","category":"section"},{"location":"release_notes/#Other-24","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed email in Project.toml\nAdded notebook to documentation tutorials (#571)","category":"page"},{"location":"release_notes/#[v1.1.0](https://github.com/odow/SDDP.jl/releases/tag/v1.1.0)-(January-12,-2023)","page":"Release notes","title":"v1.1.0 (January 12, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-10","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added the node_name_parser argument to SDDP.write_cuts_to_file and added the option to skip nodes in SDDP.read_cuts_from_file (#565)","category":"page"},{"location":"release_notes/#[v1.0.0](https://github.com/odow/SDDP.jl/releases/tag/v1.0.0)-(January-3,-2023)","page":"Release notes","title":"v1.0.0 (January 3, 2023)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Although we're bumping MAJOR version, this is a non-breaking release. Going forward:","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"New features will bump the MINOR version\nBug fixes, maintenance, and documentation updates will bump the PATCH version\nWe will support only the Long Term Support (currently v1.6.7) and the latest patch (currently v1.8.4) releases of Julia. Updates to the LTS version will bump the MINOR version\nUpdates to the compat bounds of package dependencies will bump the PATCH version.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"We do not intend any breaking changes to the public API, which would require a new MAJOR release. The public API is everything defined in the documentation. Anything not in the documentation is considered private and may change in any PATCH release.","category":"page"},{"location":"release_notes/#Added-11","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added num_nodes argument to SDDP.UnicyclicGraph (#562)\nAdded support for passing an optimizer to SDDP.Asynchronous (#545)","category":"page"},{"location":"release_notes/#Other-25","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated Plotting tools to use live plots (#563)\nAdded vale as a linter (#565)\nImproved documentation for initializing a parallel scheme (#566)","category":"page"},{"location":"release_notes/#[v0.4.9](https://github.com/odow/SDDP.jl/releases/tag/v0.4.9)-(January-3,-2023)","page":"Release notes","title":"v0.4.9 (January 3, 2023)","text":"","category":"section"},{"location":"release_notes/#Added-12","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.UnicyclicGraph (#556)","category":"page"},{"location":"release_notes/#Other-26","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added tutorial on Markov Decision Processes (#556)\nAdded two-stage newsvendor tutorial (#557)\nRefactored the layout of the documentation (#554) (#555)\nUpdated copyright to 2023 (#558)\nFixed errors in the documentation (#561)","category":"page"},{"location":"release_notes/#[v0.4.8](https://github.com/odow/SDDP.jl/releases/tag/v0.4.8)-(December-19,-2022)","page":"Release notes","title":"v0.4.8 (December 19, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-13","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added terminate_on_cycle option to SDDP.Historical (#549)\nAdded include_last_node option to SDDP.DefaultForwardPass (#547)","category":"page"},{"location":"release_notes/#Fixed-16","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Reverted then fixed (#531) because it failed to account for problems with integer variables (#546) (#551)","category":"page"},{"location":"release_notes/#[v0.4.7](https://github.com/odow/SDDP.jl/releases/tag/v0.4.7)-(December-17,-2022)","page":"Release notes","title":"v0.4.7 (December 17, 2022)","text":"","category":"section"},{"location":"release_notes/#Added-14","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added initial_node support to InSampleMonteCarlo and OutOfSampleMonteCarlo (#535)","category":"page"},{"location":"release_notes/#Fixed-17","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Rethrow InterruptException when solver is interrupted (#534)\nFixed numerical recovery when we need dual solutions (#531) (Thanks @bfpc)\nFixed re-using the dashboard = true option between solves (#538)\nFixed bug when no @stageobjective is set (now defaults to 0.0) (#539)\nFixed errors thrown when invalid inputs are provided to add_objective_state (#540)","category":"page"},{"location":"release_notes/#Other-27","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Drop support for Julia versions prior to 1.6 (#533)\nUpdated versions of dependencies (#522) (#533)\nSwitched to HiGHS in the documentation and tests (#533)\nAdded license headers (#519)\nFixed link in air conditioning example (#521) (Thanks @conema)\nClarified variable naming in deterministic equivalent (#525) (Thanks @lucasprocessi)\nAdded this change log (#536)\nCuts are now written to model.cuts.json when numerical instability is discovered. This can aid debugging because it allows to you reload the cuts as of the iteration that caused the numerical issue (#537)","category":"page"},{"location":"release_notes/#[v0.4.6](https://github.com/odow/SDDP.jl/releases/tag/v0.4.6)-(March-25,-2022)","page":"Release notes","title":"v0.4.6 (March 25, 2022)","text":"","category":"section"},{"location":"release_notes/#Other-28","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Updated to JuMP v1.0 (#517)","category":"page"},{"location":"release_notes/#[v0.4.5](https://github.com/odow/SDDP.jl/releases/tag/v0.4.5)-(March-9,-2022)","page":"Release notes","title":"v0.4.5 (March 9, 2022)","text":"","category":"section"},{"location":"release_notes/#Fixed-18","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed issue with set_silent in a subproblem (#510)","category":"page"},{"location":"release_notes/#Other-29","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed many typos (#500) (#501) (#506) (#511) (Thanks @bfpc)\nUpdate to JuMP v0.23 (#514)\nAdded auto-regressive tutorial (#507)","category":"page"},{"location":"release_notes/#[v0.4.4](https://github.com/odow/SDDP.jl/releases/tag/v0.4.4)-(December-11,-2021)","page":"Release notes","title":"v0.4.4 (December 11, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-15","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added BanditDuality (#471)\nAdded benchmark scripts (#475) (#476) (#490)\nwrite_cuts_to_file now saves visited states (#468)","category":"page"},{"location":"release_notes/#Fixed-19","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed BoundStalling in a deterministic policy (#470) (#474)\nFixed magnitude warning with zero coefficients (#483)","category":"page"},{"location":"release_notes/#Other-30","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improvements to LagrangianDuality (#481) (#482) (#487)\nImprovements to StrengthenedConicDuality (#486)\nSwitch to functional form for the tests (#478)\nFixed typos (#472) (Thanks @vfdev-5)\nUpdate to JuMP v0.22 (#498)","category":"page"},{"location":"release_notes/#[v0.4.3](https://github.com/odow/SDDP.jl/releases/tag/v0.4.3)-(August-31,-2021)","page":"Release notes","title":"v0.4.3 (August 31, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-16","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added biobjective solver (#462)\nAdded forward_pass_callback (#466)","category":"page"},{"location":"release_notes/#Other-31","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update tutorials and documentation (#459) (#465)\nOrganize how paper materials are stored (#464)","category":"page"},{"location":"release_notes/#[v0.4.2](https://github.com/odow/SDDP.jl/releases/tag/v0.4.2)-(August-24,-2021)","page":"Release notes","title":"v0.4.2 (August 24, 2021)","text":"","category":"section"},{"location":"release_notes/#Fixed-20","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed a bug in Lagrangian duality (#457)","category":"page"},{"location":"release_notes/#[v0.4.1](https://github.com/odow/SDDP.jl/releases/tag/v0.4.1)-(August-23,-2021)","page":"Release notes","title":"v0.4.1 (August 23, 2021)","text":"","category":"section"},{"location":"release_notes/#Other-32","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Minor changes to our implementation of LagrangianDuality (#454) (#455)","category":"page"},{"location":"release_notes/#[v0.4.0](https://github.com/odow/SDDP.jl/releases/tag/v0.4.0)-(August-17,-2021)","page":"Release notes","title":"v0.4.0 (August 17, 2021)","text":"","category":"section"},{"location":"release_notes/#Breaking","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"A large refactoring for how we handle stochastic integer programs. This added support for things like SDDP.ContinuousConicDuality and SDDP.LagrangianDuality. It was breaking because we removed the integrality_handler argument to PolicyGraph. (#449) (#453)","category":"page"},{"location":"release_notes/#Other-33","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#447) (#448) (#450)","category":"page"},{"location":"release_notes/#[v0.3.17](https://github.com/odow/SDDP.jl/releases/tag/v0.3.17)-(July-6,-2021)","page":"Release notes","title":"v0.3.17 (July 6, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-17","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.PSRSamplingScheme (#426)","category":"page"},{"location":"release_notes/#Other-34","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Display more model attributes (#438)\nDocumentation improvements (#433) (#437) (#439)","category":"page"},{"location":"release_notes/#[v0.3.16](https://github.com/odow/SDDP.jl/releases/tag/v0.3.16)-(June-17,-2021)","page":"Release notes","title":"v0.3.16 (June 17, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-18","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.RiskAdjustedForwardPass (#413)\nAllow SDDP.Historical to sample sequentially (#420)","category":"page"},{"location":"release_notes/#Other-35","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update risk measure docstrings (#418)","category":"page"},{"location":"release_notes/#[v0.3.15](https://github.com/odow/SDDP.jl/releases/tag/v0.3.15)-(June-1,-2021)","page":"Release notes","title":"v0.3.15 (June 1, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-19","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added SDDP.StoppingChain","category":"page"},{"location":"release_notes/#Fixed-21","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed scoping bug in SDDP.@stageobjective (#407)\nFixed a bug when the initial point is infeasible (#411)\nSet subproblems to silent by default (#409)","category":"page"},{"location":"release_notes/#Other-36","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add JuliaFormatter (#412)\nDocumentation improvements (#406) (#408)","category":"page"},{"location":"release_notes/#[v0.3.14](https://github.com/odow/SDDP.jl/releases/tag/v0.3.14)-(March-30,-2021)","page":"Release notes","title":"v0.3.14 (March 30, 2021)","text":"","category":"section"},{"location":"release_notes/#Fixed-22","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed O(N^2) behavior in get_same_children (#393)","category":"page"},{"location":"release_notes/#[v0.3.13](https://github.com/odow/SDDP.jl/releases/tag/v0.3.13)-(March-27,-2021)","page":"Release notes","title":"v0.3.13 (March 27, 2021)","text":"","category":"section"},{"location":"release_notes/#Fixed-23","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed bug in print.jl\nFixed compat of Reexport (#388)","category":"page"},{"location":"release_notes/#[v0.3.12](https://github.com/odow/SDDP.jl/releases/tag/v0.3.12)-(March-22,-2021)","page":"Release notes","title":"v0.3.12 (March 22, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-20","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added problem statistics to header (#385) (#386)","category":"page"},{"location":"release_notes/#Fixed-24","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed subtypes in visualization (#384)","category":"page"},{"location":"release_notes/#[v0.3.11](https://github.com/odow/SDDP.jl/releases/tag/v0.3.11)-(March-22,-2021)","page":"Release notes","title":"v0.3.11 (March 22, 2021)","text":"","category":"section"},{"location":"release_notes/#Fixed-25","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed constructor in direct mode (#383)","category":"page"},{"location":"release_notes/#Other-37","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix documentation (#379)","category":"page"},{"location":"release_notes/#[v0.3.10](https://github.com/odow/SDDP.jl/releases/tag/v0.3.10)-(February-23,-2021)","page":"Release notes","title":"v0.3.10 (February 23, 2021)","text":"","category":"section"},{"location":"release_notes/#Fixed-26","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed seriescolor in publication plot (#376)","category":"page"},{"location":"release_notes/#[v0.3.9](https://github.com/odow/SDDP.jl/releases/tag/v0.3.9)-(February-20,-2021)","page":"Release notes","title":"v0.3.9 (February 20, 2021)","text":"","category":"section"},{"location":"release_notes/#Added-21","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Add option to simulate with different incoming state (#372)\nAdded warning for cuts with high dynamic range (#373)","category":"page"},{"location":"release_notes/#Fixed-27","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed seriesalpha in publication plot (#375)","category":"page"},{"location":"release_notes/#[v0.3.8](https://github.com/odow/SDDP.jl/releases/tag/v0.3.8)-(January-19,-2021)","page":"Release notes","title":"v0.3.8 (January 19, 2021)","text":"","category":"section"},{"location":"release_notes/#Other-38","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#367) (#369) (#370)","category":"page"},{"location":"release_notes/#[v0.3.7](https://github.com/odow/SDDP.jl/releases/tag/v0.3.7)-(January-8,-2021)","page":"Release notes","title":"v0.3.7 (January 8, 2021)","text":"","category":"section"},{"location":"release_notes/#Other-39","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#362) (#363) (#365) (#366)\nBump copyright (#364)","category":"page"},{"location":"release_notes/#[v0.3.6](https://github.com/odow/SDDP.jl/releases/tag/v0.3.6)-(December-17,-2020)","page":"Release notes","title":"v0.3.6 (December 17, 2020)","text":"","category":"section"},{"location":"release_notes/#Other-40","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix typos (#358)\nCollapse navigation bar in docs (#359)\nUpdate TagBot.yml (#361)","category":"page"},{"location":"release_notes/#[v0.3.5](https://github.com/odow/SDDP.jl/releases/tag/v0.3.5)-(November-18,-2020)","page":"Release notes","title":"v0.3.5 (November 18, 2020)","text":"","category":"section"},{"location":"release_notes/#Other-41","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update citations (#348)\nSwitch to GitHub actions (#355)","category":"page"},{"location":"release_notes/#[v0.3.4](https://github.com/odow/SDDP.jl/releases/tag/v0.3.4)-(August-25,-2020)","page":"Release notes","title":"v0.3.4 (August 25, 2020)","text":"","category":"section"},{"location":"release_notes/#Added-22","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added non-uniform distributionally robust risk measure (#328)\nAdded numerical recovery functions (#330)\nAdded experimental StochOptFormat (#332) (#336) (#337) (#341) (#343) (#344)\nAdded entropic risk measure (#347)","category":"page"},{"location":"release_notes/#Other-42","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Documentation improvements (#327) (#333) (#339) (#340)","category":"page"},{"location":"release_notes/#[v0.3.3](https://github.com/odow/SDDP.jl/releases/tag/v0.3.3)-(June-19,-2020)","page":"Release notes","title":"v0.3.3 (June 19, 2020)","text":"","category":"section"},{"location":"release_notes/#Added-23","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added asynchronous support for price and belief states (#325)\nAdded ForwardPass plug-in system (#320)","category":"page"},{"location":"release_notes/#Fixed-28","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fix check for probabilities in Markovian graph (#322)","category":"page"},{"location":"release_notes/#[v0.3.2](https://github.com/odow/SDDP.jl/releases/tag/v0.3.2)-(April-6,-2020)","page":"Release notes","title":"v0.3.2 (April 6, 2020)","text":"","category":"section"},{"location":"release_notes/#Added-24","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added log_frequency argument to SDDP.train (#307)","category":"page"},{"location":"release_notes/#Other-43","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improve error message in deterministic equivalent (#312)\nUpdate to RecipesBase 1.0 (#313)","category":"page"},{"location":"release_notes/#[v0.3.1](https://github.com/odow/SDDP.jl/releases/tag/v0.3.1)-(February-26,-2020)","page":"Release notes","title":"v0.3.1 (February 26, 2020)","text":"","category":"section"},{"location":"release_notes/#Fixed-29","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed filename in integrality_handlers.jl (#304)","category":"page"},{"location":"release_notes/#[v0.3.0](https://github.com/odow/SDDP.jl/releases/tag/v0.3.0)-(February-20,-2020)","page":"Release notes","title":"v0.3.0 (February 20, 2020)","text":"","category":"section"},{"location":"release_notes/#Breaking-2","page":"Release notes","title":"Breaking","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Breaking changes to update to JuMP v0.21 (#300).","category":"page"},{"location":"release_notes/#[v0.2.4](https://github.com/odow/SDDP.jl/releases/tag/v0.2.4)-(February-7,-2020)","page":"Release notes","title":"v0.2.4 (February 7, 2020)","text":"","category":"section"},{"location":"release_notes/#Added-25","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added a counter for the number of total subproblem solves (#301)","category":"page"},{"location":"release_notes/#Other-44","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Update formatter (#298)\nAdded tests (#299)","category":"page"},{"location":"release_notes/#[v0.2.3](https://github.com/odow/SDDP.jl/releases/tag/v0.2.3)-(January-24,-2020)","page":"Release notes","title":"v0.2.3 (January 24, 2020)","text":"","category":"section"},{"location":"release_notes/#Added-26","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for convex risk measures (#294)","category":"page"},{"location":"release_notes/#Fixed-30","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed bug when subproblem is infeasible (#296)\nFixed bug in deterministic equivalent (#297)","category":"page"},{"location":"release_notes/#Other-45","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added example from IJOC paper (#293)","category":"page"},{"location":"release_notes/#[v0.2.2](https://github.com/odow/SDDP.jl/releases/tag/v0.2.2)-(January-10,-2020)","page":"Release notes","title":"v0.2.2 (January 10, 2020)","text":"","category":"section"},{"location":"release_notes/#Fixed-31","page":"Release notes","title":"Fixed","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Fixed flakey time limit in tests (#291)","category":"page"},{"location":"release_notes/#Other-46","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Removed MathOptFormat.jl (#289)\nUpdate copyright (#290)","category":"page"},{"location":"release_notes/#[v0.2.1](https://github.com/odow/SDDP.jl/releases/tag/v0.2.1)-(December-19,-2019)","page":"Release notes","title":"v0.2.1 (December 19, 2019)","text":"","category":"section"},{"location":"release_notes/#Added-27","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added support for approximating a Markov lattice (#282) (#285)\nAdd tools for visualizing the value function (#272) (#286)\nWrite .mof.json files on error (#284)","category":"page"},{"location":"release_notes/#Other-47","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improve documentation (#281) (#283)\nUpdate tests for Julia 1.3 (#287)","category":"page"},{"location":"release_notes/#[v0.2.0](https://github.com/odow/SDDP.jl/releases/tag/v0.2.0)-(December-16,-2019)","page":"Release notes","title":"v0.2.0 (December 16, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"This version added the asynchronous parallel implementation with a few minor breaking changes in how we iterated internally. It didn't break basic user-facing models, only implementations that implemented some of the extension features. It probably could have been a v1.1 release.","category":"page"},{"location":"release_notes/#Added-28","page":"Release notes","title":"Added","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Added asynchronous parallel implementation (#277)\nAdded roll-out algorithm for cyclic graphs (#279)","category":"page"},{"location":"release_notes/#Other-48","page":"Release notes","title":"Other","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Improved error messages in PolicyGraph (#271)\nAdded JuliaFormatter (#273) (#276)\nFixed compat bounds (#274) (#278)\nAdded documentation for simulating non-standard graphs (#280)","category":"page"},{"location":"release_notes/#[v0.1.0](https://github.com/odow/SDDP.jl/releases/tag/v0.1.0)-(October-17,-2019)","page":"Release notes","title":"v0.1.0 (October 17, 2019)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"A complete rewrite of SDDP.jl based on the policy graph framework. This was essentially a new package. It has minimal code in common with the previous implementation.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Development started on September 28, 2018 in Kokako.jl, and the code was merged into SDDP.jl on March 14, 2019.","category":"page"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"The pull request SDDP.jl#180 lists the 29 issues that the rewrite closed.","category":"page"},{"location":"release_notes/#[v0.0.1](https://github.com/odow/SDDP.jl/releases/tag/v0.0.1)-(April-18,-2018)","page":"Release notes","title":"v0.0.1 (April 18, 2018)","text":"","category":"section"},{"location":"release_notes/","page":"Release notes","title":"Release notes","text":"Initial release. Development had been underway since January 22, 2016 in the StochDualDynamicProgram.jl repository. The last development commit there was April 5, 2017. Work then continued in this repository for a year before the first tagged release.","category":"page"},{"location":"examples/asset_management_stagewise/","page":"Asset management with modifications","title":"Asset management with modifications","text":"EditURL = \"asset_management_stagewise.jl\"","category":"page"},{"location":"examples/asset_management_stagewise/#Asset-management-with-modifications","page":"Asset management with modifications","title":"Asset management with modifications","text":"","category":"section"},{"location":"examples/asset_management_stagewise/","page":"Asset management with modifications","title":"Asset management with modifications","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/asset_management_stagewise/","page":"Asset management with modifications","title":"Asset management with modifications","text":"A modified version of the Asset Management Problem Taken from the book J.R. Birge, F. Louveaux, Introduction to Stochastic Programming, Springer Series in Operations Research and Financial Engineering, Springer New York, New York, NY, 2011","category":"page"},{"location":"examples/asset_management_stagewise/","page":"Asset management with modifications","title":"Asset management with modifications","text":"using SDDP, HiGHS, Test\n\nfunction asset_management_stagewise(; cut_type)\n w_s = [1.25, 1.06]\n w_b = [1.14, 1.12]\n Phi = [-1, 5]\n Psi = [0.02, 0.0]\n\n model = SDDP.MarkovianPolicyGraph(;\n sense = :Max,\n transition_matrices = Array{Float64,2}[\n [1.0]',\n [0.5 0.5],\n [0.5 0.5; 0.5 0.5],\n [0.5 0.5; 0.5 0.5],\n ],\n upper_bound = 1000.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n t, i = node\n @variable(subproblem, xs >= 0, SDDP.State, initial_value = 0)\n @variable(subproblem, xb >= 0, SDDP.State, initial_value = 0)\n if t == 1\n @constraint(subproblem, xs.out + xb.out == 55 + xs.in + xb.in)\n @stageobjective(subproblem, 0)\n elseif t == 2 || t == 3\n @variable(subproblem, phi)\n @constraint(\n subproblem,\n w_s[i] * xs.in + w_b[i] * xb.in + phi == xs.out + xb.out\n )\n SDDP.parameterize(subproblem, [1, 2], [0.6, 0.4]) do ω\n JuMP.fix(phi, Phi[ω])\n @stageobjective(subproblem, Psi[ω] * xs.out)\n end\n else\n @variable(subproblem, u >= 0)\n @variable(subproblem, v >= 0)\n @constraint(\n subproblem,\n w_s[i] * xs.in + w_b[i] * xb.in + u - v == 80,\n )\n @stageobjective(subproblem, -4u + v)\n end\n end\n SDDP.train(\n model;\n cut_type = cut_type,\n log_frequency = 10,\n risk_measure = (node) -> begin\n if node[1] != 3\n SDDP.Expectation()\n else\n SDDP.EAVaR(; lambda = 0.5, beta = 0.5)\n end\n end,\n )\n @test SDDP.calculate_bound(model) ≈ 1.278 atol = 1e-3\n return\nend\n\nasset_management_stagewise(; cut_type = SDDP.SINGLE_CUT)\n\nasset_management_stagewise(; cut_type = SDDP.MULTI_CUT)","category":"page"},{"location":"guides/choose_a_stopping_rule/#Choose-a-stopping-rule","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"","category":"section"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"The theory of SDDP tells us that the algorithm converges to an optimal policy almost surely in a finite number of iterations. In practice, this number is very large. Therefore, we need some way of pre-emptively terminating SDDP when the solution is “good enough.” We call heuristics for pre-emptively terminating SDDP stopping rules.","category":"page"},{"location":"guides/choose_a_stopping_rule/#Basic-limits","page":"Choose a stopping rule","title":"Basic limits","text":"","category":"section"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"The training of an SDDP policy can be terminated after a fixed number of iterations using the iteration_limit keyword.","category":"page"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"SDDP.train(model; iteration_limit = 10)","category":"page"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"The training of an SDDP policy can be terminated after a fixed number of seconds using the time_limit keyword.","category":"page"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"SDDP.train(model; time_limit = 2.0)","category":"page"},{"location":"guides/choose_a_stopping_rule/#Stopping-rules","page":"Choose a stopping rule","title":"Stopping rules","text":"","category":"section"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"In addition to the limits provided as keyword arguments, a variety of other stopping rules are available. These can be passed to SDDP.train as a vector to the stopping_rules keyword. Training stops if any of the rules becomes active. To stop when all of the rules become active, use SDDP.StoppingChain. For example:","category":"page"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"# Terminate if BoundStalling becomes true\nSDDP.train(\n model;\n stopping_rules = [SDDP.BoundStalling(10, 1e-4)],\n)\n\n# Terminate if BoundStalling OR TimeLimit becomes true\nSDDP.train(\n model;\n stopping_rules = [SDDP.BoundStalling(10, 1e-4), SDDP.TimeLimit(100.0)],\n)\n\n# Terminate if BoundStalling AND TimeLimit becomes true\nSDDP.train(\n model;\n stopping_rules = [\n SDDP.StoppingChain(SDDP.BoundStalling(10, 1e-4), SDDP.TimeLimit(100.0)),\n ],\n)","category":"page"},{"location":"guides/choose_a_stopping_rule/#Supported-rules","page":"Choose a stopping rule","title":"Supported rules","text":"","category":"section"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"The stopping rules implemented in SDDP.jl are:","category":"page"},{"location":"guides/choose_a_stopping_rule/","page":"Choose a stopping rule","title":"Choose a stopping rule","text":"SDDP.IterationLimit\nSDDP.TimeLimit\nSDDP.Statistical\nSDDP.BoundStalling\nSDDP.StoppingChain\nSDDP.SimulationStoppingRule\nSDDP.FirstStageStoppingRule","category":"page"},{"location":"examples/belief/","page":"Partially observable inventory management","title":"Partially observable inventory management","text":"EditURL = \"belief.jl\"","category":"page"},{"location":"examples/belief/#Partially-observable-inventory-management","page":"Partially observable inventory management","title":"Partially observable inventory management","text":"","category":"section"},{"location":"examples/belief/","page":"Partially observable inventory management","title":"Partially observable inventory management","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/belief/","page":"Partially observable inventory management","title":"Partially observable inventory management","text":"using SDDP, HiGHS, Random, Statistics, Test\n\nfunction inventory_management_problem()\n demand_values = [1.0, 2.0]\n demand_prob = Dict(:Ah => [0.2, 0.8], :Bh => [0.8, 0.2])\n graph = SDDP.Graph(\n :root_node,\n [:Ad, :Ah, :Bd, :Bh],\n [\n (:root_node => :Ad, 0.5),\n (:root_node => :Bd, 0.5),\n (:Ad => :Ah, 1.0),\n (:Ah => :Ad, 0.8),\n (:Ah => :Bd, 0.1),\n (:Bd => :Bh, 1.0),\n (:Bh => :Bd, 0.8),\n (:Bh => :Ad, 0.1),\n ],\n )\n SDDP.add_ambiguity_set(graph, [:Ad, :Bd], 1e2)\n SDDP.add_ambiguity_set(graph, [:Ah, :Bh], 1e2)\n\n model = SDDP.PolicyGraph(\n graph;\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n @variables(\n subproblem,\n begin\n 0 <= inventory <= 2, (SDDP.State, initial_value = 0.0)\n buy >= 0\n demand\n end\n )\n @constraint(subproblem, demand == inventory.in - inventory.out + buy)\n if node == :Ad || node == :Bd || node == :D\n JuMP.fix(demand, 0)\n @stageobjective(subproblem, buy)\n else\n SDDP.parameterize(subproblem, demand_values, demand_prob[node]) do ω\n return JuMP.fix(demand, ω)\n end\n @stageobjective(subproblem, 2 * buy + inventory.out)\n end\n end\n # Train the policy.\n Random.seed!(123)\n SDDP.train(\n model;\n iteration_limit = 100,\n cut_type = SDDP.SINGLE_CUT,\n log_frequency = 10,\n parallel_scheme = SDDP.Serial(),\n )\n results = SDDP.simulate(model, 500; parallel_scheme = SDDP.Serial())\n objectives =\n [sum(s[:stage_objective] for s in simulation) for simulation in results]\n sample_mean = round(Statistics.mean(objectives); digits = 2)\n sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2)\n @test SDDP.calculate_bound(model) ≈ sample_mean atol = sample_ci\n return\nend\n\ninventory_management_problem()","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"EditURL = \"decision_hazard.jl\"","category":"page"},{"location":"tutorial/decision_hazard/#Here-and-now-and-hazard-decision","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"","category":"section"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"SDDP.jl assumes that the agent gets to make a decision after observing the realization of the random variable. This is called a wait-and-see or hazard-decision model. In contrast, you might want your agent to make decisions before observing the random variable. This is called a here-and-now or decision-hazard model.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"info: Info\nThe terms decision-hazard and hazard-decision from the French hasard, meaning chance. It could also have been translated as uncertainty-decision and decision-uncertainty, but the community seems to have settled on the transliteration hazard instead. We like the hazard-decision and decision-hazard terms because they clearly communicate the order of the decision and the uncertainty.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"The purpose of this tutorial is to demonstrate how to model here-and-now decisions in SDDP.jl.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"This tutorial uses the following packages:","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"using SDDP\nimport HiGHS","category":"page"},{"location":"tutorial/decision_hazard/#Hazard-decision-formulation","page":"Here-and-now and hazard-decision","title":"Hazard-decision formulation","text":"","category":"section"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"As an example, we're going to build a standard hydro-thermal scheduling model, with a single hydro-reservoir and a single thermal generation plant. In each of the four stages, we need to choose some mix of u_thermal and u_hydro to meet a demand of 9 units, where unmet demand is penalized at a rate of $500/unit.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"hazard_decision = SDDP.LinearPolicyGraph(;\n stages = 4,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, node\n @variables(sp, begin\n 0 <= x_storage <= 8, (SDDP.State, initial_value = 6)\n u_thermal >= 0\n u_hydro >= 0\n u_unmet_demand >= 0\n end)\n @constraint(sp, u_thermal + u_hydro == 9 - u_unmet_demand)\n @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)\n SDDP.parameterize(sp, [2, 3]) do ω_inflow\n return set_normalized_rhs(c_balance, ω_inflow)\n end\n @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal)\nend","category":"page"},{"location":"tutorial/decision_hazard/#Decision-hazard-formulation","page":"Here-and-now and hazard-decision","title":"Decision-hazard formulation","text":"","category":"section"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"In the wait-and-see formulation, we get to decide the generation variables after observing the realization of ω_inflow. However, a common modeling situation is that we need to decide the level of thermal generation u_thermal before observing the inflow.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"SDDP.jl can model here-and-now decisions with a modeling trick: a wait-and-see decision in stage t-1 is equivalent to a here-and-now decision in stage t.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"In other words, we need to convert the u_thermal decision from a control variable that is decided in stage t, to a state variable that is decided in stage t-1. Here's our new model, with the three lines that have changed:","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"decision_hazard = SDDP.LinearPolicyGraph(;\n stages = 4,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, node\n @variables(sp, begin\n 0 <= x_storage <= 8, (SDDP.State, initial_value = 6)\n u_thermal >= 0, (SDDP.State, initial_value = 0) # <-- changed\n u_hydro >= 0\n u_unmet_demand >= 0\n end)\n @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand) # <-- changed\n @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)\n SDDP.parameterize(sp, [2, 3]) do ω\n return set_normalized_rhs(c_balance, ω)\n end\n @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in) # <-- changed\nend","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"Can you understand the reformulation? In each stage, we now use the value of u_thermal.in instead of u_thermal, and the value of the outgoing u_thermal.out is the here-and-how decision for stage t+1.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"(If you can spot a \"mistake\" with this model, don't worry, we'll fix it below. Presenting it like this simplifies the exposition.)","category":"page"},{"location":"tutorial/decision_hazard/#Comparison","page":"Here-and-now and hazard-decision","title":"Comparison","text":"","category":"section"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"Let's compare the cost of operating the two models:","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"function train_and_compute_cost(model)\n SDDP.train(model; print_level = 0)\n return println(\"Cost = \\$\", SDDP.calculate_bound(model))\nend\n\ntrain_and_compute_cost(hazard_decision)","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"train_and_compute_cost(decision_hazard)","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"This suggests that choosing the thermal generation before observing the inflow adds a cost of $250. But does this make sense?","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"If we look carefully at our decision_hazard model, the incoming value of u_thermal.in in the first stage is fixed to the initial_value of 0. Therefore, we must always meet the full demand with u_hydro, which we cannot do without incurring unmet demand.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"To allow the model to choose an optimal level of u_thermal in the first-stage, we need to add an extra stage that is deterministic with no stage objective.","category":"page"},{"location":"tutorial/decision_hazard/#Fixing-the-decision-hazard","page":"Here-and-now and hazard-decision","title":"Fixing the decision-hazard","text":"","category":"section"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"In the following model, we now have five stages, so that stage t+1 in decision_hazard_2 corresponds to stage t in decision_hazard. We've also added an if-statement, which adds different constraints depending on the node. Note that we need to add an x_storage.out == x_storage.in constraint because the storage can't change in this new first-stage.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"decision_hazard_2 = SDDP.LinearPolicyGraph(;\n stages = 5, # <-- changed\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, node\n @variables(sp, begin\n 0 <= x_storage <= 8, (SDDP.State, initial_value = 6)\n u_thermal >= 0, (SDDP.State, initial_value = 0)\n u_hydro >= 0\n u_unmet_demand >= 0\n end)\n if node == 1 # <-- new\n @constraint(sp, x_storage.out == x_storage.in) # <-- new\n @stageobjective(sp, 0) # <-- new\n else\n @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand)\n @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)\n SDDP.parameterize(sp, [2, 3]) do ω\n return set_normalized_rhs(c_balance, ω)\n end\n @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in)\n end\nend\n\ntrain_and_compute_cost(decision_hazard_2)","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"Now we find that the cost of choosing the thermal generation before observing the inflow adds a much more reasonable cost of $10.","category":"page"},{"location":"tutorial/decision_hazard/#Summary","page":"Here-and-now and hazard-decision","title":"Summary","text":"","category":"section"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"To summarize, the difference between here-and-now and wait-and-see variables is a modeling choice.","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"To create a here-and-now decision, add it as a state variable to the previous stage","category":"page"},{"location":"tutorial/decision_hazard/","page":"Here-and-now and hazard-decision","title":"Here-and-now and hazard-decision","text":"In some cases, you'll need to add an additional \"first-stage\" problem to enable the model to choose an optimal value for the here-and-now decision variable. You do not need to do this if the first stage is deterministic. You must make sure that the subproblem is feasible for all possible incoming values of the here-and-now decision variable.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"EditURL = \"pglib_opf.jl\"","category":"page"},{"location":"tutorial/pglib_opf/#Alternative-forward-models","page":"Alternative forward models","title":"Alternative forward models","text":"","category":"section"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"This example demonstrates how to train convex and non-convex models.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"This example uses the following packages:","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"using SDDP\nimport Ipopt\nimport PowerModels\nimport Test","category":"page"},{"location":"tutorial/pglib_opf/#Formulation","page":"Alternative forward models","title":"Formulation","text":"","category":"section"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"For our model, we build a simple optimal power flow model with a single hydro-electric generator.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"The formulation of our optimal power flow problem depends on model_type, which must be one of the PowerModels formulations.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"(To run locally, download pglib_opf_case5_pjm.m and update filename appropriately.)","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"function build_model(model_type)\n filename = joinpath(@__DIR__, \"pglib_opf_case5_pjm.m\")\n data = PowerModels.parse_file(filename)\n return SDDP.PolicyGraph(\n SDDP.UnicyclicGraph(0.95);\n sense = :Min,\n lower_bound = 0.0,\n optimizer = Ipopt.Optimizer,\n ) do sp, t\n power_model = PowerModels.instantiate_model(\n data,\n model_type,\n PowerModels.build_opf;\n jump_model = sp,\n )\n # Now add hydro power models. Assume that generator 5 is hydro, and the\n # rest are thermal.\n pg = power_model.var[:it][:pm][:nw][0][:pg][5]\n sp[:pg] = pg\n @variable(sp, x >= 0, SDDP.State, initial_value = 10.0)\n @variable(sp, deficit >= 0)\n @constraint(sp, balance, x.out == x.in - pg + deficit)\n @stageobjective(sp, objective_function(sp) + 1e6 * deficit)\n SDDP.parameterize(sp, [0, 2, 5]) do ω\n return SDDP.set_normalized_rhs(balance, ω)\n end\n return\n end\nend","category":"page"},{"location":"tutorial/pglib_opf/#Training-a-convex-model","page":"Alternative forward models","title":"Training a convex model","text":"","category":"section"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"We can build and train a convex approximation of the optimal power flow problem.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"The problem with the convex model is that it does not accurately simulate the true dynamics of the problem. Therefore, it under-estimates the true cost of operation.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"convex = build_model(PowerModels.DCPPowerModel)\nSDDP.train(convex; iteration_limit = 10)","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"To more accurately simulate the dynamics of the problem, a common approach is to write the cuts representing the policy to a file, and then read them into a non-convex model:","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"SDDP.write_cuts_to_file(convex, \"convex.cuts.json\")\nnon_convex = build_model(PowerModels.ACPPowerModel)\nSDDP.read_cuts_from_file(non_convex, \"convex.cuts.json\")","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"Now we can simulate non_convex to evaluate the policy.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"result = SDDP.simulate(non_convex, 1)","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"A problem with reading and writing the cuts to file is that the cuts have been generated from trial points of the convex model. Therefore, the policy may be arbitrarily bad at points visited by the non-convex model.","category":"page"},{"location":"tutorial/pglib_opf/#Training-a-non-convex-model","page":"Alternative forward models","title":"Training a non-convex model","text":"","category":"section"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"We can also build and train a non-convex formulation of the optimal power flow problem.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"The problem with the non-convex model is that because it is non-convex, SDDP.jl may find a sub-optimal policy. Therefore, it may over-estimate the true cost of operation.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"non_convex = build_model(PowerModels.ACPPowerModel)\nSDDP.train(non_convex; iteration_limit = 10)\nresult = SDDP.simulate(non_convex, 1)","category":"page"},{"location":"tutorial/pglib_opf/#Combining-convex-and-non-convex-models","page":"Alternative forward models","title":"Combining convex and non-convex models","text":"","category":"section"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"To summarize, training with the convex model constructs cuts at points that may never be visited by the non-convex model, and training with the non-convex model may construct arbitrarily poor cuts because a key assumption of SDDP is convexity.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"As a compromise, we can train a policy using a combination of the convex and non-convex models; we'll use the non-convex model to generate trial points on the forward pass, and we'll use the convex model to build cuts on the backward pass.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"convex = build_model(PowerModels.DCPPowerModel)","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"non_convex = build_model(PowerModels.ACPPowerModel)","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"To do so, we train convex using the SDDP.AlternativeForwardPass forward pass, which simulates the model using non_convex, and we use SDDP.AlternativePostIterationCallback as a post-iteration callback, which copies cuts from the convex model back into the non_convex model.","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"SDDP.train(\n convex;\n forward_pass = SDDP.AlternativeForwardPass(non_convex),\n post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),\n iteration_limit = 10,\n)","category":"page"},{"location":"tutorial/pglib_opf/","page":"Alternative forward models","title":"Alternative forward models","text":"In practice, if we were to simulate non_convex now, we should obtain a better policy than either of the two previous approaches.","category":"page"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = SDDP","category":"page"},{"location":"","page":"Home","title":"Home","text":"\"logo\"","category":"page"},{"location":"#Introduction","page":"Home","title":"Introduction","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"(Image: Build Status) (Image: code coverage)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Welcome to SDDP.jl, a package for solving large convex multistage stochastic programming problems using stochastic dual dynamic programming.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SDDP.jl is built on JuMP, so it supports a number of open-source and commercial solvers, making it a powerful and flexible tool for stochastic optimization.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The implementation of the stochastic dual dynamic programming algorithm in SDDP.jl is state of the art, and it includes support for a number of advanced features not commonly found in other implementations. This includes support for:","category":"page"},{"location":"","page":"Home","title":"Home","text":"infinite horizon problems\nconvex risk measures\nmixed-integer state and control variables\npartially observable stochastic processes.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Install SDDP.jl as follows:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> import Pkg\n\njulia> Pkg.add(\"SDDP\")","category":"page"},{"location":"#License","page":"Home","title":"License","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SDDP.jl is licensed under the MPL 2.0 license.","category":"page"},{"location":"#Resources-for-getting-started","page":"Home","title":"Resources for getting started","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"There are a few ways to get started with SDDP.jl:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Become familiar with JuMP by reading the JuMP documentation\nRead the introductory tutorial An introduction to SDDP.jl\nBrowse some of the examples, such as Example: deterministic to stochastic","category":"page"},{"location":"#Getting-help","page":"Home","title":"Getting help","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"If you need help, please open a GitHub issue.","category":"page"},{"location":"#How-the-documentation-is-structured","page":"Home","title":"How the documentation is structured","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Having a high-level overview of how this documentation is structured will help you know where to look for certain things.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tutorials contains step-by-step explanations of how to use SDDP.jl. Once you've got SDDP.jl installed, start by reading An introduction to SDDP.jl.\nGuides contains \"how-to\" snippets that demonstrate specific topics within SDDP.jl. A good one to get started on is Debug a model.\nExplanation contains step-by-step explanations of the theory and algorithms that underpin SDDP.jl. If you want a basic understanding of the algorithm behind SDDP.jl, start with Introductory theory.\nExamples contain worked examples of various problems solved using SDDP.jl. A good one to get started on is the Hydro-thermal scheduling problem. In particular, it shows how to solve an infinite horizon problem.\nThe API Reference contains a complete list of the functions you can use in SDDP.jl. Look here if you want to know how to use a particular function.","category":"page"},{"location":"#Citing-SDDP.jl","page":"Home","title":"Citing SDDP.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"If you use SDDP.jl, we ask that you please cite the following:","category":"page"},{"location":"","page":"Home","title":"Home","text":"@article{dowson_sddp.jl,\n\ttitle = {{SDDP}.jl: a {Julia} package for stochastic dual dynamic programming},\n\tjournal = {INFORMS Journal on Computing},\n\tauthor = {Dowson, O. and Kapelevich, L.},\n\tdoi = {https://doi.org/10.1287/ijoc.2020.0987},\n\tyear = {2021},\n\tvolume = {33},\n\tissue = {1},\n\tpages = {27-33},\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"Here is an earlier preprint.","category":"page"},{"location":"","page":"Home","title":"Home","text":"If you use the infinite horizon functionality, we ask that you please cite the following:","category":"page"},{"location":"","page":"Home","title":"Home","text":"@article{dowson_policy_graph,\n\ttitle = {The policy graph decomposition of multistage stochastic optimization problems},\n\tdoi = {https://doi.org/10.1002/net.21932},\n\tjournal = {Networks},\n\tauthor = {Dowson, O.},\n\tvolume = {76},\n\tissue = {1},\n\tpages = {3-23},\n\tyear = {2020}\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"Here is an earlier preprint.","category":"page"},{"location":"","page":"Home","title":"Home","text":"If you use the partially observable functionality, we ask that you please cite the following:","category":"page"},{"location":"","page":"Home","title":"Home","text":"@article{dowson_pomsp,\n\ttitle = {Partially observable multistage stochastic programming},\n\tdoi = {https://doi.org/10.1016/j.orl.2020.06.005},\n\tjournal = {Operations Research Letters},\n\tauthor = {Dowson, O. and Morton, D.P. and Pagnoncelli, B.K.},\n\tvolume = {48},\n\tissue = {4},\n\tpages = {505-512},\n\tyear = {2020}\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"Here is an earlier preprint.","category":"page"},{"location":"","page":"Home","title":"Home","text":"If you use the objective state functionality, we ask that you please cite the following:","category":"page"},{"location":"","page":"Home","title":"Home","text":"@article{downward_objective,\n\ttitle = {Stochastic dual dynamic programming with stagewise-dependent objective uncertainty},\n\tdoi = {https://doi.org/10.1016/j.orl.2019.11.002},\n\tjournal = {Operations Research Letters},\n\tauthor = {Downward, A. and Dowson, O. and Baucke, R.},\n\tvolume = {48},\n\tissue = {1},\n\tpages = {33-39},\n\tyear = {2020}\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"Here is an earlier preprint.","category":"page"},{"location":"","page":"Home","title":"Home","text":"If you use the entropic risk measure, we ask that you please cite the following:","category":"page"},{"location":"","page":"Home","title":"Home","text":"@article{dowson_entropic,\n\ttitle = {Incorporating convex risk measures into multistage stochastic programming algorithms},\n\tdoi = {https://doi.org/10.1007/s10479-022-04977-w},\n\tjournal = {Annals of Operations Research},\n\tauthor = {Dowson, O. and Morton, D.P. and Pagnoncelli, B.K.},\n\tyear = {2022},\n}","category":"page"},{"location":"","page":"Home","title":"Home","text":"Here is an earlier preprint.","category":"page"},{"location":"examples/all_blacks/","page":"Deterministic All Blacks","title":"Deterministic All Blacks","text":"EditURL = \"all_blacks.jl\"","category":"page"},{"location":"examples/all_blacks/#Deterministic-All-Blacks","page":"Deterministic All Blacks","title":"Deterministic All Blacks","text":"","category":"section"},{"location":"examples/all_blacks/","page":"Deterministic All Blacks","title":"Deterministic All Blacks","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/all_blacks/","page":"Deterministic All Blacks","title":"Deterministic All Blacks","text":"using SDDP, HiGHS, Test\n\nfunction all_blacks()\n # Number of time periods, number of seats, R_ij = revenue from selling seat\n # i at time j, offer_ij = whether an offer for seat i will come at time j\n (T, N, R, offer) = (3, 2, [3 3 6; 3 3 6], [1 1 0; 1 0 1])\n model = SDDP.LinearPolicyGraph(;\n stages = T,\n sense = :Max,\n upper_bound = 100.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, stage\n # Seat remaining?\n @variable(sp, 0 <= x[1:N] <= 1, SDDP.State, Bin, initial_value = 1)\n # Action: accept offer, or don't accept offer\n @variable(sp, accept_offer, Bin)\n # Balance on seats\n @constraint(\n sp,\n [i in 1:N],\n x[i].out == x[i].in - offer[i, stage] * accept_offer\n )\n @stageobjective(\n sp,\n sum(R[i, stage] * offer[i, stage] * accept_offer for i in 1:N)\n )\n end\n SDDP.train(model; duality_handler = SDDP.LagrangianDuality())\n @test SDDP.calculate_bound(model) ≈ 9.0\n return\nend\n\nall_blacks()","category":"page"},{"location":"examples/sldp_example_one/","page":"SLDP: example 1","title":"SLDP: example 1","text":"EditURL = \"sldp_example_one.jl\"","category":"page"},{"location":"examples/sldp_example_one/#SLDP:-example-1","page":"SLDP: example 1","title":"SLDP: example 1","text":"","category":"section"},{"location":"examples/sldp_example_one/","page":"SLDP: example 1","title":"SLDP: example 1","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/sldp_example_one/","page":"SLDP: example 1","title":"SLDP: example 1","text":"This example is derived from Section 4.2 of the paper: Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz Dynamic Programming. Optimization Online. PDF","category":"page"},{"location":"examples/sldp_example_one/","page":"SLDP: example 1","title":"SLDP: example 1","text":"using SDDP, HiGHS, Test\n\nfunction sldp_example_one()\n model = SDDP.LinearPolicyGraph(;\n stages = 8,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n @variable(sp, x, SDDP.State, initial_value = 2.0)\n @variables(sp, begin\n x⁺ >= 0\n x⁻ >= 0\n 0 <= u <= 1, Bin\n ω\n end)\n @stageobjective(sp, 0.9^(t - 1) * (x⁺ + x⁻))\n @constraints(sp, begin\n x.out == x.in + 2 * u - 1 + ω\n x⁺ >= x.out\n x⁻ >= -x.out\n end)\n points = [\n -0.3089653673606697,\n -0.2718277412744214,\n -0.09611178608243474,\n 0.24645863921577763,\n 0.5204224537256875,\n ]\n return SDDP.parameterize(φ -> JuMP.fix(ω, φ), sp, [points; -points])\n end\n SDDP.train(model; log_frequency = 10)\n @test SDDP.calculate_bound(model) <= 1.1675\n return\nend\n\nsldp_example_one()","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/#Simulate-using-a-different-sampling-scheme","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"","category":"section"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"DocTestSetup = quote\n using SDDP, HiGHS\nend","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"By default, SDDP.simulate will simulate the policy using the distributions of noise terms that were defined when the model was created. We call these in-sample simulations. However, in general the in-sample distributions are an approximation of some underlying probability model which we term the true process. Therefore, SDDP.jl makes it easy to simulate the policy using different probability distributions.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"To demonstrate the different ways of simulating the policy, we're going to use the model from the tutorial Markovian policy graphs.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> using SDDP, HiGHS\n\njulia> Ω = [\n (inflow = 0.0, fuel_multiplier = 1.5),\n (inflow = 50.0, fuel_multiplier = 1.0),\n (inflow = 100.0, fuel_multiplier = 0.75),\n ]\n3-element Vector{@NamedTuple{inflow::Float64, fuel_multiplier::Float64}}:\n (inflow = 0.0, fuel_multiplier = 1.5)\n (inflow = 50.0, fuel_multiplier = 1.0)\n (inflow = 100.0, fuel_multiplier = 0.75)\n\njulia> model = SDDP.MarkovianPolicyGraph(\n transition_matrices = Array{Float64, 2}[\n [1.0]',\n [0.75 0.25],\n [0.75 0.25; 0.25 0.75],\n ],\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n # Unpack the stage and Markov index.\n t, markov_state = node\n # Define the state variable.\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Define the control variables.\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n # Define the constraints\n @constraints(subproblem, begin\n volume.out == volume.in + inflow - hydro_generation - hydro_spill\n thermal_generation + hydro_generation == 150.0\n end)\n # Note how we can use `markov_state` to dispatch an `if` statement.\n probability = if markov_state == 1 # wet climate state\n [1 / 6, 1 / 3, 1 / 2]\n else # dry climate state\n [1 / 2, 1 / 3, 1 / 6]\n end\n fuel_cost = [50.0, 100.0, 150.0]\n SDDP.parameterize(subproblem, Ω, probability) do ω\n JuMP.fix(inflow, ω.inflow)\n @stageobjective(\n subproblem,\n ω.fuel_multiplier * fuel_cost[t] * thermal_generation,\n )\n return\n end\n return\n end\nA policy graph with 5 nodes.\n Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)\n\n\njulia> SDDP.train(model; iteration_limit = 10, print_level = 0);","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/#In-sample-Monte-Carlo-simulation","page":"Simulate using a different sampling scheme","title":"In-sample Monte Carlo simulation","text":"","category":"section"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"To simulate the policy using the data defined when model was created, use SDDP.InSampleMonteCarlo.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> simulations = SDDP.simulate(\n model,\n 20;\n sampling_scheme = SDDP.InSampleMonteCarlo(),\n );\n\njulia> sort(unique([data[:noise_term] for sim in simulations for data in sim]))\n3-element Vector{@NamedTuple{inflow::Float64, fuel_multiplier::Float64}}:\n (inflow = 0.0, fuel_multiplier = 1.5)\n (inflow = 50.0, fuel_multiplier = 1.0)\n (inflow = 100.0, fuel_multiplier = 0.75)","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/#Out-of-sample-Monte-Carlo-simulation","page":"Simulate using a different sampling scheme","title":"Out-of-sample Monte Carlo simulation","text":"","category":"section"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"Instead of using the in-sample data, we can perform an out-of-sample simulation of the policy using the SDDP.OutOfSampleMonteCarlo sampling scheme.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"For each node, the SDDP.OutOfSampleMonteCarlo needs to define a new distribution for the transition probabilities between nodes in the policy graph, and a new distribution for the stagewise independent noise terms.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"note: Note\nThe support of the distribution for the stagewise independent noise terms does not have to be the same as the in-sample distributions.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> sampling_scheme = SDDP.OutOfSampleMonteCarlo(model) do node\n stage, markov_state = node\n if stage == 0\n # Called from the root node. Transition to (1, 1) with probability 1.0.\n # Only return the list of children, _not_ a list of noise terms.\n return [SDDP.Noise((1, 1), 1.0)]\n elseif stage == 3\n # Called from the final node. Return an empty list for the children,\n # and a single, deterministic realization for the noise terms.\n children = SDDP.Noise[]\n noise_terms = [SDDP.Noise((inflow = 75.0, fuel_multiplier = 1.2), 1.0)]\n return children, noise_terms\n else\n # Called from a normal node. Return the in-sample distribution for the\n # noise terms, but modify the transition probabilities so that the\n # Markov switching probability is now 50%.\n probability = markov_state == 1 ? [1/6, 1/3, 1/2] : [1/2, 1/3, 1/6]\n # Note: `Ω` is defined at the top of this page of documentation\n noise_terms = [SDDP.Noise(ω, p) for (ω, p) in zip(Ω, probability)]\n children = [\n SDDP.Noise((stage + 1, 1), 0.5), SDDP.Noise((stage + 1, 2), 0.5)\n ]\n return children, noise_terms\n end\n end;\n\njulia> simulations = SDDP.simulate(model, 1; sampling_scheme = sampling_scheme);\n\njulia> simulations[1][3][:noise_term]\n(inflow = 75.0, fuel_multiplier = 1.2)","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"Alternatively, if you only want to modify the stagewise independent noise terms, pass use_insample_transition = true.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> sampling_scheme = SDDP.OutOfSampleMonteCarlo(\n model;\n use_insample_transition = true\n ) do node\n stage, markov_state = node\n if stage == 3\n # Called from the final node. Return a single, deterministic\n # realization for the noise terms. Don't return the children because we\n # use the in-sample data.\n return [SDDP.Noise((inflow = 65.0, fuel_multiplier = 1.1), 1.0)]\n else\n # Called from a normal node. Return the in-sample distribution for the\n # noise terms. Don't return the children because we use the in-sample\n # data.\n probability = markov_state == 1 ? [1/6, 1/3, 1/2] : [1/2, 1/3, 1/6]\n # Note: `Ω` is defined at the top of this page of documentation\n return [SDDP.Noise(ω, p) for (ω, p) in zip(Ω, probability)]\n end\n end;\n\njulia> simulations = SDDP.simulate(model, 1; sampling_scheme = sampling_scheme);\n\njulia> simulations[1][3][:noise_term]\n(inflow = 65.0, fuel_multiplier = 1.1)","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/#Historical-simulation","page":"Simulate using a different sampling scheme","title":"Historical simulation","text":"","category":"section"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"Instead of performing a Monte Carlo simulation like the previous tutorials, we may want to simulate one particular sequence of noise realizations. This historical simulation can also be conducted by passing a SDDP.Historical sampling scheme to the sampling_scheme keyword of the SDDP.simulate function.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"We can confirm that the historical sequence of nodes was visited by querying the :node_index key of the simulation results.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> simulations = SDDP.simulate(\n model;\n sampling_scheme = SDDP.Historical(\n # Note: `Ω` is defined at the top of this page of documentation\n [((1, 1), Ω[1]), ((2, 2), Ω[3]), ((3, 1), Ω[2])],\n ),\n );\n\njulia> [stage[:node_index] for stage in simulations[1]]\n3-element Vector{Tuple{Int64, Int64}}:\n (1, 1)\n (2, 2)\n (3, 1)","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"You can also pass a vector of scenarios, which are sampled sequentially:","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> sampling_scheme = SDDP.Historical(\n [\n [\n ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),\n ((2,2), (inflow = 10.0, fuel_multiplier = 1.4)), # Can be out-of-sample\n ((3,1), (inflow = 65.0, fuel_multiplier = 1.1)),\n ],\n [\n ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),\n ((2,2), (inflow = 100.0, fuel_multiplier = 0.75)),\n ((3,1), (inflow = 0.0, fuel_multiplier = 1.5)),\n ],\n ],\n )\nA Historical sampler with 2 scenarios sampled sequentially.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"Or a vector of scenarios and a corresponding vector of probabilities so that the historical scenarios are sampled probabilistically:","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"julia> sampling_scheme = SDDP.Historical(\n [\n [\n ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),\n ((2,2), (inflow = 10.0, fuel_multiplier = 1.4)), # Can be out-of-sample\n ((3,1), (inflow = 65.0, fuel_multiplier = 1.1)),\n ],\n [\n ((1,1), (inflow = 65.0, fuel_multiplier = 1.1)),\n ((2,2), (inflow = 100.0, fuel_multiplier = 0.75)),\n ((3,1), (inflow = 0.0, fuel_multiplier = 1.5)),\n ],\n ],\n [0.3, 0.7],\n )\nA Historical sampler with 2 scenarios sampled probabilistically.","category":"page"},{"location":"guides/simulate_using_a_different_sampling_scheme/","page":"Simulate using a different sampling scheme","title":"Simulate using a different sampling scheme","text":"tip: Tip\nYour sample space doesn't have to be a NamedTuple. It an be any Julia type! Use a Vector if that is easier, or define your own struct.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"EditURL = \"first_steps.jl\"","category":"page"},{"location":"tutorial/first_steps/#An-introduction-to-SDDP.jl","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"SDDP.jl is a solver for multistage stochastic optimization problems. By multistage, we mean problems in which an agent makes a sequence of decisions over time. By stochastic, we mean that the agent is making decisions in the presence of uncertainty that is gradually revealed over the multiple stages.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"tip: Tip\nMultistage stochastic programming has a lot in common with fields like stochastic optimal control, approximate dynamic programming, Markov decision processes, and reinforcement learning. If it helps, you can think of SDDP as Q-learning in which we approximate the value function using linear programming duality.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"This tutorial is in two parts. First, it is an introduction to the background notation and theory we need, and second, it solves a simple multistage stochastic programming problem.","category":"page"},{"location":"tutorial/first_steps/#What-is-a-node?","page":"An introduction to SDDP.jl","title":"What is a node?","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"A common feature of multistage stochastic optimization problems is that they model an agent controlling a system over time. To simplify things initially, we're going to start by describing what happens at an instant in time at which the agent makes a decision. Only after this will we extend our problem to multiple stages and the notion of time.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"A node is a place at which the agent makes a decision.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"tip: Tip\nFor readers with a stochastic programming background, \"node\" is synonymous with \"stage\" in this section. However, for reasons that will become clear shortly, there can be more than one \"node\" per instant in time, which is why we prefer the term \"node\" over \"stage.\"","category":"page"},{"location":"tutorial/first_steps/#States,-controls,-and-random-variables","page":"An introduction to SDDP.jl","title":"States, controls, and random variables","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The system that we are modeling can be described by three types of variables.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"State variables track a property of the system over time.\nEach node has an associated incoming state variable (the value of the state at the start of the node), and an outgoing state variable (the value of the state at the end of the node).\nExamples of state variables include the volume of water in a reservoir, the number of units of inventory in a warehouse, or the spatial position of a moving vehicle.\nBecause state variables track the system over time, each node must have the same set of state variables.\nWe denote state variables by the letter x for the incoming state variable and x^prime for the outgoing state variable.\nControl variables are actions taken (implicitly or explicitly) by the agent within a node which modify the state variables.\nExamples of control variables include releases of water from the reservoir, sales or purchasing decisions, and acceleration or braking of the vehicle.\nControl variables are local to a node i, and they can differ between nodes. For example, some control variables may be available within certain nodes.\nWe denote control variables by the letter u.\nRandom variables are finite, discrete, exogenous random variables that the agent observes at the start of a node, before the control variables are decided.\nExamples of random variables include rainfall inflow into a reservoir, probabilistic perishing of inventory, and steering errors in a vehicle.\nRandom variables are local to a node i, and they can differ between nodes. For example, some nodes may have random variables, and some nodes may not.\nWe denote random variables by the Greek letter omega and the sample space from which they are drawn by Omega_i. The probability of sampling omega is denoted p_omega for simplicity.\nImportantly, the random variable associated with node i is independent of the random variables in all other nodes.","category":"page"},{"location":"tutorial/first_steps/#Dynamics","page":"An introduction to SDDP.jl","title":"Dynamics","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"In a node i, the three variables are related by a transition function, which maps the incoming state, the controls, and the random variables to the outgoing state as follows: x^prime = T_i(x u omega).","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"As a result of entering a node i with the incoming state x, observing random variable omega, and choosing control u, the agent incurs a cost C_i(x u omega). (If the agent is a maximizer, this can be a profit, or a negative cost.) We call C_i the stage objective.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"To choose their control variables in node i, the agent uses a decision rule u = pi_i(x omega), which is a function that maps the incoming state variable and observation of the random variable to a control u. This control must satisfy some feasibility requirements u in U_i(x omega).","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Here is a schematic which we can use to visualize a single node:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"(Image: Hazard-decision node)","category":"page"},{"location":"tutorial/first_steps/#Policy-graphs","page":"An introduction to SDDP.jl","title":"Policy graphs","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Now that we have a node, we need to connect multiple nodes together to form a multistage stochastic program. We call the graph created by connecting nodes together a policy graph.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The simplest type of policy graph is a linear policy graph. Here's a linear policy graph with three nodes:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"(Image: Linear policy graph)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Here we have dropped the notations inside each node and replaced them by a label (1, 2, and 3) to represent nodes i=1, i=2, and i=3.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"In addition to nodes 1, 2, and 3, there is also a root node (the circle), and three arcs. Each arc has an origin node and a destination node, like 1 => 2, and a corresponding probability of transitioning from the origin to the destination. Unless specified, we assume that the arc probabilities are uniform over the number of outgoing arcs. Thus, in this picture the arc probabilities are all 1.0.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"State variables flow long the arcs of the graph. Thus, the outgoing state variable x^prime from node 1 becomes the incoming state variable x to node 2, and so on.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"We denote the set of nodes by mathcalN, the root node by R, and the probability of transitioning from node i to node j by p_ij. (If no arc exists, then p_ij = 0.) We define the set of successors of node i as i^+ = j in mathcalN p_ij 0.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Each node in the graph corresponds to a place at which the agent makes a decision, and we call moments in time at which the agent makes a decision stages. By convention, we try to draw policy graphs from left-to-right, with the stages as columns. There can be more than one node in a stage! Here's an example of a structure we call Markovian policy graphs:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"(Image: Markovian policy graph)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Here each column represents a moment in time, the squiggly lines represent stochastic rainfall, and the rows represent the world in two discrete states: El Niño and La Niña. In the El Niño states, the distribution of the rainfall random variable is different to the distribution of the rainfall random variable in the La Niña states, and there is some switching probability between the two states that can be modelled by a Markov chain.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Moreover, policy graphs can have cycles! This allows them to model infinite horizon problems. Here's another example, taken from the paper Dowson (2020):","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"(Image: POWDer policy graph)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The columns represent time, and the rows represent different states of the world. In this case, the rows represent different prices that milk can be sold for at the end of each year. The squiggly lines denote a multivariate random variable that models the weekly amount of rainfall that occurs.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"note: Note\nThe sum of probabilities on the outgoing arcs of node i can be less than 1, i.e., sumlimits_jin i^+ p_ij le 1. What does this mean? One interpretation is that the probability is a discount factor. Another interpretation is that there is an implicit \"zero\" node that we have not modeled, with p_i0 = 1 - sumlimits_jin i^+ p_ij. This zero node has C_0(x u omega) = 0, and 0^+ = varnothing.","category":"page"},{"location":"tutorial/first_steps/#More-notation","page":"An introduction to SDDP.jl","title":"More notation","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Recall that each node i has a decision rule u = pi_i(x omega), which is a function that maps the incoming state variable and observation of the random variable to a control u.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The set of decision rules, with one element for each node in the policy graph, is called a policy.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The goal of the agent is to find a policy that minimizes the expected cost of starting at the root node with some initial condition x_R, and proceeding from node to node along the probabilistic arcs until they reach a node with no outgoing arcs (or it reaches an implicit \"zero\" node).","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"min_pi mathbbE_i in R^+ omega in Omega_iV_i^pi(x_R omega)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"where","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"V_i^pi(x omega) = C_i(x u omega) + mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"where u = pi_i(x omega) in U_i(x omega), and x^prime = T_i(x u omega).","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The expectations are a bit complicated, but they are equivalent to:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi) = sumlimits_j in i^+ p_ij sumlimits_varphi in Omega_j p_varphiV_j(x^prime varphi)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"An optimal policy is the set of decision rules that the agent can use to make decisions and achieve the smallest expected cost.","category":"page"},{"location":"tutorial/first_steps/#Assumptions","page":"An introduction to SDDP.jl","title":"Assumptions","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"warning: Warning\nThis section is important!","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The space of problems you can model with this framework is very large. Too large, in fact, for us to form tractable solution algorithms for! Stochastic dual dynamic programming requires the following assumptions in order to work:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Assumption 1: finite nodes","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"There is a finite number of nodes in mathcalN.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Assumption 2: finite random variables","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The sample space Omega_i is finite and discrete for each node iinmathcalN.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Assumption 3: convex problems","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Given fixed omega, C_i(x u omega) is a convex function, T_i(x u omega) is linear, and U_i(x u omega) is a non-empty, bounded convex set with respect to x and u.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Assumption 4: no infinite loops","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"For all loops in the policy graph, the product of the arc transition probabilities around the loop is strictly less than 1.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Assumption 5: relatively complete recourse","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"This is a technical but important assumption. See Relatively complete recourse for more details.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"note: Note\nSDDP.jl relaxes assumption (3) to allow for integer state and control variables, but we won't go into the details here. Assumption (4) essentially means that we obtain a discounted-cost solution for infinite-horizon problems, instead of an average-cost solution; see Dowson (2020) for details.","category":"page"},{"location":"tutorial/first_steps/#Dynamic-programming-and-subproblems","page":"An introduction to SDDP.jl","title":"Dynamic programming and subproblems","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Now that we have formulated our problem, we need some ways of computing optimal decision rules. One way is to just use a heuristic like \"choose a control randomly from the set of feasible controls.\" However, such a policy is unlikely to be optimal.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"A better way of obtaining an optimal policy is to use Bellman's principle of optimality, a.k.a Dynamic Programming, and define a recursive subproblem as follows:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"beginaligned\nV_i(x omega) = minlimits_barx x^prime u C_i(barx u omega) + mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi)\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x\nendaligned","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Our decision rule, pi_i(x omega), solves this optimization problem and returns a u^* corresponding to an optimal solution.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"note: Note\nWe add barx as a decision variable, along with the fishing constraint barx = x for two reasons: it makes it obvious that formulating a problem with x times u results in a bilinear program instead of a linear program (see Assumption 3), and it simplifies the implementation of the SDDP algorithm.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"These subproblems are very difficult to solve exactly, because they involve recursive optimization problems with lots of nested expectations.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Therefore, instead of solving them exactly, SDDP.jl works by iteratively approximating the expectation term of each subproblem, which is also called the cost-to-go term. For now, you don't need to understand the details, other than that there is a nasty cost-to-go term that we deal with behind-the-scenes.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The subproblem view of a multistage stochastic program is also important, because it provides a convenient way of communicating the different parts of the broader problem, and it is how we will communicate the problem to SDDP.jl. All we need to do is drop the cost-to-go term and fishing constraint, and define a new subproblem SP as:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"beginaligned\ntextttSP_i(x omega) minlimits_barx x^prime u C_i(barx u omega) \n x^prime = T_i(barx u omega) \n u in U_i(barx omega)\nendaligned","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"note: Note\nWhen we talk about formulating a subproblem with SDDP.jl, this is the formulation we mean.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"We've retained the transition function and uncertainty set because they help to motivate the different components of the subproblem. However, in general, the subproblem can be more general. A better (less restrictive) representation might be:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"beginaligned\ntextttSP_i(x omega) minlimits_barx x^prime u C_i(barx x^prime u omega) \n (barx x^prime u) in mathcalX_i(omega)\nendaligned","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Note that the outgoing state variable can appear in the objective, and we can add constraints involving the incoming and outgoing state variables. It should be obvious how to map between the two representations.","category":"page"},{"location":"tutorial/first_steps/#Example:-hydro-thermal-scheduling","page":"An introduction to SDDP.jl","title":"Example: hydro-thermal scheduling","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Hydrothermal scheduling is the most common application of stochastic dual dynamic programming. To illustrate some of the basic functionality of SDDP.jl, we implement a very simple model of the hydrothermal scheduling problem.","category":"page"},{"location":"tutorial/first_steps/#Problem-statement","page":"An introduction to SDDP.jl","title":"Problem statement","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"We consider the problem of scheduling electrical generation over three weeks in order to meet a known demand of 150 MWh in each week.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"There are two generators: a thermal generator, and a hydro generator. In each week, the agent needs to decide how much energy to generate from thermal, and how much energy to generate from hydro.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The thermal generator has a short-run marginal cost of $50/MWh in the first stage, $100/MWh in the second stage, and $150/MWh in the third stage.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The hydro generator has a short-run marginal cost of $0/MWh.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The hydro generator draws water from a reservoir which has a maximum capacity of 200 MWh. (Although water is usually measured in m³, we measure it in the energy-equivalent MWh to simplify things. In practice, there is a conversion function between m³ flowing throw the turbine and MWh.) At the start of the first time period, the reservoir is full.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"In addition to the ability to generate electricity by passing water through the hydroelectric turbine, the hydro generator can also spill water down a spillway (bypassing the turbine) in order to prevent the water from over-topping the dam. We assume that there is no cost of spillage.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"In addition to water leaving the reservoir, water that flows into the reservoir through rainfall or rivers are referred to as inflows. These inflows are uncertain, and are the cause of the main trade-off in hydro-thermal scheduling: the desire to use water now to generate cheap electricity, against the risk that future inflows will be low, leading to blackouts or expensive thermal generation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"For our simple model, we assume that the inflows can be modelled by a discrete distribution with the three outcomes given in the following table:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"ω 0 50 100\nP(ω) 1/3 1/3 1/3","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The value of the noise (the random variable) is observed by the agent at the start of each stage. This makes the problem a wait-and-see or hazard-decision formulation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The goal of the agent is to minimize the expected cost of generation over the three weeks.","category":"page"},{"location":"tutorial/first_steps/#Formulating-the-problem","page":"An introduction to SDDP.jl","title":"Formulating the problem","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Before going further, we need to load SDDP.jl:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"using SDDP","category":"page"},{"location":"tutorial/first_steps/#Graph-structure","page":"An introduction to SDDP.jl","title":"Graph structure","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"First, we need to identify the structure of the policy graph. From the problem statement, we want to model the problem over three weeks in weekly stages. Therefore, the policy graph is a linear graph with three stages:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"graph = SDDP.LinearGraph(3)","category":"page"},{"location":"tutorial/first_steps/#Building-the-subproblem","page":"An introduction to SDDP.jl","title":"Building the subproblem","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Next, we need to construct the associated subproblem for each node in graph. To do so, we need to provide SDDP.jl a function which takes two arguments. The first is subproblem::Model, which is an empty JuMP model. The second is node, which is the name of each node in the policy graph. If the graph is linear, SDDP defaults to naming the nodes using the integers in 1:T. Here's an example that we are going to flesh out over the next few paragraphs:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # ... stuff to go here ...\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"warning: Warning\nIf you use a different type of graph, node may be a type different to Int. For example, in SDDP.MarkovianGraph, node is a Tuple{Int,Int}.","category":"page"},{"location":"tutorial/first_steps/#State-variables","page":"An introduction to SDDP.jl","title":"State variables","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The first part of the subproblem we need to identify are the state variables. Since we only have one reservoir, there is only one state variable, volume, the volume of water in the reservoir [MWh].","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The volume had bounds of [0, 200], and the reservoir was full at the start of time, so x_R = 200.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"We add state variables to our subproblem using JuMP's @variable macro. However, in addition to the usual syntax, we also pass SDDP.State, and we need to provide the initial value (x_R) using the initial_value keyword.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The syntax for adding a state variable is a little obtuse, because volume is not single JuMP variable. Instead, volume is a struct with two fields, .in and .out, corresponding to the incoming and outgoing state variables respectively.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"note: Note\nWe don't need to add the fishing constraint barx = x; SDDP.jl does this automatically.","category":"page"},{"location":"tutorial/first_steps/#Control-variables","page":"An introduction to SDDP.jl","title":"Control variables","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The next part of the subproblem we need to identify are the control variables. The control variables for our problem are:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"thermal_generation: the quantity of energy generated from thermal [MWh/week]\nhydro_generation: the quantity of energy generated from hydro [MWh/week]\nhydro_spill: the volume of water spilled from the reservoir in each week [MWh/week]","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Each of these variables is non-negative.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"We add control variables to our subproblem as normal JuMP variables, using @variable or @variables:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Control variables\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n end)\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"tip: Tip\nModeling is an art, and a tricky part of that art is figuring out which variables are state variables, and which are control variables. A good rule is: if you need a value of a control variable in some future node to make a decision, it is a state variable instead.","category":"page"},{"location":"tutorial/first_steps/#Random-variables","page":"An introduction to SDDP.jl","title":"Random variables","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The next step is to identify any random variables. In our example, we had","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"inflow: the quantity of water that flows into the reservoir each week [MWh/week]","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"To add an uncertain variable to the model, we create a new JuMP variable inflow, and then call the function SDDP.parameterize. The SDDP.parameterize function takes three arguments: the subproblem, a vector of realizations, and a corresponding vector of probabilities.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Control variables\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n end)\n # Random variables\n @variable(subproblem, inflow)\n Ω = [0.0, 50.0, 100.0]\n P = [1 / 3, 1 / 3, 1 / 3]\n SDDP.parameterize(subproblem, Ω, P) do ω\n return JuMP.fix(inflow, ω)\n end\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Note how we use the JuMP function JuMP.fix to set the value of the inflow variable to ω.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"warning: Warning\nSDDP.parameterize can only be called once in each subproblem definition! If your random variable is multi-variate, read Add multi-dimensional noise terms.","category":"page"},{"location":"tutorial/first_steps/#Transition-function-and-constraints","page":"An introduction to SDDP.jl","title":"Transition function and constraints","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Now that we've identified our variables, we can define the transition function and the constraints.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"For our problem, the state variable is the volume of water in the reservoir. The volume of water decreases in response to water being used for hydro generation and spillage. So the transition function is: volume.out = volume.in - hydro_generation - hydro_spill + inflow. (Note how we use volume.in and volume.out to refer to the incoming and outgoing state variables.)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"There is also a constraint that the total generation must sum to 150 MWh.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Both the transition function and any additional constraint are added using JuMP's @constraint and @constraints macro.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Control variables\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n end)\n # Random variables\n @variable(subproblem, inflow)\n Ω = [0.0, 50.0, 100.0]\n P = [1 / 3, 1 / 3, 1 / 3]\n SDDP.parameterize(subproblem, Ω, P) do ω\n return JuMP.fix(inflow, ω)\n end\n # Transition function and constraints\n @constraints(\n subproblem,\n begin\n volume.out == volume.in - hydro_generation - hydro_spill + inflow\n demand_constraint, hydro_generation + thermal_generation == 150\n end\n )\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/#Objective-function","page":"An introduction to SDDP.jl","title":"Objective function","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Finally, we need to add an objective function using @stageobjective. The objective of the agent is to minimize the cost of thermal generation. This is complicated by a fuel cost that depends on the node.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"One possibility is to use an if statement on node to define the correct objective:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Control variables\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n end)\n # Random variables\n @variable(subproblem, inflow)\n Ω = [0.0, 50.0, 100.0]\n P = [1 / 3, 1 / 3, 1 / 3]\n SDDP.parameterize(subproblem, Ω, P) do ω\n return JuMP.fix(inflow, ω)\n end\n # Transition function and constraints\n @constraints(\n subproblem,\n begin\n volume.out == volume.in - hydro_generation - hydro_spill + inflow\n demand_constraint, hydro_generation + thermal_generation == 150\n end\n )\n # Stage-objective\n if node == 1\n @stageobjective(subproblem, 50 * thermal_generation)\n elseif node == 2\n @stageobjective(subproblem, 100 * thermal_generation)\n else\n @assert node == 3\n @stageobjective(subproblem, 150 * thermal_generation)\n end\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"A second possibility is to use an array of fuel costs, and use node to index the correct value:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"function subproblem_builder(subproblem::Model, node::Int)\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Control variables\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n end)\n # Random variables\n @variable(subproblem, inflow)\n Ω = [0.0, 50.0, 100.0]\n P = [1 / 3, 1 / 3, 1 / 3]\n SDDP.parameterize(subproblem, Ω, P) do ω\n return JuMP.fix(inflow, ω)\n end\n # Transition function and constraints\n @constraints(\n subproblem,\n begin\n volume.out == volume.in - hydro_generation - hydro_spill + inflow\n demand_constraint, hydro_generation + thermal_generation == 150\n end\n )\n # Stage-objective\n fuel_cost = [50, 100, 150]\n @stageobjective(subproblem, fuel_cost[node] * thermal_generation)\n return subproblem\nend","category":"page"},{"location":"tutorial/first_steps/#Constructing-the-model","page":"An introduction to SDDP.jl","title":"Constructing the model","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Now that we've written our subproblem, we need to construct the full model. For that, we're going to need a linear solver. Let's choose HiGHS:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"using HiGHS","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"warning: Warning\nIn larger problems, you should use a more robust commercial LP solver like Gurobi. Read Words of warning for more details.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Then, we can create a full model using SDDP.PolicyGraph, passing our subproblem_builder function as the first argument, and our graph as the second:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"model = SDDP.PolicyGraph(\n subproblem_builder,\n graph;\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"sense: the optimization sense. Must be :Min or :Max.\nlower_bound: you must supply a valid bound on the objective. For our problem, we know that we cannot incur a negative cost so $0 is a valid lower bound.\noptimizer: This is borrowed directly from JuMP's Model constructor: Model(HiGHS.Optimizer)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Because linear policy graphs are the most commonly used structure, we can use SDDP.LinearPolicyGraph instead of passing SDDP.LinearGraph(3) to SDDP.PolicyGraph.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"model = SDDP.LinearPolicyGraph(\n subproblem_builder;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"There is also the option is to use Julia's do syntax to avoid needing to define a subproblem_builder function separately:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"model = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, node\n # State variables\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Control variables\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n end)\n # Random variables\n @variable(subproblem, inflow)\n Ω = [0.0, 50.0, 100.0]\n P = [1 / 3, 1 / 3, 1 / 3]\n SDDP.parameterize(subproblem, Ω, P) do ω\n return JuMP.fix(inflow, ω)\n end\n # Transition function and constraints\n @constraints(\n subproblem,\n begin\n volume.out == volume.in - hydro_generation - hydro_spill + inflow\n demand_constraint, hydro_generation + thermal_generation == 150\n end\n )\n # Stage-objective\n if node == 1\n @stageobjective(subproblem, 50 * thermal_generation)\n elseif node == 2\n @stageobjective(subproblem, 100 * thermal_generation)\n else\n @assert node == 3\n @stageobjective(subproblem, 150 * thermal_generation)\n end\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"info: Info\nJulia's do syntax is just a different way of passing an anonymous function inner to some function outer which takes inner as the first argument. For example, given:outer(inner::Function, x, y) = inner(x, y)thenouter(1, 2) do x, y\n return x^2 + y^2\nendis equivalent to:outer((x, y) -> x^2 + y^2, 1, 2)For our purpose, inner is subproblem_builder, and outer is SDDP.PolicyGraph.","category":"page"},{"location":"tutorial/first_steps/#Training-a-policy","page":"An introduction to SDDP.jl","title":"Training a policy","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Now we have a model, which is a description of the policy graph, we need to train a policy. Models can be trained using the SDDP.train function. It accepts a number of keyword arguments. iteration_limit terminates the training after the provided number of iterations.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"SDDP.train(model; iteration_limit = 10)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"There's a lot going on in this printout! Let's break it down.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The first section, \"problem,\" gives some problem statistics. In this example there are 3 nodes, 1 state variable, and 27 scenarios (3^3). We haven't solved this problem before so there are no existing cuts.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The \"options\" section lists some options we are using to solve the problem. For more information on the numerical stability report, read the Numerical stability report section.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The \"subproblem structure\" section also needs explaining. This looks at all of the nodes in the policy graph and reports the minimum and maximum number of variables and each constraint type in the corresponding subproblem. In this case each subproblem has 7 variables and various numbers of different constraint types. Note that the exact numbers may not correspond to the formulation as you wrote it, because SDDP.jl adds some extra variables for the cost-to-go function.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Then comes the iteration log, which is the main part of the printout. It has the following columns:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"iteration: the SDDP iteration\nsimulation: the cost of the single forward pass simulation for that iteration. This value is stochastic and is not guaranteed to improve over time. However, it's useful to check that the units are reasonable, and that it is not deterministic if you intended for the problem to be stochastic, etc.\nbound: this is a lower bound (upper if maximizing) for the value of the optimal policy. This bound should be monotonically improving (increasing if minimizing, decreasing if maximizing), but in some cases it can temporarily worsen due to cut selection, especially in the early iterations of the algorithm.\ntime (s): the total number of seconds spent solving so far\nsolves: the total number of subproblem solves to date. This can be very large!\npid: the ID of the processor used to solve that iteration. This should be 1 unless you are using parallel computation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"In addition, if the first character of a line is †, then SDDP.jl experienced numerical issues during the solve, but successfully recovered.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"The printout finishes with some summary statistics:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"status: why did the solver stop?\ntotal time (s), best bound, and total solves are the values from the last iteration of the solve.\nsimulation ci: a confidence interval that estimates the quality of the policy from the Simulation column.\nnumeric issues: the number of iterations that experienced numerical issues.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"warning: Warning\nThe simulation ci result can be misleading if you run a small number of iterations, or if the initial simulations are very bad. On a more technical note, it is an in-sample simulation, which may not reflect the true performance of the policy. See Obtaining bounds for more details.","category":"page"},{"location":"tutorial/first_steps/#Obtaining-the-decision-rule","page":"An introduction to SDDP.jl","title":"Obtaining the decision rule","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"After training a policy, we can create a decision rule using SDDP.DecisionRule:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"rule = SDDP.DecisionRule(model; node = 1)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Then, to evaluate the decision rule, we use SDDP.evaluate:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"solution = SDDP.evaluate(\n rule;\n incoming_state = Dict(:volume => 150.0),\n noise = 50.0,\n controls_to_record = [:hydro_generation, :thermal_generation],\n)","category":"page"},{"location":"tutorial/first_steps/#Simulating-the-policy","page":"An introduction to SDDP.jl","title":"Simulating the policy","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Once you have a trained policy, you can also simulate it using SDDP.simulate. The return value from simulate is a vector with one element for each replication. Each element is itself a vector, with one element for each stage. Each element, corresponding to a particular stage in a particular replication, is a dictionary that records information from the simulation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"simulations = SDDP.simulate(\n # The trained model to simulate.\n model,\n # The number of replications.\n 100,\n # A list of names to record the values of.\n [:volume, :thermal_generation, :hydro_generation, :hydro_spill],\n)\n\nreplication = 1\nstage = 2\nsimulations[replication][stage]","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Ignore many of the entries for now; they will be relevant later.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"One element of interest is :volume.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"outgoing_volume = map(simulations[1]) do node\n return node[:volume].out\nend","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Another is :thermal_generation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"thermal_generation = map(simulations[1]) do node\n return node[:thermal_generation]\nend","category":"page"},{"location":"tutorial/first_steps/#Obtaining-bounds","page":"An introduction to SDDP.jl","title":"Obtaining bounds","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Because the optimal policy is stochastic, one common approach to quantify the quality of the policy is to construct a confidence interval for the expected cost by summing the stage objectives along each simulation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"objectives = map(simulations) do simulation\n return sum(stage[:stage_objective] for stage in simulation)\nend\n\nμ, ci = SDDP.confidence_interval(objectives)\nprintln(\"Confidence interval: \", μ, \" ± \", ci)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"This confidence interval is an estimate for an upper bound of the policy's quality. We can calculate the lower bound using SDDP.calculate_bound.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"println(\"Lower bound: \", SDDP.calculate_bound(model))","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"tip: Tip\nThe upper- and lower-bounds are reversed if maximizing, i.e., SDDP.calculate_bound. returns an upper bound.","category":"page"},{"location":"tutorial/first_steps/#Custom-recorders","page":"An introduction to SDDP.jl","title":"Custom recorders","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"In addition to simulating the primal values of variables, we can also pass custom recorder functions. Each of these functions takes one argument, the JuMP subproblem corresponding to each node. This function gets called after we have solved each node as we traverse the policy graph in the simulation.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"For example, the dual of the demand constraint (which we named demand_constraint) corresponds to the price we should charge for electricity, since it represents the cost of each additional unit of demand. To calculate this, we can go:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"simulations = SDDP.simulate(\n model,\n 1; ## Perform a single simulation\n custom_recorders = Dict{Symbol,Function}(\n :price => (sp::JuMP.Model) -> JuMP.dual(sp[:demand_constraint]),\n ),\n)\n\nprices = map(simulations[1]) do node\n return node[:price]\nend","category":"page"},{"location":"tutorial/first_steps/#Extracting-the-marginal-water-values","page":"An introduction to SDDP.jl","title":"Extracting the marginal water values","text":"","category":"section"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Finally, we can use SDDP.ValueFunction and SDDP.evaluate to obtain and evaluate the value function at different points in the state-space.","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"note: Note\nBy \"value function\" we mean mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi), not the function V_i(x omega).","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"First, we construct a value function from the first subproblem:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"V = SDDP.ValueFunction(model; node = 1)","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"Then we can evaluate V at a point:","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"cost, price = SDDP.evaluate(V, Dict(\"volume\" => 10))","category":"page"},{"location":"tutorial/first_steps/","page":"An introduction to SDDP.jl","title":"An introduction to SDDP.jl","text":"This returns the cost-to-go (cost), and the gradient of the cost-to-go function with respect to each state variable. Note that since we are minimizing, the price has a negative sign: each additional unit of water leads to a decrease in the expected long-run cost.","category":"page"},{"location":"examples/StochDynamicProgramming.jl_stock/","page":"StochDynamicProgramming: the stock problem","title":"StochDynamicProgramming: the stock problem","text":"EditURL = \"StochDynamicProgramming.jl_stock.jl\"","category":"page"},{"location":"examples/StochDynamicProgramming.jl_stock/#StochDynamicProgramming:-the-stock-problem","page":"StochDynamicProgramming: the stock problem","title":"StochDynamicProgramming: the stock problem","text":"","category":"section"},{"location":"examples/StochDynamicProgramming.jl_stock/","page":"StochDynamicProgramming: the stock problem","title":"StochDynamicProgramming: the stock problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/StochDynamicProgramming.jl_stock/","page":"StochDynamicProgramming: the stock problem","title":"StochDynamicProgramming: the stock problem","text":"This example comes from StochDynamicProgramming.jl.","category":"page"},{"location":"examples/StochDynamicProgramming.jl_stock/","page":"StochDynamicProgramming: the stock problem","title":"StochDynamicProgramming: the stock problem","text":"using SDDP, HiGHS, Test\n\nfunction stock_example()\n model = SDDP.PolicyGraph(\n SDDP.LinearGraph(5);\n lower_bound = -2,\n optimizer = HiGHS.Optimizer,\n ) do sp, stage\n @variable(sp, 0 <= state <= 1, SDDP.State, initial_value = 0.5)\n @variable(sp, 0 <= control <= 0.5)\n @variable(sp, ξ)\n @constraint(sp, state.out == state.in - control + ξ)\n SDDP.parameterize(sp, 0.0:1/30:0.3) do ω\n return JuMP.fix(ξ, ω)\n end\n @stageobjective(sp, (sin(3 * stage) - 1) * control)\n end\n SDDP.train(model; log_frequency = 10)\n @test SDDP.calculate_bound(model) ≈ -1.471 atol = 0.001\n simulation_results = SDDP.simulate(model, 1_000)\n @test length(simulation_results) == 1_000\n μ = SDDP.Statistics.mean(\n sum(data[:stage_objective] for data in simulation) for\n simulation in simulation_results\n )\n @test μ ≈ -1.471 atol = 0.05\n return\nend\n\nstock_example()","category":"page"},{"location":"examples/agriculture_mccardle_farm/","page":"The farm planning problem","title":"The farm planning problem","text":"EditURL = \"agriculture_mccardle_farm.jl\"","category":"page"},{"location":"examples/agriculture_mccardle_farm/#The-farm-planning-problem","page":"The farm planning problem","title":"The farm planning problem","text":"","category":"section"},{"location":"examples/agriculture_mccardle_farm/","page":"The farm planning problem","title":"The farm planning problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/agriculture_mccardle_farm/","page":"The farm planning problem","title":"The farm planning problem","text":"There are four stages. The first stage is a deterministic planning stage. The next three are wait-and-see operational stages. The uncertainty in the three operational stages is a Markov chain for weather. There are three Markov states: dry, normal, and wet.","category":"page"},{"location":"examples/agriculture_mccardle_farm/","page":"The farm planning problem","title":"The farm planning problem","text":"Inspired by R. McCardle, Farm management optimization. Masters thesis, University of Louisville, Louisville, Kentucky, United States of America (2009).","category":"page"},{"location":"examples/agriculture_mccardle_farm/","page":"The farm planning problem","title":"The farm planning problem","text":"All data, including short variable names, is taken from that thesis.","category":"page"},{"location":"examples/agriculture_mccardle_farm/","page":"The farm planning problem","title":"The farm planning problem","text":"using SDDP, HiGHS, Test\n\nfunction test_mccardle_farm_model()\n S = [ # cutting, stage\n 0 1 2\n 0 0 1\n 0 0 0\n ]\n t = [60, 60, 245] # days in period\n D = [210, 210, 858] # demand\n q = [ # selling price per bale\n [4.5 4.5 4.5; 4.5 4.5 4.5; 4.5 4.5 4.5],\n [5.5 5.5 5.5; 5.5 5.5 5.5; 5.5 5.5 5.5],\n [6.5 6.5 6.5; 6.5 6.5 6.5; 6.5 6.5 6.5],\n ]\n b = [ # predicted yield (bales/acres) from cutting i in weather j.\n 30 75 37.5\n 15 37.5 18.25\n 7.5 18.75 9.325\n ]\n w = 3000 # max storage\n C = [50 50 50; 50 50 50; 50 50 50] # cost to grow hay\n r = [ # Cost per bale of hay from cutting i during weather condition j.\n [5 5 5; 5 5 5; 5 5 5],\n [6 6 6; 6 6 6; 6 6 6],\n [7 7 7; 7 7 7; 7 7 7],\n ]\n M = 60.0 # max acreage for planting\n H = 0.0 # initial inventory\n V = [0.05, 0.05, 0.05] # inventory cost\n L = 3000.0 # max demand for hay\n\n graph = SDDP.MarkovianGraph([\n ones(Float64, 1, 1),\n [0.14 0.69 0.17],\n [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17],\n [0.14 0.69 0.17; 0.14 0.69 0.17; 0.14 0.69 0.17],\n ])\n\n model = SDDP.PolicyGraph(\n graph;\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, index\n stage, weather = index\n # ===================== State Variables =====================\n # Area planted.\n @variable(subproblem, 0 <= acres <= M, SDDP.State, initial_value = M)\n @variable(\n subproblem,\n bales[i = 1:3] >= 0,\n SDDP.State,\n initial_value = (i == 1 ? H : 0)\n )\n # ===================== Variables =====================\n @variables(subproblem, begin\n buy[1:3] >= 0 # Quantity of bales to buy from each cutting.\n sell[1:3] >= 0 # Quantity of bales to sell from each cutting.\n eat[1:3] >= 0 # Quantity of bales to eat from each cutting.\n pen_p[1:3] >= 0 # Penalties\n pen_n[1:3] >= 0 # Penalties\n end)\n # ===================== Constraints =====================\n if stage == 1\n @constraint(subproblem, acres.out <= acres.in)\n @constraint(subproblem, [i = 1:3], bales[i].in == bales[i].out)\n else\n @expression(\n subproblem,\n cut_ex[c = 1:3],\n bales[c].in + buy[c] - eat[c] - sell[c] + pen_p[c] - pen_n[c]\n )\n @constraints(\n subproblem,\n begin\n # Cannot plant more land than previously cropped.\n acres.out <= acres.in\n # In each stage we need to meet demand.\n sum(eat) >= D[stage-1]\n # We can buy and sell other cuttings.\n bales[stage-1].out ==\n cut_ex[stage-1] + acres.in * b[stage-1, weather]\n [c = 1:3; c != stage - 1], bales[c].out == cut_ex[c]\n # There is some maximum storage.\n sum(bales[i].out for i in 1:3) <= w\n # We can only sell what is in storage.\n [c = 1:3], sell[c] <= bales[c].in\n # Maximum sales quantity.\n sum(sell) <= L\n end\n )\n end\n # ===================== Stage objective =====================\n if stage == 1\n @stageobjective(subproblem, 0.0)\n else\n @stageobjective(\n subproblem,\n 1000 * (sum(pen_p) + sum(pen_n)) +\n # cost of growing\n C[stage-1, weather] * acres.in +\n sum(\n # inventory cost\n V[stage-1] * bales[cutting].in * t[stage-1] +\n # purchase cost\n r[cutting][stage-1, weather] * buy[cutting] +\n # feed cost\n S[cutting, stage-1] * eat[cutting] -\n # sell reward\n q[cutting][stage-1, weather] * sell[cutting] for\n cutting in 1:3\n )\n )\n end\n return\n end\n SDDP.train(model)\n @test SDDP.termination_status(model) == :simulation_stopping\n @test SDDP.calculate_bound(model) ≈ 4074.1391 atol = 1e-5\nend\n\ntest_mccardle_farm_model()","category":"page"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"EditURL = \"vehicle_location.jl\"","category":"page"},{"location":"examples/vehicle_location/#Vehicle-location","page":"Vehicle location","title":"Vehicle location","text":"","category":"section"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"This problem is a version of the Ambulance dispatch problem. A hospital is located at 0 on the number line that stretches from 0 to 100. Ambulance bases are located at points 20, 40, 60, 80, and 100. When not responding to a call, Ambulances must be located at a base, or the hospital. In this example there are three ambulances.","category":"page"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"Example location:","category":"page"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"H B B B B B\n0 ---- 20 ---- 40 ---- 60 ---- 80 ---- 100","category":"page"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"Each stage, a call comes in from somewhere on the number line. The agent must decide which ambulance to dispatch. They pay the cost of twice the driving distance. If an ambulance is not dispatched in a stage, the ambulance can be relocated to a different base in preparation for future calls. This incurs a cost of the driving distance.","category":"page"},{"location":"examples/vehicle_location/","page":"Vehicle location","title":"Vehicle location","text":"using SDDP\nimport HiGHS\nimport Test\n\nfunction vehicle_location_model(duality_handler)\n hospital_location = 0\n bases = vcat(hospital_location, [20, 40, 60, 80, 100])\n vehicles = [1, 2, 3]\n requests = 0:10:100\n shift_cost(src, dest) = abs(src - dest)\n function dispatch_cost(base, request)\n return 2 * (abs(request - hospital_location) + abs(request - base))\n end\n # Initial state of emergency vehicles at bases. All ambulances start at the\n # hospital.\n initial_state(b, v) = b == hospital_location ? 1.0 : 0.0\n model = SDDP.LinearPolicyGraph(;\n stages = 10,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n # Current location of each vehicle at each base.\n @variable(\n sp,\n 0 <= location[b = bases, v = vehicles] <= 1,\n SDDP.State,\n initial_value = initial_state(b, v)\n )\n @variables(sp, begin\n # Which vehicle is dispatched?\n 0 <= dispatch[bases, vehicles] <= 1, Bin\n # Shifting vehicles between bases: [src, dest, vehicle]\n 0 <= shift[bases, bases, vehicles] <= 1, Bin\n end)\n # Flow of vehicles in and out of bases:\n @expression(\n sp,\n base_balance[b in bases, v in vehicles],\n location[b, v].in - dispatch[b, v] - sum(shift[b, :, v]) +\n sum(shift[:, b, v])\n )\n @constraints(\n sp,\n begin\n # Only one vehicle dispatched to call.\n sum(dispatch) == 1\n # Can only dispatch vehicle from base if vehicle is at that base.\n [b in bases, v in vehicles],\n dispatch[b, v] <= location[b, v].in\n # Can only shift vehicle if vehicle is at that src base.\n [b in bases, v in vehicles],\n sum(shift[b, :, v]) <= location[b, v].in\n # Can only shift vehicle if vehicle is not being dispatched.\n [b in bases, v in vehicles],\n sum(shift[b, :, v]) + dispatch[b, v] <= 1\n # Can't shift to same base.\n [b in bases, v in vehicles], shift[b, b, v] == 0\n # Update states for non-home/non-hospital bases.\n [b in bases[2:end], v in vehicles],\n location[b, v].out == base_balance[b, v]\n # Update states for home/hospital bases.\n [v in vehicles],\n location[hospital_location, v].out ==\n base_balance[hospital_location, v] + sum(dispatch[:, v])\n end\n )\n SDDP.parameterize(sp, requests) do request\n @stageobjective(\n sp,\n sum(\n # Distance to travel from base to emergency and then to hospital.\n dispatch[b, v] * dispatch_cost(b, request) +\n # Distance travelled by vehicles relocating bases.\n sum(\n shift_cost(b, dest) * shift[b, dest, v] for\n dest in bases\n ) for b in bases, v in vehicles\n )\n )\n end\n end\n if get(ARGS, 1, \"\") == \"--write\"\n # Run `$ julia vehicle_location.jl --write` to update the benchmark\n # model directory\n model_dir = joinpath(@__DIR__, \"..\", \"..\", \"..\", \"benchmarks\", \"models\")\n SDDP.write_to_file(\n model,\n joinpath(model_dir, \"vehicle_location.sof.json.gz\");\n test_scenarios = 100,\n )\n exit(0)\n end\n SDDP.train(\n model;\n iteration_limit = 20,\n log_frequency = 10,\n cut_deletion_minimum = 100,\n duality_handler = duality_handler,\n )\n Test.@test SDDP.calculate_bound(model) >= 1000\n return\nend\n\n# TODO(odow): find out why this fails\n# vehicle_location_model(SDDP.ContinuousConicDuality())","category":"page"},{"location":"guides/improve_computational_performance/#Improve-computational-performance","page":"Improve computational performance","title":"Improve computational performance","text":"","category":"section"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"SDDP is a computationally intensive algorithm. Here are some suggestions for how the computational performance can be improved.","category":"page"},{"location":"guides/improve_computational_performance/#Numerical-stability-(again)","page":"Improve computational performance","title":"Numerical stability (again)","text":"","category":"section"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"We've already discussed this in the Numerical stability section of Words of warning. But, it's so important that we're going to say it again: improving the problem scaling is one of the best ways to improve the numerical performance of your models.","category":"page"},{"location":"guides/improve_computational_performance/#Solver-selection","page":"Improve computational performance","title":"Solver selection","text":"","category":"section"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"The majority of the solution time is spent inside the low-level solvers. Choosing which solver (and the associated settings) correctly can lead to big speed-ups.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"Choose a commercial solver.\nOptions include CPLEX, Gurobi, and Xpress. Using free solvers such as CLP and HiGHS isn't a viable approach for large problems.\nTry different solvers.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"Even commercial solvers can have wildly different solution times. We've seen models on which CPLEX was 50% fast than Gurobi, and vice versa.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"Experiment with different solver options.\nUsing the default settings is usually a good option. However, sometimes it can pay to change these. In particular, forcing solvers to use the dual simplex algorithm (e.g., Method=1 in Gurobi ) is usually a performance win.","category":"page"},{"location":"guides/improve_computational_performance/#Single-cut-vs.-multi-cut","page":"Improve computational performance","title":"Single-cut vs. multi-cut","text":"","category":"section"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"There are two competing ways that cuts can be created in SDDP: single-cut and multi-cut. By default, SDDP.jl uses the single-cut version of SDDP.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"The performance of each method is problem-dependent. We recommend that you try both in order to see which one performs better. In general, the single-cut method works better when the number of realizations of the stagewise-independent random variable is large, whereas the multi-cut method works better on small problems. However, the multi-cut method can cause numerical stability problems, particularly if used in conjunction with objective or belief state variables.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"You can switch between the methods by passing the relevant flag to cut_type in SDDP.train.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"SDDP.train(model; cut_type = SDDP.SINGLE_CUT)\nSDDP.train(model; cut_type = SDDP.MULTI_CUT)","category":"page"},{"location":"guides/improve_computational_performance/#Parallelism","page":"Improve computational performance","title":"Parallelism","text":"","category":"section"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"SDDP.jl can take advantage of the parallel nature of modern computers to solve problems across multiple threads.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"Start Julia from a command line with N threads using the --threads flag:","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"julia --threads N","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"Then, pass an instance of SDDP.Threaded to the parallel_scheme argument of SDDP.train and SDDP.simulate.","category":"page"},{"location":"guides/improve_computational_performance/","page":"Improve computational performance","title":"Improve computational performance","text":"using SDDP, HiGHS\nmodel = SDDP.LinearPolicyGraph(\n stages = 2, lower_bound = 0, optimizer = HiGHS.Optimizer\n) do sp, t\n @variable(sp, x >= 0, SDDP.State, initial_value = 1)\n @stageobjective(sp, x.out)\nend\nSDDP.train(model; iteration_limit = 10, parallel_scheme = SDDP.Threaded())\nSDDP.simulate(model, 10; parallel_scheme = SDDP.Threaded())","category":"page"},{"location":"examples/FAST_quickstart/","page":"FAST: the quickstart problem","title":"FAST: the quickstart problem","text":"EditURL = \"FAST_quickstart.jl\"","category":"page"},{"location":"examples/FAST_quickstart/#FAST:-the-quickstart-problem","page":"FAST: the quickstart problem","title":"FAST: the quickstart problem","text":"","category":"section"},{"location":"examples/FAST_quickstart/","page":"FAST: the quickstart problem","title":"FAST: the quickstart problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/FAST_quickstart/","page":"FAST: the quickstart problem","title":"FAST: the quickstart problem","text":"An implementation of the QuickStart example from FAST","category":"page"},{"location":"examples/FAST_quickstart/","page":"FAST: the quickstart problem","title":"FAST: the quickstart problem","text":"using SDDP, HiGHS, Test\n\nfunction fast_quickstart()\n model = SDDP.PolicyGraph(\n SDDP.LinearGraph(2);\n lower_bound = -5,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n @variable(sp, x >= 0, SDDP.State, initial_value = 0.0)\n if t == 1\n @stageobjective(sp, x.out)\n else\n @variable(sp, s >= 0)\n @constraint(sp, s <= x.in)\n SDDP.parameterize(sp, [2, 3]) do ω\n return JuMP.set_upper_bound(s, ω)\n end\n @stageobjective(sp, -2s)\n end\n end\n\n det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\n set_silent(det)\n JuMP.optimize!(det)\n @test JuMP.objective_value(det) == -2\n\n SDDP.train(model; log_every_iteration = true)\n @test SDDP.calculate_bound(model) == -2\nend\n\nfast_quickstart()","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_3stages/","page":"StructDualDynProg: Problem 5.2, 3 stages","title":"StructDualDynProg: Problem 5.2, 3 stages","text":"EditURL = \"StructDualDynProg.jl_prob5.2_3stages.jl\"","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_3stages/#StructDualDynProg:-Problem-5.2,-3-stages","page":"StructDualDynProg: Problem 5.2, 3 stages","title":"StructDualDynProg: Problem 5.2, 3 stages","text":"","category":"section"},{"location":"examples/StructDualDynProg.jl_prob5.2_3stages/","page":"StructDualDynProg: Problem 5.2, 3 stages","title":"StructDualDynProg: Problem 5.2, 3 stages","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_3stages/","page":"StructDualDynProg: Problem 5.2, 3 stages","title":"StructDualDynProg: Problem 5.2, 3 stages","text":"This example comes from StochasticDualDynamicProgramming.jl.","category":"page"},{"location":"examples/StructDualDynProg.jl_prob5.2_3stages/","page":"StructDualDynProg: Problem 5.2, 3 stages","title":"StructDualDynProg: Problem 5.2, 3 stages","text":"using SDDP, HiGHS, Test\n\nfunction test_prob52_3stages()\n model = SDDP.LinearPolicyGraph(;\n stages = 3,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n n = 4\n m = 3\n i_c = [16, 5, 32, 2]\n C = [25, 80, 6.5, 160]\n T = [8760, 7000, 1500] / 8760\n D2 = [diff([0, 3919, 7329, 10315]) diff([0, 7086, 9004, 11169])]\n p2 = [0.9, 0.1]\n @variable(sp, x[i = 1:n] >= 0, SDDP.State, initial_value = 0.0)\n @variables(sp, begin\n y[1:n, 1:m] >= 0\n v[1:n] >= 0\n penalty >= 0\n ξ[j = 1:m]\n end)\n @constraints(sp, begin\n [i = 1:n], x[i].out == x[i].in + v[i]\n [i = 1:n], sum(y[i, :]) <= x[i].in\n [j = 1:m], sum(y[:, j]) + penalty >= ξ[j]\n end)\n @stageobjective(sp, i_c'v + C' * y * T + 1e5 * penalty)\n if t != 1 # no uncertainty in first stage\n SDDP.parameterize(sp, 1:size(D2, 2), p2) do ω\n for j in 1:m\n JuMP.fix(ξ[j], D2[j, ω])\n end\n end\n end\n if t == 3\n @constraint(sp, sum(v) == 0)\n end\n end\n\n det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\n set_silent(det)\n JuMP.optimize!(det)\n @test JuMP.objective_value(det) ≈ 406712.49 atol = 0.1\n\n SDDP.train(model; log_frequency = 10)\n @test SDDP.calculate_bound(model) ≈ 406712.49 atol = 0.1\n return\nend\n\ntest_prob52_3stages()","category":"page"},{"location":"examples/infinite_horizon_hydro_thermal/","page":"Infinite horizon hydro-thermal","title":"Infinite horizon hydro-thermal","text":"EditURL = \"infinite_horizon_hydro_thermal.jl\"","category":"page"},{"location":"examples/infinite_horizon_hydro_thermal/#Infinite-horizon-hydro-thermal","page":"Infinite horizon hydro-thermal","title":"Infinite horizon hydro-thermal","text":"","category":"section"},{"location":"examples/infinite_horizon_hydro_thermal/","page":"Infinite horizon hydro-thermal","title":"Infinite horizon hydro-thermal","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/infinite_horizon_hydro_thermal/","page":"Infinite horizon hydro-thermal","title":"Infinite horizon hydro-thermal","text":"using SDDP, HiGHS, Test, Statistics\n\nfunction infinite_hydro_thermal(; cut_type)\n Ω = [\n (inflow = 0.0, demand = 7.5),\n (inflow = 5.0, demand = 5),\n (inflow = 10.0, demand = 2.5),\n ]\n graph = SDDP.Graph(\n :root_node,\n [:week],\n [(:root_node => :week, 1.0), (:week => :week, 0.9)],\n )\n model = SDDP.PolicyGraph(\n graph;\n lower_bound = 0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n @variable(\n subproblem,\n 5.0 <= reservoir <= 15.0,\n SDDP.State,\n initial_value = 10.0\n )\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n spill >= 0\n inflow\n demand\n end)\n @constraints(\n subproblem,\n begin\n reservoir.out == reservoir.in - hydro_generation - spill + inflow\n hydro_generation + thermal_generation == demand\n end\n )\n @stageobjective(subproblem, 10 * spill + thermal_generation)\n SDDP.parameterize(subproblem, Ω) do ω\n JuMP.fix(inflow, ω.inflow)\n return JuMP.fix(demand, ω.demand)\n end\n end\n SDDP.train(\n model;\n cut_type = cut_type,\n log_frequency = 100,\n sampling_scheme = SDDP.InSampleMonteCarlo(; terminate_on_cycle = true),\n parallel_scheme = SDDP.Serial(),\n cycle_discretization_delta = 0.1,\n )\n @test SDDP.calculate_bound(model) ≈ 119.167 atol = 0.1\n\n results = SDDP.simulate(model, 500)\n objectives =\n [sum(s[:stage_objective] for s in simulation) for simulation in results]\n sample_mean = round(Statistics.mean(objectives); digits = 2)\n sample_ci = round(1.96 * Statistics.std(objectives) / sqrt(500); digits = 2)\n println(\"Confidence_interval = $(sample_mean) ± $(sample_ci)\")\n @test sample_mean - sample_ci <= 119.167 <= sample_mean + sample_ci\n return\nend\n\ninfinite_hydro_thermal(; cut_type = SDDP.SINGLE_CUT)\ninfinite_hydro_thermal(; cut_type = SDDP.MULTI_CUT)","category":"page"},{"location":"apireference/#api_reference_list","page":"API Reference","title":"API Reference","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"This page lists the public API of SDDP.jl. Any functions in SDDP that are not listed here are considered part of the private API and may change in any future release.","category":"page"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"info: Info\nThis page is a semi-structured list of the SDDP.jl API. For a more structured overview, read the How-to guides or Tutorial parts of this documentation.","category":"page"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"Load SDDP using:","category":"page"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"using SDDP","category":"page"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP exports only @stageobjective. Therefore, all other calls must be prefixed with SDDP..","category":"page"},{"location":"apireference/#Graph","page":"API Reference","title":"Graph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Graph","category":"page"},{"location":"apireference/#SDDP.Graph","page":"API Reference","title":"SDDP.Graph","text":"Graph(root_node::T) where T\n\nCreate an empty graph struture with the root node root_node.\n\nExample\n\njulia> graph = SDDP.Graph(0)\nRoot\n 0\nNodes\n {}\nArcs\n {}\n\njulia> graph = SDDP.Graph(:root)\nRoot\n root\nNodes\n {}\nArcs\n {}\n\njulia> graph = SDDP.Graph((0, 0))\nRoot\n (0, 0)\nNodes\n {}\nArcs\n {}\n\n\n\n\n\n","category":"type"},{"location":"apireference/#add_node","page":"API Reference","title":"add_node","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.add_node","category":"page"},{"location":"apireference/#SDDP.add_node","page":"API Reference","title":"SDDP.add_node","text":"add_node(graph::Graph{T}, node::T) where {T}\n\nAdd a node to the graph graph.\n\nExamples\n\njulia> graph = SDDP.Graph(:root);\n\njulia> SDDP.add_node(graph, :A)\n\njulia> graph\nRoot\n root\nNodes\n A\nArcs\n {}\n\njulia> graph = SDDP.Graph(0);\n\njulia> SDDP.add_node(graph, 2)\n\njulia> graph\nRoot\n 0\nNodes\n 2\nArcs\n {}\n\n\n\n\n\n","category":"function"},{"location":"apireference/#add_edge","page":"API Reference","title":"add_edge","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.add_edge","category":"page"},{"location":"apireference/#SDDP.add_edge","page":"API Reference","title":"SDDP.add_edge","text":"add_edge(graph::Graph{T}, edge::Pair{T, T}, probability::Float64) where {T}\n\nAdd an edge to the graph graph.\n\nExamples\n\njulia> graph = SDDP.Graph(0);\n\njulia> SDDP.add_node(graph, 1)\n\njulia> SDDP.add_edge(graph, 0 => 1, 0.9)\n\njulia> graph\nRoot\n 0\nNodes\n 1\nArcs\n 0 => 1 w.p. 0.9\n\njulia> graph = SDDP.Graph(:root);\n\njulia> SDDP.add_node(graph, :A)\n\njulia> SDDP.add_edge(graph, :root => :A, 1.0)\n\njulia> graph\nRoot\n root\nNodes\n A\nArcs\n root => A w.p. 1.0\n\n\n\n\n\n","category":"function"},{"location":"apireference/#add_ambiguity_set","page":"API Reference","title":"add_ambiguity_set","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.add_ambiguity_set","category":"page"},{"location":"apireference/#SDDP.add_ambiguity_set","page":"API Reference","title":"SDDP.add_ambiguity_set","text":"add_ambiguity_set(\n graph::Graph{T},\n set::Vector{T},\n lipschitz::Vector{Float64},\n) where {T}\n\nAdd set to the belief partition of graph.\n\nlipschitz is a vector of Lipschitz constants, with one element for each node in set. The Lipschitz constant is the maximum slope of the cost-to-go function with respect to the belief state associated with each node at any point in the state-space.\n\nExamples\n\njulia> graph = SDDP.LinearGraph(3)\nRoot\n 0\nNodes\n 1\n 2\n 3\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 3 w.p. 1.0\n\njulia> SDDP.add_ambiguity_set(graph, [1, 2], [1e3, 1e2])\n\njulia> SDDP.add_ambiguity_set(graph, [3], [1e5])\n\njulia> graph\nRoot\n 0\nNodes\n 1\n 2\n 3\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 3 w.p. 1.0\nPartitions\n {1, 2}\n {3}\n\n\n\n\n\nadd_ambiguity_set(graph::Graph{T}, set::Vector{T}, lipschitz::Float64)\n\nAdd set to the belief partition of graph.\n\nlipschitz is a Lipschitz constant for each node in set. The Lipschitz constant is the maximum slope of the cost-to-go function with respect to the belief state associated with each node at any point in the state-space.\n\nExamples\n\njulia> graph = SDDP.LinearGraph(3);\n\njulia> SDDP.add_ambiguity_set(graph, [1, 2], 1e3)\n\njulia> SDDP.add_ambiguity_set(graph, [3], 1e5)\n\njulia> graph\nRoot\n 0\nNodes\n 1\n 2\n 3\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 3 w.p. 1.0\nPartitions\n {1, 2}\n {3}\n\n\n\n\n\n","category":"function"},{"location":"apireference/#LinearGraph","page":"API Reference","title":"LinearGraph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.LinearGraph","category":"page"},{"location":"apireference/#SDDP.LinearGraph","page":"API Reference","title":"SDDP.LinearGraph","text":"LinearGraph(stages::Int)\n\nCreate a linear graph with stages number of nodes.\n\nExamples\n\njulia> graph = SDDP.LinearGraph(3)\nRoot\n 0\nNodes\n 1\n 2\n 3\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 3 w.p. 1.0\n\n\n\n\n\n","category":"function"},{"location":"apireference/#MarkovianGraph","page":"API Reference","title":"MarkovianGraph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.MarkovianGraph","category":"page"},{"location":"apireference/#SDDP.MarkovianGraph","page":"API Reference","title":"SDDP.MarkovianGraph","text":"MarkovianGraph(transition_matrices::Vector{Matrix{Float64}})\n\nConstruct a Markovian graph from the vector of transition matrices.\n\ntransition_matrices[t][i, j] gives the probability of transitioning from Markov state i in stage t - 1 to Markov state j in stage t.\n\nThe dimension of the first transition matrix should be (1, N), and transition_matrics[1][1, i] is the probability of transitioning from the root node to the Markov state i.\n\nExamples\n\njulia> graph = SDDP.MarkovianGraph([ones(1, 1), [0.5 0.5], [0.8 0.2; 0.2 0.8]])\nRoot\n (0, 1)\nNodes\n (1, 1)\n (2, 1)\n (2, 2)\n (3, 1)\n (3, 2)\nArcs\n (0, 1) => (1, 1) w.p. 1.0\n (1, 1) => (2, 1) w.p. 0.5\n (1, 1) => (2, 2) w.p. 0.5\n (2, 1) => (3, 1) w.p. 0.8\n (2, 1) => (3, 2) w.p. 0.2\n (2, 2) => (3, 1) w.p. 0.2\n (2, 2) => (3, 2) w.p. 0.8\n\n\n\n\n\nMarkovianGraph(;\n stages::Int,\n transition_matrix::Matrix{Float64},\n root_node_transition::Vector{Float64},\n)\n\nConstruct a Markovian graph object with stages number of stages and time-independent Markov transition probabilities.\n\ntransition_matrix must be a square matrix, and the probability of transitioning from Markov state i in stage t to Markov state j in stage t + 1 is given by transition_matrix[i, j].\n\nroot_node_transition[i] is the probability of transitioning from the root node to Markov state i in the first stage.\n\nExamples\n\njulia> graph = SDDP.MarkovianGraph(;\n stages = 3,\n transition_matrix = [0.8 0.2; 0.2 0.8],\n root_node_transition = [0.5, 0.5],\n )\nRoot\n (0, 1)\nNodes\n (1, 1)\n (1, 2)\n (2, 1)\n (2, 2)\n (3, 1)\n (3, 2)\nArcs\n (0, 1) => (1, 1) w.p. 0.5\n (0, 1) => (1, 2) w.p. 0.5\n (1, 1) => (2, 1) w.p. 0.8\n (1, 1) => (2, 2) w.p. 0.2\n (1, 2) => (2, 1) w.p. 0.2\n (1, 2) => (2, 2) w.p. 0.8\n (2, 1) => (3, 1) w.p. 0.8\n (2, 1) => (3, 2) w.p. 0.2\n (2, 2) => (3, 1) w.p. 0.2\n (2, 2) => (3, 2) w.p. 0.8\n\n\n\n\n\nMarkovianGraph(\n simulator::Function;\n budget::Union{Int,Vector{Int}},\n scenarios::Int = 1000,\n)\n\nConstruct a Markovian graph by fitting Markov chain to scenarios generated by simulator().\n\nbudget is the total number of nodes in the resulting Markov chain. This can either be specified as a single Int, in which case we will attempt to intelligently distributed the nodes between stages. Alternatively, budget can be a Vector{Int}, which details the number of Markov state to have in each stage.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#UnicyclicGraph","page":"API Reference","title":"UnicyclicGraph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.UnicyclicGraph","category":"page"},{"location":"apireference/#SDDP.UnicyclicGraph","page":"API Reference","title":"SDDP.UnicyclicGraph","text":"UnicyclicGraph(discount_factor::Float64; num_nodes::Int = 1)\n\nConstruct a graph composed of num_nodes nodes that form a single cycle, with a probability of discount_factor of continuing the cycle.\n\nExamples\n\njulia> graph = SDDP.UnicyclicGraph(0.9; num_nodes = 2)\nRoot\n 0\nNodes\n 1\n 2\nArcs\n 0 => 1 w.p. 1.0\n 1 => 2 w.p. 1.0\n 2 => 1 w.p. 0.9\n\n\n\n\n\n","category":"function"},{"location":"apireference/#LinearPolicyGraph","page":"API Reference","title":"LinearPolicyGraph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.LinearPolicyGraph","category":"page"},{"location":"apireference/#SDDP.LinearPolicyGraph","page":"API Reference","title":"SDDP.LinearPolicyGraph","text":"LinearPolicyGraph(builder::Function; stages::Int, kwargs...)\n\nCreate a linear policy graph with stages number of stages.\n\nKeyword arguments\n\nstages: the number of stages in the graph\nkwargs: other keyword arguments are passed to SDDP.PolicyGraph.\n\nExamples\n\njulia> SDDP.LinearPolicyGraph(; stages = 2, lower_bound = 0.0) do sp, t\n # ... build model ...\nend\nA policy graph with 2 nodes.\nNode indices: 1, 2\n\nis equivalent to\n\njulia> graph = SDDP.LinearGraph(2);\n\njulia> SDDP.PolicyGraph(graph; lower_bound = 0.0) do sp, t\n # ... build model ...\nend\nA policy graph with 2 nodes.\nNode indices: 1, 2\n\n\n\n\n\n","category":"function"},{"location":"apireference/#MarkovianPolicyGraph","page":"API Reference","title":"MarkovianPolicyGraph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.MarkovianPolicyGraph","category":"page"},{"location":"apireference/#SDDP.MarkovianPolicyGraph","page":"API Reference","title":"SDDP.MarkovianPolicyGraph","text":"MarkovianPolicyGraph(\n builder::Function;\n transition_matrices::Vector{Array{Float64,2}},\n kwargs...\n)\n\nCreate a Markovian policy graph based on the transition matrices given in transition_matrices.\n\nKeyword arguments\n\ntransition_matrices[t][i, j] gives the probability of transitioning from Markov state i in stage t - 1 to Markov state j in stage t. The dimension of the first transition matrix should be (1, N), and transition_matrics[1][1, i] is the probability of transitioning from the root node to the Markov state i.\nkwargs: other keyword arguments are passed to SDDP.PolicyGraph.\n\nSee also\n\nSee SDDP.MarkovianGraph for other ways of specifying a Markovian policy graph.\n\nSee SDDP.PolicyGraph for the other keyword arguments.\n\nExamples\n\njulia> SDDP.MarkovianPolicyGraph(;\n transition_matrices = [ones(1, 1), [0.5 0.5], [0.8 0.2; 0.2 0.8]],\n lower_bound = 0.0,\n ) do sp, node\n # ... build model ...\n end\nA policy graph with 5 nodes.\n Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)\n\nis equivalent to\n\njulia> graph = SDDP.MarkovianGraph([ones(1, 1), [0.5 0.5], [0.8 0.2; 0.2 0.8]]);\n\njulia> SDDP.PolicyGraph(graph; lower_bound = 0.0) do sp, t\n # ... build model ...\nend\nA policy graph with 5 nodes.\n Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)\n\n\n\n\n\n","category":"function"},{"location":"apireference/#PolicyGraph","page":"API Reference","title":"PolicyGraph","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.PolicyGraph","category":"page"},{"location":"apireference/#SDDP.PolicyGraph","page":"API Reference","title":"SDDP.PolicyGraph","text":"PolicyGraph(\n builder::Function,\n graph::Graph{T};\n sense::Symbol = :Min,\n lower_bound = -Inf,\n upper_bound = Inf,\n optimizer = nothing,\n) where {T}\n\nConstruct a policy graph based on the graph structure of graph. (See SDDP.Graph for details.)\n\nKeyword arguments\n\nsense: whether we are minimizing (:Min) or maximizing (:Max).\nlower_bound: if mimimizing, a valid lower bound for the cost to go in all subproblems.\nupper_bound: if maximizing, a valid upper bound for the value to go in all subproblems.\noptimizer: the optimizer to use for each of the subproblems\n\nExamples\n\nfunction builder(subproblem::JuMP.Model, index)\n # ... subproblem definition ...\nend\n\nmodel = PolicyGraph(\n builder,\n graph;\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n)\n\nOr, using the Julia do ... end syntax:\n\nmodel = PolicyGraph(\n graph;\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, index\n # ... subproblem definitions ...\nend\n\n\n\n\n\n","category":"type"},{"location":"apireference/#@stageobjective","page":"API Reference","title":"@stageobjective","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"@stageobjective","category":"page"},{"location":"apireference/#SDDP.@stageobjective","page":"API Reference","title":"SDDP.@stageobjective","text":"@stageobjective(subproblem, expr)\n\nSet the stage-objective of subproblem to expr.\n\nExamples\n\n@stageobjective(subproblem, 2x + y)\n\n\n\n\n\n","category":"macro"},{"location":"apireference/#parameterize","page":"API Reference","title":"parameterize","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.parameterize","category":"page"},{"location":"apireference/#SDDP.parameterize","page":"API Reference","title":"SDDP.parameterize","text":"parameterize(\n modify::Function,\n subproblem::JuMP.Model,\n realizations::Vector{T},\n probability::Vector{Float64} = fill(1.0 / length(realizations))\n) where {T}\n\nAdd a parameterization function modify to subproblem. The modify function takes one argument and modifies subproblem based on the realization of the noise sampled from realizations with corresponding probabilities probability.\n\nIn order to conduct an out-of-sample simulation, modify should accept arguments that are not in realizations (but still of type T).\n\nExamples\n\nSDDP.parameterize(subproblem, [1, 2, 3], [0.4, 0.3, 0.3]) do ω\n JuMP.set_upper_bound(x, ω)\nend\n\n\n\n\n\nparameterize(node::Node, noise)\n\nParameterize node node with the noise noise.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#add_objective_state","page":"API Reference","title":"add_objective_state","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.add_objective_state","category":"page"},{"location":"apireference/#SDDP.add_objective_state","page":"API Reference","title":"SDDP.add_objective_state","text":"add_objective_state(update::Function, subproblem::JuMP.Model; kwargs...)\n\nAdd an objective state variable to subproblem.\n\nRequired kwargs are:\n\ninitial_value: The initial value of the objective state variable at the root node.\nlipschitz: The lipschitz constant of the objective state variable.\n\nSetting a tight value for the lipschitz constant can significantly improve the speed of convergence.\n\nOptional kwargs are:\n\nlower_bound: A valid lower bound for the objective state variable. Can be -Inf.\nupper_bound: A valid upper bound for the objective state variable. Can be +Inf.\n\nSetting tight values for these optional variables can significantly improve the speed of convergence.\n\nIf the objective state is N-dimensional, each keyword argument must be an NTuple{N,Float64}. For example, initial_value = (0.0, 1.0).\n\n\n\n\n\n","category":"function"},{"location":"apireference/#objective_state","page":"API Reference","title":"objective_state","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.objective_state","category":"page"},{"location":"apireference/#SDDP.objective_state","page":"API Reference","title":"SDDP.objective_state","text":"objective_state(subproblem::JuMP.Model)\n\nReturn the current objective state of the problem.\n\nCan only be called from SDDP.parameterize.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#Noise","page":"API Reference","title":"Noise","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Noise","category":"page"},{"location":"apireference/#SDDP.Noise","page":"API Reference","title":"SDDP.Noise","text":"Noise(support, probability)\n\nAn atom of a discrete random variable at the point of support support and associated probability probability.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#numerical_stability_report","page":"API Reference","title":"numerical_stability_report","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.numerical_stability_report","category":"page"},{"location":"apireference/#SDDP.numerical_stability_report","page":"API Reference","title":"SDDP.numerical_stability_report","text":"numerical_stability_report(\n [io::IO = stdout,]\n model::PolicyGraph;\n by_node::Bool = false,\n print::Bool = true,\n warn::Bool = true,\n)\n\nPrint a report identifying possible numeric stability issues.\n\nKeyword arguments\n\nIf by_node, print a report for each node in the graph.\nIf print, print to io.\nIf warn, warn if the coefficients may cause numerical issues.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#train","page":"API Reference","title":"train","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.train","category":"page"},{"location":"apireference/#SDDP.train","page":"API Reference","title":"SDDP.train","text":"SDDP.train(model::PolicyGraph; kwargs...)\n\nTrain the policy for model.\n\nKeyword arguments\n\niteration_limit::Int: number of iterations to conduct before termination.\ntime_limit::Float64: number of seconds to train before termination.\nstoping_rules: a vector of SDDP.AbstractStoppingRules. Defaults to SimulationStoppingRule.\nprint_level::Int: control the level of printing to the screen. Defaults to 1. Set to 0 to disable all printing.\nlog_file::String: filepath at which to write a log of the training progress. Defaults to SDDP.log.\nlog_frequency::Int: control the frequency with which the logging is outputted (iterations/log). It must be at least 1. Defaults to 1.\nlog_every_seconds::Float64: control the frequency with which the logging is outputted (seconds/log). Defaults to 0.0.\nlog_every_iteration::Bool; over-rides log_frequency and log_every_seconds to force every iteration to be printed. Defaults to false.\nrun_numerical_stability_report::Bool: generate (and print) a numerical stability report prior to solve. Defaults to true.\nrefine_at_similar_nodes::Bool: if SDDP can detect that two nodes have the same children, it can cheaply add a cut discovered at one to the other. In almost all cases this should be set to true.\ncut_deletion_minimum::Int: the minimum number of cuts to cache before deleting cuts from the subproblem. The impact on performance is solver specific; however, smaller values result in smaller subproblems (and therefore quicker solves), at the expense of more time spent performing cut selection.\nrisk_measure: the risk measure to use at each node. Defaults to Expectation.\nroot_node_risk_measure::AbstractRiskMeasure: the risk measure to use at the root node when computing the Bound column. Note that the choice of this option does not change the primal policy, and it applies only if the transition from the root node to the first stage is stochastic. Defaults to Expectation.\nsampling_scheme: a sampling scheme to use on the forward pass of the algorithm. Defaults to InSampleMonteCarlo.\nbackward_sampling_scheme: a backward pass sampling scheme to use on the backward pass of the algorithm. Defaults to CompleteSampler.\ncut_type: choose between SDDP.SINGLE_CUT and SDDP.MULTI_CUT versions of SDDP.\ndashboard::Bool: open a visualization of the training over time. Defaults to false.\nparallel_scheme::AbstractParallelScheme: specify a scheme for solving in parallel. Defaults to Threaded().\nforward_pass::AbstractForwardPass: specify a scheme to use for the forward passes.\nforward_pass_resampling_probability::Union{Nothing,Float64}: set to a value in (0, 1) to enable RiskAdjustedForwardPass. Defaults to nothing (disabled).\nadd_to_existing_cuts::Bool: set to true to allow training a model that was previously trained. Defaults to false.\nduality_handler::AbstractDualityHandler: specify a duality handler to use when creating cuts.\npost_iteration_callback::Function: a callback with the signature post_iteration_callback(::IterationResult) that is evaluated after each iteration of the algorithm.\n\nThere is also a special option for infinite horizon problems\n\ncycle_discretization_delta: the maximum distance between states allowed on the forward pass. This is for advanced users only and needs to be used in conjunction with a different sampling_scheme.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#termination_status","page":"API Reference","title":"termination_status","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.termination_status","category":"page"},{"location":"apireference/#SDDP.termination_status","page":"API Reference","title":"SDDP.termination_status","text":"termination_status(model::PolicyGraph)::Symbol\n\nQuery the reason why the training stopped.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#write_cuts_to_file","page":"API Reference","title":"write_cuts_to_file","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.write_cuts_to_file","category":"page"},{"location":"apireference/#SDDP.write_cuts_to_file","page":"API Reference","title":"SDDP.write_cuts_to_file","text":"write_cuts_to_file(\n model::PolicyGraph{T},\n filename::String;\n kwargs...,\n) where {T}\n\nWrite the cuts that form the policy in model to filename in JSON format.\n\nKeyword arguments\n\nnode_name_parser is a function which converts the name of each node into a string representation. It has the signature: node_name_parser(::T)::String.\nwrite_only_selected_cuts write only the selected cuts to the json file. Defaults to false.\n\nSee also SDDP.read_cuts_from_file.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#read_cuts_from_file","page":"API Reference","title":"read_cuts_from_file","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.read_cuts_from_file","category":"page"},{"location":"apireference/#SDDP.read_cuts_from_file","page":"API Reference","title":"SDDP.read_cuts_from_file","text":"read_cuts_from_file(\n model::PolicyGraph{T},\n filename::String;\n kwargs...,\n) where {T}\n\nRead cuts (saved using SDDP.write_cuts_to_file) from filename into model.\n\nSince T can be an arbitrary Julia type, the conversion to JSON is lossy. When reading, read_cuts_from_file only supports T=Int, T=NTuple{N, Int}, and T=Symbol. If you have manually created a policy graph with a different node type T, provide a function node_name_parser with the signature\n\nKeyword arguments\n\nnode_name_parser(T, name::String)::T where {T} that returns the name of each node given the string name name. If node_name_parser returns nothing, those cuts are skipped.\ncut_selection::Bool run or not the cut selection algorithm when adding the cuts to the model.\n\nSee also SDDP.write_cuts_to_file.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#write_log_to_csv","page":"API Reference","title":"write_log_to_csv","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.write_log_to_csv","category":"page"},{"location":"apireference/#SDDP.write_log_to_csv","page":"API Reference","title":"SDDP.write_log_to_csv","text":"write_log_to_csv(model::PolicyGraph, filename::String)\n\nWrite the log of the most recent training to a csv for post-analysis.\n\nAssumes that the model has been trained via SDDP.train.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#set_numerical_difficulty_callback","page":"API Reference","title":"set_numerical_difficulty_callback","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.set_numerical_difficulty_callback","category":"page"},{"location":"apireference/#SDDP.set_numerical_difficulty_callback","page":"API Reference","title":"SDDP.set_numerical_difficulty_callback","text":"set_numerical_difficulty_callback(\n model::PolicyGraph,\n callback::Function,\n)\n\nSet a callback function callback(::PolicyGraph, ::Node; require_dual::Bool) that is run when the optimizer terminates without finding a primal solution (and dual solution if require_dual is true).\n\nDefault callback\n\nThe default callback is a small variation of:\n\nfunction callback(::PolicyGraph, node::Node; require_dual::Bool)\n MOI.Utilities.reset_optimizer(node.subproblem)\n optimize!(node.subproblem)\n return\nend\n\nThis callback is the default because a common issue is solvers declaring the infeasible because of numerical issues related to the large number of cutting planes. Resetting the subproblem–-and therefore starting from a fresh problem instead of warm-starting from the previous solution–-is often enough to fix the problem and allow more iterations.\n\nOther callbacks\n\nIn cases where the problem is truely infeasible (not because of numerical issues ), it may be helpful to write out the irreducible infeasible subsystem (IIS) for debugging. For this use-case, use a callback as follows:\n\nfunction callback(::PolicyGraph, node::Node; require_dual::Bool)\n JuMP.compute_conflict!(node.suprobblem)\n status = JuMP.get_attribute(node.subproblem, MOI.ConflictStatus())\n if status == MOI.CONFLICT_FOUND\n iis_model, _ = JuMP.copy_conflict(node.subproblem)\n print(iis_model)\n end\n return\nend\nSDDP.set_numerical_difficulty_callback(model, callback)\n\n\n\n\n\n","category":"function"},{"location":"apireference/#AbstractStoppingRule","page":"API Reference","title":"AbstractStoppingRule","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AbstractStoppingRule","category":"page"},{"location":"apireference/#SDDP.AbstractStoppingRule","page":"API Reference","title":"SDDP.AbstractStoppingRule","text":"AbstractStoppingRule\n\nThe abstract type for the stopping-rule interface.\n\nYou need to define the following methods:\n\nSDDP.stopping_rule_status\nSDDP.convergence_test\n\n\n\n\n\n","category":"type"},{"location":"apireference/#stopping_rule_status","page":"API Reference","title":"stopping_rule_status","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.stopping_rule_status","category":"page"},{"location":"apireference/#SDDP.stopping_rule_status","page":"API Reference","title":"SDDP.stopping_rule_status","text":"stopping_rule_status(::AbstractStoppingRule)::Symbol\n\nReturn a symbol describing the stopping rule.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#convergence_test","page":"API Reference","title":"convergence_test","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.convergence_test","category":"page"},{"location":"apireference/#SDDP.convergence_test","page":"API Reference","title":"SDDP.convergence_test","text":"convergence_test(\n model::PolicyGraph,\n log::Vector{Log},\n ::AbstractStoppingRule,\n)::Bool\n\nReturn a Bool indicating if the algorithm should terminate the training.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#IterationLimit","page":"API Reference","title":"IterationLimit","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.IterationLimit","category":"page"},{"location":"apireference/#SDDP.IterationLimit","page":"API Reference","title":"SDDP.IterationLimit","text":"IterationLimit(limit::Int)\n\nTeriminate the algorithm after limit number of iterations.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#TimeLimit","page":"API Reference","title":"TimeLimit","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.TimeLimit","category":"page"},{"location":"apireference/#SDDP.TimeLimit","page":"API Reference","title":"SDDP.TimeLimit","text":"TimeLimit(limit::Float64)\n\nTeriminate the algorithm after limit seconds of computation.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Statistical","page":"API Reference","title":"Statistical","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Statistical","category":"page"},{"location":"apireference/#SDDP.Statistical","page":"API Reference","title":"SDDP.Statistical","text":"Statistical(;\n num_replications::Int,\n iteration_period::Int = 1,\n z_score::Float64 = 1.96,\n verbose::Bool = true,\n disable_warning::Bool = false,\n)\n\nPerform an in-sample Monte Carlo simulation of the policy with num_replications replications every iteration_periods and terminate if the deterministic bound (lower if minimizing) falls into the confidence interval for the mean of the simulated cost.\n\nIf verbose = true, print the confidence interval.\n\nIf disable_warning = true, disable the warning telling you not to use this stopping rule (see below).\n\nWhy this stopping rule is not good\n\nThis stopping rule is one of the most common stopping rules seen in the literature. Don't follow the crowd. It is a poor choice for your model, and should be rarely used. Instead, you should use the default stopping rule, or use a fixed limit like a time or iteration limit.\n\nTo understand why this stopping rule is a bad idea, assume we have conducted num_replications simulations and the objectives are in a vector objectives::Vector{Float64}.\n\nOur mean is μ = mean(objectives) and the half-width of the confidence interval is w = z_score * std(objectives) / sqrt(num_replications).\n\nMany papers suggest terminating the algorithm once the deterministic bound (lower if minimizing, upper if maximizing) is contained within the confidence interval. That is, if μ - w <= bound <= μ + w. Even worse, some papers define an optimization gap of (μ + w) / bound (if minimizing) or (μ - w) / bound (if maximizing), and they terminate once the gap is less than a value like 1%.\n\nBoth of these approaches are misleading, and more often than not, they will result in terminating with a sub-optimal policy that performs worse than expected. There are two main reasons for this:\n\nThe half-width depends on the number of replications. To reduce the computational cost, users are often tempted to choose a small number of replications. This increases the half-width and makes it more likely that the algorithm will stop early. But if we choose a large number of replications, then the computational cost is high, and we would have been better off to run a fixed number of iterations and use that computational time to run extra training iterations.\nThe confidence interval assumes that the simulated values are normally distributed. In infinite horizon models, this is almost never the case. The distribution is usually closer to exponential or log-normal.\n\nThere is a third, more technical reason which relates to the conditional dependence of constructing multiple confidence intervals.\n\nThe default value of z_score = 1.96 corresponds to a 95% confidence interval. You should interpret the interval as \"if we re-run this simulation 100 times, then the true mean will lie in the confidence interval 95 times out of 100.\" But if the bound is within the confidence interval, then we know the true mean cannot be better than the bound. Therfore, there is a more than 95% chance that the mean is within the interval.\n\nA separate problem arises if we simulate, find that the bound is outside the confidence interval, keep training, and then re-simulate to compute a new confidence interval. Because we will terminate when the bound enters the confidence interval, the repeated construction of a confidence interval means that the unconditional probability that we terminate with a false positive is larger than 5% (there are now more chances that the sample mean is optimistic and that the confidence interval includes the bound but not the true mean). One fix is to simulate with a sequentially increasing number of replicates, so that the unconditional probability stays at 95%, but this runs into the problem of computational cost. For more information on sequential sampling, see, for example, Güzin Bayraksan, David P. Morton, (2011) A Sequential Sampling Procedure for Stochastic Programming. Operations Research 59(4):898-913.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#BoundStalling","page":"API Reference","title":"BoundStalling","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.BoundStalling","category":"page"},{"location":"apireference/#SDDP.BoundStalling","page":"API Reference","title":"SDDP.BoundStalling","text":"BoundStalling(num_previous_iterations::Int, tolerance::Float64)\n\nTeriminate the algorithm once the deterministic bound (lower if minimizing, upper if maximizing) fails to improve by more than tolerance in absolute terms for more than num_previous_iterations consecutve iterations, provided it has improved relative to the bound after the first iteration.\n\nChecking for an improvement relative to the first iteration avoids early termination in a situation where the bound fails to improve for the first N iterations. This frequently happens in models with a large number of stages, where it takes time for the cuts to propogate backward enough to modify the bound of the root node.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#StoppingChain","page":"API Reference","title":"StoppingChain","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.StoppingChain","category":"page"},{"location":"apireference/#SDDP.StoppingChain","page":"API Reference","title":"SDDP.StoppingChain","text":"StoppingChain(rules::AbstractStoppingRule...)\n\nTerminate once all of the rules are statified.\n\nThis stopping rule short-circuits, so subsequent rules are only tested if the previous pass.\n\nExamples\n\nA stopping rule that runs 100 iterations, then checks for the bound stalling:\n\nStoppingChain(IterationLimit(100), BoundStalling(5, 0.1))\n\n\n\n\n\n","category":"type"},{"location":"apireference/#SimulationStoppingRule","page":"API Reference","title":"SimulationStoppingRule","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.SimulationStoppingRule","category":"page"},{"location":"apireference/#SDDP.SimulationStoppingRule","page":"API Reference","title":"SDDP.SimulationStoppingRule","text":"SimulationStoppingRule(;\n sampling_scheme::AbstractSamplingScheme = SDDP.InSampleMonteCarlo(),\n replications::Int = -1,\n period::Int = -1,\n distance_tol::Float64 = 1e-2,\n bound_tol::Float64 = 1e-4,\n)\n\nTerminate the algorithm using a mix of heuristics. Unless you know otherwise, this is typically a good default.\n\nTermination criteria\n\nFirst, we check that the deterministic bound has stabilized. That is, over the last five iterations, the deterministic bound has changed by less than an absolute or relative tolerance of bound_tol.\n\nThen, if we have not done one in the last period iterations, we perform a primal simulation of the policy using replications out-of-sample realizations from sampling_scheme. The realizations are stored and re-used in each simulation. From each simulation, we record the value of the stage objective. We terminate the policy if each of the trajectories in two consecutive simulations differ by less than distance_tol.\n\nBy default, replications and period are -1, and SDDP.jl will guess good values for these. Over-ride the default behavior by setting an appropriate value.\n\nExample\n\nSDDP.train(model; stopping_rules = [SimulationStoppingRule()])\n\n\n\n\n\n","category":"type"},{"location":"apireference/#FirstStageStoppingRule","page":"API Reference","title":"FirstStageStoppingRule","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.FirstStageStoppingRule","category":"page"},{"location":"apireference/#SDDP.FirstStageStoppingRule","page":"API Reference","title":"SDDP.FirstStageStoppingRule","text":"FirstStageStoppingRule(; atol::Float64 = 1e-3, iterations::Int = 50)\n\nTerminate the algorithm when the outgoing values of the first-stage state variables have not changed by more than atol for iterations number of consecutive iterations.\n\nExample\n\nSDDP.train(model; stopping_rules = [FirstStageStoppingRule()])\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AbstractSamplingScheme","page":"API Reference","title":"AbstractSamplingScheme","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AbstractSamplingScheme","category":"page"},{"location":"apireference/#SDDP.AbstractSamplingScheme","page":"API Reference","title":"SDDP.AbstractSamplingScheme","text":"AbstractSamplingScheme\n\nThe abstract type for the sampling-scheme interface.\n\nYou need to define the following methods:\n\nSDDP.sample_scenario\n\n\n\n\n\n","category":"type"},{"location":"apireference/#sample_scenario","page":"API Reference","title":"sample_scenario","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.sample_scenario","category":"page"},{"location":"apireference/#SDDP.sample_scenario","page":"API Reference","title":"SDDP.sample_scenario","text":"sample_scenario(graph::PolicyGraph{T}, ::AbstractSamplingScheme) where {T}\n\nSample a scenario from the policy graph graph based on the sampling scheme.\n\nReturns ::Tuple{Vector{Tuple{T, <:Any}}, Bool}, where the first element is the scenario, and the second element is a Boolean flag indicating if the scenario was terminated due to the detection of a cycle.\n\nThe scenario is a list of tuples (type Vector{Tuple{T, <:Any}}) where the first component of each tuple is the index of the node, and the second component is the stagewise-independent noise term observed in that node.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#InSampleMonteCarlo","page":"API Reference","title":"InSampleMonteCarlo","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.InSampleMonteCarlo","category":"page"},{"location":"apireference/#SDDP.InSampleMonteCarlo","page":"API Reference","title":"SDDP.InSampleMonteCarlo","text":"InSampleMonteCarlo(;\n max_depth::Int = 0,\n terminate_on_cycle::Function = false,\n terminate_on_dummy_leaf::Function = true,\n rollout_limit::Function = (i::Int) -> typemax(Int),\n initial_node::Any = nothing,\n)\n\nA Monte Carlo sampling scheme using the in-sample data from the policy graph definition.\n\nIf terminate_on_cycle, terminate the forward pass once a cycle is detected. If max_depth > 0, return once max_depth nodes have been sampled. If terminate_on_dummy_leaf, terminate the forward pass with 1 - probability of sampling a child node.\n\nNote that if terminate_on_cycle = false and terminate_on_dummy_leaf = false then max_depth must be set > 0.\n\nControl which node the trajectories start from using initial_node. If it is left as nothing, the root node is used as the starting node.\n\nYou can use rollout_limit to set iteration specific depth limits. For example:\n\nInSampleMonteCarlo(rollout_limit = i -> 2 * i)\n\n\n\n\n\n","category":"type"},{"location":"apireference/#OutOfSampleMonteCarlo","page":"API Reference","title":"OutOfSampleMonteCarlo","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.OutOfSampleMonteCarlo","category":"page"},{"location":"apireference/#SDDP.OutOfSampleMonteCarlo","page":"API Reference","title":"SDDP.OutOfSampleMonteCarlo","text":"OutOfSampleMonteCarlo(\n f::Function,\n graph::PolicyGraph;\n use_insample_transition::Bool = false,\n max_depth::Int = 0,\n terminate_on_cycle::Bool = false,\n terminate_on_dummy_leaf::Bool = true,\n rollout_limit::Function = i -> typemax(Int),\n initial_node = nothing,\n)\n\nCreate a Monte Carlo sampler using out-of-sample probabilities and/or supports for the stagewise-independent noise terms, and out-of-sample probabilities for the node-transition matrix.\n\nf is a function that takes the name of a node and returns a tuple containing a vector of new SDDP.Noise terms for the children of that node, and a vector of new SDDP.Noise terms for the stagewise-independent noise.\n\nIf f is called with the name of the root node (e.g., 0 in a linear policy graph, (0, 1) in a Markovian Policy Graph), then return a vector of SDDP.Noise for the children of the root node.\n\nIf use_insample_transition, the in-sample transition probabilities will be used. Therefore, f should only return a vector of the stagewise-independent noise terms, and f will not be called for the root node.\n\nIf terminate_on_cycle, terminate the forward pass once a cycle is detected. If max_depth > 0, return once max_depth nodes have been sampled. If terminate_on_dummy_leaf, terminate the forward pass with 1 - probability of sampling a child node.\n\nNote that if terminate_on_cycle = false and terminate_on_dummy_leaf = false then max_depth must be set > 0.\n\nControl which node the trajectories start from using initial_node. If it is left as nothing, the root node is used as the starting node.\n\nIf a node is deterministic, pass [SDDP.Noise(nothing, 1.0)] as the vector of noise terms.\n\nYou can use rollout_limit to set iteration specific depth limits. For example:\n\nOutOfSampleMonteCarlo(rollout_limit = i -> 2 * i)\n\nExamples\n\nGiven linear policy graph graph with T stages:\n\nsampler = OutOfSampleMonteCarlo(graph) do node\n if node == 0\n return [SDDP.Noise(1, 1.0)]\n else\n noise_terms = [SDDP.Noise(node, 0.3), SDDP.Noise(node + 1, 0.7)]\n children = node < T ? [SDDP.Noise(node + 1, 0.9)] : SDDP.Noise{Int}[]\n return children, noise_terms\n end\nend\n\nGiven linear policy graph graph with T stages:\n\nsampler = OutOfSampleMonteCarlo(graph, use_insample_transition=true) do node\n return [SDDP.Noise(node, 0.3), SDDP.Noise(node + 1, 0.7)]\nend\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Historical","page":"API Reference","title":"Historical","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Historical","category":"page"},{"location":"apireference/#SDDP.Historical","page":"API Reference","title":"SDDP.Historical","text":"Historical(\n scenarios::Vector{Vector{Tuple{T,S}}},\n probability::Vector{Float64};\n terminate_on_cycle::Bool = false,\n) where {T,S}\n\nA sampling scheme that samples a scenario from the vector of scenarios scenarios according to probability.\n\nExamples\n\nHistorical(\n [\n [(1, 0.5), (2, 1.0), (3, 0.5)],\n [(1, 0.5), (2, 0.0), (3, 1.0)],\n [(1, 1.0), (2, 0.0), (3, 0.0)]\n ],\n [0.2, 0.5, 0.3],\n)\n\n\n\n\n\nHistorical(\n scenarios::Vector{Vector{Tuple{T,S}}};\n terminate_on_cycle::Bool = false,\n) where {T,S}\n\nA deterministic sampling scheme that iterates through the vector of provided scenarios.\n\nExamples\n\nHistorical([\n [(1, 0.5), (2, 1.0), (3, 0.5)],\n [(1, 0.5), (2, 0.0), (3, 1.0)],\n [(1, 1.0), (2, 0.0), (3, 0.0)],\n])\n\n\n\n\n\nHistorical(\n scenario::Vector{Tuple{T,S}};\n terminate_on_cycle::Bool = false,\n) where {T,S}\n\nA deterministic sampling scheme that always samples scenario.\n\nExamples\n\nHistorical([(1, 0.5), (2, 1.5), (3, 0.75)])\n\n\n\n\n\n","category":"type"},{"location":"apireference/#PSRSamplingScheme","page":"API Reference","title":"PSRSamplingScheme","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.PSRSamplingScheme","category":"page"},{"location":"apireference/#SDDP.PSRSamplingScheme","page":"API Reference","title":"SDDP.PSRSamplingScheme","text":"PSRSamplingScheme(N::Int; sampling_scheme = InSampleMonteCarlo())\n\nA sampling scheme with N scenarios, similar to how PSR does it.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#SimulatorSamplingScheme","page":"API Reference","title":"SimulatorSamplingScheme","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.SimulatorSamplingScheme","category":"page"},{"location":"apireference/#SDDP.SimulatorSamplingScheme","page":"API Reference","title":"SDDP.SimulatorSamplingScheme","text":"SimulatorSamplingScheme(simulator::Function)\n\nCreate a sampling scheme based on a univariate scenario generator simulator, which returns a Vector{Float64} when called with no arguments like simulator().\n\nThis sampling scheme must be used with a Markovian graph constructed from the same simulator.\n\nThe sample space for SDDP.parameterize must be a tuple with 1 or 2 values, value is the Markov state and the second value is the random variable for the current node. If the node is deterministic, use Ω = [(markov_state,)].\n\nThis sampling scheme generates a new scenario by calling simulator(), and then picking the sequence of nodes in the Markovian graph that is closest to the new trajectory.\n\nExample\n\njulia> using SDDP\n\njulia> import HiGHS\n\njulia> simulator() = cumsum(rand(10))\nsimulator (generic function with 1 method)\n\njulia> model = SDDP.PolicyGraph(\n SDDP.MarkovianGraph(simulator; budget = 20, scenarios = 100);\n sense = :Max,\n upper_bound = 12,\n optimizer = HiGHS.Optimizer,\n ) do sp, node\n t, markov_state = node\n @variable(sp, x >= 0, SDDP.State, initial_value = 1)\n @variable(sp, u >= 0)\n @constraint(sp, x.out == x.in - u)\n # Elements of Ω MUST be a tuple in which `markov_state` is the first\n # element.\n Ω = [(markov_state, (u = u_max,)) for u_max in (0.0, 0.5)]\n SDDP.parameterize(sp, Ω) do (markov_state, ω)\n set_upper_bound(u, ω.u)\n @stageobjective(sp, markov_state * u)\n end\n end;\n\njulia> SDDP.train(\n model;\n print_level = 0,\n iteration_limit = 10,\n sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),\n )\n\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AbstractParallelScheme","page":"API Reference","title":"AbstractParallelScheme","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AbstractParallelScheme","category":"page"},{"location":"apireference/#SDDP.AbstractParallelScheme","page":"API Reference","title":"SDDP.AbstractParallelScheme","text":"AbstractParallelScheme\n\nAbstract type for different parallelism schemes.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Serial","page":"API Reference","title":"Serial","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Serial","category":"page"},{"location":"apireference/#SDDP.Serial","page":"API Reference","title":"SDDP.Serial","text":"Serial()\n\nRun SDDP in serial mode.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Threaded","page":"API Reference","title":"Threaded","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Threaded","category":"page"},{"location":"apireference/#SDDP.Threaded","page":"API Reference","title":"SDDP.Threaded","text":"Threaded()\n\nRun SDDP in multi-threaded mode.\n\nUse julia --threads N to start Julia with N threads. In most cases, you should pick N to be the number of physical cores on your machine.\n\ndanger: Danger\nThis plug-in is experimental, and parts of SDDP.jl may not be threadsafe. If you encounter any problems or crashes, please open a GitHub issue.\n\nExample\n\nSDDP.train(model; parallel_scheme = SDDP.Threaded())\nSDDP.simulate(model; parallel_scheme = SDDP.Threaded())\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Asynchronous","page":"API Reference","title":"Asynchronous","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Asynchronous","category":"page"},{"location":"apireference/#SDDP.Asynchronous","page":"API Reference","title":"SDDP.Asynchronous","text":"Asynchronous(\n [init_callback::Function,]\n slave_pids::Vector{Int} = workers();\n use_master::Bool = true,\n)\n\nRun SDDP in asynchronous mode workers with pid's slave_pids.\n\nAfter initializing the models on each worker, call init_callback(model). Note that init_callback is run locally on the worker and not on the master thread.\n\nIf use_master is true, iterations are also conducted on the master process.\n\n\n\n\n\nAsynchronous(\n solver::Any,\n slave_pids::Vector{Int} = workers();\n use_master::Bool = true,\n)\n\nRun SDDP in asynchronous mode workers with pid's slave_pids.\n\nSet the optimizer on each worker by calling JuMP.set_optimizer(model, solver).\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AbstractForwardPass","page":"API Reference","title":"AbstractForwardPass","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AbstractForwardPass","category":"page"},{"location":"apireference/#SDDP.AbstractForwardPass","page":"API Reference","title":"SDDP.AbstractForwardPass","text":"AbstractForwardPass\n\nAbstract type for different forward passes.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#DefaultForwardPass","page":"API Reference","title":"DefaultForwardPass","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.DefaultForwardPass","category":"page"},{"location":"apireference/#SDDP.DefaultForwardPass","page":"API Reference","title":"SDDP.DefaultForwardPass","text":"DefaultForwardPass(; include_last_node::Bool = true)\n\nThe default forward pass.\n\nIf include_last_node = false and the sample terminated due to a cycle, then the last node (which forms the cycle) is omitted. This can be useful option to set when training, but it comes at the cost of not knowing which node formed the cycle (if there are multiple possibilities).\n\n\n\n\n\n","category":"type"},{"location":"apireference/#RevisitingForwardPass","page":"API Reference","title":"RevisitingForwardPass","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.RevisitingForwardPass","category":"page"},{"location":"apireference/#SDDP.RevisitingForwardPass","page":"API Reference","title":"SDDP.RevisitingForwardPass","text":"RevisitingForwardPass(\n period::Int = 500;\n sub_pass::AbstractForwardPass = DefaultForwardPass(),\n)\n\nA forward pass scheme that generate period new forward passes (using sub_pass), then revisits all previously explored forward passes. This can be useful to encourage convergence at a diversity of points in the state-space.\n\nSet period = typemax(Int) to disable.\n\nFor example, if period = 2, then the forward passes will be revisited as follows: 1, 2, 1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 1, 2, ....\n\n\n\n\n\n","category":"type"},{"location":"apireference/#RiskAdjustedForwardPass","page":"API Reference","title":"RiskAdjustedForwardPass","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.RiskAdjustedForwardPass","category":"page"},{"location":"apireference/#SDDP.RiskAdjustedForwardPass","page":"API Reference","title":"SDDP.RiskAdjustedForwardPass","text":"RiskAdjustedForwardPass(;\n forward_pass::AbstractForwardPass,\n risk_measure::AbstractRiskMeasure,\n resampling_probability::Float64,\n rejection_count::Int = 5,\n)\n\nA forward pass that resamples a previous forward pass with resampling_probability probability, and otherwise samples a new forward pass using forward_pass.\n\nThe forward pass to revisit is chosen based on the risk-adjusted (using risk_measure) probability of the cumulative stage objectives.\n\nNote that this objective corresponds to the first time we visited the trajectory. Subsequent visits may have improved things, but we don't have the mechanisms in-place to update it. Therefore, remove the forward pass from resampling consideration after rejection_count revisits.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AlternativeForwardPass","page":"API Reference","title":"AlternativeForwardPass","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AlternativeForwardPass","category":"page"},{"location":"apireference/#SDDP.AlternativeForwardPass","page":"API Reference","title":"SDDP.AlternativeForwardPass","text":"AlternativeForwardPass(\n forward_model::SDDP.PolicyGraph{T};\n forward_pass::AbstractForwardPass = DefaultForwardPass(),\n)\n\nA forward pass that simulates using forward_model, which may be different to the model used in the backwards pass.\n\nWhen using this forward pass, you should almost always pass SDDP.AlternativePostIterationCallback to the post_iteration_callback argument of SDDP.train.\n\nThis forward pass is most useful when the forward_model is non-convex and we use a convex approximation of the model in the backward pass.\n\nFor example, in optimal power flow models, we can use an AC-OPF formulation as the forward_model and a DC-OPF formulation as the backward model.\n\nFor more details see the paper:\n\nRosemberg, A., and Street, A., and Garcia, J.D., and Valladão, D.M., and Silva, T., and Dowson, O. (2021). Assessing the cost of network simplifications in long-term hydrothermal dispatch planning models. IEEE Transactions on Sustainable Energy. 13(1), 196-206.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AlternativePostIterationCallback","page":"API Reference","title":"AlternativePostIterationCallback","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AlternativePostIterationCallback","category":"page"},{"location":"apireference/#SDDP.AlternativePostIterationCallback","page":"API Reference","title":"SDDP.AlternativePostIterationCallback","text":"AlternativePostIterationCallback(forward_model::PolicyGraph)\n\nA post-iteration callback that should be used whenever SDDP.AlternativeForwardPass is used.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#RegularizedForwardPass","page":"API Reference","title":"RegularizedForwardPass","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.RegularizedForwardPass","category":"page"},{"location":"apireference/#SDDP.RegularizedForwardPass","page":"API Reference","title":"SDDP.RegularizedForwardPass","text":"RegularizedForwardPass(;\n rho::Float64 = 0.05,\n forward_pass::AbstractForwardPass = DefaultForwardPass(),\n)\n\nA forward pass that regularizes the outgoing first-stage state variables with an L-infty trust-region constraint about the previous iteration's solution. Specifically, the bounds of the outgoing state variable x are updated from (l, u) to max(l, x^k - rho * (u - l)) <= x <= min(u, x^k + rho * (u - l)), where x^k is the optimal solution of x in the previous iteration. On the first iteration, the value of the state at the root node is used.\n\nBy default, rho is set to 5%, which seems to work well empirically.\n\nPass a different forward_pass to control the forward pass within the regularized forward pass.\n\nThis forward pass is largely intended to be used for investment problems in which the first stage makes a series of capacity decisions that then influence the rest of the graph. An error is thrown if the first stage problem is not deterministic, and states are silently skipped if they do not have finite bounds.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AbstractRiskMeasure","page":"API Reference","title":"AbstractRiskMeasure","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AbstractRiskMeasure","category":"page"},{"location":"apireference/#SDDP.AbstractRiskMeasure","page":"API Reference","title":"SDDP.AbstractRiskMeasure","text":"AbstractRiskMeasure\n\nThe abstract type for the risk measure interface.\n\nYou need to define the following methods:\n\nSDDP.adjust_probability\n\n\n\n\n\n","category":"type"},{"location":"apireference/#adjust_probability","page":"API Reference","title":"adjust_probability","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.adjust_probability","category":"page"},{"location":"apireference/#SDDP.adjust_probability","page":"API Reference","title":"SDDP.adjust_probability","text":"adjust_probability(\n measure::Expectation\n risk_adjusted_probability::Vector{Float64},\n original_probability::Vector{Float64},\n noise_support::Vector{Noise{T}},\n objective_realizations::Vector{Float64},\n is_minimization::Bool,\n) where {T}\n\n\n\n\n\n","category":"function"},{"location":"apireference/#Expectation","page":"API Reference","title":"Expectation","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Expectation","category":"page"},{"location":"apireference/#SDDP.Expectation","page":"API Reference","title":"SDDP.Expectation","text":"Expectation()\n\nThe Expectation risk measure.\n\nThis risk measure is identical to taking the expectation with respect to the nominal distribution.\n\nExample\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> SDDP.adjust_probability(\n SDDP.Expectation(),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.1\n 0.2\n 0.3\n 0.4\n\n\n\n\n\n","category":"type"},{"location":"apireference/#WorstCase","page":"API Reference","title":"WorstCase","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.WorstCase","category":"page"},{"location":"apireference/#SDDP.WorstCase","page":"API Reference","title":"SDDP.WorstCase","text":"WorstCase()\n\nThe worst-case risk measure.\n\nThis risk measure places all of the probability weight on the worst outcome.\n\nExample\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> SDDP.adjust_probability(\n SDDP.WorstCase(),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.0\n 0.0\n 1.0\n 0.0\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AVaR","page":"API Reference","title":"AVaR","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AVaR","category":"page"},{"location":"apireference/#SDDP.AVaR","page":"API Reference","title":"SDDP.AVaR","text":"AVaR(β)\n\nThe average value at risk (AV@R) risk measure.\n\nThis risk measure computes the expectation of the β fraction of worst outcomes. β must be in [0, 1].\n\nWhen β=1, this is equivalent to the Expectation risk measure. When β=0, this is equivalent to the WorstCase risk measure.\n\nAV@R is also known as the conditional value at risk (CV@R) or expected shortfall.\n\nExample\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> SDDP.adjust_probability(\n SDDP.AVaR(0.5),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.2\n 0.19999999999999996\n 0.6\n 0.0\n\njulia> SDDP.adjust_probability(\n SDDP.AVaR(1.0),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.1\n 0.2\n 0.3\n 0.4\n\njulia> SDDP.adjust_probability(\n SDDP.AVaR(0.0),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.0\n 0.0\n 1.0\n 0.0\n\n\n\n\n\n","category":"type"},{"location":"apireference/#CVaR","page":"API Reference","title":"CVaR","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.CVaR","category":"page"},{"location":"apireference/#SDDP.CVaR","page":"API Reference","title":"SDDP.CVaR","text":"CVaR(γ)\n\nThe conditional value at risk (CV@R) risk measure.\n\nThis risk measure computes the expectation of the γ fraction of worst outcomes. γ must be in [0, 1].\n\nWhen γ=1, this is equivalent to the Expectation risk measure. When γ=0, this is equivalent to the WorstCase risk measure.\n\nCV@R is also known as the average value at risk (AV@R) or expected shortfall.\n\nExample\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> SDDP.adjust_probability(\n SDDP.CVaR(0.5),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.2\n 0.19999999999999996\n 0.6\n 0.0\n\njulia> SDDP.adjust_probability(\n SDDP.CVaR(1.0),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.1\n 0.2\n 0.3\n 0.4\n\njulia> SDDP.adjust_probability(\n SDDP.CVaR(0.0),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.0\n 0.0\n 1.0\n 0.0\n\n\n\n\n\n","category":"type"},{"location":"apireference/#ConvexCombination","page":"API Reference","title":"ConvexCombination","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.ConvexCombination","category":"page"},{"location":"apireference/#SDDP.ConvexCombination","page":"API Reference","title":"SDDP.ConvexCombination","text":"ConvexCombination((weight::Float64, measure::AbstractRiskMeasure)...)\n\nCreate a weighted combination of risk measures.\n\nExamples\n\njulia> SDDP.ConvexCombination(\n (0.5, SDDP.Expectation()),\n (0.5, SDDP.AVaR(0.25))\n )\nA convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25)\n\nConvex combinations can also be constructed by adding weighted risk measures together as follows:\n\njulia> 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.5)\nA convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.5)\n\n\n\n\n\n","category":"type"},{"location":"apireference/#EAVaR","page":"API Reference","title":"EAVaR","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.EAVaR","category":"page"},{"location":"apireference/#SDDP.EAVaR","page":"API Reference","title":"SDDP.EAVaR","text":"EAVaR(;lambda=1.0, beta=1.0)\n\nA risk measure that is a convex combination of Expectation and Average Value @ Risk (also called Conditional Value @ Risk).\n\n λ * E[x] + (1 - λ) * AV@R(β)[x]\n\nKeyword Arguments\n\nlambda: Convex weight on the expectation ((1-lambda) weight is put on the AV@R component. Inreasing values of lambda are less risk averse (more weight on expectation).\nbeta: The quantile at which to calculate the Average Value @ Risk. Increasing values of beta are less risk averse. If beta=0, then the AV@R component is the worst case risk measure.\n\nExample\n\njulia> SDDP.EAVaR(; lambda = 1.0, beta = 1.0)\nA convex combination of 1.0 * SDDP.Expectation() + 0.0 * SDDP.AVaR(1.0)\n\njulia> SDDP.EAVaR(; lambda = 0.0, beta = 1.0)\nA convex combination of 0.0 * SDDP.Expectation() + 1.0 * SDDP.AVaR(1.0)\n\njulia> SDDP.EAVaR(; lambda = 0.5, beta = 0.5)\nA convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.5)\n\n\n\n\n\n","category":"function"},{"location":"apireference/#ModifiedChiSquared","page":"API Reference","title":"ModifiedChiSquared","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.ModifiedChiSquared","category":"page"},{"location":"apireference/#SDDP.ModifiedChiSquared","page":"API Reference","title":"SDDP.ModifiedChiSquared","text":"ModifiedChiSquared(radius::Float64; minimum_std=1e-5)\n\nThe distributionally robust SDDP risk measure of Philpott, A., de Matos, V., Kapelevich, L. Distributionally robust SDDP. Computational Management Science (2018) 165:431-454.\n\nExplanation\n\nIn a Distributionally Robust Optimization (DRO) approach, we modify the probabilities we associate with all future scenarios so that the resulting probability distribution is the \"worst case\" probability distribution, in some sense.\n\nIn each backward pass we will compute a worst case probability distribution vector p. We compute p so that:\n\np ∈ argmax p'z\n s.t. [r; p - a] in SecondOrderCone()\n sum(p) == 1\n p >= 0\n\nwhere\n\nz is a vector of future costs. We assume that our aim is to minimize future cost p'z. If we maximize reward, we would have p ∈ argmin{p'z}.\na is the uniform distribution\nr is a user specified radius - the larger the radius, the more conservative the policy.\n\nNotes\n\nThe largest radius that will work with S scenarios is sqrt((S-1)/S).\n\nIf the uncorrected standard deviation of the objecive realizations is less than minimum_std, then the risk-measure will default to Expectation().\n\nThis code was contributed by Lea Kapelevich.\n\nExample\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> SDDP.adjust_probability(\n SDDP.ModifiedChiSquared(0.5),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.2267731382092775\n 0.1577422872635742\n 0.5958039891549808\n 0.019680585372167547\n\njulia> SDDP.adjust_probability(\n SDDP.ModifiedChiSquared(0.5),\n risk_adjusted_probability,\n [0.25, 0.25, 0.25, 0.25], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.25, 0.25, 0.25, 0.25]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.3333333333333333\n 0.044658198738520394\n 0.6220084679281462\n 0.0\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Entropic","page":"API Reference","title":"Entropic","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Entropic","category":"page"},{"location":"apireference/#SDDP.Entropic","page":"API Reference","title":"SDDP.Entropic","text":"Entropic(γ::Float64)\n\nThe entropic risk measure as described by:\n\nDowson, O., Morton, D.P. & Pagnoncelli, B.K. Incorporating convex risk\nmeasures into multistage stochastic programming algorithms. Annals of\nOperations Research (2022). [doi](https://doi.org/10.1007/s10479-022-04977-w).\n\nAs γ increases, the measure becomes more risk-averse.\n\nExample\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> SDDP.adjust_probability(\n SDDP.Entropic(0.1),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n-0.14333892665462006\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.1100296362588547\n 0.19911786395979578\n 0.3648046623591841\n 0.3260478374221655\n\njulia> SDDP.adjust_probability(\n SDDP.Entropic(1.0),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n-0.12038063114659443\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.09911045746726178\n 0.07292139941460454\n 0.8082304666305623\n 0.019737676487571337\n\njulia> SDDP.adjust_probability(\n SDDP.Entropic(10.0),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1, 2, 3, 4], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n-0.12038063114659443\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 1.5133080886430772e-5\n 1.374081618667918e-9\n 0.999984865545032\n 5.664386611687232e-18\n\n\n\n\n\n","category":"type"},{"location":"apireference/#Wasserstein","page":"API Reference","title":"Wasserstein","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.Wasserstein","category":"page"},{"location":"apireference/#SDDP.Wasserstein","page":"API Reference","title":"SDDP.Wasserstein","text":"Wasserstein(norm::Function, solver_factory; alpha::Float64)\n\nA distributionally-robust risk measure based on the Wasserstein distance.\n\nAs alpha increases, the measure becomes more risk-averse. When alpha=0, the measure is equivalent to the expectation operator. As alpha increases, the measure approaches the Worst-case risk measure.\n\nnorm\n\nThe norm argument is a fuction that computes the distance between two supports of your distribution. It must have the signature:\n\nwasserstein_norm(x::SDDP.Noise, y::SDDP.Noise)::Float64\n\nThe input arguments are of type Noise. The .term values will depend on what supports you passed to parameterize.\n\nExample\n\njulia> import HiGHS\n\njulia> risk_adjusted_probability = zeros(4);\n\njulia> wasserstein_norm(x::SDDP.Noise, y::SDDP.Noise) = abs(x.term - y.term);\n\njulia> SDDP.adjust_probability(\n SDDP.Wasserstein(wasserstein_norm, HiGHS.Optimizer; alpha = 0.5),\n risk_adjusted_probability,\n [0.1, 0.2, 0.3, 0.4], # nominal_probability,\n SDDP.Noise.([1.0, 2.0, 3.0, 4.0], [0.1, 0.2, 0.3, 0.4]), # noise_supports,\n [5.0, 4.0, 6.0, 2.0], # cost_realizations,\n true, # is_minimization\n )\n0.0\n\njulia> risk_adjusted_probability\n4-element Vector{Float64}:\n 0.1\n 0.10000000000000003\n 0.7999999999999999\n -0.0\n\n\n\n\n\n","category":"type"},{"location":"apireference/#AbstractDualityHandler","page":"API Reference","title":"AbstractDualityHandler","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.AbstractDualityHandler","category":"page"},{"location":"apireference/#SDDP.AbstractDualityHandler","page":"API Reference","title":"SDDP.AbstractDualityHandler","text":"AbstractDualityHandler\n\nThe abstract type for the duality handler interface.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#ContinuousConicDuality","page":"API Reference","title":"ContinuousConicDuality","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.ContinuousConicDuality","category":"page"},{"location":"apireference/#SDDP.ContinuousConicDuality","page":"API Reference","title":"SDDP.ContinuousConicDuality","text":"ContinuousConicDuality()\n\nCompute dual variables in the backward pass using conic duality, relaxing any binary or integer restrictions as necessary.\n\nTheory\n\nGiven the problem\n\nmin Cᵢ(x̄, u, w) + θᵢ\n st (x̄, x′, u) in Xᵢ(w) ∩ S\n x̄ - x == 0 [λ]\n\nwhere S ⊆ ℝ×ℤ, we relax integrality and using conic duality to solve for λ in the problem:\n\nmin Cᵢ(x̄, u, w) + θᵢ\n st (x̄, x′, u) in Xᵢ(w)\n x̄ - x == 0 [λ]\n\n\n\n\n\n","category":"type"},{"location":"apireference/#LagrangianDuality","page":"API Reference","title":"LagrangianDuality","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.LagrangianDuality","category":"page"},{"location":"apireference/#SDDP.LagrangianDuality","page":"API Reference","title":"SDDP.LagrangianDuality","text":"LagrangianDuality(;\n method::LocalImprovementSearch.AbstractSearchMethod =\n LocalImprovementSearch.BFGS(100),\n)\n\nObtain dual variables in the backward pass using Lagrangian duality.\n\nArguments\n\nmethod: the LocalImprovementSearch method for maximizing the Lagrangian dual problem.\n\nTheory\n\nGiven the problem\n\nmin Cᵢ(x̄, u, w) + θᵢ\n st (x̄, x′, u) in Xᵢ(w) ∩ S\n x̄ - x == 0 [λ]\n\nwhere S ⊆ ℝ×ℤ, we solve the problem max L(λ), where:\n\nL(λ) = min Cᵢ(x̄, u, w) + θᵢ - λ' h(x̄)\n st (x̄, x′, u) in Xᵢ(w) ∩ S\n\nand where h(x̄) = x̄ - x.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#StrengthenedConicDuality","page":"API Reference","title":"StrengthenedConicDuality","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.StrengthenedConicDuality","category":"page"},{"location":"apireference/#SDDP.StrengthenedConicDuality","page":"API Reference","title":"SDDP.StrengthenedConicDuality","text":"StrengthenedConicDuality()\n\nObtain dual variables in the backward pass using strengthened conic duality.\n\nTheory\n\nGiven the problem\n\nmin Cᵢ(x̄, u, w) + θᵢ\n st (x̄, x′, u) in Xᵢ(w) ∩ S\n x̄ - x == 0 [λ]\n\nwe first obtain an estimate for λ using ContinuousConicDuality.\n\nThen, we evaluate the Lagrangian function:\n\nL(λ) = min Cᵢ(x̄, u, w) + θᵢ - λ' (x̄ - x`)\n st (x̄, x′, u) in Xᵢ(w) ∩ S\n\nto obtain a better estimate of the intercept.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#BanditDuality","page":"API Reference","title":"BanditDuality","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.BanditDuality","category":"page"},{"location":"apireference/#SDDP.BanditDuality","page":"API Reference","title":"SDDP.BanditDuality","text":"BanditDuality()\n\nFormulates the problem of choosing a duality handler as a multi-armed bandit problem. The arms to choose between are:\n\nContinuousConicDuality\nStrengthenedConicDuality\nLagrangianDuality\n\nOur problem isn't a typical multi-armed bandit for a two reasons:\n\nThe reward distribution is non-stationary (each arm converges to 0 as it keeps getting pulled.\nThe distribution of rewards is dependent on the history of the arms that were chosen.\n\nWe choose a very simple heuristic: pick the arm with the best mean + 1 standard deviation. That should ensure we consistently pick the arm with the best likelihood of improving the value function.\n\nIn future, we should consider discounting the rewards of earlier iterations, and focus more on the more-recent rewards.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#simulate","page":"API Reference","title":"simulate","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.simulate","category":"page"},{"location":"apireference/#SDDP.simulate","page":"API Reference","title":"SDDP.simulate","text":"simulate(\n model::PolicyGraph,\n number_replications::Int = 1,\n variables::Vector{Symbol} = Symbol[];\n sampling_scheme::AbstractSamplingScheme =\n InSampleMonteCarlo(),\n custom_recorders = Dict{Symbol, Function}(),\n duality_handler::Union{Nothing,AbstractDualityHandler} = nothing,\n skip_undefined_variables::Bool = false,\n parallel_scheme::AbstractParallelScheme = Serial(),\n incoming_state::Dict{String,Float64} = _initial_state(model),\n )::Vector{Vector{Dict{Symbol,Any}}}\n\nPerform a simulation of the policy model with number_replications replications.\n\nReturn data structure\n\nReturns a vector with one element for each replication. Each element is a vector with one-element for each node in the scenario that was sampled. Each element in that vector is a dictionary containing information about the subproblem that was solved.\n\nIn that dictionary there are four special keys:\n\n:node_index, which records the index of the sampled node in the policy model\n:noise_term, which records the noise observed at the node\n:stage_objective, which records the stage-objective of the subproblem\n:bellman_term, which records the cost/value-to-go of the node.\n\nThe sum of :stage_objective + :bellman_term will equal the objective value of the solved subproblem.\n\nIn addition to the special keys, the dictionary will contain the result of key => JuMP.value(subproblem[key]) for each key in variables. This is useful to obtain the primal value of the state and control variables.\n\nPositonal arguments\n\nmodel: the model to simulate\nnumber_replications::Int = 1: the number of simulation replications to conduct, that is, the length of the simulation vector that is returned by this function. If omitted, this defaults to 1.`\nvariables::Vector{Symbol} = Symbol[]: a list of the variable names to record the value of in each stage.\n\nKeyword arguments\n\nsampling_scheme: the sampling scheme used when simulating.\ncustom_recorders: see Custom recorders section below.\nduality_handler: the SDDP.AbstractDualityHandler used to compute dual variables. If you do not require dual variables (or if they are not available), pass duality_handler = nothing.\nskip_undefined_variables: If you attempt to simulate the value of a variable that is only defined in some of the stage problems, an error will be thrown. To over-ride this (and return a NaN instead), pass skip_undefined_variables = true.\nparallel_scheme: Use parallel_scheme::[AbstractParallelScheme](@ref) to specify a scheme for simulating in parallel. Defaults to Serial.\ninitial_state: Use incoming_state to pass an initial value of the state variable, if it differs from that at the root node. Each key should be the string name of the state variable.\n\nCustom recorders\n\nFor more complicated data, the custom_recorders keyword argument can be used.\n\nFor example, to record the dual of a constraint named my_constraint, pass the following:\n\nsimulation_results = SDDP.simulate(model, 2;\n custom_recorders = Dict{Symbol, Function}(\n :constraint_dual => sp -> JuMP.dual(sp[:my_constraint])\n )\n)\n\nThe value of the dual in the first stage of the second replication can be accessed as:\n\nsimulation_results[2][1][:constraint_dual]\n\n\n\n\n\n","category":"function"},{"location":"apireference/#calculate_bound","page":"API Reference","title":"calculate_bound","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.calculate_bound","category":"page"},{"location":"apireference/#SDDP.calculate_bound","page":"API Reference","title":"SDDP.calculate_bound","text":"SDDP.calculate_bound(\n model::PolicyGraph,\n state::Dict{Symbol,Float64} = model.initial_root_state;\n risk_measure::AbstractRiskMeasure = Expectation(),\n)\n\nCalculate the lower bound (if minimizing, otherwise upper bound) of the problem model at the point state, assuming the risk measure at the root node is risk_measure.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#add_all_cuts","page":"API Reference","title":"add_all_cuts","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.add_all_cuts","category":"page"},{"location":"apireference/#SDDP.add_all_cuts","page":"API Reference","title":"SDDP.add_all_cuts","text":"add_all_cuts(model::PolicyGraph)\n\nAdd all cuts that may have been deleted back into the model.\n\nExplanation\n\nDuring the solve, SDDP.jl may decide to remove cuts for a variety of reasons.\n\nThese can include cuts that define the optimal value function, particularly around the extremes of the state-space (e.g., reservoirs empty).\n\nThis function ensures that all cuts discovered are added back into the model.\n\nYou should call this after train and before simulate.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#Decision-rules","page":"API Reference","title":"Decision rules","text":"","category":"section"},{"location":"apireference/#DecisionRule","page":"API Reference","title":"DecisionRule","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.DecisionRule","category":"page"},{"location":"apireference/#SDDP.DecisionRule","page":"API Reference","title":"SDDP.DecisionRule","text":"DecisionRule(model::PolicyGraph{T}; node::T)\n\nCreate a decision rule for node node in model.\n\nExample\n\nrule = SDDP.DecisionRule(model; node = 1)\n\n\n\n\n\n","category":"type"},{"location":"apireference/#evaluate","page":"API Reference","title":"evaluate","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.evaluate","category":"page"},{"location":"apireference/#SDDP.evaluate","page":"API Reference","title":"SDDP.evaluate","text":"evaluate(\n rule::DecisionRule;\n incoming_state::Dict{Symbol,Float64},\n noise = nothing,\n controls_to_record = Symbol[],\n)\n\nEvalute the decision rule rule at the point described by the incoming_state and noise.\n\nIf the node is deterministic, omit the noise argument.\n\nPass a list of symbols to controls_to_record to save the optimal primal solution corresponding to the names registered in the model.\n\n\n\n\n\nevaluate(\n V::ValueFunction,\n point::Dict{Union{Symbol,String},<:Real}\n objective_state = nothing,\n belief_state = nothing\n)\n\nEvaluate the value function V at point in the state-space.\n\nReturns a tuple containing the height of the function, and the subgradient w.r.t. the convex state-variables.\n\nExamples\n\nevaluate(V, Dict(:volume => 1.0))\n\nIf the state variable is constructed like @variable(sp, volume[1:4] >= 0, SDDP.State, initial_value = 0.0), use [i] to index the state variable:\n\nevaluate(V, Dict(Symbol(\"volume[1]\") => 1.0))\n\nYou can also use strings or symbols for the keys.\n\nevaluate(V, Dict(\"volume[1]\" => 1))\n\n\n\n\n\nevalute(V::ValueFunction{Nothing, Nothing}; kwargs...)\n\nEvalute the value function V at the point in the state-space specified by kwargs.\n\nExamples\n\nevaluate(V; volume = 1)\n\n\n\n\n\nevaluate(\n model::PolicyGraph{T},\n validation_scenarios::ValidationScenarios{T,S},\n) where {T,S}\n\nEvaluate the performance of the policy contained in model after a call to train on the scenarios specified by validation_scenarios.\n\nExamples\n\nmodel, validation_scenarios = read_from_file(\"my_model.sof.json\")\ntrain(model; iteration_limit = 100)\nsimulations = evaluate(model, validation_scenarios)\n\n\n\n\n\n","category":"function"},{"location":"apireference/#SpaghettiPlot","page":"API Reference","title":"SpaghettiPlot","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.SpaghettiPlot","category":"page"},{"location":"apireference/#SDDP.SpaghettiPlot","page":"API Reference","title":"SDDP.SpaghettiPlot","text":"SDDP.SpaghettiPlot(; stages, scenarios)\n\nInitialize a new SpaghettiPlot with stages stages and scenarios number of replications.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#add_spaghetti","page":"API Reference","title":"add_spaghetti","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.add_spaghetti","category":"page"},{"location":"apireference/#SDDP.add_spaghetti","page":"API Reference","title":"SDDP.add_spaghetti","text":"SDDP.add_spaghetti(data_function::Function, plt::SpaghettiPlot; kwargs...)\n\nDescription\n\nAdd a new figure to the SpaghettiPlot plt, where the y-value of the scenarioth line when x = stage is given by data_function(plt.simulations[scenario][stage]).\n\nKeyword arguments\n\nxlabel: set the xaxis label\nylabel: set the yaxis label\ntitle: set the title of the plot\nymin: set the minimum y value\nymax: set the maximum y value\ncumulative: plot the additive accumulation of the value across the stages\ninterpolate: interpolation method for lines between stages.\n\nDefaults to \"linear\" see the d3 docs \tfor all options.\n\nExamples\n\nsimulations = simulate(model, 10)\nplt = SDDP.spaghetti_plot(simulations)\nSDDP.add_spaghetti(plt; title = \"Stage objective\") do data\n return data[:stage_objective]\nend\n\n\n\n\n\n","category":"function"},{"location":"apireference/#publication_plot","page":"API Reference","title":"publication_plot","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.publication_plot","category":"page"},{"location":"apireference/#SDDP.publication_plot","page":"API Reference","title":"SDDP.publication_plot","text":"SDDP.publication_plot(\n data_function, simulations;\n quantile = [0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 1.0],\n kwargs...)\n\nCreate a Plots.jl recipe plot of the simulations.\n\nSee Plots.jl for the list of keyword arguments.\n\nExamples\n\nSDDP.publication_plot(simulations; title = \"My title\") do data\n return data[:stage_objective]\nend\n\n\n\n\n\n","category":"function"},{"location":"apireference/#ValueFunction","page":"API Reference","title":"ValueFunction","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.ValueFunction","category":"page"},{"location":"apireference/#SDDP.ValueFunction","page":"API Reference","title":"SDDP.ValueFunction","text":"ValueFunction\n\nA representation of the value function. SDDP.jl uses the following unique representation of the value function that is undocumented in the literature.\n\nIt supports three types of state variables:\n\nx - convex \"resource\" states\nb - concave \"belief\" states\ny - concave \"objective\" states\n\nIn addition, we have three types of cuts:\n\nSingle-cuts (also called \"average\" cuts in the literature), which involve the risk-adjusted expectation of the cost-to-go.\nMulti-cuts, which use a different cost-to-go term for each realization w.\nRisk-cuts, which correspond to the facets of the dual interpretation of a coherent risk measure.\n\nTherefore, ValueFunction returns a JuMP model of the following form:\n\nV(x, b, y) = min: μᵀb + νᵀy + θ\n s.t. # \"Single\" / \"Average\" cuts\n μᵀb(j) + νᵀy(j) + θ >= α(j) + xᵀβ(j), ∀ j ∈ J\n # \"Multi\" cuts\n μᵀb(k) + νᵀy(k) + φ(w) >= α(k, w) + xᵀβ(k, w), ∀w ∈ Ω, k ∈ K\n # \"Risk-set\" cuts\n θ ≥ Σ{p(k, w) * φ(w)}_w - μᵀb(k) - νᵀy(k), ∀ k ∈ K\n\n\n\n\n\n","category":"type"},{"location":"apireference/#evaluate-2","page":"API Reference","title":"evaluate","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.evaluate(::SDDP.ValueFunction, ::Dict{Symbol,Float64})","category":"page"},{"location":"apireference/#SDDP.evaluate-Tuple{SDDP.ValueFunction, Dict{Symbol, Float64}}","page":"API Reference","title":"SDDP.evaluate","text":"evaluate(\n V::ValueFunction,\n point::Dict{Union{Symbol,String},<:Real}\n objective_state = nothing,\n belief_state = nothing\n)\n\nEvaluate the value function V at point in the state-space.\n\nReturns a tuple containing the height of the function, and the subgradient w.r.t. the convex state-variables.\n\nExamples\n\nevaluate(V, Dict(:volume => 1.0))\n\nIf the state variable is constructed like @variable(sp, volume[1:4] >= 0, SDDP.State, initial_value = 0.0), use [i] to index the state variable:\n\nevaluate(V, Dict(Symbol(\"volume[1]\") => 1.0))\n\nYou can also use strings or symbols for the keys.\n\nevaluate(V, Dict(\"volume[1]\" => 1))\n\n\n\n\n\n","category":"method"},{"location":"apireference/#plot","page":"API Reference","title":"plot","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.plot","category":"page"},{"location":"apireference/#SDDP.plot","page":"API Reference","title":"SDDP.plot","text":"plot(plt::SpaghettiPlot[, filename::String]; open::Bool = true)\n\nThe SpaghettiPlot plot plt to filename. If filename is not given, it will be saved to a temporary directory. If open = true, then a browser window will be opened to display the resulting HTML file.\n\n\n\n\n\n","category":"function"},{"location":"apireference/#write_subproblem_to_file","page":"API Reference","title":"write_subproblem_to_file","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.write_subproblem_to_file","category":"page"},{"location":"apireference/#SDDP.write_subproblem_to_file","page":"API Reference","title":"SDDP.write_subproblem_to_file","text":"write_subproblem_to_file(\n node::Node,\n filename::String;\n throw_error::Bool = false,\n)\n\nWrite the subproblem contained in node to the file filename.\n\nThe throw_error is an argument used internally by SDDP.jl. If set, an error will be thrown.\n\nExample\n\nSDDP.write_subproblem_to_file(model[1], \"subproblem_1.lp\")\n\n\n\n\n\n","category":"function"},{"location":"apireference/#deterministic_equivalent","page":"API Reference","title":"deterministic_equivalent","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.deterministic_equivalent","category":"page"},{"location":"apireference/#SDDP.deterministic_equivalent","page":"API Reference","title":"SDDP.deterministic_equivalent","text":"deterministic_equivalent(\n pg::PolicyGraph{T},\n optimizer = nothing;\n time_limit::Union{Real,Nothing} = 60.0,\n)\n\nForm a JuMP model that represents the deterministic equivalent of the problem.\n\nExamples\n\ndeterministic_equivalent(model)\n\ndeterministic_equivalent(model, HiGHS.Optimizer)\n\n\n\n\n\n","category":"function"},{"location":"apireference/#write_to_file","page":"API Reference","title":"write_to_file","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.write_to_file","category":"page"},{"location":"apireference/#SDDP.write_to_file","page":"API Reference","title":"SDDP.write_to_file","text":"write_to_file(\n model::PolicyGraph,\n filename::String;\n compression::MOI.FileFormats.AbstractCompressionScheme =\n MOI.FileFormats.AutomaticCompression(),\n kwargs...\n)\n\nWrite model to filename in the StochOptFormat file format.\n\nPass an argument to compression to override the default of automatically detecting the file compression to use based on the extension of filename.\n\nSee Base.write(::IO, ::PolicyGraph) for information on the keyword arguments that can be provided.\n\nwarning: Warning\nThis function is experimental. See the full warning in Base.write(::IO, ::PolicyGraph).\n\nExamples\n\nwrite_to_file(model, \"my_model.sof.json\"; validation_scenarios = 10)\n\n\n\n\n\n","category":"function"},{"location":"apireference/#read_from_file","page":"API Reference","title":"read_from_file","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.read_from_file","category":"page"},{"location":"apireference/#SDDP.read_from_file","page":"API Reference","title":"SDDP.read_from_file","text":"read_from_file(\n filename::String;\n compression::MOI.FileFormats.AbstractCompressionScheme =\n MOI.FileFormats.AutomaticCompression(),\n kwargs...\n)::Tuple{PolicyGraph, ValidationScenarios}\n\nReturn a tuple containing a PolicyGraph object and a ValidationScenarios read from filename in the StochOptFormat file format.\n\nPass an argument to compression to override the default of automatically detecting the file compression to use based on the extension of filename.\n\nSee Base.read(::IO, ::Type{PolicyGraph}) for information on the keyword arguments that can be provided.\n\nwarning: Warning\nThis function is experimental. See the full warning in Base.read(::IO, ::Type{PolicyGraph}).\n\nExamples\n\nmodel, validation_scenarios = read_from_file(\"my_model.sof.json\")\n\n\n\n\n\n","category":"function"},{"location":"apireference/#write","page":"API Reference","title":"write","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"Base.write(::IO, ::SDDP.PolicyGraph)","category":"page"},{"location":"apireference/#Base.write-Tuple{IO, SDDP.PolicyGraph}","page":"API Reference","title":"Base.write","text":"Base.write(\n io::IO,\n model::PolicyGraph;\n validation_scenarios::Union{Nothing,Int,ValidationScenarios} = nothing,\n sampling_scheme::AbstractSamplingScheme = InSampleMonteCarlo(),\n kwargs...\n)\n\nWrite model to io in the StochOptFormat file format.\n\nPass an Int to validation_scenarios (default nothing) to specify the number of test scenarios to generate using the sampling_scheme sampling scheme. Alternatively, pass a ValidationScenarios object to manually specify the test scenarios to use.\n\nAny additional kwargs passed to write will be stored in the top-level of the resulting StochOptFormat file. Valid arguments include name, author, date, and description.\n\nCompatibility\n\nwarning: Warning\nTHIS FUNCTION IS EXPERIMENTAL. THINGS MAY CHANGE BETWEEN COMMITS. YOU SHOULD NOT RELY ON THIS FUNCTIONALITY AS A LONG-TERM FILE FORMAT (YET).\n\nIn addition to potential changes to the underlying format, only a subset of possible modifications are supported. These include:\n\nJuMP.fix\nJuMP.set_lower_bound\nJuMP.set_upper_bound\nJuMP.set_normalized_rhs\nChanges to the constant or affine terms in a stage objective.\n\nIf your model uses something other than this, this function will silently write an incorrect formulation of the problem.\n\nExamples\n\nopen(\"my_model.sof.json\", \"w\") do io\n write(\n io,\n model;\n validation_scenarios = 10,\n name = \"MyModel\",\n author = \"@odow\",\n date = \"2020-07-20\",\n description = \"Example problem for the SDDP.jl documentation\",\n )\nend\n\n\n\n\n\n","category":"method"},{"location":"apireference/#read","page":"API Reference","title":"read","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"Base.read(::IO, ::Type{SDDP.PolicyGraph})","category":"page"},{"location":"apireference/#Base.read-Tuple{IO, Type{SDDP.PolicyGraph}}","page":"API Reference","title":"Base.read","text":"Base.read(\n io::IO,\n ::Type{PolicyGraph};\n bound::Float64 = 1e6,\n)::Tuple{PolicyGraph,ValidationScenarios}\n\nReturn a tuple containing a PolicyGraph object and a ValidationScenarios read from io in the StochOptFormat file format.\n\nSee also: evaluate.\n\nCompatibility\n\nwarning: Warning\nThis function is experimental. Things may change between commits. You should not rely on this functionality as a long-term file format (yet).\n\nIn addition to potential changes to the underlying format, only a subset of possible modifications are supported. These include:\n\nAdditive random variables in the constraints or in the objective\nMultiplicative random variables in the objective\n\nIf your model uses something other than this, this function may throw an error or silently build a non-convex model.\n\nExamples\n\nopen(\"my_model.sof.json\", \"r\") do io\n model, validation_scenarios = read(io, PolicyGraph)\nend\n\n\n\n\n\n","category":"method"},{"location":"apireference/#evaluate-3","page":"API Reference","title":"evaluate","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.evaluate(::SDDP.PolicyGraph{T}, ::SDDP.ValidationScenarios{T}) where {T}","category":"page"},{"location":"apireference/#SDDP.evaluate-Union{Tuple{T}, Tuple{SDDP.PolicyGraph{T}, SDDP.ValidationScenarios{T}}} where T","page":"API Reference","title":"SDDP.evaluate","text":"evaluate(\n model::PolicyGraph{T},\n validation_scenarios::ValidationScenarios{T,S},\n) where {T,S}\n\nEvaluate the performance of the policy contained in model after a call to train on the scenarios specified by validation_scenarios.\n\nExamples\n\nmodel, validation_scenarios = read_from_file(\"my_model.sof.json\")\ntrain(model; iteration_limit = 100)\nsimulations = evaluate(model, validation_scenarios)\n\n\n\n\n\n","category":"method"},{"location":"apireference/#ValidationScenarios","page":"API Reference","title":"ValidationScenarios","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.ValidationScenarios","category":"page"},{"location":"apireference/#SDDP.ValidationScenarios","page":"API Reference","title":"SDDP.ValidationScenarios","text":"ValidationScenario{T,S}(scenarios::Vector{ValidationScenario{T,S}})\n\nAn AbstractSamplingScheme based on a vector of scenarios.\n\nEach scenario is a vector of Tuple{T, S} where the first element is the node to visit and the second element is the realization of the stagewise-independent noise term. Pass nothing if the node is deterministic.\n\n\n\n\n\n","category":"type"},{"location":"apireference/#ValidationScenario","page":"API Reference","title":"ValidationScenario","text":"","category":"section"},{"location":"apireference/","page":"API Reference","title":"API Reference","text":"SDDP.ValidationScenario","category":"page"},{"location":"apireference/#SDDP.ValidationScenario","page":"API Reference","title":"SDDP.ValidationScenario","text":"ValidationScenario{T,S}(scenario::Vector{Tuple{T,S}})\n\nA single scenario for testing.\n\nSee also: ValidationScenarios.\n\n\n\n\n\n","category":"type"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"EditURL = \"markov_uncertainty.jl\"","category":"page"},{"location":"tutorial/markov_uncertainty/#Markovian-policy-graphs","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"","category":"section"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"In our previous tutorials (An introduction to SDDP.jl and Uncertainty in the objective function), we formulated a simple hydrothermal scheduling problem with stagewise-independent random variables in the right-hand side of the constraints and in the objective function. Now, in this tutorial, we introduce some stagewise-dependent uncertainty using a Markov chain.","category":"page"},{"location":"tutorial/markov_uncertainty/#Formulating-the-problem","page":"Markovian policy graphs","title":"Formulating the problem","text":"","category":"section"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"In this tutorial we consider a Markov chain with two climate states: wet and dry. Each Markov state is associated with an integer, in this case the wet climate state is Markov state 1 and the dry climate state is Markov state 2. In the wet climate state, the probability of the high inflow increases to 50%, and the probability of the low inflow decreases to 1/6. In the dry climate state, the converse happens. There is also persistence in the climate state: the probability of remaining in the current state is 75%, and the probability of transitioning to the other climate state is 25%. We assume that the first stage starts in the wet climate state.","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"Here is a picture of the model we're going to implement.","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"(Image: Markovian policy graph)","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"There are five nodes in our graph. Each node is named by a tuple (t, i), where t is the stage for t=1,2,3, and i is the Markov state for i=1,2. As before, the wavy lines denote the stagewise-independent random variable.","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"For each stage, we need to provide a Markov transition matrix. This is an MxN matrix, where the element A[i, j] gives the probability of transitioning from Markov state i in the previous stage to Markov state j in the current stage. The first stage is special because we assume there is a \"zero'th\" stage which has one Markov state (the round node in the graph above). Furthermore, the number of columns in the transition matrix of a stage (i.e. the number of Markov states) must equal the number of rows in the next stage's transition matrix. For our example, the vector of Markov transition matrices is given by:","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"T = Array{Float64,2}[[1.0]', [0.75 0.25], [0.75 0.25; 0.25 0.75]]","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"note: Note\nMake sure to add the ' after the first transition matrix so Julia can distinguish between a vector and a matrix.","category":"page"},{"location":"tutorial/markov_uncertainty/#Creating-a-model","page":"Markovian policy graphs","title":"Creating a model","text":"","category":"section"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"using SDDP, HiGHS\n\nΩ = [\n (inflow = 0.0, fuel_multiplier = 1.5),\n (inflow = 50.0, fuel_multiplier = 1.0),\n (inflow = 100.0, fuel_multiplier = 0.75),\n]\n\nmodel = SDDP.MarkovianPolicyGraph(;\n transition_matrices = Array{Float64,2}[\n [1.0]',\n [0.75 0.25],\n [0.75 0.25; 0.25 0.75],\n ],\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, node\n # Unpack the stage and Markov index.\n t, markov_state = node\n # Define the state variable.\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Define the control variables.\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n # Define the constraints\n @constraints(\n subproblem,\n begin\n volume.out == volume.in + inflow - hydro_generation - hydro_spill\n thermal_generation + hydro_generation == 150.0\n end\n )\n # Note how we can use `markov_state` to dispatch an `if` statement.\n probability = if markov_state == 1 # wet climate state\n [1 / 6, 1 / 3, 1 / 2]\n else # dry climate state\n [1 / 2, 1 / 3, 1 / 6]\n end\n\n fuel_cost = [50.0, 100.0, 150.0]\n SDDP.parameterize(subproblem, Ω, probability) do ω\n JuMP.fix(inflow, ω.inflow)\n @stageobjective(\n subproblem,\n ω.fuel_multiplier * fuel_cost[t] * thermal_generation\n )\n end\nend","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"tip: Tip\nFor more information on SDDP.MarkovianPolicyGraphs, read Create a general policy graph.","category":"page"},{"location":"tutorial/markov_uncertainty/#Training-and-simulating-the-policy","page":"Markovian policy graphs","title":"Training and simulating the policy","text":"","category":"section"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"As in the previous three tutorials, we train the policy:","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"SDDP.train(model)","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"Instead of performing a Monte Carlo simulation like the previous tutorials, we may want to simulate one particular sequence of noise realizations. This historical simulation can also be conducted by passing a SDDP.Historical sampling scheme to the sampling_scheme keyword of the SDDP.simulate function.","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"We can confirm that the historical sequence of nodes was visited by querying the :node_index key of the simulation results.","category":"page"},{"location":"tutorial/markov_uncertainty/","page":"Markovian policy graphs","title":"Markovian policy graphs","text":"simulations = SDDP.simulate(\n model;\n sampling_scheme = SDDP.Historical([\n ((1, 1), Ω[1]),\n ((2, 2), Ω[3]),\n ((3, 1), Ω[2]),\n ]),\n)\n\n[stage[:node_index] for stage in simulations[1]]","category":"page"},{"location":"examples/FAST_hydro_thermal/","page":"FAST: the hydro-thermal problem","title":"FAST: the hydro-thermal problem","text":"EditURL = \"FAST_hydro_thermal.jl\"","category":"page"},{"location":"examples/FAST_hydro_thermal/#FAST:-the-hydro-thermal-problem","page":"FAST: the hydro-thermal problem","title":"FAST: the hydro-thermal problem","text":"","category":"section"},{"location":"examples/FAST_hydro_thermal/","page":"FAST: the hydro-thermal problem","title":"FAST: the hydro-thermal problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/FAST_hydro_thermal/","page":"FAST: the hydro-thermal problem","title":"FAST: the hydro-thermal problem","text":"An implementation of the Hydro-thermal example from FAST","category":"page"},{"location":"examples/FAST_hydro_thermal/","page":"FAST: the hydro-thermal problem","title":"FAST: the hydro-thermal problem","text":"using SDDP, HiGHS, Test\n\nfunction fast_hydro_thermal()\n model = SDDP.LinearPolicyGraph(;\n stages = 2,\n upper_bound = 0.0,\n sense = :Max,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n @variable(sp, 0 <= x <= 8, SDDP.State, initial_value = 0.0)\n @variables(sp, begin\n y >= 0\n p >= 0\n ξ\n end)\n @constraints(sp, begin\n p + y >= 6\n x.out <= x.in - y + ξ\n end)\n RAINFALL = (t == 1 ? [6] : [2, 10])\n SDDP.parameterize(sp, RAINFALL) do ω\n return JuMP.fix(ξ, ω)\n end\n @stageobjective(sp, -5 * p)\n end\n\n det = SDDP.deterministic_equivalent(model, HiGHS.Optimizer)\n set_silent(det)\n JuMP.optimize!(det)\n @test JuMP.objective_sense(det) == MOI.MAX_SENSE\n @test JuMP.objective_value(det) == -10\n SDDP.train(model)\n @test SDDP.calculate_bound(model) == -10\n return\nend\n\nfast_hydro_thermal()","category":"page"},{"location":"examples/StochDynamicProgramming.jl_multistock/","page":"StochDynamicProgramming: the multistock problem","title":"StochDynamicProgramming: the multistock problem","text":"EditURL = \"StochDynamicProgramming.jl_multistock.jl\"","category":"page"},{"location":"examples/StochDynamicProgramming.jl_multistock/#StochDynamicProgramming:-the-multistock-problem","page":"StochDynamicProgramming: the multistock problem","title":"StochDynamicProgramming: the multistock problem","text":"","category":"section"},{"location":"examples/StochDynamicProgramming.jl_multistock/","page":"StochDynamicProgramming: the multistock problem","title":"StochDynamicProgramming: the multistock problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/StochDynamicProgramming.jl_multistock/","page":"StochDynamicProgramming: the multistock problem","title":"StochDynamicProgramming: the multistock problem","text":"This example comes from StochDynamicProgramming.jl.","category":"page"},{"location":"examples/StochDynamicProgramming.jl_multistock/","page":"StochDynamicProgramming: the multistock problem","title":"StochDynamicProgramming: the multistock problem","text":"using SDDP, HiGHS, Test\n\nfunction test_multistock_example()\n model = SDDP.LinearPolicyGraph(;\n stages = 5,\n lower_bound = -5.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, stage\n @variable(\n subproblem,\n 0 <= stock[i = 1:3] <= 1,\n SDDP.State,\n initial_value = 0.5\n )\n @variables(subproblem, begin\n 0 <= control[i = 1:3] <= 0.5\n ξ[i = 1:3] # Dummy for RHS noise.\n end)\n @constraints(\n subproblem,\n begin\n sum(control) - 0.5 * 3 <= 0\n [i = 1:3], stock[i].out == stock[i].in + control[i] - ξ[i]\n end\n )\n Ξ = collect(\n Base.product((0.0, 0.15, 0.3), (0.0, 0.15, 0.3), (0.0, 0.15, 0.3)),\n )[:]\n SDDP.parameterize(subproblem, Ξ) do ω\n return JuMP.fix.(ξ, ω)\n end\n @stageobjective(subproblem, (sin(3 * stage) - 1) * sum(control))\n end\n SDDP.train(\n model;\n iteration_limit = 100,\n cut_type = SDDP.SINGLE_CUT,\n log_frequency = 10,\n )\n @test SDDP.calculate_bound(model) ≈ -4.349 atol = 0.01\n\n simulation_results = SDDP.simulate(model, 5000)\n @test length(simulation_results) == 5000\n μ = SDDP.Statistics.mean(\n sum(data[:stage_objective] for data in simulation) for\n simulation in simulation_results\n )\n @test μ ≈ -4.349 atol = 0.1\n return\nend\n\ntest_multistock_example()","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"EditURL = \"plotting.jl\"","category":"page"},{"location":"tutorial/plotting/#Plotting-tools","page":"Plotting tools","title":"Plotting tools","text":"","category":"section"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"In our previous tutorials, we formulated, solved, and simulated multistage stochastic optimization problems. However, we haven't really investigated what the solution looks like. Luckily, SDDP.jl includes a number of plotting tools to help us do that. In this tutorial, we explain the tools and make some pretty pictures.","category":"page"},{"location":"tutorial/plotting/#Preliminaries","page":"Plotting tools","title":"Preliminaries","text":"","category":"section"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"The next two plot types help visualize the policy. Thus, we first need to create a policy and simulate some trajectories. So, let's take the model from Markovian policy graphs, train it for 20 iterations, and then simulate 100 Monte Carlo realizations of the policy.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"using SDDP, HiGHS\n\nΩ = [\n (inflow = 0.0, fuel_multiplier = 1.5),\n (inflow = 50.0, fuel_multiplier = 1.0),\n (inflow = 100.0, fuel_multiplier = 0.75),\n]\n\nmodel = SDDP.MarkovianPolicyGraph(;\n transition_matrices = Array{Float64,2}[\n [1.0]',\n [0.75 0.25],\n [0.75 0.25; 0.25 0.75],\n ],\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, node\n t, markov_state = node\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n @constraints(\n subproblem,\n begin\n volume.out == volume.in + inflow - hydro_generation - hydro_spill\n thermal_generation + hydro_generation == 150.0\n end\n )\n probability =\n markov_state == 1 ? [1 / 6, 1 / 3, 1 / 2] : [1 / 2, 1 / 3, 1 / 6]\n fuel_cost = [50.0, 100.0, 150.0]\n SDDP.parameterize(subproblem, Ω, probability) do ω\n JuMP.fix(inflow, ω.inflow)\n @stageobjective(\n subproblem,\n ω.fuel_multiplier * fuel_cost[t] * thermal_generation\n )\n end\nend\n\nSDDP.train(model; iteration_limit = 20, run_numerical_stability_report = false)\n\nsimulations = SDDP.simulate(\n model,\n 100,\n [:volume, :thermal_generation, :hydro_generation, :hydro_spill],\n)\n\nprintln(\"Completed $(length(simulations)) simulations.\")","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"Great! Now we have some data in simulations to visualize.","category":"page"},{"location":"tutorial/plotting/#Spaghetti-plots","page":"Plotting tools","title":"Spaghetti plots","text":"","category":"section"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"The first plotting utility we discuss is a spaghetti plot (you'll understand the name when you see the graph).","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"To create a spaghetti plot, begin by creating a new SDDP.SpaghettiPlot instance as follows:","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"plt = SDDP.SpaghettiPlot(simulations)","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"We can add plots to plt using the SDDP.add_spaghetti function.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.add_spaghetti(plt; title = \"Reservoir volume\") do data\n return data[:volume].out\nend","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"In addition to returning values from the simulation, you can compute things:","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.add_spaghetti(plt; title = \"Fuel cost\", ymin = 0, ymax = 250) do data\n if data[:thermal_generation] > 0\n return data[:stage_objective] / data[:thermal_generation]\n else # No thermal generation, so return 0.0.\n return 0.0\n end\nend","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"Note that there are many keyword arguments in addition to title. For example, we fixed the minimum and maximum values of the y-axis using ymin and ymax. See the SDDP.add_spaghetti documentation for all the arguments.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"Having built the plot, we now need to display it using SDDP.plot.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.plot(plt, \"spaghetti_plot.html\")","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"This should open a webpage that looks like this one.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"Using the mouse, you can highlight individual trajectories by hovering over them. This makes it possible to visualize a single trajectory across multiple dimensions.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"If you click on the plot, then trajectories that are close to the mouse pointer are shown darker and those further away are shown lighter.","category":"page"},{"location":"tutorial/plotting/#Publication-plots","page":"Plotting tools","title":"Publication plots","text":"","category":"section"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"Instead of the interactive Javascript plots, you can also create some publication ready plots using the SDDP.publication_plot function.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"info: Info\nYou need to install the Plots.jl package for this to work. We used the GR backend (gr()), but any Plots.jl backend should work.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.publication_plot implements a plot recipe to create ribbon plots of each variable against the stages. The first argument is the vector of simulation dictionaries and the second argument is the dictionary key that you want to plot. Standard Plots.jl keyword arguments such as title and xlabel can be used to modify the look of each plot. By default, the plot displays ribbons of the 0-100, 10-90, and 25-75 percentiles. The dark, solid line in the middle is the median (i.e. 50'th percentile).","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"import Plots\nPlots.plot(\n SDDP.publication_plot(simulations; title = \"Outgoing volume\") do data\n return data[:volume].out\n end,\n SDDP.publication_plot(simulations; title = \"Thermal generation\") do data\n return data[:thermal_generation]\n end;\n xlabel = \"Stage\",\n ylims = (0, 200),\n layout = (1, 2),\n)","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"You can save this plot as a PDF using the Plots.jl function savefig:","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"Plots.savefig(\"my_picture.pdf\")","category":"page"},{"location":"tutorial/plotting/#Plotting-the-value-function","page":"Plotting tools","title":"Plotting the value function","text":"","category":"section"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"You can obtain an object representing the value function of a node using SDDP.ValueFunction.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"V = SDDP.ValueFunction(model[(1, 1)])","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"The value function can be evaluated using SDDP.evaluate.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.evaluate(V; volume = 1)","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"evaluate returns the height of the value function, and a subgradient with respect to the convex state variables.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"You can also plot the value function using SDDP.plot","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.plot(V, volume = 0:200, filename = \"value_function.html\")","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"This should open a webpage that looks like this one.","category":"page"},{"location":"tutorial/plotting/#Convergence-dashboard","page":"Plotting tools","title":"Convergence dashboard","text":"","category":"section"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"If the text-based logging isn't to your liking, you can open a visualization of the training by passing dashboard = true to SDDP.train.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"SDDP.train(model; dashboard = true)","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"By default, dashboard = false because there is an initial overhead associated with opening and preparing the plot.","category":"page"},{"location":"tutorial/plotting/","page":"Plotting tools","title":"Plotting tools","text":"warning: Warning\nThe dashboard is experimental. There are known bugs associated with it, e.g., SDDP.jl#226.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"EditURL = \"the_farmers_problem.jl\"","category":"page"},{"location":"examples/the_farmers_problem/#The-farmer's-problem","page":"The farmer's problem","title":"The farmer's problem","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"This problem is taken from Section 1.1 of the book Birge, J. R., & Louveaux, F. (2011). Introduction to Stochastic Programming. New York, NY: Springer New York. Paragraphs in quotes are taken verbatim.","category":"page"},{"location":"examples/the_farmers_problem/#Problem-description","page":"The farmer's problem","title":"Problem description","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Consider a European farmer who specializes in raising wheat, corn, and sugar beets on his 500 acres of land. During the winter, [they want] to decide how much land to devote to each crop.The farmer knows that at least 200 tons (T) of wheat and 240 T of corn are needed for cattle feed. These amounts can be raised on the farm or bought from a wholesaler. Any production in excess of the feeding requirement would be sold.Over the last decade, mean selling prices have been $170 and $150 per ton of wheat and corn, respectively. The purchase prices are 40% more than this due to the wholesaler’s margin and transportation costs.Another profitable crop is sugar beet, which [they expect] to sell at $36/T; however, the European Commission imposes a quota on sugar beet production. Any amount in excess of the quota can be sold only at $10/T. The farmer’s quota for next year is 6000 T.\"Based on past experience, the farmer knows that the mean yield on [their] land is roughly 2.5 T, 3 T, and 20 T per acre for wheat, corn, and sugar beets, respectively.[To introduce uncertainty,] assume some correlation among the yields of the different crops. A very simplified representation of this would be to assume that years are good, fair, or bad for all crops, resulting in above average, average, or below average yields for all crops. To fix these ideas, above and below average indicate a yield 20% above or below the mean yield.","category":"page"},{"location":"examples/the_farmers_problem/#Problem-data","page":"The farmer's problem","title":"Problem data","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"The area of the farm.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"MAX_AREA = 500.0","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"There are three crops:","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"CROPS = [:wheat, :corn, :sugar_beet]","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Each of the crops has a different planting cost ($/acre).","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"PLANTING_COST = Dict(:wheat => 150.0, :corn => 230.0, :sugar_beet => 260.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"The farmer requires a minimum quantity of wheat and corn, but not of sugar beet (tonnes).","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"MIN_QUANTITIES = Dict(:wheat => 200.0, :corn => 240.0, :sugar_beet => 0.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"In Europe, there is a quota system for producing crops. The farmer owns the following quota for each crop (tonnes):","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"QUOTA_MAX = Dict(:wheat => Inf, :corn => Inf, :sugar_beet => 6_000.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"The farmer can sell crops produced under the quota for the following amounts ($/tonne):","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"SELL_IN_QUOTA = Dict(:wheat => 170.0, :corn => 150.0, :sugar_beet => 36.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"If they sell more than their allotted quota, the farmer earns the following on each tonne of crop above the quota ($/tonne):","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"SELL_NO_QUOTA = Dict(:wheat => 0.0, :corn => 0.0, :sugar_beet => 10.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"The purchase prices for wheat and corn are 40% more than their sales price. However, the description does not address the purchase price of sugar beet. Therefore, we use a large value of $1,000/tonne.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"BUY_PRICE = Dict(:wheat => 238.0, :corn => 210.0, :sugar_beet => 1_000.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"On average, each crop has the following yield in tonnes/acre:","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"MEAN_YIELD = Dict(:wheat => 2.5, :corn => 3.0, :sugar_beet => 20.0)","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"However, the yield is random. In good years, the yield is +20% above average, and in bad years, the yield is -20% below average.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"YIELD_MULTIPLIER = Dict(:good => 1.2, :fair => 1.0, :bad => 0.8)","category":"page"},{"location":"examples/the_farmers_problem/#Mathematical-formulation","page":"The farmer's problem","title":"Mathematical formulation","text":"","category":"section"},{"location":"examples/the_farmers_problem/#SDDP.jl-code","page":"The farmer's problem","title":"SDDP.jl code","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"note: Note\nIn what follows, we make heavy use of the fact that you can look up variables by their symbol name in a JuMP model as follows:@variable(model, x)\nmodel[:x]Read the JuMP documentation if this isn't familiar to you.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"First up, load SDDP.jl and a solver. For this example, we use HiGHS.jl.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"using SDDP, HiGHS","category":"page"},{"location":"examples/the_farmers_problem/#State-variables","page":"The farmer's problem","title":"State variables","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"State variables are the information that flows between stages. In our example, the state variables are the areas of land devoted to growing each crop.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"function add_state_variables(subproblem)\n @variable(subproblem, area[c = CROPS] >= 0, SDDP.State, initial_value = 0)\nend","category":"page"},{"location":"examples/the_farmers_problem/#First-stage-problem","page":"The farmer's problem","title":"First stage problem","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"We can only plant a maximum of 500 acres, and we want to minimize the planting cost","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"function create_first_stage_problem(subproblem)\n @constraint(\n subproblem,\n sum(subproblem[:area][c].out for c in CROPS) <= MAX_AREA\n )\n @stageobjective(\n subproblem,\n -sum(PLANTING_COST[c] * subproblem[:area][c].out for c in CROPS)\n )\nend","category":"page"},{"location":"examples/the_farmers_problem/#Second-stage-problem","page":"The farmer's problem","title":"Second stage problem","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Now let's consider the second stage problem. This is more complicated than the first stage, so we've broken it down into four sections:","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"control variables\nconstraints\nthe objective\nthe uncertainty","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"First, let's add the second stage control variables.","category":"page"},{"location":"examples/the_farmers_problem/#Variables","page":"The farmer's problem","title":"Variables","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"We add four types of control variables. Technically, the yield isn't a control variable. However, we add it as a dummy \"helper\" variable because it will be used when we add uncertainty.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"function second_stage_variables(subproblem)\n @variables(subproblem, begin\n 0 <= yield[c = CROPS] # tonnes/acre\n 0 <= buy[c = CROPS] # tonnes\n 0 <= sell_in_quota[c = CROPS] <= QUOTA_MAX[c] # tonnes\n 0 <= sell_no_quota[c = CROPS] # tonnes\n end)\nend","category":"page"},{"location":"examples/the_farmers_problem/#Constraints","page":"The farmer's problem","title":"Constraints","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"We need to define is the minimum quantity constraint. This ensures that MIN_QUANTITIES[c] of each crop is produced.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"function second_stage_constraint_min_quantity(subproblem)\n @constraint(\n subproblem,\n [c = CROPS],\n subproblem[:yield][c] + subproblem[:buy][c] -\n subproblem[:sell_in_quota][c] - subproblem[:sell_no_quota][c] >=\n MIN_QUANTITIES[c]\n )\nend","category":"page"},{"location":"examples/the_farmers_problem/#Objective","page":"The farmer's problem","title":"Objective","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"The objective of the second stage is to maximise revenue from selling crops, less the cost of buying corn and wheat if necessary to meet the minimum quantity constraint.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"function second_stage_objective(subproblem)\n @stageobjective(\n subproblem,\n sum(\n SELL_IN_QUOTA[c] * subproblem[:sell_in_quota][c] +\n SELL_NO_QUOTA[c] * subproblem[:sell_no_quota][c] -\n BUY_PRICE[c] * subproblem[:buy][c] for c in CROPS\n )\n )\nend","category":"page"},{"location":"examples/the_farmers_problem/#Random-variables","page":"The farmer's problem","title":"Random variables","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Then, in the SDDP.parameterize function, we set the coefficient using JuMP.set_normalized_coefficient.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"function second_stage_uncertainty(subproblem)\n @constraint(\n subproblem,\n uncertainty[c = CROPS],\n 1.0 * subproblem[:area][c].in == subproblem[:yield][c]\n )\n SDDP.parameterize(subproblem, [:good, :fair, :bad]) do ω\n for c in CROPS\n JuMP.set_normalized_coefficient(\n uncertainty[c],\n subproblem[:area][c].in,\n MEAN_YIELD[c] * YIELD_MULTIPLIER[ω],\n )\n end\n end\nend","category":"page"},{"location":"examples/the_farmers_problem/#Putting-it-all-together","page":"The farmer's problem","title":"Putting it all together","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Now we're ready to build the multistage stochastic programming model. In addition to the things already discussed, we need a few extra pieces of information.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"First, we are maximizing, so we set sense = :Max. Second, we need to provide a valid upper bound. (See Choosing an initial bound for more on this.) We know from Birge and Louveaux that the optimal solution is $108,390. So, let's choose $500,000 just to be safe.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Here is the full model.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"model = SDDP.LinearPolicyGraph(;\n stages = 2,\n sense = :Max,\n upper_bound = 500_000.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, stage\n add_state_variables(subproblem)\n if stage == 1\n create_first_stage_problem(subproblem)\n else\n second_stage_variables(subproblem)\n second_stage_constraint_min_quantity(subproblem)\n second_stage_uncertainty(subproblem)\n second_stage_objective(subproblem)\n end\nend","category":"page"},{"location":"examples/the_farmers_problem/#Training-a-policy","page":"The farmer's problem","title":"Training a policy","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Now that we've built a model, we need to train it using SDDP.train. The keyword iteration_limit stops the training after 40 iterations. See Choose a stopping rule for other ways to stop the training.","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"SDDP.train(model; iteration_limit = 40)","category":"page"},{"location":"examples/the_farmers_problem/#Checking-the-policy","page":"The farmer's problem","title":"Checking the policy","text":"","category":"section"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"Birge and Louveaux report that the optimal objective value is $108,390. Check that we got the correct solution using SDDP.calculate_bound:","category":"page"},{"location":"examples/the_farmers_problem/","page":"The farmer's problem","title":"The farmer's problem","text":"@assert isapprox(SDDP.calculate_bound(model), 108_390.0, atol = 0.1)","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"EditURL = \"warnings.jl\"","category":"page"},{"location":"tutorial/warnings/#Words-of-warning","page":"Words of warning","title":"Words of warning","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"SDDP is a powerful solution technique for multistage stochastic programming. However, there are a number of subtle things to be aware of before creating your own models.","category":"page"},{"location":"tutorial/warnings/#Relatively-complete-recourse","page":"Words of warning","title":"Relatively complete recourse","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"Models built in SDDP.jl need a property called relatively complete recourse.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"One definition of relatively complete recourse is that all feasible decisions (not necessarily optimal) in a subproblem lead to feasible decisions in future subproblems.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"For example, in the following problem, one feasible first stage decision is x.out = 0. But this causes an infeasibility in the second stage which requires x.in >= 1. This will throw an error about infeasibility if you try to solve.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"using SDDP, HiGHS\n\nmodel = SDDP.LinearPolicyGraph(;\n stages = 2,\n lower_bound = 0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, x >= 0, SDDP.State, initial_value = 1)\n if t == 2\n @constraint(sp, x.in >= 1)\n end\n @stageobjective(sp, x.out)\nend\n\ntry #hide\n SDDP.train(model; iteration_limit = 1, print_level = 0)\ncatch err #hide\n showerror(stderr, err) #hide\nend #hide","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"warning: Warning\nThe actual constraints causing the infeasibilities can be deceptive! A good strategy to debug is to comment out all constraints. Then, one-by-one, un-comment the constraints and try resolving the model to check if it finds a feasible solution.","category":"page"},{"location":"tutorial/warnings/#Numerical-stability","page":"Words of warning","title":"Numerical stability","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"If you aren't aware, SDDP builds an outer-approximation to a convex function using cutting planes. This results in a formulation that is particularly hard for solvers like HiGHS, Gurobi, and CPLEX to deal with. As a result, you may run into weird behavior. This behavior could include:","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"Iterations suddenly taking a long time (the solver stalled)\nSubproblems turning infeasible or unbounded after many iterations\nSolvers returning \"Numerical Error\" statuses","category":"page"},{"location":"tutorial/warnings/#Problem-scaling","page":"Words of warning","title":"Problem scaling","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"In almost all cases, the cause of this is poor problem scaling. For our purpose, poor problem scaling means having variables with very large numbers and variables with very small numbers in the same model.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"tip: Tip\nGurobi has an excellent set of articles on numerical issues and how to avoid them.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"Consider, for example, the hydro-thermal scheduling problem we have been discussing in previous tutorials.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"If we define the volume of the reservoir in terms of m³, then a lake might have a capacity of 10^10 m³: @variable(subproblem, 0 <= volume <= 10^10). Moreover, the cost per cubic meter might be around $0.05/m³. To calculate the value of water in our reservoir, we need to multiple a variable on the order of 10^10, by one on the order of 10⁻²! That is twelve orders of magnitude!","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"To improve the performance of the SDDP algorithm (and reduce the chance of weird behavior), try to re-scale the units of the problem in order to reduce the largest difference in magnitude. For example, if we talk in terms of million m³, then we have a capacity of 10⁴ million m³, and a price of $50,000 per million m³. Now things are only one order of magnitude apart.","category":"page"},{"location":"tutorial/warnings/#Numerical-stability-report","page":"Words of warning","title":"Numerical stability report","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"To aid in the diagnose of numerical issues, you can call SDDP.numerical_stability_report. By default, this aggregates all of the nodes into a single report. You can produce a stability report for each node by passing by_node=true.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"using SDDP\n\nmodel =\n SDDP.LinearPolicyGraph(; stages = 2, lower_bound = -1e10) do subproblem, t\n @variable(subproblem, x >= -1e7, SDDP.State, initial_value = 1e-5)\n @constraint(subproblem, 1e9 * x.out >= 1e-6 * x.in + 1e-8)\n @stageobjective(subproblem, 1e9 * x.out)\n end\n\nSDDP.numerical_stability_report(model)","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"The report analyses the magnitude (in absolute terms) of the coefficients in the constraint matrix, the objective function, any variable bounds, and in the RHS of the constraints. A warning will be thrown in SDDP.jl detects very large or small values. As discussed in Problem scaling, this is an indication that you should reformulate your model.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"By default, a numerical stability check is run when you call SDDP.train, although it can be turned off by passing run_numerical_stability_report = false.","category":"page"},{"location":"tutorial/warnings/#Solver-specific-options","page":"Words of warning","title":"Solver-specific options","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"If you have a particularly troublesome model, you should investigate setting solver-specific options to improve the numerical stability of each solver. For example, Gurobi has a NumericFocus option.","category":"page"},{"location":"tutorial/warnings/#Choosing-an-initial-bound","page":"Words of warning","title":"Choosing an initial bound","text":"","category":"section"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"One of the important requirements when building a SDDP model is to choose an appropriate bound on the objective (lower if minimizing, upper if maximizing). However, it can be hard to choose a bound if you don't know the solution! (Which is very likely.)","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"The bound should not be as large as possible (since this will help with convergence and the numerical issues discussed above), but if chosen too small, it may cut off the feasible region and lead to a sub-optimal solution.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"Consider the following simple model, where we first set lower_bound to 0.0.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"using SDDP, HiGHS\n\nmodel = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, t\n @variable(subproblem, x >= 0, SDDP.State, initial_value = 2)\n @variable(subproblem, u >= 0)\n @variable(subproblem, v >= 0)\n @constraint(subproblem, x.out == x.in - u)\n @constraint(subproblem, u + v == 1.5)\n @stageobjective(subproblem, t * v)\nend\n\nSDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false)","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"Now consider the case when we set the lower_bound to 10.0:","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"using SDDP, HiGHS\n\nmodel = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 10.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, t\n @variable(subproblem, x >= 0, SDDP.State, initial_value = 2)\n @variable(subproblem, u >= 0)\n @variable(subproblem, v >= 0)\n @constraint(subproblem, x.out == x.in - u)\n @constraint(subproblem, u + v == 1.5)\n @stageobjective(subproblem, t * v)\nend\n\nSDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false)","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"How do we tell which is more appropriate? There are a few clues that you should look out for.","category":"page"},{"location":"tutorial/warnings/","page":"Words of warning","title":"Words of warning","text":"The bound converges to a value above (if minimizing) the simulated cost of the policy. In this case, the problem is deterministic, so it is easy to tell. But you can also check by performing a Monte Carlo simulation like we did in An introduction to SDDP.jl.\nThe bound converges to different values when we change the bound. This is another clear give-away. The bound provided by the user is only used in the initial iterations. It should not change the value of the converged policy. Thus, if you don't know an appropriate value for the bound, choose an initial value, and then increase (or decrease) the value of the bound to confirm that the value of the policy doesn't change.\nThe bound converges to a value close to the bound provided by the user. This varies between models, but notice that 11.0 is quite close to 10.0 compared with 3.5 and 0.0.","category":"page"},{"location":"guides/add_a_multidimensional_state_variable/#Add-a-multi-dimensional-state-variable","page":"Add a multi-dimensional state variable","title":"Add a multi-dimensional state variable","text":"","category":"section"},{"location":"guides/add_a_multidimensional_state_variable/","page":"Add a multi-dimensional state variable","title":"Add a multi-dimensional state variable","text":"DocTestSetup = quote\n using SDDP, HiGHS\nend","category":"page"},{"location":"guides/add_a_multidimensional_state_variable/","page":"Add a multi-dimensional state variable","title":"Add a multi-dimensional state variable","text":"Just like normal JuMP variables, it is possible to create containers of state variables.","category":"page"},{"location":"guides/add_a_multidimensional_state_variable/","page":"Add a multi-dimensional state variable","title":"Add a multi-dimensional state variable","text":"julia> model = SDDP.LinearPolicyGraph(\n stages=1, lower_bound = 0, optimizer = HiGHS.Optimizer\n ) do subproblem, t\n # A scalar state variable.\n @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)\n println(\"Lower bound of outgoing x is: \", JuMP.lower_bound(x.out))\n # A vector of state variables.\n @variable(subproblem, y[i = 1:2] >= i, SDDP.State, initial_value = i)\n println(\"Lower bound of outgoing y[1] is: \", JuMP.lower_bound(y[1].out))\n # A JuMP.Containers.DenseAxisArray of state variables.\n @variable(subproblem,\n z[i = 3:4, j = [:A, :B]] >= i, SDDP.State, initial_value = i)\n println(\"Lower bound of outgoing z[3, :B] is: \", JuMP.lower_bound(z[3, :B].out))\n end;\nLower bound of outgoing x is: 0.0\nLower bound of outgoing y[1] is: 1.0\nLower bound of outgoing z[3, :B] is: 3.0","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"EditURL = \"objective_uncertainty.jl\"","category":"page"},{"location":"tutorial/objective_uncertainty/#Uncertainty-in-the-objective-function","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"","category":"section"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"In the previous tutorial, An introduction to SDDP.jl, we created a stochastic hydro-thermal scheduling model. In this tutorial, we extend the problem by adding uncertainty to the fuel costs.","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"Previously, we assumed that the fuel cost was deterministic: $50/MWh in the first stage, $100/MWh in the second stage, and $150/MWh in the third stage. For this tutorial, we assume that in addition to these base costs, the actual fuel cost is correlated with the inflows.","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"Our new model for the uncertainty is given by the following table:","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"ω 1 2 3\nP(ω) 1/3 1/3 1/3\ninflow 0 50 100\nfuel multiplier 1.5 1.0 0.75","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"In stage t, the objective is now to minimize:","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"fuel_multiplier * fuel_cost[t] * thermal_generation","category":"page"},{"location":"tutorial/objective_uncertainty/#Creating-a-model","page":"Uncertainty in the objective function","title":"Creating a model","text":"","category":"section"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"To add an uncertain objective, we can simply call @stageobjective from inside the SDDP.parameterize function.","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"using SDDP, HiGHS\n\nmodel = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, t\n # Define the state variable.\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n # Define the control variables.\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n # Define the constraints\n @constraints(\n subproblem,\n begin\n volume.out == volume.in + inflow - hydro_generation - hydro_spill\n thermal_generation + hydro_generation == 150.0\n end\n )\n fuel_cost = [50.0, 100.0, 150.0]\n # Parameterize the subproblem.\n Ω = [\n (inflow = 0.0, fuel_multiplier = 1.5),\n (inflow = 50.0, fuel_multiplier = 1.0),\n (inflow = 100.0, fuel_multiplier = 0.75),\n ]\n SDDP.parameterize(subproblem, Ω, [1 / 3, 1 / 3, 1 / 3]) do ω\n JuMP.fix(inflow, ω.inflow)\n @stageobjective(\n subproblem,\n ω.fuel_multiplier * fuel_cost[t] * thermal_generation\n )\n end\nend","category":"page"},{"location":"tutorial/objective_uncertainty/#Training-and-simulating-the-policy","page":"Uncertainty in the objective function","title":"Training and simulating the policy","text":"","category":"section"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"As in the previous two tutorials, we train and simulate the policy:","category":"page"},{"location":"tutorial/objective_uncertainty/","page":"Uncertainty in the objective function","title":"Uncertainty in the objective function","text":"SDDP.train(model)\n\nsimulations = SDDP.simulate(model, 500)\n\nobjective_values =\n [sum(stage[:stage_objective] for stage in sim) for sim in simulations]\n\nusing Statistics\n\nμ = round(mean(objective_values); digits = 2)\nci = round(1.96 * std(objective_values) / sqrt(500); digits = 2)\n\nprintln(\"Confidence interval: \", μ, \" ± \", ci)\nprintln(\"Lower bound: \", round(SDDP.calculate_bound(model); digits = 2))","category":"page"},{"location":"guides/add_a_risk_measure/#Add-a-risk-measure","page":"Add a risk measure","title":"Add a risk measure","text":"","category":"section"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"DocTestSetup = quote\n using SDDP, HiGHS\nend","category":"page"},{"location":"guides/add_a_risk_measure/#Training-a-risk-averse-model","page":"Add a risk measure","title":"Training a risk-averse model","text":"","category":"section"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"SDDP.jl supports a variety of risk measures. Two common ones are SDDP.Expectation and SDDP.WorstCase. Let's see how to train a policy using them. There are three possible ways.","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"If the same risk measure is used at every node in the policy graph, we can just pass an instance of one of the risk measures to the risk_measure keyword argument of the SDDP.train function.","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"SDDP.train(\n model,\n risk_measure = SDDP.WorstCase(),\n iteration_limit = 10\n)","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"However, if you want different risk measures at different nodes, there are two options. First, you can pass risk_measure a dictionary of risk measures, with one entry for each node. The keys of the dictionary are the indices of the nodes.","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"SDDP.train(\n model,\n risk_measure = Dict(\n 1 => SDDP.Expectation(),\n 2 => SDDP.WorstCase()\n ),\n iteration_limit = 10\n)","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"An alternative method is to pass risk_measure a function that takes one argument, the index of a node, and returns an instance of a risk measure:","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"SDDP.train(\n model,\n risk_measure = (node_index) -> begin\n if node_index == 1\n return SDDP.Expectation()\n else\n return SDDP.WorstCase()\n end\n end,\n iteration_limit = 10\n)","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"note: Note\nIf you simulate the policy, the simulated value is the risk-neutral value of the policy.","category":"page"},{"location":"guides/add_a_risk_measure/#Supported-risk-measures","page":"Add a risk measure","title":"Supported risk measures","text":"","category":"section"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"The following risk measures are implemented in SDDP.jl:","category":"page"},{"location":"guides/add_a_risk_measure/","page":"Add a risk measure","title":"Add a risk measure","text":"SDDP.Expectation [default]\nSDDP.AVaR\nSDDP.ConvexCombination\nSDDP.CVaR\nSDDP.EAVaR\nSDDP.Entropic\nSDDP.ModifiedChiSquared\nSDDP.Wasserstein\nSDDP.WorstCase","category":"page"},{"location":"examples/infinite_horizon_trivial/","page":"Infinite horizon trivial","title":"Infinite horizon trivial","text":"EditURL = \"infinite_horizon_trivial.jl\"","category":"page"},{"location":"examples/infinite_horizon_trivial/#Infinite-horizon-trivial","page":"Infinite horizon trivial","title":"Infinite horizon trivial","text":"","category":"section"},{"location":"examples/infinite_horizon_trivial/","page":"Infinite horizon trivial","title":"Infinite horizon trivial","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/infinite_horizon_trivial/","page":"Infinite horizon trivial","title":"Infinite horizon trivial","text":"using SDDP, HiGHS, Test\n\nfunction infinite_trivial()\n graph = SDDP.Graph(\n :root_node,\n [:week],\n [(:root_node => :week, 1.0), (:week => :week, 0.9)],\n )\n model = SDDP.PolicyGraph(\n graph;\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n @variable(subproblem, state, SDDP.State, initial_value = 0)\n @constraint(subproblem, state.in == state.out)\n @stageobjective(subproblem, 2.0)\n end\n SDDP.train(model; log_frequency = 10)\n @test SDDP.calculate_bound(model) ≈ 2.0 / (1 - 0.9) atol = 1e-3\n return\nend\n\ninfinite_trivial()","category":"page"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"EditURL = \"air_conditioning.jl\"","category":"page"},{"location":"examples/air_conditioning/#Air-conditioning","page":"Air conditioning","title":"Air conditioning","text":"","category":"section"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"Taken from Anthony Papavasiliou's notes on SDDP This is a variation of the problem that first appears in the book Introduction to Stochastic Programming by Birge and Louveaux, 1997, Springer-Verlag, New York, on page 237, Example 1. For a rescaled problem, they reported an optimal value of 6.25 with a first-stage solution of x1 = 2 (production)and y1 = 1 (store production). On this variation, without rescaling, it would be equivalent to 62500, 200 and 100, respectively.","category":"page"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"Consider the following problem","category":"page"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"Produce air conditioners for 3 months\n200 units/month at 100 $/unit\nOvertime costs 300 $/unit\nKnown demand of 100 units for period 1\nEqually likely demand, 100 or 300 units, for periods 2, 3\nStorage cost is 50 $/unit\nAll demand must be met","category":"page"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"The known optimal solution is $62,500","category":"page"},{"location":"examples/air_conditioning/","page":"Air conditioning","title":"Air conditioning","text":"using SDDP, HiGHS, Test\n\nfunction air_conditioning_model(duality_handler)\n model = SDDP.LinearPolicyGraph(;\n stages = 3,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, stage\n @variable(\n sp,\n 0 <= stored_production <= 100,\n Int,\n SDDP.State,\n initial_value = 0\n )\n @variable(sp, 0 <= production <= 200, Int)\n @variable(sp, overtime >= 0, Int)\n @variable(sp, demand)\n DEMAND = [[100.0], [100.0, 300.0], [100.0, 300.0]]\n SDDP.parameterize(ω -> JuMP.fix(demand, ω), sp, DEMAND[stage])\n @constraint(\n sp,\n stored_production.out ==\n stored_production.in + production + overtime - demand\n )\n @stageobjective(\n sp,\n 100 * production + 300 * overtime + 50 * stored_production.out\n )\n end\n SDDP.train(model; duality_handler = duality_handler)\n lb = SDDP.calculate_bound(model)\n println(\"Lower bound is: $lb\")\n @test isapprox(lb, 62_500.0, atol = 0.1)\n sims = SDDP.simulate(model, 1, [:production, :stored_production])\n x1 = sims[1][1][:production]\n y1 = sims[1][1][:stored_production].out\n @test isapprox(x1, 200, atol = 0.1)\n @test isapprox(y1, 100, atol = 0.1)\n println(\n \"With first stage solutions $(x1) (production) and $(y1) (stored_production).\",\n )\n return\nend\n\nfor duality_handler in [SDDP.LagrangianDuality(), SDDP.ContinuousConicDuality()]\n air_conditioning_model(duality_handler)\nend","category":"page"},{"location":"examples/sldp_example_two/","page":"SLDP: example 2","title":"SLDP: example 2","text":"EditURL = \"sldp_example_two.jl\"","category":"page"},{"location":"examples/sldp_example_two/#SLDP:-example-2","page":"SLDP: example 2","title":"SLDP: example 2","text":"","category":"section"},{"location":"examples/sldp_example_two/","page":"SLDP: example 2","title":"SLDP: example 2","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/sldp_example_two/","page":"SLDP: example 2","title":"SLDP: example 2","text":"This example is derived from Section 4.3 of the paper: Ahmed, S., Cabral, F. G., & da Costa, B. F. P. (2019). Stochastic Lipschitz Dynamic Programming. Optimization Online. PDF","category":"page"},{"location":"examples/sldp_example_two/","page":"SLDP: example 2","title":"SLDP: example 2","text":"using SDDP\nimport HiGHS\nimport Test\n\nfunction sldp_example_two(; first_stage_integer::Bool = true, N = 2)\n model = SDDP.LinearPolicyGraph(;\n stages = 2,\n lower_bound = -100.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n @variable(sp, 0 <= x[1:2] <= 5, SDDP.State, initial_value = 0.0)\n if t == 1\n if first_stage_integer\n @variable(sp, 0 <= u[1:2] <= 5, Int)\n @constraint(sp, [i = 1:2], u[i] == x[i].out)\n end\n @stageobjective(sp, -1.5 * x[1].out - 4 * x[2].out)\n else\n @variable(sp, 0 <= y[1:4] <= 1, Bin)\n @variable(sp, ω[1:2])\n @stageobjective(sp, -16 * y[1] - 19 * y[2] - 23 * y[3] - 28 * y[4])\n @constraint(\n sp,\n 2 * y[1] + 3 * y[2] + 4 * y[3] + 5 * y[4] <= ω[1] - x[1].in\n )\n @constraint(\n sp,\n 6 * y[1] + 1 * y[2] + 3 * y[3] + 2 * y[4] <= ω[2] - x[2].in\n )\n steps = range(5; stop = 15, length = N)\n SDDP.parameterize(sp, [[i, j] for i in steps for j in steps]) do φ\n return JuMP.fix.(ω, φ)\n end\n end\n end\n if get(ARGS, 1, \"\") == \"--write\"\n # Run `$ julia sldp_example_two.jl --write` to update the benchmark\n # model directory\n model_dir = joinpath(@__DIR__, \"..\", \"..\", \"..\", \"benchmarks\", \"models\")\n SDDP.write_to_file(\n model,\n joinpath(model_dir, \"sldp_example_two_$(N).sof.json.gz\");\n test_scenarios = 30,\n )\n return\n end\n SDDP.train(model; log_frequency = 10)\n bound = SDDP.calculate_bound(model)\n\n if N == 2\n Test.@test bound <= -57.0\n elseif N == 3\n Test.@test bound <= -59.33\n elseif N == 6\n Test.@test bound <= -61.22\n end\n return\nend\n\nsldp_example_two(; N = 2)\nsldp_example_two(; N = 3)\nsldp_example_two(; N = 6)","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"EditURL = \"objective_states.jl\"","category":"page"},{"location":"tutorial/objective_states/#Objective-states","page":"Objective states","title":"Objective states","text":"","category":"section"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"There are many applications in which we want to model a price process that follows some auto-regressive process. Common examples include stock prices on financial exchanges and spot-prices in energy markets.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"However, it is well known that these cannot be incorporated in to SDDP because they result in cost-to-go functions that are convex with respect to some state variables (e.g., the reservoir levels) and concave with respect to other state variables (e.g., the spot price in the current stage).","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"To overcome this problem, the approach in the literature has been to discretize the price process in order to model it using a Markovian policy graph like those discussed in Markovian policy graphs.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"However, recent work offers a way to include stagewise-dependent objective uncertainty into the objective function of SDDP subproblems. Readers are directed to the following works for an introduction:","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"Downward, A., Dowson, O., and Baucke, R. (2017). Stochastic dual dynamic programming with stagewise dependent objective uncertainty. Optimization Online. link\nDowson, O. PhD Thesis. University of Auckland, 2018. link","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"The method discussed in the above works introduces the concept of an objective state into SDDP. Unlike normal state variables in SDDP (e.g., the volume of water in the reservoir), the cost-to-go function is concave with respect to the objective states. Thus, the method builds an outer approximation of the cost-to-go function in the normal state-space, and an inner approximation of the cost-to-go function in the objective state-space.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"warning: Warning\nSupport for objective states in SDDP.jl is experimental. Models are considerably more computational intensive, the interface is less user-friendly, and there are subtle gotchas to be aware of. Only use this if you have read and understood the theory behind the method.","category":"page"},{"location":"tutorial/objective_states/#One-dimensional-objective-states","page":"Objective states","title":"One-dimensional objective states","text":"","category":"section"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"Let's assume that the fuel cost is not fixed, but instead evolves according to a multiplicative auto-regressive process: fuel_cost[t] = ω * fuel_cost[t-1], where ω is drawn from the sample space [0.75, 0.9, 1.1, 1.25] with equal probability.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"An objective state can be added to a subproblem using the SDDP.add_objective_state function. This can only be called once per subproblem. If you want to add a multi-dimensional objective state, read Multi-dimensional objective states. SDDP.add_objective_state takes a number of keyword arguments. The two required ones are","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"initial_value: the value of the objective state at the root node of the policy graph (i.e., identical to the initial_value when defining normal state variables.\nlipschitz: the Lipschitz constant of the cost-to-go function with respect to the objective state. In other words, this value is the maximum change in the cost-to-go function at any point in the state space, given a one-unit change in the objective state.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"There are also two optional keyword arguments: lower_bound and upper_bound, which give SDDP.jl hints (importantly, not constraints) about the domain of the objective state. Setting these bounds appropriately can improve the speed of convergence.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"Finally, SDDP.add_objective_state requires an update function. This function takes two arguments. The first is the incoming value of the objective state, and the second is the realization of the stagewise-independent noise term (set using SDDP.parameterize). The function should return the value of the objective state to be used in the current subproblem.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"This connection with the stagewise-independent noise term means that SDDP.parameterize must be called in a subproblem that defines an objective state. Inside SDDP.parameterize, the value of the objective state to be used in the current subproblem (i.e., after the update function), can be queried using SDDP.objective_state.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"Here is the full model with the objective state.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"using SDDP, HiGHS\n\nmodel = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, t\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n @constraints(\n subproblem,\n begin\n volume.out == volume.in + inflow - hydro_generation - hydro_spill\n demand_constraint, thermal_generation + hydro_generation == 150.0\n end\n )\n\n # Add an objective state. ω will be the same value that is called in\n # `SDDP.parameterize`.\n\n SDDP.add_objective_state(\n subproblem;\n initial_value = 50.0,\n lipschitz = 10_000.0,\n lower_bound = 50.0,\n upper_bound = 150.0,\n ) do fuel_cost, ω\n return ω.fuel * fuel_cost\n end\n\n # Create the cartesian product of a multi-dimensional random variable.\n\n Ω = [\n (fuel = f, inflow = w) for f in [0.75, 0.9, 1.1, 1.25] for\n w in [0.0, 50.0, 100.0]\n ]\n\n SDDP.parameterize(subproblem, Ω) do ω\n # Query the current fuel cost.\n fuel_cost = SDDP.objective_state(subproblem)\n @stageobjective(subproblem, fuel_cost * thermal_generation)\n return JuMP.fix(inflow, ω.inflow)\n end\nend","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"After creating our model, we can train and simulate as usual.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"SDDP.train(model; run_numerical_stability_report = false)\n\nsimulations = SDDP.simulate(model, 1)\n\nprint(\"Finished training and simulating.\")","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"To demonstrate how the objective states are updated, consider the sequence of noise observations:","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"[stage[:noise_term] for stage in simulations[1]]","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"This, the fuel cost in the first stage should be 0.75 * 50 = 37.5. The fuel cost in the second stage should be 1.1 * 37.5 = 41.25. The fuel cost in the third stage should be 0.75 * 41.25 = 30.9375.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"To confirm this, the values of the objective state in a simulation can be queried using the :objective_state key.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"[stage[:objective_state] for stage in simulations[1]]","category":"page"},{"location":"tutorial/objective_states/#Multi-dimensional-objective-states","page":"Objective states","title":"Multi-dimensional objective states","text":"","category":"section"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"You can construct multi-dimensional price processes using NTuples. Just replace every scalar value associated with the objective state by a tuple. For example, initial_value = 1.0 becomes initial_value = (1.0, 2.0).","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"Here is an example:","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"model = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, t\n @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n @variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n @constraints(\n subproblem,\n begin\n volume.out == volume.in + inflow - hydro_generation - hydro_spill\n demand_constraint, thermal_generation + hydro_generation == 150.0\n end\n )\n\n SDDP.add_objective_state(\n subproblem;\n initial_value = (50.0, 50.0),\n lipschitz = (10_000.0, 10_000.0),\n lower_bound = (50.0, 50.0),\n upper_bound = (150.0, 150.0),\n ) do fuel_cost, ω\n # fuel_cost is a tuple, containing the (fuel_cost[t-1], fuel_cost[t-2])\n # This function returns a new tuple containing\n # (fuel_cost[t], fuel_cost[t-1]). Thus, we need to compute the new\n # cost:\n new_cost = fuel_cost[1] + 0.5 * (fuel_cost[1] - fuel_cost[2]) + ω.fuel\n # And then return the appropriate tuple:\n return (new_cost, fuel_cost[1])\n end\n\n Ω = [\n (fuel = f, inflow = w) for f in [-10.0, -5.0, 5.0, 10.0] for\n w in [0.0, 50.0, 100.0]\n ]\n\n SDDP.parameterize(subproblem, Ω) do ω\n fuel_cost, _ = SDDP.objective_state(subproblem)\n @stageobjective(subproblem, fuel_cost * thermal_generation)\n return JuMP.fix(inflow, ω.inflow)\n end\nend\n\nSDDP.train(model; run_numerical_stability_report = false)\n\nsimulations = SDDP.simulate(model, 1)\n\nprint(\"Finished training and simulating.\")","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"This time, since our objective state is two-dimensional, the objective states are tuples with two elements:","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"[stage[:objective_state] for stage in simulations[1]]","category":"page"},{"location":"tutorial/objective_states/#objective_state_warnings","page":"Objective states","title":"Warnings","text":"","category":"section"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"There are number of things to be aware of when using objective states.","category":"page"},{"location":"tutorial/objective_states/","page":"Objective states","title":"Objective states","text":"The key assumption is that price is independent of the states and actions in the model.\nThat means that the price cannot appear in any @constraints. Nor can you use any @variables in the update function.\nChoosing an appropriate Lipschitz constant is difficult.\nThe points discussed in Choosing an initial bound are relevant. The Lipschitz constant should not be chosen as large as possible (since this will help with convergence and the numerical issues discussed above), but if chosen to small, it may cut of the feasible region and lead to a sub-optimal solution.\nYou need to ensure that the cost-to-go function is concave with respect to the objective state before the update.\nIf the update function is linear, this is always the case. In some situations, the update function can be nonlinear (e.g., multiplicative as we have above). In general, placing constraints on the price (e.g., clamp(price, 0, 1)) will destroy concavity. Caveat emptor. It's up to you if this is a problem. If it isn't you'll get a good heuristic with no guarantee of global optimality.","category":"page"},{"location":"examples/air_conditioning_forward/","page":"Training with a different forward model","title":"Training with a different forward model","text":"EditURL = \"air_conditioning_forward.jl\"","category":"page"},{"location":"examples/air_conditioning_forward/#Training-with-a-different-forward-model","page":"Training with a different forward model","title":"Training with a different forward model","text":"","category":"section"},{"location":"examples/air_conditioning_forward/","page":"Training with a different forward model","title":"Training with a different forward model","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/air_conditioning_forward/","page":"Training with a different forward model","title":"Training with a different forward model","text":"using SDDP\nimport HiGHS\nimport Test\n\nfunction create_air_conditioning_model(; convex::Bool)\n return SDDP.LinearPolicyGraph(;\n stages = 3,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n ) do sp, t\n @variable(sp, 0 <= x <= 100, SDDP.State, initial_value = 0)\n @variable(sp, 0 <= u_production <= 200)\n @variable(sp, u_overtime >= 0)\n if !convex\n set_integer(x.out)\n set_integer(u_production)\n set_integer(u_overtime)\n end\n @constraint(sp, demand, x.in - x.out + u_production + u_overtime == 0)\n Ω = [[100.0], [100.0, 300.0], [100.0, 300.0]]\n SDDP.parameterize(ω -> JuMP.set_normalized_rhs(demand, ω), sp, Ω[t])\n @stageobjective(sp, 100 * u_production + 300 * u_overtime + 50 * x.out)\n end\nend\n\nconvex = create_air_conditioning_model(; convex = true)\nnon_convex = create_air_conditioning_model(; convex = false)\nSDDP.train(\n convex;\n forward_pass = SDDP.AlternativeForwardPass(non_convex),\n post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),\n iteration_limit = 10,\n)\nTest.@test isapprox(SDDP.calculate_bound(non_convex), 62_500.0, atol = 0.1)\nTest.@test isapprox(SDDP.calculate_bound(convex), 62_500.0, atol = 0.1)","category":"page"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":"EditURL = \"objective_state_newsvendor.jl\"","category":"page"},{"location":"examples/objective_state_newsvendor/#Newsvendor","page":"Newsvendor","title":"Newsvendor","text":"","category":"section"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":"This example is based on the classical newsvendor problem, but features an AR(1) spot-price.","category":"page"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":" V(x[t-1], ω[t]) = max p[t] × u[t]\n subject to x[t] = x[t-1] - u[t] + ω[t]\n u[t] ∈ [0, 1]\n x[t] ≥ 0\n p[t] = p[t-1] + ϕ[t]","category":"page"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":"The initial conditions are","category":"page"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":"x[0] = 2.0\np[0] = 1.5\nω[t] ~ {0, 0.05, 0.10, ..., 0.45, 0.5} with uniform probability.\nϕ[t] ~ {-0.25, -0.125, 0.125, 0.25} with uniform probability.","category":"page"},{"location":"examples/objective_state_newsvendor/","page":"Newsvendor","title":"Newsvendor","text":"using SDDP, HiGHS, Statistics, Test\n\nfunction joint_distribution(; kwargs...)\n names = tuple([first(kw) for kw in kwargs]...)\n values = tuple([last(kw) for kw in kwargs]...)\n output_type = NamedTuple{names,Tuple{eltype.(values)...}}\n distribution = map(output_type, Base.product(values...))\n return distribution[:]\nend\n\nfunction newsvendor_example(; cut_type)\n model = SDDP.PolicyGraph(\n SDDP.LinearGraph(3);\n sense = :Max,\n upper_bound = 50.0,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, stage\n @variables(subproblem, begin\n x >= 0, (SDDP.State, initial_value = 2)\n 0 <= u <= 1\n w\n end)\n @constraint(subproblem, x.out == x.in - u + w)\n SDDP.add_objective_state(\n subproblem;\n initial_value = 1.5,\n lower_bound = 0.75,\n upper_bound = 2.25,\n lipschitz = 100.0,\n ) do y, ω\n return y + ω.price_noise\n end\n noise_terms = joint_distribution(;\n demand = 0:0.05:0.5,\n price_noise = [-0.25, -0.125, 0.125, 0.25],\n )\n SDDP.parameterize(subproblem, noise_terms) do ω\n JuMP.fix(w, ω.demand)\n price = SDDP.objective_state(subproblem)\n @stageobjective(subproblem, price * u)\n end\n end\n SDDP.train(\n model;\n log_frequency = 10,\n time_limit = 20.0,\n cut_type = cut_type,\n )\n @test SDDP.calculate_bound(model) ≈ 4.04 atol = 0.05\n results = SDDP.simulate(model, 500)\n objectives =\n [sum(s[:stage_objective] for s in simulation) for simulation in results]\n @test round(Statistics.mean(objectives); digits = 2) ≈ 4.04 atol = 0.1\n return\nend\n\nnewsvendor_example(; cut_type = SDDP.SINGLE_CUT)\nnewsvendor_example(; cut_type = SDDP.MULTI_CUT)","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"EditURL = \"arma.jl\"","category":"page"},{"location":"tutorial/arma/#Auto-regressive-stochastic-processes","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"","category":"section"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"SDDP.jl assumes that the random variable in each node is independent of the random variables in all other nodes. However, a common request is to model the random variables by some auto-regressive process.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"There are two ways to do this:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"model the random variable as a Markov chain\nuse the \"state-space expansion\" trick","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"info: Info\nThis tutorial is in the context of a hydro-thermal scheduling example, but it should be apparent how the ideas transfer to other applications.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"using SDDP\nimport HiGHS","category":"page"},{"location":"tutorial/arma/#state-space-expansion","page":"Auto-regressive stochastic processes","title":"The state-space expansion trick","text":"","category":"section"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"In An introduction to SDDP.jl, we assumed that the inflows were stagewise-independent. However, in many cases this is not correct, and inflow models are more accurately described by an auto-regressive process such as:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"inflow_t = inflow_t-1 + varepsilon","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"Here varepsilon is a random variable, and the inflow in stage t is the inflow in stage t-1 plus varepsilon (which might be negative).","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"For simplicity, we omit any coefficients and other terms, but this could easily be extended to a model like","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"inflow_t = a times inflow_t-1 + b + varepsilon","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"In practice, you can estimate a distribution for varepsilon by fitting the chosen statistical model to historical data, and then using the empirical residuals.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"To implement the auto-regressive model in SDDP.jl, we introduce inflow as a state variable.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"tip: Tip\nOur rule of thumb for \"when is something a state variable?\" is: if you need the value of a variable from a previous stage to compute something in stage t, then that variable is a state variable.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"model = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)\n @variable(sp, g_t >= 0)\n @variable(sp, g_h >= 0)\n @variable(sp, s >= 0)\n @constraint(sp, g_h + g_t == 150)\n c = [50, 100, 150]\n @stageobjective(sp, c[t] * g_t)\n # =========================================================================\n # New stuff below Here\n # Add inflow as a state\n @variable(sp, inflow, SDDP.State, initial_value = 50.0)\n # Add the random variable as a control variable\n @variable(sp, ε)\n # The equation describing our statistical model\n @constraint(sp, inflow.out == inflow.in + ε)\n # The new water balance constraint using the state variable\n @constraint(sp, x.out == x.in - g_h - s + inflow.out)\n # Assume we have some empirical residuals:\n Ω = [-10.0, 0.1, 9.6]\n SDDP.parameterize(sp, Ω) do ω\n return JuMP.fix(ε, ω)\n end\nend","category":"page"},{"location":"tutorial/arma/#When-can-this-trick-be-used?","page":"Auto-regressive stochastic processes","title":"When can this trick be used?","text":"","category":"section"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The state-space expansion trick should be used when:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The random variable appears additively in the objective or in the constraints. Something like inflow * decision_variable will not work.\nThe statistical model is linear, or can be written using the JuMP @constraint macro.\nThe dimension of the random variable is small (see Vector auto-regressive models for the multi-variate case).","category":"page"},{"location":"tutorial/arma/#The-Markov-chain-approach","page":"Auto-regressive stochastic processes","title":"The Markov chain approach","text":"","category":"section"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"In the Markov chain approach, we model the stochastic process for inflow by a discrete Markov chain. Markov chains are nodes with transition probabilities between the nodes. SDDP.jl has good support for solving problems in which the uncertainty is formulated as a Markov chain.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The first step of the Markov chain approach is to write a function which simulates the stochastic process. Here is a simulator for our inflow model:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"function simulator()\n inflow = zeros(3)\n current = 50.0\n Ω = [-10.0, 0.1, 9.6]\n for t in 1:3\n current += rand(Ω)\n inflow[t] = current\n end\n return inflow\nend","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"When called with no arguments, it produces a vector of inflows:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"simulator()","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"warning: Warning\nThe simulator must return a Vector{Float64}, so it is limited to a uni-variate random variable. It is possible to do something similar for multi-variate random variable, but you'll have to manually construct the Markov transition matrix, and solution times scale poorly, even in the two-dimensional case.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The next step is to call SDDP.MarkovianGraph with our simulator. This function will attempt to fit a Markov chain to the stochastic process produced by your simulator. There are two key arguments:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"budget is the total number of nodes we want in the Markov chain\nscenarios is a limit on the number of times we can call simulator","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"graph = SDDP.MarkovianGraph(simulator; budget = 8, scenarios = 30)","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"Here we can see we have created a MarkovianGraph with nodes like (2, 59.7). The first element of each node is the stage, and the second element is the inflow.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"Create a SDDP.PolicyGraph using graph as follows:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"model = SDDP.PolicyGraph(\n graph; # <--- New stuff\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, node\n t, inflow = node # <--- New stuff\n @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)\n @variable(sp, g_t >= 0)\n @variable(sp, g_h >= 0)\n @variable(sp, s >= 0)\n @constraint(sp, g_h + g_t == 150)\n c = [50, 100, 150]\n @stageobjective(sp, c[t] * g_t)\n # The new water balance constraint using the node:\n @constraint(sp, x.out == x.in - g_h - s + inflow)\nend","category":"page"},{"location":"tutorial/arma/#When-can-this-trick-be-used?-2","page":"Auto-regressive stochastic processes","title":"When can this trick be used?","text":"","category":"section"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The Markov chain approach should be used when:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The random variable is uni-variate\nThe random variable appears in the objective function or as a variable coefficient in the constraint matrix\nIt's non-trivial to write the stochastic process as a series of constraints (for example, it uses nonlinear terms)\nThe number of nodes is modest (for example, a budget of hundreds, up to perhaps 1000)","category":"page"},{"location":"tutorial/arma/#Vector-auto-regressive-models","page":"Auto-regressive stochastic processes","title":"Vector auto-regressive models","text":"","category":"section"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"The state-space expansion section assumed that the random variable was uni-variate. However, the approach naturally extends to vector auto-regressive models. For example, if inflow is a 2-dimensional vector, then we can model a vector auto-regressive model to it as follows:","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"inflow_t = A times inflow_t-1 + b + varepsilon","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"Here A is a 2-by-2 matrix, and b and varepsilon are 2-by-1 vectors.","category":"page"},{"location":"tutorial/arma/","page":"Auto-regressive stochastic processes","title":"Auto-regressive stochastic processes","text":"model = SDDP.LinearPolicyGraph(;\n stages = 3,\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)\n @variable(sp, g_t >= 0)\n @variable(sp, g_h >= 0)\n @variable(sp, s >= 0)\n @constraint(sp, g_h + g_t == 150)\n c = [50, 100, 150]\n @stageobjective(sp, c[t] * g_t)\n # =========================================================================\n # New stuff below Here\n # Add inflow as a state\n @variable(sp, inflow[1:2], SDDP.State, initial_value = 50.0)\n # Add the random variable as a control variable\n @variable(sp, ε[1:2])\n # The equation describing our statistical model\n A = [0.8 0.2; 0.2 0.8]\n @constraint(\n sp,\n [i = 1:2],\n inflow[i].out == sum(A[i, j] * inflow[j].in for j in 1:2) + ε[i],\n )\n # The new water balance constraint using the state variable\n @constraint(sp, x.out == x.in - g_h - s + inflow[1].out + inflow[2].out)\n # Assume we have some empirical residuals:\n Ω₁ = [-10.0, 0.1, 9.6]\n Ω₂ = [-10.0, 0.1, 9.6]\n Ω = [(ω₁, ω₂) for ω₁ in Ω₁ for ω₂ in Ω₂]\n SDDP.parameterize(sp, Ω) do ω\n JuMP.fix(ε[1], ω[1])\n JuMP.fix(ε[2], ω[2])\n return\n end\nend","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"EditURL = \"mdps.jl\"","category":"page"},{"location":"tutorial/mdps/#Example:-Markov-Decision-Processes","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"","category":"section"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"SDDP.jl can be used to solve a variety of Markov Decision processes. If the problem has continuous state and control spaces, and the objective and transition function are convex, then SDDP.jl can find a globally optimal policy. In other cases, SDDP.jl will find a locally optimal policy.","category":"page"},{"location":"tutorial/mdps/#A-simple-example","page":"Example: Markov Decision Processes","title":"A simple example","text":"","category":"section"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"A simple demonstration of this is the example taken from page 98 of the book \"Markov Decision Processes: Discrete stochastic Dynamic Programming\", by Martin L. Putterman.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"The example, as described in Section 4.6.3 of the book, is to minimize a sum of squares of N non-negative variables, subject to a budget constraint that the variable values add up to M. Put mathematically, that is:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"beginaligned\nmin sumlimits_i=1^N x_i^2 \nst sumlimits_i=1^N x_i = M \n x_i ge 0 quad i in 1ldotsN\nendaligned","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"The optimal objective value is M^2N, and the optimal solution is x_i = M N, which can be shown by induction.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"This can be reformulated as a Markov Decision Process by introducing a state variable, s, which tracks the un-spent budget over N stages.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"beginaligned\nV_t(s) = min x^2 + V_t+1(s^prime) \nst s^prime = s - x \n x le s \n x ge 0 \n s ge 0\nendaligned","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"and in the last stage V_N, there is an additional constraint that s^prime = 0.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"The budget of M is computed by solving for V_1(M).","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"info: Info\nSince everything here is continuous and convex, SDDP.jl will find the globally optimal policy.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"If the reformulation from the single problem into the recursive form of the Markov Decision Process is not obvious, consult Putterman's book.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"We can model and solve this problem using SDDP.jl as follows:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"using SDDP\nimport Ipopt\n\nM, N = 5, 3\n\nmodel = SDDP.LinearPolicyGraph(;\n stages = N,\n lower_bound = 0.0,\n optimizer = Ipopt.Optimizer,\n) do subproblem, node\n @variable(subproblem, s >= 0, SDDP.State, initial_value = M)\n @variable(subproblem, x >= 0)\n @stageobjective(subproblem, x^2)\n @constraint(subproblem, x <= s.in)\n @constraint(subproblem, s.out == s.in - x)\n if node == N\n fix(s.out, 0.0; force = true)\n end\n return\nend\n\nSDDP.train(model)","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Check that we got the theoretical optimum:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"SDDP.calculate_bound(model), M^2 / N","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"And check that we found the theoretical value for each x_i:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"simulations = SDDP.simulate(model, 1, [:x])\nfor data in simulations[1]\n println(\"x_$(data[:node_index]) = $(data[:x])\")\nend","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Close enough! We don't get exactly 5/3 because of numerical tolerances within our choice of optimization solver (in this case, Ipopt).","category":"page"},{"location":"tutorial/mdps/#A-more-complicated-policy","page":"Example: Markov Decision Processes","title":"A more complicated policy","text":"","category":"section"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"SDDP.jl is also capable of finding policies for other types of Markov Decision Processes. A classic example of a Markov Decision Process is the problem of finding a path through a maze.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Here's one example of a maze. Try changing the parameters to explore different mazes:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"M, N = 3, 4\ninitial_square = (1, 1)\nreward, illegal_squares, penalties = (3, 4), [(2, 2)], [(3, 1), (2, 4)]\npath = fill(\"⋅\", M, N)\npath[initial_square...] = \"1\"\nfor (k, v) in (illegal_squares => \"▩\", penalties => \"†\", [reward] => \"*\")\n for (i, j) in k\n path[i, j] = v\n end\nend\nprint(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\\n'))","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Our goal is to get from square 1 to square *. If we step on a †, we incur a penalty of 1. Squares with ▩ are blocked; we cannot move there.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"There are a variety of ways that we can solve this problem. We're going to solve it using a stationary binary stochastic programming formulation.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Our state variable will be a matrix of binary variables x_ij, where each element is 1 if the agent is in the square and 0 otherwise. In each period, we incur a reward of 1 if we are in the reward square and a penalty of -1 if we are in a penalties square. We cannot move to the illegal_squares, so those x_ij = 0. Feasibility between moves is modelled by constraints of the form:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"x^prime_ij le sumlimits_(ab)in P x_ab","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"where P is the set of squares from which it is valid to move from (a, b) to (i, j).","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Because we are looking for a stationary policy, we need a unicyclic graph with a discount factor:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"discount_factor = 0.9\ngraph = SDDP.UnicyclicGraph(discount_factor)","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Then we can formulate our full model:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"import HiGHS\n\nmodel = SDDP.PolicyGraph(\n graph;\n sense = :Max,\n upper_bound = 1 / (1 - discount_factor),\n optimizer = HiGHS.Optimizer,\n) do sp, _\n # Our state is a binary variable for each square\n @variable(\n sp,\n x[i = 1:M, j = 1:N],\n Bin,\n SDDP.State,\n initial_value = (i, j) == initial_square,\n )\n # Can only be in one square at a time\n @constraint(sp, sum(x[i, j].out for i in 1:M, j in 1:N) == 1)\n # Incur rewards and penalties\n @stageobjective(\n sp,\n x[reward...].out - sum(x[i, j].out for (i, j) in penalties)\n )\n # Some squares are illegal\n @constraint(sp, [(i, j) in illegal_squares], x[i, j].out <= 0)\n # Constraints on valid moves\n for i in 1:M, j in 1:N\n moves = [(i - 1, j), (i + 1, j), (i, j), (i, j + 1), (i, j - 1)]\n filter!(v -> 1 <= v[1] <= M && 1 <= v[2] <= N, moves)\n @constraint(sp, x[i, j].out <= sum(x[a, b].in for (a, b) in moves))\n end\n return\nend","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"The upper bound is obtained by assuming that we reach the reward square in one move and stay there.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"warning: Warning\nSince there are discrete decisions here, SDDP.jl is not guaranteed to find the globally optimal policy.","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"SDDP.train(model)","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Simulating a cyclic policy graph requires an explicit sampling_scheme that does not terminate early based on the cycle probability:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"simulations = SDDP.simulate(\n model,\n 1,\n [:x];\n sampling_scheme = SDDP.InSampleMonteCarlo(;\n max_depth = 5,\n terminate_on_dummy_leaf = false,\n ),\n);\nnothing #hide","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"Fill in the path with the time-step in which we visit the square:","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"for (t, data) in enumerate(simulations[1]), i in 1:M, j in 1:N\n if data[:x][i, j].in > 0.5\n path[i, j] = \"$t\"\n end\nend\n\nprint(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\\n'))","category":"page"},{"location":"tutorial/mdps/","page":"Example: Markov Decision Processes","title":"Example: Markov Decision Processes","text":"tip: Tip\nThis formulation will likely struggle as the number of cells in the maze increases. Can you think of an equivalent formulation that uses fewer state variables?","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"EditURL = \"Hydro_thermal.jl\"","category":"page"},{"location":"examples/Hydro_thermal/#Hydro-thermal-scheduling","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/Hydro_thermal/#Problem-Description","page":"Hydro-thermal scheduling","title":"Problem Description","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"In a hydro-thermal problem, the agent controls a hydro-electric generator and reservoir. Each time period, they need to choose a generation quantity from thermal g_t, and hydro g_h, in order to meet demand w_d, which is a stagewise-independent random variable. The state variable, x, is the quantity of water in the reservoir at the start of each time period, and it has a minimum level of 5 units and a maximum level of 15 units. We assume that there are 10 units of water in the reservoir at the start of time, so that x_0 = 10. The state-variable is connected through time by the water balance constraint: x.out = x.in - g_h - s + w_i, where x.out is the quantity of water at the end of the time period, x.in is the quantity of water at the start of the time period, s is the quantity of water spilled from the reservoir, and w_i is a stagewise-independent random variable that represents the inflow into the reservoir during the time period.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"We assume that there are three stages, t=1, 2, 3, representing summer-fall, winter, and spring, and that we are solving this problem in an infinite-horizon setting with a discount factor of 0.95.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"In each stage, the agent incurs the cost of spillage, plus the cost of thermal generation. We assume that the cost of thermal generation is dependent on the stage t = 1, 2, 3, and that in each stage, w is drawn from the set (w_i, w_d) = {(0, 7.5), (3, 5), (10, 2.5)} with equal probability.","category":"page"},{"location":"examples/Hydro_thermal/#Importing-packages","page":"Hydro-thermal scheduling","title":"Importing packages","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"For this example, in addition to SDDP, we need HiGHS as a solver and Statisitics to compute the mean of our simulations.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"using HiGHS\nusing SDDP\nusing Statistics","category":"page"},{"location":"examples/Hydro_thermal/#Constructing-the-policy-graph","page":"Hydro-thermal scheduling","title":"Constructing the policy graph","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"There are three stages in our infinite-horizon problem, so we construct a unicyclic policy graph using SDDP.UnicyclicGraph:","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"graph = SDDP.UnicyclicGraph(0.95; num_nodes = 3)","category":"page"},{"location":"examples/Hydro_thermal/#Constructing-the-model","page":"Hydro-thermal scheduling","title":"Constructing the model","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"Much of the macro code (i.e., lines starting with @) in the first part of the following should be familiar to users of JuMP.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"Inside the do-end block, sp is a standard JuMP model, and t is an index for the state variable that will be called with t = 1, 2, 3.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"The state variable x, constructed by passing the SDDP.State tag to @variable is actually a Julia struct with two fields: x.in and x.out corresponding to the incoming and outgoing state variables respectively. Both x.in and x.out are standard JuMP variables. The initial_value keyword provides the value of the state variable in the root node (i.e., x_0).","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"Compared to a JuMP model, one key difference is that we use @stageobjective instead of @objective. The SDDP.parameterize function takes a list of supports for w and parameterizes the JuMP model sp by setting the right-hand sides of the appropriate constraints (note how the constraints initially have a right-hand side of 0). By default, it is assumed that the realizations have uniform probability, but a probability mass vector can also be provided.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"model = SDDP.PolicyGraph(\n graph;\n sense = :Min,\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do sp, t\n @variable(sp, 5 <= x <= 15, SDDP.State, initial_value = 10)\n @variable(sp, g_t >= 0)\n @variable(sp, g_h >= 0)\n @variable(sp, s >= 0)\n @constraint(sp, balance, x.out - x.in + g_h + s == 0)\n @constraint(sp, demand, g_h + g_t == 0)\n @stageobjective(sp, s + t * g_t)\n SDDP.parameterize(sp, [[0, 7.5], [3, 5], [10, 2.5]]) do w\n set_normalized_rhs(balance, w[1])\n return set_normalized_rhs(demand, w[2])\n end\nend","category":"page"},{"location":"examples/Hydro_thermal/#Training-the-policy","page":"Hydro-thermal scheduling","title":"Training the policy","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"Once a model has been constructed, the next step is to train the policy. This can be achieved using SDDP.train. There are many options that can be passed, but iteration_limit terminates the training after the prescribed number of SDDP iterations.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"SDDP.train(model; iteration_limit = 100)","category":"page"},{"location":"examples/Hydro_thermal/#Simulating-the-policy","page":"Hydro-thermal scheduling","title":"Simulating the policy","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"After training, we can simulate the policy using SDDP.simulate.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"sims = SDDP.simulate(model, 100, [:g_t])\nmu = round(mean([s[1][:g_t] for s in sims]); digits = 2)\nprintln(\"On average, $(mu) units of thermal are used in the first stage.\")","category":"page"},{"location":"examples/Hydro_thermal/#Extracting-the-water-values","page":"Hydro-thermal scheduling","title":"Extracting the water values","text":"","category":"section"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"Finally, we can use SDDP.ValueFunction and SDDP.evaluate to obtain and evaluate the value function at different points in the state-space. Note that since we are minimizing, the price has a negative sign: each additional unit of water leads to a decrease in the expected long-run cost.","category":"page"},{"location":"examples/Hydro_thermal/","page":"Hydro-thermal scheduling","title":"Hydro-thermal scheduling","text":"V = SDDP.ValueFunction(model[1])\ncost, price = SDDP.evaluate(V; x = 10)","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"EditURL = \"hydro_valley.jl\"","category":"page"},{"location":"examples/hydro_valley/#Hydro-valleys","page":"Hydro valleys","title":"Hydro valleys","text":"","category":"section"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"This problem is a version of the hydro-thermal scheduling problem. The goal is to operate two hydro-dams in a valley chain over time in the face of inflow and price uncertainty.","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"Turbine response curves are modelled by piecewise linear functions which map the flow rate into a power. These can be controlled by specifying the breakpoints in the piecewise linear function as the knots in the Turbine struct.","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"The model can be created using the hydro_valley_model function. It has a few keyword arguments to allow automated testing of the library. hasstagewiseinflows determines if the RHS noise constraint should be added. hasmarkovprice determines if the price uncertainty (modelled by a Markov chain) should be added.","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"In the third stage, the Markov chain has some unreachable states to test some code-paths in the library.","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"We can also set the sense to :Min or :Max (the objective and bound are flipped appropriately).","category":"page"},{"location":"examples/hydro_valley/","page":"Hydro valleys","title":"Hydro valleys","text":"using SDDP, HiGHS, Test, Random\n\nstruct Turbine\n flowknots::Vector{Float64}\n powerknots::Vector{Float64}\nend\n\nstruct Reservoir\n min::Float64\n max::Float64\n initial::Float64\n turbine::Turbine\n spill_cost::Float64\n inflows::Vector{Float64}\nend\n\nfunction hydro_valley_model(;\n hasstagewiseinflows::Bool = true,\n hasmarkovprice::Bool = true,\n sense::Symbol = :Max,\n)\n valley_chain = [\n Reservoir(\n 0,\n 200,\n 200,\n Turbine([50, 60, 70], [55, 65, 70]),\n 1000,\n [0, 20, 50],\n ),\n Reservoir(\n 0,\n 200,\n 200,\n Turbine([50, 60, 70], [55, 65, 70]),\n 1000,\n [0, 0, 20],\n ),\n ]\n\n turbine(i) = valley_chain[i].turbine\n\n # Prices[t, Markov state]\n prices = [\n 1 2 0\n 2 1 0\n 3 4 0\n ]\n\n # Transition matrix\n if hasmarkovprice\n transition =\n Array{Float64,2}[[1.0]', [0.6 0.4], [0.6 0.4 0.0; 0.3 0.7 0.0]]\n else\n transition = [ones(Float64, (1, 1)) for t in 1:3]\n end\n\n flipobj = (sense == :Max) ? 1.0 : -1.0\n lower = (sense == :Max) ? -Inf : -1e6\n upper = (sense == :Max) ? 1e6 : Inf\n\n N = length(valley_chain)\n\n # Initialise SDDP Model\n return m = SDDP.MarkovianPolicyGraph(;\n sense = sense,\n lower_bound = lower,\n upper_bound = upper,\n transition_matrices = transition,\n optimizer = HiGHS.Optimizer,\n ) do subproblem, node\n t, markov_state = node\n\n # ------------------------------------------------------------------\n # SDDP State Variables\n # Level of upper reservoir\n @variable(\n subproblem,\n valley_chain[r].min <= reservoir[r = 1:N] <= valley_chain[r].max,\n SDDP.State,\n initial_value = valley_chain[r].initial\n )\n\n # ------------------------------------------------------------------\n # Additional variables\n @variables(\n subproblem,\n begin\n outflow[r = 1:N] >= 0\n spill[r = 1:N] >= 0\n inflow[r = 1:N] >= 0\n generation_quantity >= 0 # Total quantity of water\n # Proportion of levels to dispatch on\n 0 <=\n dispatch[r = 1:N, level = 1:length(turbine(r).flowknots)] <=\n 1\n rainfall[i = 1:N]\n end\n )\n\n # ------------------------------------------------------------------\n # Constraints\n @constraints(\n subproblem,\n begin\n # flow from upper reservoir\n reservoir[1].out ==\n reservoir[1].in + inflow[1] - outflow[1] - spill[1]\n\n # other flows\n flow[i = 2:N],\n reservoir[i].out ==\n reservoir[i].in + inflow[i] - outflow[i] - spill[i] +\n outflow[i-1] +\n spill[i-1]\n\n # Total quantity generated\n generation_quantity == sum(\n turbine(r).powerknots[level] * dispatch[r, level] for\n r in 1:N for level in 1:length(turbine(r).powerknots)\n )\n\n # ------------------------------------------------------------------\n # Flow out\n turbineflow[r = 1:N],\n outflow[r] == sum(\n turbine(r).flowknots[level] * dispatch[r, level] for\n level in 1:length(turbine(r).flowknots)\n )\n\n # Dispatch combination of levels\n dispatched[r = 1:N],\n sum(\n dispatch[r, level] for\n level in 1:length(turbine(r).flowknots)\n ) <= 1\n end\n )\n\n # rainfall noises\n if hasstagewiseinflows && t > 1 # in future stages random inflows\n @constraint(subproblem, inflow_noise[i = 1:N], inflow[i] <= rainfall[i])\n\n SDDP.parameterize(\n subproblem,\n [\n (valley_chain[1].inflows[i], valley_chain[2].inflows[i]) for i in 1:length(transition)\n ],\n ) do ω\n for i in 1:N\n JuMP.fix(rainfall[i], ω[i])\n end\n end\n else # in the first stage deterministic inflow\n @constraint(\n subproblem,\n initial_inflow_noise[i = 1:N],\n inflow[i] <= valley_chain[i].inflows[1]\n )\n end\n\n # ------------------------------------------------------------------\n # Objective Function\n if hasmarkovprice\n @stageobjective(\n subproblem,\n flipobj * (\n prices[t, markov_state] * generation_quantity -\n sum(valley_chain[i].spill_cost * spill[i] for i in 1:N)\n )\n )\n else\n @stageobjective(\n subproblem,\n flipobj * (\n prices[t, 1] * generation_quantity -\n sum(valley_chain[i].spill_cost * spill[i] for i in 1:N)\n )\n )\n end\n end\nend\n\nfunction test_hydro_valley_model()\n\n # For repeatability\n Random.seed!(11111)\n\n # deterministic\n deterministic_model = hydro_valley_model(;\n hasmarkovprice = false,\n hasstagewiseinflows = false,\n )\n SDDP.train(\n deterministic_model;\n iteration_limit = 10,\n cut_deletion_minimum = 1,\n print_level = 0,\n )\n @test SDDP.calculate_bound(deterministic_model) ≈ 835.0 atol = 1e-3\n\n # stagewise inflows\n stagewise_model = hydro_valley_model(; hasmarkovprice = false)\n SDDP.train(stagewise_model; iteration_limit = 20, print_level = 0)\n @test SDDP.calculate_bound(stagewise_model) ≈ 838.33 atol = 1e-2\n\n # Markov prices\n markov_model = hydro_valley_model(; hasstagewiseinflows = false)\n SDDP.train(markov_model; iteration_limit = 10, print_level = 0)\n @test SDDP.calculate_bound(markov_model) ≈ 851.8 atol = 1e-2\n\n # stagewise inflows and Markov prices\n markov_stagewise_model =\n hydro_valley_model(; hasstagewiseinflows = true, hasmarkovprice = true)\n SDDP.train(markov_stagewise_model; iteration_limit = 10, print_level = 0)\n @test SDDP.calculate_bound(markov_stagewise_model) ≈ 855.0 atol = 1.0\n\n # risk averse stagewise inflows and Markov prices\n riskaverse_model = hydro_valley_model()\n SDDP.train(\n riskaverse_model;\n risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.66),\n iteration_limit = 10,\n print_level = 0,\n )\n @test SDDP.calculate_bound(riskaverse_model) ≈ 828.157 atol = 1.0\n\n # stagewise inflows and Markov prices\n worst_case_model = hydro_valley_model(; sense = :Min)\n SDDP.train(\n worst_case_model;\n risk_measure = SDDP.EAVaR(; lambda = 0.5, beta = 0.0),\n iteration_limit = 10,\n print_level = 0,\n )\n @test SDDP.calculate_bound(worst_case_model) ≈ -780.867 atol = 1.0\n\n # stagewise inflows and Markov prices\n cutselection_model = hydro_valley_model()\n SDDP.train(\n cutselection_model;\n iteration_limit = 10,\n print_level = 0,\n cut_deletion_minimum = 2,\n )\n @test SDDP.calculate_bound(cutselection_model) ≈ 855.0 atol = 1.0\n\n # Distributionally robust Optimization\n dro_model = hydro_valley_model(; hasmarkovprice = false)\n SDDP.train(\n dro_model;\n risk_measure = SDDP.ModifiedChiSquared(sqrt(2 / 3) - 1e-6),\n iteration_limit = 10,\n print_level = 0,\n )\n @test SDDP.calculate_bound(dro_model) ≈ 835.0 atol = 1.0\n\n dro_model = hydro_valley_model(; hasmarkovprice = false)\n SDDP.train(\n dro_model;\n risk_measure = SDDP.ModifiedChiSquared(1 / 6),\n iteration_limit = 20,\n print_level = 0,\n )\n @test SDDP.calculate_bound(dro_model) ≈ 836.695 atol = 1.0\n # (Note) radius ≈ sqrt(2/3), will set all noise probabilities to zero except the worst case noise\n # (Why?):\n # The distance from the uniform distribution (the assumed \"true\" distribution)\n # to a corner of a unit simplex is sqrt(S-1)/sqrt(S) if we have S scenarios. The corner\n # of a unit simplex is just a unit vector, i.e.: [0 ... 0 1 0 ... 0]. With this probability\n # vector, only one noise has a non-zero probablity.\n # In the worst case rhsnoise (0 inflows) the profit is:\n # Reservoir1: 70 * $3 + 70 * $2 + 65 * $1 +\n # Reservoir2: 70 * $3 + 70 * $2 + 70 * $1\n ### = $835\nend\n\ntest_hydro_valley_model()","category":"page"},{"location":"guides/add_noise_in_the_constraint_matrix/#Add-noise-in-the-constraint-matrix","page":"Add noise in the constraint matrix","title":"Add noise in the constraint matrix","text":"","category":"section"},{"location":"guides/add_noise_in_the_constraint_matrix/","page":"Add noise in the constraint matrix","title":"Add noise in the constraint matrix","text":"DocTestSetup = quote\n using SDDP, HiGHS\nend","category":"page"},{"location":"guides/add_noise_in_the_constraint_matrix/","page":"Add noise in the constraint matrix","title":"Add noise in the constraint matrix","text":"SDDP.jl supports coefficients in the constraint matrix through the JuMP.set_normalized_coefficient function.","category":"page"},{"location":"guides/add_noise_in_the_constraint_matrix/","page":"Add noise in the constraint matrix","title":"Add noise in the constraint matrix","text":"julia> model = SDDP.LinearPolicyGraph(\n stages=3, lower_bound = 0, optimizer = HiGHS.Optimizer\n ) do subproblem, t\n @variable(subproblem, x, SDDP.State, initial_value = 0.0)\n @constraint(subproblem, emissions, 1 * x.out <= 1)\n SDDP.parameterize(subproblem, [0.2, 0.5, 1.0]) do ω\n ## rewrite 1 * x.out <= 1 to ω * x.out <= 1\n JuMP.set_normalized_coefficient(emissions, x.out, ω)\n end\n @stageobjective(subproblem, -x.out)\n end\nA policy graph with 3 nodes.\n Node indices: 1, 2, 3","category":"page"},{"location":"guides/add_noise_in_the_constraint_matrix/","page":"Add noise in the constraint matrix","title":"Add noise in the constraint matrix","text":"note: Note\nJuMP will normalize constraints by moving all variables to the left-hand side. Thus, @constraint(model, 0 <= 1 - x.out) becomes x.out <= 1. JuMP.set_normalized_coefficient sets the coefficient on the normalized constraint.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"EditURL = \"risk.jl\"","category":"page"},{"location":"explanation/risk/#Risk-aversion","page":"Risk aversion","title":"Risk aversion","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"In Introductory theory, we implemented a basic version of the SDDP algorithm. This tutorial extends that implementation to add risk-aversion.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Packages","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"This tutorial uses the following packages. For clarity, we call import PackageName so that we must prefix PackageName. to all functions and structs provided by that package. Everything not prefixed is either part of base Julia, or we wrote it.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"import ForwardDiff\nimport HiGHS\nimport Ipopt\nimport JuMP\nimport Statistics","category":"page"},{"location":"explanation/risk/#Risk-aversion:-what-and-why?","page":"Risk aversion","title":"Risk aversion: what and why?","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Often, the agents making decisions in complex systems are risk-averse, that is, they care more about avoiding very bad outcomes, than they do about having a good average outcome.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"As an example, consumers in a hydro-thermal problem may be willing to pay a slightly higher electricity price on average, if it means that there is a lower probability of blackouts.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Risk aversion in multistage stochastic programming has been well studied in the academic literature, and is widely used in production implementations around the world.","category":"page"},{"location":"explanation/risk/#Risk-measures","page":"Risk aversion","title":"Risk measures","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"One way to add risk aversion to models is to use a risk measure. A risk measure is a function that maps a random variable to a real number.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"You are probably already familiar with lots of different risk measures. For example, the mean, median, mode, and maximum are all risk measures.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We call the act of applying a risk measure to a random variable \"computing the risk\" of a random variable.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"To keep things simple, and because we need it for SDDP, we restrict our attention to random variables Z with a finite sample space Omega and positive probabilities p_omega for all omega in Omega. We denote the realizations of Z by Z(omega) = z_omega.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"A risk measure, mathbbFZ, is a convex risk measure if it satisfies the following axioms:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Axiom 1: monotonicity","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Given two random variables Z_1 and Z_2, with Z_1 le Z_2 almost surely, then mathbbFZ_1 le FZ_2.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Axiom 2: translation equivariance","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Given two random variables Z_1 and Z_2, then for all a in mathbbR, mathbbFZ + a = mathbbFZ + a.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Axiom 3: convexity","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Given two random variables Z_1 and Z_2, then for all a in 0 1,","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbFa Z_1 + (1 - a) Z_2 le a mathbbFZ_1 + (1-a)mathbbFZ_2","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Now we know what a risk measure is, let's see how we can use them to form risk-averse decision rules.","category":"page"},{"location":"explanation/risk/#Risk-averse-decision-rules:-Part-I","page":"Risk aversion","title":"Risk-averse decision rules: Part I","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We started this tutorial by explaining that we are interested in risk aversion because some agents are risk-averse. What that really means, is that they want a policy that is also risk-averse. The question then becomes, how do we create risk-averse decision rules and policies?","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Recall from Introductory theory that we can form an optimal decision rule using the recursive formulation:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"beginaligned\nV_i(x omega) = minlimits_barx x^prime u C_i(barx u omega) + mathbbE_j in i^+ varphi in Omega_jV_j(x^prime varphi)\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x\nendaligned","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"where our decision rule, pi_i(x omega), solves this optimization problem and returns a u^* corresponding to an optimal solution.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"If we can replace the expectation operator mathbbE with another (more risk-averse) risk measure mathbbF, then our decision rule will attempt to choose a control decision now that minimizes the risk of the future costs, as opposed to the expectation of the future costs. This makes our decisions more risk-averse, because we care more about the worst outcomes than we do about the average.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Therefore, we can form a risk-averse decision rule using the formulation:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"beginaligned\nV_i(x omega) = minlimits_barx x^prime u C_i(barx u omega) + mathbbF_j in i^+ varphi in Omega_jV_j(x^prime varphi)\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x\nendaligned","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"To convert this problem into a tractable equivalent, we apply Kelley's algorithm to the risk-averse cost-to-go term mathbbF_j in i^+ varphi in Omega_jV_j(x^prime varphi), to obtain the approximated problem:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"beginaligned\nV_i^K(x omega) = minlimits_barx x^prime u C_i(barx u omega) + theta\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x \n theta ge mathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right + fracddx^primemathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right^top (x^prime - x^prime_k)quad k=1ldotsK\nendaligned","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"warning: Warning\nNote how we need to explicitly compute a risk-averse subgradient! (We need a subgradient because the function might not be differentiable.) When constructing cuts with the expectation operator in Introductory theory, we implicitly used the law of total expectation to combine the two expectations; we can't do that for a general risk measure.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"tip: Homework challenge\nIf it's not obvious why we can use Kelley's here, try to use the axioms of a convex risk measure to show that mathbbF_j in i^+ varphi in Omega_jV_j(x^prime varphi) is a convex function w.r.t. x^prime if V_j is also a convex function.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Our challenge is now to find a way to compute the risk-averse cost-to-go function mathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right, and a way to compute a subgradient of the risk-averse cost-to-go function with respect to x^prime.","category":"page"},{"location":"explanation/risk/#Primal-risk-measures","page":"Risk aversion","title":"Primal risk measures","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Now we know what a risk measure is, and how we will use it, let's implement some code to see how we can compute the risk of some random variables.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"note: Note\nWe're going to start by implementing the primal version of each risk measure. We implement the dual version in the next section.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"First, we need some data:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Z = [1.0, 2.0, 3.0, 4.0]","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"with probabilities:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"p = [0.1, 0.2, 0.4, 0.3]","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We're going to implement a number of different risk measures, so to leverage Julia's multiple dispatch, we create an abstract type:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"abstract type AbstractRiskMeasure end","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"and function to overload:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"\"\"\"\n primal_risk(F::AbstractRiskMeasure, Z::Vector{<:Real}, p::Vector{Float64})\n\nUse `F` to compute the risk of the random variable defined by a vector of costs\n`Z` and non-zero probabilities `p`.\n\"\"\"\nfunction primal_risk end","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"note: Note\nWe want Vector{<:Real} instead of Vector{Float64} because we're going to automatically differentiate this function in the next section.","category":"page"},{"location":"explanation/risk/#Expectation","page":"Risk aversion","title":"Expectation","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The expectation, mathbbE, also called the mean or the average, is the most widely used convex risk measure. The expectation of a random variable is just the sum of Z weighted by the probability:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbFZ = mathbbE_pZ = sumlimits_omegainOmega p_omega z_omega","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"struct Expectation <: AbstractRiskMeasure end\n\nfunction primal_risk(::Expectation, Z::Vector{<:Real}, p::Vector{Float64})\n return sum(p[i] * Z[i] for i in 1:length(p))\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Let's try it out:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"primal_risk(Expectation(), Z, p)","category":"page"},{"location":"explanation/risk/#WorstCase","page":"Risk aversion","title":"WorstCase","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The worst-case risk measure, also called the maximum, is another widely used convex risk measure. This risk measure doesn't care about the probability vector p, only the cost vector Z:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbFZ = maxZ = maxlimits_omegainOmega z_omega","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"struct WorstCase <: AbstractRiskMeasure end\n\nfunction primal_risk(::WorstCase, Z::Vector{<:Real}, ::Vector{Float64})\n return maximum(Z)\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Let's try it out:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"primal_risk(WorstCase(), Z, p)","category":"page"},{"location":"explanation/risk/#Entropic","page":"Risk aversion","title":"Entropic","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"A more interesting, and less widely used risk measure is the entropic risk measure. The entropic risk measure is parameterized by a value gamma 0, and computes the risk of a random variable as:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbF_gammaZ = frac1gammalogleft(mathbbE_pe^gamma Zright) = frac1gammalogleft(sumlimits_omegainOmegap_omega e^gamma z_omegaright)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"tip: Homework challenge\nProve that the entropic risk measure satisfies the three axioms of a convex risk measure.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"struct Entropic <: AbstractRiskMeasure\n γ::Float64\n function Entropic(γ)\n if !(γ > 0)\n throw(DomainError(γ, \"Entropic risk measure must have γ > 0.\"))\n end\n return new(γ)\n end\nend\n\nfunction primal_risk(F::Entropic, Z::Vector{<:Real}, p::Vector{Float64})\n return 1 / F.γ * log(sum(p[i] * exp(F.γ * Z[i]) for i in 1:length(p)))\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"warning: Warning\nexp(x) overflows when x 709. Therefore, if we are passed a vector of Float64, use arbitrary precision arithmetic with big.(Z).","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function primal_risk(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})\n return Float64(primal_risk(F, big.(Z), p))\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Let's try it out for different values of gamma:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1_000.0]\n println(\"γ = $(γ), F[Z] = \", primal_risk(Entropic(γ), Z, p))\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"info: Info\nThe entropic has two extremes. As gamma rightarrow 0, the entropic acts like the expectation risk measure, and as gamma rightarrow infty, the entropic acts like the worst-case risk measure.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Computing risk measures this way works well for computing the primal value. However, there isn't an obvious way to compute a subgradient of the risk-averse cost-to-go function, which we need for our cut calculation.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"There is a nice solution to this problem, and that is to use the dual representation of a risk measure, instead of the primal.","category":"page"},{"location":"explanation/risk/#Dual-risk-measures","page":"Risk aversion","title":"Dual risk measures","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Convex risk measures have a dual representation as follows:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbFZ = suplimits_q inmathcalM(p) mathbbE_qZ - alpha(p q)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"where alpha is a concave function that maps the probability vectors p and q to a real number, and mathcalM(p) subseteq mathcalP is a convex subset of the probability simplex:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathcalP = p ge 0sumlimits_omegainOmegap_omega = 1","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The dual of a convex risk measure can be interpreted as taking the expectation of the random variable Z with respect to the worst probability vector q that lies within the set mathcalM, less some concave penalty term alpha(p q).","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"If we define a function dual_risk_inner that computes q and α:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"\"\"\"\n dual_risk_inner(\n F::AbstractRiskMeasure, Z::Vector{Float64}, p::Vector{Float64}\n )::Tuple{Vector{Float64},Float64}\n\nReturn a tuple formed by the worst-case probability vector `q` and the\ncorresponding evaluation `α(p, q)`.\n\"\"\"\nfunction dual_risk_inner end","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"then we can write a generic dual_risk function as:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function dual_risk(\n F::AbstractRiskMeasure,\n Z::Vector{Float64},\n p::Vector{Float64},\n)\n q, α = dual_risk_inner(F, Z, p)\n return sum(q[i] * Z[i] for i in 1:length(q)) - α\nend","category":"page"},{"location":"explanation/risk/#Expectation-2","page":"Risk aversion","title":"Expectation","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"For the expectation risk measure, mathcalM(p) = p, and alpha(cdot cdot) = 0. Therefore:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function dual_risk_inner(::Expectation, ::Vector{Float64}, p::Vector{Float64})\n return p, 0.0\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We can check we get the same result as the primal version:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"dual_risk(Expectation(), Z, p) == primal_risk(Expectation(), Z, p)","category":"page"},{"location":"explanation/risk/#Worst-case","page":"Risk aversion","title":"Worst-case","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"For the worst-case risk measure, mathcalM(p) = mathcalP, and alpha(cdot cdot) = 0. Therefore, the dual representation just puts all of the probability weight on the maximum outcome:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function dual_risk_inner(::WorstCase, Z::Vector{Float64}, ::Vector{Float64})\n q = zeros(length(Z))\n _, index = findmax(Z)\n q[index] = 1.0\n return q, 0.0\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We can check we get the same result as the primal version:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"dual_risk(WorstCase(), Z, p) == primal_risk(WorstCase(), Z, p)","category":"page"},{"location":"explanation/risk/#Entropic-2","page":"Risk aversion","title":"Entropic","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"For the entropic risk measure, mathcalM(p) = mathcalP, and:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"alpha(p q) = frac1gammasumlimits_omegainOmega q_omega logleft(fracq_omegap_omegaright)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"One way to solve the dual problem is to explicitly solve a nonlinear optimization problem:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})\n N = length(p)\n model = JuMP.Model(Ipopt.Optimizer)\n JuMP.set_silent(model)\n # For this problem, the solve is more accurate if we turn off problem\n # scaling.\n JuMP.set_optimizer_attribute(model, \"nlp_scaling_method\", \"none\")\n JuMP.@variable(model, 0 <= q[1:N] <= 1)\n JuMP.@constraint(model, sum(q) == 1)\n JuMP.@NLexpression(\n model,\n α,\n 1 / F.γ * sum(q[i] * log(q[i] / p[i]) for i in 1:N),\n )\n JuMP.@NLobjective(model, Max, sum(q[i] * Z[i] for i in 1:N) - α)\n JuMP.optimize!(model)\n return JuMP.value.(q), JuMP.value(α)\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We can check we get the same result as the primal version:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]\n primal = primal_risk(Entropic(γ), Z, p)\n dual = dual_risk(Entropic(γ), Z, p)\n success = primal ≈ dual ? \"✓\" : \"×\"\n println(\"$(success) γ = $(γ), primal = $(primal), dual = $(dual)\")\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"info: Info\nThis method of solving the dual problem \"on-the-side\" is used by SDDP.jl for a number of risk measures, including a distributionally robust risk measure with the Wasserstein distance. Check out all the risk measures that SDDP.jl supports in Add a risk measure.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The \"on-the-side\" method is very general, and it lets us incorporate any convex risk measure into SDDP. However, this comes at an increased computational cost and potential numerical issues (e.g., not converging to the exact solution).","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"However, for the entropic risk measure, Dowson, Morton, and Pagnoncelli (2020) derive the following closed form solution for q^*:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"q_omega^* = fracp_omega e^gamma z_omegasumlimits_varphi in Omega p_varphi e^gamma z_varphi","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"This is faster because we don't need to use Ipopt, and it avoids some of the numerical issues associated with solving a nonlinear program.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function dual_risk_inner(F::Entropic, Z::Vector{Float64}, p::Vector{Float64})\n q, α = zeros(length(p)), big(0.0)\n peγz = p .* exp.(F.γ .* big.(Z))\n sum_peγz = sum(peγz)\n for i in 1:length(q)\n big_q = peγz[i] / sum_peγz\n α += big_q * log(big_q / p[i])\n q[i] = Float64(big_q)\n end\n return q, Float64(α / F.γ)\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"warning: Warning\nAgain, note that we use big to avoid introducing overflow errors, before explicitly casting back to Float64 for the values we return.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We can check we get the same result as the primal version:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]\n primal = primal_risk(Entropic(γ), Z, p)\n dual = dual_risk(Entropic(γ), Z, p)\n success = primal ≈ dual ? \"✓\" : \"×\"\n println(\"$(success) γ = $(γ), primal = $(primal), dual = $(dual)\")\nend","category":"page"},{"location":"explanation/risk/#Risk-averse-subgradients","page":"Risk aversion","title":"Risk-averse subgradients","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We ended the section on primal risk measures by explaining how we couldn't use the primal risk measure in the cut calculation because we needed some way of computing a risk-averse subgradient:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"theta ge mathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right + fracddx^primemathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right^top (x^prime - x^prime_k)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The reason we use the dual representation is because of the following theorem, which explains how to compute a risk-averse gradient.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"info: The risk-averse subgradient theorem\nLet omega in Omega index a random vector with finite support and with nominal probability mass function, p in mathcalP, which satisfies p 0.Consider a convex risk measure, mathbbF, with a convex risk set, mathcalM(p), so that mathbbF can be expressed as the dual form.Let V(xomega) be convex with respect to x for all fixed omegainOmega, and let lambda(tildex omega) be a subgradient of V(xomega) with respect to x at x = tildex for each omega in Omega.Then, sum_omegainOmegaq^*_omega lambda(tildexomega) is a subgradient of mathbbFV(xomega) at tildex, whereq^* in argmax_q in mathcalM(p)leftmathbbE_qV(tildexomega) - alpha(p q)right","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"This theorem can be a little hard to unpack, so let's see an example:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function dual_risk_averse_subgradient(\n V::Function,\n # Use automatic differentiation to compute the gradient of V w.r.t. x,\n # given a fixed ω.\n λ::Function = (x, ω) -> ForwardDiff.gradient(x -> V(x, ω), x);\n F::AbstractRiskMeasure,\n Ω::Vector,\n p::Vector{Float64},\n x̃::Vector{Float64},\n)\n # Evaluate the function at x=x̃ for all ω ∈ Ω.\n V_ω = [V(x̃, ω) for ω in Ω]\n # Solve the dual problem to obtain an optimal q^*.\n q, α = dual_risk_inner(F, V_ω, p)\n # Compute the risk-averse subgradient by taking the expectation of the\n # subgradients w.r.t. q^*.\n dVdx = sum(q[i] * λ(x̃, ω) for (i, ω) in enumerate(Ω))\n return dVdx\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We can compare the subgradient obtained with the dual form against the automatic differentiation of the primal_risk function.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function primal_risk_averse_subgradient(\n V::Function;\n F::AbstractRiskMeasure,\n Ω::Vector,\n p::Vector{Float64},\n x̃::Vector{Float64},\n)\n inner(x) = primal_risk(F, [V(x, ω) for ω in Ω], p)\n return ForwardDiff.gradient(inner, x̃)\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"As our example function, we use:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"V(x, ω) = ω * x[1]^2","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"with:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Ω = [1.0, 2.0, 3.0]","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"and:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"p = [0.3, 0.4, 0.3]","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"at the point:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"x̃ = [3.0]","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"If mathbbF is the expectation risk-measure, then:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbFV(x omega) = 2 x^2","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The function evaluation x=3 is 18 and the subgradient is 12. Let's check we get it right with the dual form:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"dual_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"and the primal form:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"primal_risk_averse_subgradient(V; F = Expectation(), Ω = Ω, p = p, x̃ = x̃)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"If mathbbF is the worst-case risk measure, then:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"mathbbFV(x omega) = 3 x^2","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The function evaluation at x=3 is 27, and the subgradient is 18. Let's check we get it right with the dual form:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"dual_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"and the primal form:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"primal_risk_averse_subgradient(V; F = WorstCase(), Ω = Ω, p = p, x̃ = x̃)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"If mathbbF is the entropic risk measure, the math is a little more difficult to derive analytically. However, we can check against our primal version:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"for γ in [0.001, 0.01, 0.1, 1.0, 10.0, 100.0]\n dual =\n dual_risk_averse_subgradient(V; F = Entropic(γ), Ω = Ω, p = p, x̃ = x̃)\n primal = primal_risk_averse_subgradient(\n V;\n F = Entropic(γ),\n Ω = Ω,\n p = p,\n x̃ = x̃,\n )\n success = primal ≈ dual ? \"✓\" : \"×\"\n println(\"$(success) γ = $(γ), primal = $(primal), dual = $(dual)\")\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Uh oh! What happened with the last line? It looks our primal_risk_averse_subgradient encountered an error and returned a subgradient of NaN. This is because of the overflow issue with exp(x). However, we can be confident that our dual method of computing the risk-averse subgradient is both correct and more numerically robust than the primal version.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"info: Info\nAs another sanity check, notice how as gamma rightarrow 0, we tend toward the solution of the expectation risk-measure [12], and as gamma rightarrow infty, we tend toward the solution of the worse-case risk measure [18].","category":"page"},{"location":"explanation/risk/#Risk-averse-decision-rules:-Part-II","page":"Risk aversion","title":"Risk-averse decision rules: Part II","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Why is the risk-averse subgradient theorem helpful? Using the dual representation of a convex risk measure, we can re-write the cut:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"theta ge mathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right + fracddx^primemathbbF_j in i^+ varphi in Omega_jleftV_j^k(x^prime_k varphi)right^top (x^prime - x^prime_k)quad k=1ldotsK","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"as:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"theta ge mathbbE_q_kleftV_j^k(x^prime_k varphi) + fracddx^primeV_j^k(x^prime_k varphi)^top (x^prime - x^prime_k)right - alpha(p q_k)quad k=1ldotsK","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"where q_k = mathrmargsuplimits_q inmathcalM(p) mathbbE_qV_j^k(x_k^prime varphi) - alpha(p q).","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Therefore, we can formulate a risk-averse decision rule as:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"beginaligned\nV_i^K(x omega) = minlimits_barx x^prime u C_i(barx u omega) + theta\n x^prime = T_i(barx u omega) \n u in U_i(barx omega) \n barx = x \n theta ge mathbbE_q_kleftV_j^k(x^prime_k varphi) + fracddx^primeV_j^k(x^prime_k varphi)^top (x^prime - x^prime_k)right - alpha(p q_k)quad k=1ldotsK \n theta ge M\nendaligned","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"where q_k = mathrmargsuplimits_q inmathcalM(p) mathbbE_qV_j^k(x_k^prime varphi) - alpha(p q).","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Thus, to implement risk-averse SDDP, all we need to do is modify the backward pass to include this calculation of q_k, form the cut using q_k instead of p, and subtract the penalty term alpha(p q_k).","category":"page"},{"location":"explanation/risk/#Implementation","page":"Risk aversion","title":"Implementation","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Now we're ready to implement our risk-averse version of SDDP.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"As a prerequisite, we need most of the code from Introductory theory.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"

\nClick to view code from the tutorial \"Introductory theory\".","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"struct State\n in::JuMP.VariableRef\n out::JuMP.VariableRef\nend\n\nstruct Uncertainty\n parameterize::Function\n Ω::Vector{Any}\n P::Vector{Float64}\nend\n\nstruct Node\n subproblem::JuMP.Model\n states::Dict{Symbol,State}\n uncertainty::Uncertainty\n cost_to_go::JuMP.VariableRef\nend\n\nstruct PolicyGraph\n nodes::Vector{Node}\n arcs::Vector{Dict{Int,Float64}}\nend\n\nfunction Base.show(io::IO, model::PolicyGraph)\n println(io, \"A policy graph with $(length(model.nodes)) nodes\")\n println(io, \"Arcs:\")\n for (from, arcs) in enumerate(model.arcs)\n for (to, probability) in arcs\n println(io, \" $(from) => $(to) w.p. $(probability)\")\n end\n end\n return\nend\n\nfunction PolicyGraph(\n subproblem_builder::Function;\n graph::Vector{Dict{Int,Float64}},\n lower_bound::Float64,\n optimizer,\n)\n nodes = Node[]\n for t in 1:length(graph)\n model = JuMP.Model(optimizer)\n states, uncertainty = subproblem_builder(model, t)\n JuMP.@variable(model, cost_to_go >= lower_bound)\n obj = JuMP.objective_function(model)\n JuMP.@objective(model, Min, obj + cost_to_go)\n if length(graph[t]) == 0\n JuMP.fix(cost_to_go, 0.0; force = true)\n end\n push!(nodes, Node(model, states, uncertainty, cost_to_go))\n end\n return PolicyGraph(nodes, graph)\nend\n\nfunction sample_uncertainty(uncertainty::Uncertainty)\n r = rand()\n for (p, ω) in zip(uncertainty.P, uncertainty.Ω)\n r -= p\n if r < 0.0\n return ω\n end\n end\n return error(\"We should never get here because P should sum to 1.0.\")\nend\n\nfunction sample_next_node(model::PolicyGraph, current::Int)\n if length(model.arcs[current]) == 0\n return nothing\n else\n r = rand()\n for (to, probability) in model.arcs[current]\n r -= probability\n if r < 0.0\n return to\n end\n end\n return nothing\n end\nend\n\nfunction forward_pass(model::PolicyGraph, io::IO = stdout)\n incoming_state =\n Dict(k => JuMP.fix_value(v.in) for (k, v) in model.nodes[1].states)\n simulation_cost = 0.0\n trajectory = Tuple{Int,Dict{Symbol,Float64}}[]\n t = 1\n while t !== nothing\n node = model.nodes[t]\n ω = sample_uncertainty(node.uncertainty)\n node.uncertainty.parameterize(ω)\n for (k, v) in incoming_state\n JuMP.fix(node.states[k].in, v; force = true)\n end\n JuMP.optimize!(node.subproblem)\n if JuMP.termination_status(node.subproblem) != JuMP.MOI.OPTIMAL\n error(\"Something went terribly wrong!\")\n end\n outgoing_state = Dict(k => JuMP.value(v.out) for (k, v) in node.states)\n stage_cost =\n JuMP.objective_value(node.subproblem) - JuMP.value(node.cost_to_go)\n simulation_cost += stage_cost\n incoming_state = outgoing_state\n push!(trajectory, (t, outgoing_state))\n t = sample_next_node(model, t)\n end\n return trajectory, simulation_cost\nend\n\nfunction upper_bound(model::PolicyGraph; replications::Int)\n simulations = [forward_pass(model, devnull) for i in 1:replications]\n z = [s[2] for s in simulations]\n μ = Statistics.mean(z)\n tσ = 1.96 * Statistics.std(z) / sqrt(replications)\n return μ, tσ\nend\n\nfunction lower_bound(model::PolicyGraph)\n node = model.nodes[1]\n bound = 0.0\n for (p, ω) in zip(node.uncertainty.P, node.uncertainty.Ω)\n node.uncertainty.parameterize(ω)\n JuMP.optimize!(node.subproblem)\n bound += p * JuMP.objective_value(node.subproblem)\n end\n return bound\nend\n\nfunction evaluate_policy(\n model::PolicyGraph;\n node::Int,\n incoming_state::Dict{Symbol,Float64},\n random_variable,\n)\n the_node = model.nodes[node]\n the_node.uncertainty.parameterize(random_variable)\n for (k, v) in incoming_state\n JuMP.fix(the_node.states[k].in, v; force = true)\n end\n JuMP.optimize!(the_node.subproblem)\n return Dict(\n k => JuMP.value.(v) for\n (k, v) in JuMP.object_dictionary(the_node.subproblem)\n )\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"

","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"First, we need to modify the backward pass to compute the cuts using the risk-averse subgradient theorem:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function backward_pass(\n model::PolicyGraph,\n trajectory::Vector{Tuple{Int,Dict{Symbol,Float64}}},\n io::IO = stdout;\n risk_measure::AbstractRiskMeasure,\n)\n println(io, \"| Backward pass\")\n for i in reverse(1:length(trajectory))\n index, outgoing_states = trajectory[i]\n node = model.nodes[index]\n println(io, \"| | Visiting node $(index)\")\n if length(model.arcs[index]) == 0\n continue\n end\n # =====================================================================\n # New! Create vectors to store the cut expressions, V(x,ω) and p:\n cut_expressions, V_ω, p = JuMP.AffExpr[], Float64[], Float64[]\n # =====================================================================\n for (j, P_ij) in model.arcs[index]\n next_node = model.nodes[j]\n for (k, v) in outgoing_states\n JuMP.fix(next_node.states[k].in, v; force = true)\n end\n for (pφ, φ) in zip(next_node.uncertainty.P, next_node.uncertainty.Ω)\n next_node.uncertainty.parameterize(φ)\n JuMP.optimize!(next_node.subproblem)\n V = JuMP.objective_value(next_node.subproblem)\n dVdx = Dict(\n k => JuMP.reduced_cost(v.in) for (k, v) in next_node.states\n )\n # =============================================================\n # New! Construct and append the expression\n # `V_j^K(x_k, φ) + dVdx_j^K(x'_k, φ)ᵀ(x - x_k)` to the list of\n # cut expressions.\n push!(\n cut_expressions,\n JuMP.@expression(\n node.subproblem,\n V + sum(\n dVdx[k] * (x.out - outgoing_states[k]) for\n (k, x) in node.states\n ),\n )\n )\n # Add the objective value to Z:\n push!(V_ω, V)\n # Add the probability to p:\n push!(p, P_ij * pφ)\n # =============================================================\n end\n end\n # =====================================================================\n # New! Using the solutions in V_ω, compute q and α:\n q, α = dual_risk_inner(risk_measure, V_ω, p)\n println(io, \"| | | Z = \", Z)\n println(io, \"| | | p = \", p)\n println(io, \"| | | q = \", q)\n println(io, \"| | | α = \", α)\n # Then add the cut:\n c = JuMP.@constraint(\n node.subproblem,\n node.cost_to_go >=\n sum(q[i] * cut_expressions[i] for i in 1:length(q)) - α\n )\n # =====================================================================\n println(io, \"| | | Adding cut : \", c)\n end\n return nothing\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We also need to update the train loop of SDDP to pass a risk measure to the backward pass:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"function train(\n model::PolicyGraph;\n iteration_limit::Int,\n replications::Int,\n # =========================================================================\n # New! Add a risk_measure argument\n risk_measure::AbstractRiskMeasure,\n # =========================================================================\n io::IO = stdout,\n)\n for i in 1:iteration_limit\n println(io, \"Starting iteration $(i)\")\n outgoing_states, _ = forward_pass(model, io)\n # =====================================================================\n # New! Pass the risk measure to the backward pass.\n backward_pass(model, outgoing_states, io; risk_measure = risk_measure)\n # =====================================================================\n println(io, \"| Finished iteration\")\n println(io, \"| | lower_bound = \", lower_bound(model))\n end\n μ, tσ = upper_bound(model; replications = replications)\n println(io, \"Upper bound = $(μ) ± $(tσ)\")\n return\nend","category":"page"},{"location":"explanation/risk/#Risk-averse-bounds","page":"Risk aversion","title":"Risk-averse bounds","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"warning: Warning\nThis section is important.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"When we had a risk-neutral policy (i.e., we only used the expectation risk measure), we discussed how we could form valid lower and upper bounds.","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"The upper bound is still valid as a Monte Carlo simulation of the expected cost of the policy. (Although this upper bound doesn't capture the change in the policy we wanted to achieve, namely that the impact of the worst outcomes were reduced.)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"However, if we use a different risk measure, the lower bound is no longer valid!","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"We can still calculate a \"lower bound\" as the objective of the first-stage approximated subproblem, and this will converge to a finite value. However, we can't meaningfully interpret it as a bound with respect to the optimal policy. Therefore, it's best to just ignore the lower bound when training a risk-averse policy.","category":"page"},{"location":"explanation/risk/#Example:-risk-averse-hydro-thermal-scheduling","page":"Risk aversion","title":"Example: risk-averse hydro-thermal scheduling","text":"","category":"section"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Now it's time for an example. We create the same problem as Introductory theory:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"model = PolicyGraph(;\n graph = [Dict(2 => 1.0), Dict(3 => 1.0), Dict{Int,Float64}()],\n lower_bound = 0.0,\n optimizer = HiGHS.Optimizer,\n) do subproblem, t\n JuMP.set_silent(subproblem)\n JuMP.@variable(subproblem, volume_in == 200)\n JuMP.@variable(subproblem, 0 <= volume_out <= 200)\n states = Dict(:volume => State(volume_in, volume_out))\n JuMP.@variables(subproblem, begin\n thermal_generation >= 0\n hydro_generation >= 0\n hydro_spill >= 0\n inflow\n end)\n JuMP.@constraints(\n subproblem,\n begin\n volume_out == volume_in + inflow - hydro_generation - hydro_spill\n demand_constraint, thermal_generation + hydro_generation == 150.0\n end\n )\n fuel_cost = [50.0, 100.0, 150.0]\n JuMP.@objective(subproblem, Min, fuel_cost[t] * thermal_generation)\n uncertainty =\n Uncertainty([0.0, 50.0, 100.0], [1 / 3, 1 / 3, 1 / 3]) do ω\n return JuMP.fix(inflow, ω)\n end\n return states, uncertainty\nend","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Then we train a risk-averse policy, passing a risk measure to train:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"train(\n model;\n iteration_limit = 3,\n replications = 100,\n risk_measure = Entropic(1.0),\n)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"Finally, evaluate the decision rule:","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"evaluate_policy(\n model;\n node = 1,\n incoming_state = Dict(:volume => 150.0),\n random_variable = 75,\n)","category":"page"},{"location":"explanation/risk/","page":"Risk aversion","title":"Risk aversion","text":"info: Info\nFor this trivial example, the risk-averse policy isn't very different from the policy obtained using the expectation risk-measure. If you try it on some bigger/more interesting problems, you should see the expected cost increase, and the upper tail of the policy decrease.","category":"page"}] +} diff --git a/previews/PR826/siteinfo.js b/previews/PR826/siteinfo.js new file mode 100644 index 0000000000..db1cedecd2 --- /dev/null +++ b/previews/PR826/siteinfo.js @@ -0,0 +1 @@ +var DOCUMENTER_CURRENT_VERSION = "previews/PR826"; diff --git a/previews/PR826/tutorial/SDDP.log b/previews/PR826/tutorial/SDDP.log new file mode 100644 index 0000000000..56f2c915f0 --- /dev/null +++ b/previews/PR826/tutorial/SDDP.log @@ -0,0 +1,819 @@ +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 30 + state variables : 5 + scenarios : 1.00586e+12 + existing cuts : false +options + solver : serial mode + risk measure : A convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25) + sampling scheme : SDDP.SimulatorSamplingScheme{typeof(Main.__atexample__named__example_milk_producer.simulator)} +subproblem structure + VariableRef : [15, 15] + AffExpr in MOI.EqualTo{Float64} : [5, 5] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [8, 9] + VariableRef in MOI.LessThan{Float64} : [4, 4] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+01] + bounds range [2e+00, 1e+02] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 -2.475708e+01 6.553769e+01 1.232314e+00 162 1 + 61 1.008092e+01 7.886870e+00 2.234701e+00 9882 1 + 114 9.037845e+00 7.875605e+00 3.247699e+00 18468 1 + 162 9.712452e+00 7.873005e+00 4.266038e+00 26244 1 + 203 9.189887e+00 7.871622e+00 5.276566e+00 32886 1 + 243 9.000019e+00 7.871388e+00 6.279205e+00 39366 1 + 280 8.267105e+00 7.871006e+00 7.280654e+00 45360 1 + 314 1.138611e+01 7.870940e+00 8.280932e+00 50868 1 + 348 9.957101e+00 7.870627e+00 9.298395e+00 56376 1 + 487 9.672940e+00 7.870582e+00 1.430990e+01 78894 1 + 594 1.020155e+01 7.870581e+00 1.931252e+01 96228 1 + 611 8.129233e+00 7.870444e+00 2.003531e+01 98982 1 +------------------------------------------------------------------- +status : time_limit +total time (s) : 2.003531e+01 +total solves : 98982 +best bound : 7.870444e+00 +simulation ci : 8.880022e+00 ± 3.094901e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 1 + scenarios : 1.00000e+02 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [4, 4] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + AffExpr in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [2, 3] + VariableRef in MOI.LessThan{Float64} : [1, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e-01, 5e+00] + bounds range [2e+02, 1e+03] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 0.000000e+00 7.285509e+02 5.853176e-03 103 1 + 2 5.159109e+02 6.009811e+02 2.118611e-02 406 1 + 3 4.777421e+02 5.725761e+02 2.545404e-02 509 1 + 4 6.415596e+02 5.687018e+02 2.974415e-02 612 1 + 5 6.205034e+02 5.667033e+02 3.400421e-02 715 1 + 6 6.088708e+02 5.661918e+02 3.829598e-02 818 1 + 7 6.154551e+02 5.660775e+02 4.261398e-02 921 1 + 8 4.542303e+02 5.660394e+02 4.687405e-02 1024 1 + 9 5.518068e+02 5.660369e+02 5.112410e-02 1127 1 + 10 5.804826e+02 5.660369e+02 5.534601e-02 1230 1 + 11 5.439791e+02 5.660369e+02 5.985403e-02 1333 1 + 12 6.076753e+02 5.660369e+02 6.435800e-02 1436 1 + 13 4.545232e+02 5.660369e+02 6.880498e-02 1539 1 + 14 6.113499e+02 5.660369e+02 7.327199e-02 1642 1 + 15 5.063055e+02 5.660369e+02 7.771802e-02 1745 1 + 16 6.113499e+02 5.660369e+02 8.227921e-02 1848 1 + 17 4.822513e+02 5.660369e+02 8.678007e-02 1951 1 + 18 3.938110e+02 5.660369e+02 9.124207e-02 2054 1 + 19 4.881845e+02 5.660369e+02 9.571409e-02 2157 1 + 20 5.722176e+02 5.660369e+02 1.002121e-01 2260 1 + 21 5.661409e+02 5.660369e+02 1.183491e-01 2563 1 + 22 5.313244e+02 5.660369e+02 1.229300e-01 2666 1 + 23 6.113499e+02 5.660369e+02 1.275401e-01 2769 1 + 24 4.107931e+02 5.660369e+02 1.321032e-01 2872 1 + 25 6.113499e+02 5.660369e+02 1.366980e-01 2975 1 + 26 4.927614e+02 5.660369e+02 1.413081e-01 3078 1 + 27 4.378365e+02 5.660369e+02 1.459022e-01 3181 1 + 28 6.113499e+02 5.660369e+02 1.505282e-01 3284 1 + 29 4.704839e+02 5.660369e+02 1.550400e-01 3387 1 + 30 5.685328e+02 5.660369e+02 1.596131e-01 3490 1 + 31 4.542352e+02 5.660369e+02 1.641421e-01 3593 1 + 32 5.304119e+02 5.660369e+02 1.686690e-01 3696 1 + 33 5.027909e+02 5.660369e+02 1.731472e-01 3799 1 + 34 4.927614e+02 5.660369e+02 1.776941e-01 3902 1 + 35 6.113499e+02 5.660369e+02 1.822882e-01 4005 1 + 36 6.113499e+02 5.660369e+02 1.868992e-01 4108 1 + 37 4.525856e+02 5.660369e+02 1.914592e-01 4211 1 + 38 6.113499e+02 5.660369e+02 1.960070e-01 4314 1 + 39 5.063055e+02 5.660369e+02 2.006261e-01 4417 1 + 40 6.113499e+02 5.660369e+02 2.052431e-01 4520 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 2.052431e-01 +total solves : 4520 +best bound : 5.660369e+02 +simulation ci : 5.258656e+02 ± 3.392366e+01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 52 + state variables : 1 + scenarios : 1.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+01] + bounds range [1e+01, 3e+02] + rhs range [7e+00, 1e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.079600e+03 3.157700e+02 4.043794e-02 104 1 + 10 6.829100e+02 6.829100e+02 1.332049e-01 1040 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 1.332049e-01 +total solves : 1040 +best bound : 6.829100e+02 +simulation ci : 7.289889e+02 ± 7.726064e+01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 52 + state variables : 1 + scenarios : 6.46108e+24 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [2, 3] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+01] + bounds range [1e+01, 3e+02] + rhs range [7e+00, 1e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 5.280100e+02 9.139270e+01 4.416203e-02 208 1 + 48 1.323019e+02 2.485841e+02 1.048049e+00 9984 1 + 86 2.048329e+02 2.659575e+02 2.052619e+00 17888 1 + 100 3.091833e+01 2.685173e+02 2.415835e+00 20800 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 2.415835e+00 +total solves : 20800 +best bound : 2.685173e+02 +simulation ci : 3.077718e+02 ± 4.613924e+01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 52 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [2, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+01] + bounds range [1e+01, 3e+02] + rhs range [7e+00, 1e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.678376e+04 2.751676e+04 1.518590e-01 1667 1 + 5 3.604433e+04 8.652705e+04 1.233367e+00 14575 1 + 8 1.690164e+05 9.035063e+04 2.378571e+00 26648 1 + 12 1.891482e+05 9.253085e+04 3.772616e+00 40388 1 + 14 1.791072e+05 9.302733e+04 5.044939e+00 51210 1 + 15 2.934068e+05 9.323453e+04 6.732738e+00 63901 1 + 17 2.261693e+05 9.331255e+04 8.266854e+00 74515 1 + 19 4.024393e+04 9.334521e+04 9.281785e+00 81177 1 + 26 9.668378e+04 9.336095e+04 1.502346e+01 115102 1 + 41 3.634169e+05 9.337154e+04 2.300425e+01 153419 1 + 47 6.305561e+04 9.337393e+04 2.812257e+01 175485 1 + 50 1.441756e+05 9.337725e+04 3.462104e+01 200662 1 + 55 5.266733e+03 9.338192e+04 3.972483e+01 219189 1 + 60 1.273406e+05 9.338272e+04 4.535771e+01 238340 1 + 65 3.348033e+05 9.338388e+04 5.219400e+01 259987 1 + 73 1.033069e+05 9.338631e+04 5.726585e+01 275403 1 + 75 4.509049e+05 9.338659e+04 6.591720e+01 299953 1 + 78 1.306847e+05 9.338672e+04 7.112395e+01 314314 1 + 83 1.617152e+05 9.338762e+04 7.764913e+01 331801 1 + 89 3.303921e+05 9.338846e+04 8.663575e+01 354491 1 + 92 2.115573e+05 9.338912e+04 9.491650e+01 374468 1 + 96 1.860085e+05 9.339135e+04 1.026589e+02 392368 1 + 99 3.774492e+04 9.339221e+04 1.082910e+02 403817 1 + 100 2.990582e+04 9.339229e+04 1.089285e+02 405276 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 1.089285e+02 +total solves : 405276 +best bound : 9.339229e+04 +simulation ci : 9.110187e+04 ± 1.846262e+04 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 2.70000e+01 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+02] + bounds range [2e+02, 2e+02] + rhs range [2e+02, 2e+02] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.750000e+04 2.500000e+03 3.611088e-03 12 1 + 10 1.250000e+04 8.333333e+03 1.414299e-02 120 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 1.414299e-02 +total solves : 120 +best bound : 8.333333e+03 +simulation ci : 1.187500e+04 ± 6.125000e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 11 + state variables : 3 + scenarios : 1.02400e+13 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 2] + VariableRef in MOI.GreaterThan{Float64} : [4, 5] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 4e+01] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.886158e+05 4.573582e+04 1.809502e-02 212 1 + 52 1.007447e+05 1.443365e+05 1.032124e+00 14324 1 + 111 1.574605e+05 1.443373e+05 2.047157e+00 29032 1 + 176 7.539737e+04 1.443373e+05 3.061774e+00 42812 1 + 224 1.980079e+05 1.443373e+05 4.071919e+00 54088 1 + 277 1.612500e+05 1.443373e+05 5.074022e+00 65324 1 + 286 1.260500e+05 1.443373e+05 5.250725e+00 67232 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 5.250725e+00 +total solves : 67232 +best bound : 1.443373e+05 +simulation ci : 1.446033e+05 ± 3.621723e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 2 + state variables : 3 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 2] + VariableRef in MOI.GreaterThan{Float64} : [4, 5] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 4e+01] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.976053e+04 3.345593e+04 6.365776e-03 85 1 + 31 1.542236e+05 3.083482e+05 1.021843e+00 14605 1 + 67 3.600012e+05 3.125402e+05 2.044755e+00 27157 1 + 87 1.688336e+05 3.126470e+05 3.048394e+00 37803 1 + 107 4.105309e+05 3.126622e+05 4.107988e+00 47210 1 + 129 4.259734e+04 3.126646e+05 5.110773e+00 55947 1 + 156 9.569557e+05 3.126650e+05 6.282574e+00 65193 1 + 172 1.536492e+06 3.126650e+05 7.344901e+00 72979 1 + 183 9.826052e+04 3.126650e+05 8.354020e+00 79731 1 + 204 3.062605e+05 3.126650e+05 9.407581e+00 86535 1 + 270 1.845763e+05 3.126650e+05 1.445588e+01 113145 1 + 317 5.972079e+05 3.126650e+05 1.965487e+01 130727 1 + 343 6.835237e+05 3.126650e+05 2.510433e+01 143164 1 + 360 1.253763e+05 3.126650e+05 3.011826e+01 152631 1 + 376 3.714395e+05 3.126650e+05 3.544222e+01 160774 1 + 399 4.517763e+05 3.126650e+05 4.100058e+01 168588 1 + 400 3.821342e+05 3.126650e+05 4.137211e+01 169114 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 4.137211e+01 +total solves : 169114 +best bound : 3.126650e+05 +simulation ci : 3.018209e+05 ± 2.740583e+04 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 1 + scenarios : 1.08000e+02 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+02] + bounds range [2e+02, 2e+02] + rhs range [2e+02, 2e+02] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 9.375000e+03 1.991887e+03 4.940033e-03 18 1 + 40 1.875000e+03 8.072917e+03 1.270659e-01 1320 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.270659e-01 +total solves : 1320 +best bound : 8.072917e+03 +simulation ci : 5.917822e+03 ± 1.372472e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [4, 4] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + AffExpr in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [2, 3] + VariableRef in MOI.LessThan{Float64} : [1, 1] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [0e+00, 0e+00] + bounds range [0e+00, 0e+00] + rhs range [0e+00, 0e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 2.499895e+01 1.562631e+00 1.639104e-02 6 1 + 40 8.333333e+00 8.333333e+00 6.995511e-01 246 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 6.995511e-01 +total solves : 246 +best bound : 8.333333e+00 +simulation ci : 8.810723e+00 ± 8.167195e-01 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 12 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [25, 25] + AffExpr in MOI.EqualTo{Float64} : [1, 1] + AffExpr in MOI.LessThan{Float64} : [13, 13] + VariableRef in MOI.LessThan{Float64} : [1, 1] + VariableRef in MOI.ZeroOne : [12, 12] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+00] + bounds range [1e+01, 1e+01] + rhs range [1e+00, 1e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 0.000000e+00 7.217100e+00 4.740000e-03 13 1 + 40 2.500000e+01 6.561000e+00 8.170640e-01 3144 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 8.170640e-01 +total solves : 3144 +best bound : 6.561000e+00 +simulation ci : 8.075000e+00 ± 2.944509e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.72800e+03 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [8, 8] + AffExpr in MOI.EqualTo{Float64} : [2, 4] + AffExpr in MOI.GreaterThan{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [6, 6] + VariableRef in MOI.LessThan{Float64} : [2, 3] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 6.806250e+03 4.408308e+03 8.273125e-03 39 1 + 182 7.218750e+03 5.092593e+03 7.173290e-01 8598 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 7.173290e-01 +total solves : 8598 +best bound : 5.092593e+03 +simulation ci : 4.992895e+03 ± 5.635857e+02 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.72800e+03 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [9, 9] + AffExpr in MOI.EqualTo{Float64} : [2, 10] + AffExpr in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [7, 7] + VariableRef in MOI.LessThan{Float64} : [3, 4] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 7.250000e+03 3.529412e+03 9.008884e-03 39 1 + 184 1.875000e+03 5.135984e+03 1.009576e+00 8676 1 + 290 1.150000e+04 5.135984e+03 1.490031e+00 13110 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.490031e+00 +total solves : 13110 +best bound : 5.135984e+03 +simulation ci : 5.362165e+03 ± 4.590779e+02 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 2.70000e+01 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 2] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 2e+02] + bounds range [2e+02, 2e+02] + rhs range [2e+02, 2e+02] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 3.750000e+04 3.958333e+03 3.351927e-03 12 1 + 60 1.125000e+04 1.062500e+04 1.046178e-01 963 1 +------------------------------------------------------------------- +status : simulation_stopping +total time (s) : 1.046178e-01 +total solves : 963 +best bound : 1.062500e+04 +simulation ci : 1.142388e+04 ± 2.185147e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [20, 20] + AffExpr in MOI.EqualTo{Float64} : [13, 13] + AffExpr in MOI.Interval{Float64} : [6, 6] + VariableRef in MOI.GreaterThan{Float64} : [14, 14] + VariableRef in MOI.LessThan{Float64} : [11, 11] +numerical stability report + matrix range [1e+00, 2e+02] + objective range [1e+00, 1e+06] + bounds range [4e-01, 6e+00] + rhs range [5e-01, 5e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.458563e+07 3.264622e+04 1.050538e+00 271 1 + 3 1.883248e+06 9.059830e+04 2.166527e+00 461 1 + 8 4.978020e+05 3.616759e+05 3.320592e+00 636 1 + 10 4.551755e+06 3.694773e+05 5.082268e+00 930 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 5.082268e+00 +total solves : 930 +best bound : 3.694773e+05 +simulation ci : 2.225401e+06 ± 2.832083e+06 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [48, 48] + AffExpr in MOI.EqualTo{Float64} : [12, 12] + AffExpr in MOI.Interval{Float64} : [6, 6] + NonlinearExpr in MOI.EqualTo{Float64} : [24, 24] + QuadExpr in MOI.LessThan{Float64} : [12, 12] + VariableRef in MOI.GreaterThan{Float64} : [42, 42] + VariableRef in MOI.LessThan{Float64} : [39, 39] +numerical stability report + matrix range [1e+00, 1e+00] + objective range [1e+00, 1e+06] + bounds range [3e-01, 6e+00] + rhs range [5e-01, 2e+01] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 9.332938e+06 8.143094e+04 1.611966e+00 179 1 + 2 4.523914e+05 2.661422e+05 3.026099e+00 250 1 + 5 8.792993e+04 3.180986e+05 4.283293e+00 327 1 + 6 7.123937e+05 3.339911e+05 6.131846e+00 458 1 + 9 4.448369e+05 3.623498e+05 7.606387e+00 563 1 + 10 1.927026e+05 3.892585e+05 8.158246e+00 598 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 8.158246e+00 +total solves : 598 +best bound : 3.892585e+05 +simulation ci : 1.188252e+06 ± 1.778773e+06 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 1 + state variables : 1 + scenarios : Inf + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [20, 20] + AffExpr in MOI.EqualTo{Float64} : [13, 13] + AffExpr in MOI.Interval{Float64} : [6, 6] + VariableRef in MOI.GreaterThan{Float64} : [14, 14] + VariableRef in MOI.LessThan{Float64} : [11, 11] +numerical stability report + matrix range [1e+00, 2e+02] + objective range [1e+00, 1e+06] + bounds range [4e-01, 6e+00] + rhs range [5e-01, 5e+00] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 1.616405e+06 6.633473e+04 1.995130e-01 30 1 + 3 7.336368e+05 2.131314e+05 1.607509e+00 141 1 + 7 1.613523e+06 3.688984e+05 4.447348e+00 387 1 + 8 1.001434e+07 3.810947e+05 6.989353e+00 564 1 + 10 1.367906e+06 3.877966e+05 9.855739e+00 783 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 9.855739e+00 +total solves : 783 +best bound : 3.877966e+05 +simulation ci : 1.638935e+06 ± 1.863579e+06 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 5 + state variables : 1 + scenarios : 1.08000e+02 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [7, 7] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.EqualTo{Float64} : [1, 1] + VariableRef in MOI.GreaterThan{Float64} : [5, 5] + VariableRef in MOI.LessThan{Float64} : [1, 2] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 2.812500e+04 1.991887e+03 4.665136e-03 18 1 + 20 1.125000e+04 8.072917e+03 3.899813e-02 360 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 3.899813e-02 +total solves : 360 +best bound : 8.072917e+03 +simulation ci : 1.082898e+04 ± 2.947323e+03 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 5] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.LessThan{Float64} : [1, 1] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 6.500000e+00 3.000000e+00 2.904177e-03 6 1 + 5 3.500000e+00 3.500000e+00 5.540133e-03 30 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 5.540133e-03 +total solves : 30 +best bound : 3.500000e+00 +simulation ci : 4.100000e+00 ± 1.176000e+00 +numeric issues : 0 +------------------------------------------------------------------- + +------------------------------------------------------------------- + SDDP.jl (c) Oscar Dowson and contributors, 2017-25 +------------------------------------------------------------------- +problem + nodes : 3 + state variables : 1 + scenarios : 1.00000e+00 + existing cuts : false +options + solver : serial mode + risk measure : SDDP.Expectation() + sampling scheme : SDDP.InSampleMonteCarlo +subproblem structure + VariableRef : [5, 5] + AffExpr in MOI.EqualTo{Float64} : [2, 2] + VariableRef in MOI.GreaterThan{Float64} : [4, 4] + VariableRef in MOI.LessThan{Float64} : [1, 1] +------------------------------------------------------------------- + iteration simulation bound time (s) solves pid +------------------------------------------------------------------- + 1 6.500000e+00 1.100000e+01 3.064871e-03 6 1 + 5 5.500000e+00 1.100000e+01 5.489826e-03 30 1 +------------------------------------------------------------------- +status : iteration_limit +total time (s) : 5.489826e-03 +total solves : 30 +best bound : 1.100000e+01 +simulation ci : 5.700000e+00 ± 3.920000e-01 +numeric issues : 0 +------------------------------------------------------------------- + diff --git a/previews/PR826/tutorial/arma.ipynb b/previews/PR826/tutorial/arma.ipynb new file mode 100644 index 0000000000..747b10497e --- /dev/null +++ b/previews/PR826/tutorial/arma.ipynb @@ -0,0 +1,404 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Auto-regressive stochastic processes" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP.jl assumes that the random variable in each node is independent of the\n", + "random variables in all other nodes. However, a common request is to model\n", + "the random variables by some auto-regressive process." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are two ways to do this:\n", + " 1. model the random variable as a Markov chain\n", + " 2. use the \"state-space expansion\" trick" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> This tutorial is in the context of a hydro-thermal scheduling example, but\n", + "> it should be apparent how the ideas transfer to other applications." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## The state-space expansion trick" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In An introduction to SDDP.jl, we assumed that the inflows were\n", + "stagewise-independent. However, in many cases this is not correct, and inflow\n", + "models are more accurately described by an auto-regressive process such as:\n", + "$$\n", + "inflow_{t} = inflow_{t-1} + \\varepsilon\n", + "$$\n", + "Here $\\varepsilon$ is a random variable, and the inflow in stage $t$ is\n", + "the inflow in stage $t-1$ plus $\\varepsilon$ (which might be negative)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For simplicity, we omit any coefficients and other terms, but this could\n", + "easily be extended to a model like\n", + "$$\n", + "inflow_{t} = a \\times inflow_{t-1} + b + \\varepsilon\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In practice, you can estimate a distribution for $\\varepsilon$ by fitting\n", + "the chosen statistical model to historical data, and then using the empirical\n", + "residuals." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To implement the auto-regressive model in SDDP.jl, we introduce `inflow` as a\n", + "state variable." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> Our rule of thumb for \"when is something a state variable?\" is: if you\n", + "> need the value of a variable from a previous stage to compute something in\n", + "> stage $t$, then that variable is a state variable." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)\n", + " @variable(sp, g_t >= 0)\n", + " @variable(sp, g_h >= 0)\n", + " @variable(sp, s >= 0)\n", + " @constraint(sp, g_h + g_t == 150)\n", + " c = [50, 100, 150]\n", + " @stageobjective(sp, c[t] * g_t)\n", + " # =========================================================================\n", + " # New stuff below Here\n", + " # Add inflow as a state\n", + " @variable(sp, inflow, SDDP.State, initial_value = 50.0)\n", + " # Add the random variable as a control variable\n", + " @variable(sp, ε)\n", + " # The equation describing our statistical model\n", + " @constraint(sp, inflow.out == inflow.in + ε)\n", + " # The new water balance constraint using the state variable\n", + " @constraint(sp, x.out == x.in - g_h - s + inflow.out)\n", + " # Assume we have some empirical residuals:\n", + " Ω = [-10.0, 0.1, 9.6]\n", + " SDDP.parameterize(sp, Ω) do ω\n", + " return JuMP.fix(ε, ω)\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### When can this trick be used?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The state-space expansion trick should be used when:\n", + "\n", + " * The random variable appears additively in the objective or in the\n", + " constraints. Something like `inflow * decision_variable` will _not_ work.\n", + " * The statistical model is linear, or can be written using the JuMP\n", + " `@constraint` macro.\n", + " * The dimension of the random variable is small (see\n", + " Vector auto-regressive models for the multi-variate case)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## The Markov chain approach" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the Markov chain approach, we model the stochastic process for inflow by a\n", + "discrete Markov chain. Markov chains are nodes with transition probabilities\n", + "between the nodes. SDDP.jl has good support for solving problems in which the\n", + "uncertainty is formulated as a Markov chain." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The first step of the Markov chain approach is to write a function which\n", + "simulates the stochastic process. Here is a simulator for our inflow model:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function simulator()\n", + " inflow = zeros(3)\n", + " current = 50.0\n", + " Ω = [-10.0, 0.1, 9.6]\n", + " for t in 1:3\n", + " current += rand(Ω)\n", + " inflow[t] = current\n", + " end\n", + " return inflow\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "When called with no arguments, it produces a vector of inflows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulator()" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> The `simulator` must return a `Vector{Float64}`, so it is limited to a\n", + "> uni-variate random variable. It is possible to do something similar for\n", + "> multi-variate random variable, but you'll have to manually construct the\n", + "> Markov transition matrix, and solution times scale poorly, even in the\n", + "> two-dimensional case." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The next step is to call `SDDP.MarkovianGraph` with our simulator.\n", + "This function will attempt to fit a Markov chain to the stochastic process\n", + "produced by your `simulator`. There are two key arguments:\n", + " * `budget` is the total number of nodes we want in the Markov chain\n", + " * `scenarios` is a limit on the number of times we can call `simulator`" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "graph = SDDP.MarkovianGraph(simulator; budget = 8, scenarios = 30)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Here we can see we have created a MarkovianGraph with nodes like `(2, 59.7)`.\n", + "The first element of each node is the stage, and the second element is the\n", + "inflow." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Create a `SDDP.PolicyGraph` using `graph` as follows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.PolicyGraph(\n", + " graph; # <--- New stuff\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, node\n", + " t, inflow = node # <--- New stuff\n", + " @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)\n", + " @variable(sp, g_t >= 0)\n", + " @variable(sp, g_h >= 0)\n", + " @variable(sp, s >= 0)\n", + " @constraint(sp, g_h + g_t == 150)\n", + " c = [50, 100, 150]\n", + " @stageobjective(sp, c[t] * g_t)\n", + " # The new water balance constraint using the node:\n", + " @constraint(sp, x.out == x.in - g_h - s + inflow)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### When can this trick be used?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The Markov chain approach should be used when:\n", + "\n", + " * The random variable is uni-variate\n", + " * The random variable appears in the objective function or as a variable\n", + " coefficient in the constraint matrix\n", + " * It's non-trivial to write the stochastic process as a series of constraints\n", + " (for example, it uses nonlinear terms)\n", + " * The number of nodes is modest (for example, a budget of hundreds, up to\n", + " perhaps 1000)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Vector auto-regressive models" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The state-space expansion section assumed that\n", + "the random variable was uni-variate. However, the approach naturally extends\n", + "to vector auto-regressive models. For example, if `inflow` is a 2-dimensional\n", + "vector, then we can model a vector auto-regressive model to it as follows:\n", + "$$\n", + "inflow_{t} = A \\times inflow_{t-1} + b + \\varepsilon\n", + "$$\n", + "Here `A` is a 2-by-2 matrix, and `b` and $\\varepsilon$ are 2-by-1 vectors." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)\n", + " @variable(sp, g_t >= 0)\n", + " @variable(sp, g_h >= 0)\n", + " @variable(sp, s >= 0)\n", + " @constraint(sp, g_h + g_t == 150)\n", + " c = [50, 100, 150]\n", + " @stageobjective(sp, c[t] * g_t)\n", + " # =========================================================================\n", + " # New stuff below Here\n", + " # Add inflow as a state\n", + " @variable(sp, inflow[1:2], SDDP.State, initial_value = 50.0)\n", + " # Add the random variable as a control variable\n", + " @variable(sp, ε[1:2])\n", + " # The equation describing our statistical model\n", + " A = [0.8 0.2; 0.2 0.8]\n", + " @constraint(\n", + " sp,\n", + " [i = 1:2],\n", + " inflow[i].out == sum(A[i, j] * inflow[j].in for j in 1:2) + ε[i],\n", + " )\n", + " # The new water balance constraint using the state variable\n", + " @constraint(sp, x.out == x.in - g_h - s + inflow[1].out + inflow[2].out)\n", + " # Assume we have some empirical residuals:\n", + " Ω₁ = [-10.0, 0.1, 9.6]\n", + " Ω₂ = [-10.0, 0.1, 9.6]\n", + " Ω = [(ω₁, ω₂) for ω₁ in Ω₁ for ω₂ in Ω₂]\n", + " SDDP.parameterize(sp, Ω) do ω\n", + " JuMP.fix(ε[1], ω[1])\n", + " JuMP.fix(ε[2], ω[2])\n", + " return\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/arma.jl b/previews/PR826/tutorial/arma.jl new file mode 100644 index 0000000000..390c9a8353 --- /dev/null +++ b/previews/PR826/tutorial/arma.jl @@ -0,0 +1,217 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Auto-regressive stochastic processes + +# SDDP.jl assumes that the random variable in each node is independent of the +# random variables in all other nodes. However, a common request is to model +# the random variables by some auto-regressive process. + +# There are two ways to do this: +# 1. model the random variable as a Markov chain +# 2. use the "state-space expansion" trick + +# !!! info +# This tutorial is in the context of a hydro-thermal scheduling example, but +# it should be apparent how the ideas transfer to other applications. + +using SDDP +import HiGHS + +# ## [The state-space expansion trick](@id state-space-expansion) + +# In [An introduction to SDDP.jl](@ref), we assumed that the inflows were +# stagewise-independent. However, in many cases this is not correct, and inflow +# models are more accurately described by an auto-regressive process such as: +# ```math +# inflow_{t} = inflow_{t-1} + \varepsilon +# ``` +# Here ``\varepsilon`` is a random variable, and the inflow in stage ``t`` is +# the inflow in stage ``t-1`` plus ``\varepsilon`` (which might be negative). + +# For simplicity, we omit any coefficients and other terms, but this could +# easily be extended to a model like +# ```math +# inflow_{t} = a \times inflow_{t-1} + b + \varepsilon +# ``` + +# In practice, you can estimate a distribution for ``\varepsilon`` by fitting +# the chosen statistical model to historical data, and then using the empirical +# residuals. + +# To implement the auto-regressive model in SDDP.jl, we introduce `inflow` as a +# state variable. + +# !!! tip +# Our rule of thumb for "when is something a state variable?" is: if you +# need the value of a variable from a previous stage to compute something in +# stage ``t``, then that variable is a state variable. + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200) + @variable(sp, g_t >= 0) + @variable(sp, g_h >= 0) + @variable(sp, s >= 0) + @constraint(sp, g_h + g_t == 150) + c = [50, 100, 150] + @stageobjective(sp, c[t] * g_t) + ## ========================================================================= + ## New stuff below Here + ## Add inflow as a state + @variable(sp, inflow, SDDP.State, initial_value = 50.0) + ## Add the random variable as a control variable + @variable(sp, ε) + ## The equation describing our statistical model + @constraint(sp, inflow.out == inflow.in + ε) + ## The new water balance constraint using the state variable + @constraint(sp, x.out == x.in - g_h - s + inflow.out) + ## Assume we have some empirical residuals: + Ω = [-10.0, 0.1, 9.6] + SDDP.parameterize(sp, Ω) do ω + return JuMP.fix(ε, ω) + end +end + +# ### When can this trick be used? + +# The state-space expansion trick should be used when: +# +# * The random variable appears additively in the objective or in the +# constraints. Something like `inflow * decision_variable` will _not_ work. +# * The statistical model is linear, or can be written using the JuMP +# `@constraint` macro. +# * The dimension of the random variable is small (see +# [Vector auto-regressive models](@ref) for the multi-variate case). + +# ## The Markov chain approach + +# In the Markov chain approach, we model the stochastic process for inflow by a +# discrete Markov chain. Markov chains are nodes with transition probabilities +# between the nodes. SDDP.jl has good support for solving problems in which the +# uncertainty is formulated as a Markov chain. + +# The first step of the Markov chain approach is to write a function which +# simulates the stochastic process. Here is a simulator for our inflow model: + +function simulator() + inflow = zeros(3) + current = 50.0 + Ω = [-10.0, 0.1, 9.6] + for t in 1:3 + current += rand(Ω) + inflow[t] = current + end + return inflow +end + +# When called with no arguments, it produces a vector of inflows: + +simulator() + +# !!! warning +# The `simulator` must return a `Vector{Float64}`, so it is limited to a +# uni-variate random variable. It is possible to do something similar for +# multi-variate random variable, but you'll have to manually construct the +# Markov transition matrix, and solution times scale poorly, even in the +# two-dimensional case. + +# The next step is to call [`SDDP.MarkovianGraph`](@ref) with our simulator. +# This function will attempt to fit a Markov chain to the stochastic process +# produced by your `simulator`. There are two key arguments: +# * `budget` is the total number of nodes we want in the Markov chain +# * `scenarios` is a limit on the number of times we can call `simulator` + +graph = SDDP.MarkovianGraph(simulator; budget = 8, scenarios = 30) + +# Here we can see we have created a MarkovianGraph with nodes like `(2, 59.7)`. +# The first element of each node is the stage, and the second element is the +# inflow. + +# Create a [`SDDP.PolicyGraph`](@ref) using `graph` as follows: + +model = SDDP.PolicyGraph( + graph; # <--- New stuff + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, node + t, inflow = node # <--- New stuff + @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200) + @variable(sp, g_t >= 0) + @variable(sp, g_h >= 0) + @variable(sp, s >= 0) + @constraint(sp, g_h + g_t == 150) + c = [50, 100, 150] + @stageobjective(sp, c[t] * g_t) + ## The new water balance constraint using the node: + @constraint(sp, x.out == x.in - g_h - s + inflow) +end + +# ### When can this trick be used? + +# The Markov chain approach should be used when: +# +# * The random variable is uni-variate +# * The random variable appears in the objective function or as a variable +# coefficient in the constraint matrix +# * It's non-trivial to write the stochastic process as a series of constraints +# (for example, it uses nonlinear terms) +# * The number of nodes is modest (for example, a budget of hundreds, up to +# perhaps 1000) + +# ## Vector auto-regressive models + +# The [state-space expansion](@ref state-space-expansion) section assumed that +# the random variable was uni-variate. However, the approach naturally extends +# to vector auto-regressive models. For example, if `inflow` is a 2-dimensional +# vector, then we can model a vector auto-regressive model to it as follows: +# ```math +# inflow_{t} = A \times inflow_{t-1} + b + \varepsilon +# ``` +# Here `A` is a 2-by-2 matrix, and `b` and ``\varepsilon`` are 2-by-1 vectors. + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200) + @variable(sp, g_t >= 0) + @variable(sp, g_h >= 0) + @variable(sp, s >= 0) + @constraint(sp, g_h + g_t == 150) + c = [50, 100, 150] + @stageobjective(sp, c[t] * g_t) + ## ========================================================================= + ## New stuff below Here + ## Add inflow as a state + @variable(sp, inflow[1:2], SDDP.State, initial_value = 50.0) + ## Add the random variable as a control variable + @variable(sp, ε[1:2]) + ## The equation describing our statistical model + A = [0.8 0.2; 0.2 0.8] + @constraint( + sp, + [i = 1:2], + inflow[i].out == sum(A[i, j] * inflow[j].in for j in 1:2) + ε[i], + ) + ## The new water balance constraint using the state variable + @constraint(sp, x.out == x.in - g_h - s + inflow[1].out + inflow[2].out) + ## Assume we have some empirical residuals: + Ω₁ = [-10.0, 0.1, 9.6] + Ω₂ = [-10.0, 0.1, 9.6] + Ω = [(ω₁, ω₂) for ω₁ in Ω₁ for ω₂ in Ω₂] + SDDP.parameterize(sp, Ω) do ω + JuMP.fix(ε[1], ω[1]) + JuMP.fix(ε[2], ω[2]) + return + end +end diff --git a/previews/PR826/tutorial/arma/index.html b/previews/PR826/tutorial/arma/index.html new file mode 100644 index 0000000000..f16e2b3276 --- /dev/null +++ b/previews/PR826/tutorial/arma/index.html @@ -0,0 +1,134 @@ + +Auto-regressive stochastic processes · SDDP.jl

Auto-regressive stochastic processes

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

SDDP.jl assumes that the random variable in each node is independent of the random variables in all other nodes. However, a common request is to model the random variables by some auto-regressive process.

There are two ways to do this:

  1. model the random variable as a Markov chain
  2. use the "state-space expansion" trick
Info

This tutorial is in the context of a hydro-thermal scheduling example, but it should be apparent how the ideas transfer to other applications.

using SDDP
+import HiGHS

The state-space expansion trick

In An introduction to SDDP.jl, we assumed that the inflows were stagewise-independent. However, in many cases this is not correct, and inflow models are more accurately described by an auto-regressive process such as:

\[inflow_{t} = inflow_{t-1} + \varepsilon\]

Here $\varepsilon$ is a random variable, and the inflow in stage $t$ is the inflow in stage $t-1$ plus $\varepsilon$ (which might be negative).

For simplicity, we omit any coefficients and other terms, but this could easily be extended to a model like

\[inflow_{t} = a \times inflow_{t-1} + b + \varepsilon\]

In practice, you can estimate a distribution for $\varepsilon$ by fitting the chosen statistical model to historical data, and then using the empirical residuals.

To implement the auto-regressive model in SDDP.jl, we introduce inflow as a state variable.

Tip

Our rule of thumb for "when is something a state variable?" is: if you need the value of a variable from a previous stage to compute something in stage $t$, then that variable is a state variable.

model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)
+    @variable(sp, g_t >= 0)
+    @variable(sp, g_h >= 0)
+    @variable(sp, s >= 0)
+    @constraint(sp, g_h + g_t == 150)
+    c = [50, 100, 150]
+    @stageobjective(sp, c[t] * g_t)
+    # =========================================================================
+    # New stuff below Here
+    # Add inflow as a state
+    @variable(sp, inflow, SDDP.State, initial_value = 50.0)
+    # Add the random variable as a control variable
+    @variable(sp, ε)
+    # The equation describing our statistical model
+    @constraint(sp, inflow.out == inflow.in + ε)
+    # The new water balance constraint using the state variable
+    @constraint(sp, x.out == x.in - g_h - s + inflow.out)
+    # Assume we have some empirical residuals:
+    Ω = [-10.0, 0.1, 9.6]
+    SDDP.parameterize(sp, Ω) do ω
+        return JuMP.fix(ε, ω)
+    end
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+

When can this trick be used?

The state-space expansion trick should be used when:

  • The random variable appears additively in the objective or in the constraints. Something like inflow * decision_variable will not work.
  • The statistical model is linear, or can be written using the JuMP @constraint macro.
  • The dimension of the random variable is small (see Vector auto-regressive models for the multi-variate case).

The Markov chain approach

In the Markov chain approach, we model the stochastic process for inflow by a discrete Markov chain. Markov chains are nodes with transition probabilities between the nodes. SDDP.jl has good support for solving problems in which the uncertainty is formulated as a Markov chain.

The first step of the Markov chain approach is to write a function which simulates the stochastic process. Here is a simulator for our inflow model:

function simulator()
+    inflow = zeros(3)
+    current = 50.0
+    Ω = [-10.0, 0.1, 9.6]
+    for t in 1:3
+        current += rand(Ω)
+        inflow[t] = current
+    end
+    return inflow
+end
simulator (generic function with 1 method)

When called with no arguments, it produces a vector of inflows:

simulator()
3-element Vector{Float64}:
+ 50.1
+ 50.2
+ 59.800000000000004
Warning

The simulator must return a Vector{Float64}, so it is limited to a uni-variate random variable. It is possible to do something similar for multi-variate random variable, but you'll have to manually construct the Markov transition matrix, and solution times scale poorly, even in the two-dimensional case.

The next step is to call SDDP.MarkovianGraph with our simulator. This function will attempt to fit a Markov chain to the stochastic process produced by your simulator. There are two key arguments:

  • budget is the total number of nodes we want in the Markov chain
  • scenarios is a limit on the number of times we can call simulator
graph = SDDP.MarkovianGraph(simulator; budget = 8, scenarios = 30)
Root
+ (0, 0.0)
+Nodes
+ (1, 48.24153190148669)
+ (1, 59.6)
+ (2, 39.61229359616014)
+ (2, 49.89912009901389)
+ (2, 68.0129560962994)
+ (3, 31.13645208918522)
+ (3, 51.11504076178513)
+ (3, 75.64121112055942)
+Arcs
+ (0, 0.0) => (1, 48.24153190148669) w.p. 0.6
+ (0, 0.0) => (1, 59.6) w.p. 0.4
+ (1, 48.24153190148669) => (2, 39.61229359616014) w.p. 0.3888888888888889
+ (1, 48.24153190148669) => (2, 49.89912009901389) w.p. 0.5
+ (1, 48.24153190148669) => (2, 68.0129560962994) w.p. 0.1111111111111111
+ (1, 59.6) => (2, 39.61229359616014) w.p. 0.0
+ (1, 59.6) => (2, 49.89912009901389) w.p. 0.25
+ (1, 59.6) => (2, 68.0129560962994) w.p. 0.75
+ (2, 39.61229359616014) => (3, 31.13645208918522) w.p. 0.7142857142857143
+ (2, 39.61229359616014) => (3, 51.11504076178513) w.p. 0.2857142857142857
+ (2, 39.61229359616014) => (3, 75.64121112055942) w.p. 0.0
+ (2, 49.89912009901389) => (3, 31.13645208918522) w.p. 0.25
+ (2, 49.89912009901389) => (3, 51.11504076178513) w.p. 0.75
+ (2, 49.89912009901389) => (3, 75.64121112055942) w.p. 0.0
+ (2, 68.0129560962994) => (3, 31.13645208918522) w.p. 0.0
+ (2, 68.0129560962994) => (3, 51.11504076178513) w.p. 0.5454545454545454
+ (2, 68.0129560962994) => (3, 75.64121112055942) w.p. 0.45454545454545453

Here we can see we have created a MarkovianGraph with nodes like (2, 59.7). The first element of each node is the stage, and the second element is the inflow.

Create a SDDP.PolicyGraph using graph as follows:

model = SDDP.PolicyGraph(
+    graph;  # <--- New stuff
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, node
+    t, inflow = node  # <--- New stuff
+    @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)
+    @variable(sp, g_t >= 0)
+    @variable(sp, g_h >= 0)
+    @variable(sp, s >= 0)
+    @constraint(sp, g_h + g_t == 150)
+    c = [50, 100, 150]
+    @stageobjective(sp, c[t] * g_t)
+    # The new water balance constraint using the node:
+    @constraint(sp, x.out == x.in - g_h - s + inflow)
+end
A policy graph with 8 nodes.
+ Node indices: (1, 48.24153190148669), (1, 59.6), (2, 39.61229359616014), (2, 49.89912009901389), (2, 68.0129560962994), (3, 31.13645208918522), (3, 51.11504076178513), (3, 75.64121112055942)
+

When can this trick be used?

The Markov chain approach should be used when:

  • The random variable is uni-variate
  • The random variable appears in the objective function or as a variable coefficient in the constraint matrix
  • It's non-trivial to write the stochastic process as a series of constraints (for example, it uses nonlinear terms)
  • The number of nodes is modest (for example, a budget of hundreds, up to perhaps 1000)

Vector auto-regressive models

The state-space expansion section assumed that the random variable was uni-variate. However, the approach naturally extends to vector auto-regressive models. For example, if inflow is a 2-dimensional vector, then we can model a vector auto-regressive model to it as follows:

\[inflow_{t} = A \times inflow_{t-1} + b + \varepsilon\]

Here A is a 2-by-2 matrix, and b and $\varepsilon$ are 2-by-1 vectors.

model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(sp, 0 <= x <= 200, SDDP.State, initial_value = 200)
+    @variable(sp, g_t >= 0)
+    @variable(sp, g_h >= 0)
+    @variable(sp, s >= 0)
+    @constraint(sp, g_h + g_t == 150)
+    c = [50, 100, 150]
+    @stageobjective(sp, c[t] * g_t)
+    # =========================================================================
+    # New stuff below Here
+    # Add inflow as a state
+    @variable(sp, inflow[1:2], SDDP.State, initial_value = 50.0)
+    # Add the random variable as a control variable
+    @variable(sp, ε[1:2])
+    # The equation describing our statistical model
+    A = [0.8 0.2; 0.2 0.8]
+    @constraint(
+        sp,
+        [i = 1:2],
+        inflow[i].out == sum(A[i, j] * inflow[j].in for j in 1:2) + ε[i],
+    )
+    # The new water balance constraint using the state variable
+    @constraint(sp, x.out == x.in - g_h - s + inflow[1].out + inflow[2].out)
+    # Assume we have some empirical residuals:
+    Ω₁ = [-10.0, 0.1, 9.6]
+    Ω₂ = [-10.0, 0.1, 9.6]
+    Ω = [(ω₁, ω₂) for ω₁ in Ω₁ for ω₂ in Ω₂]
+    SDDP.parameterize(sp, Ω) do ω
+        JuMP.fix(ε[1], ω[1])
+        JuMP.fix(ε[2], ω[2])
+        return
+    end
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+
diff --git a/previews/PR826/tutorial/convex.cuts.json b/previews/PR826/tutorial/convex.cuts.json new file mode 100644 index 0000000000..d15a02b979 --- /dev/null +++ b/previews/PR826/tutorial/convex.cuts.json @@ -0,0 +1 @@ +[{"risk_set_cuts":[],"node":"1","single_cuts":[{"state":{"x":0.0},"intercept":243326.5932873207,"coefficients":{"x":-317616.6666712048}},{"state":{"x":0.0},"intercept":321380.875475521,"coefficients":{"x":-318249.99997748446}},{"state":{"x":0.0},"intercept":346483.98068044573,"coefficients":{"x":-318250.0000028512}},{"state":{"x":0.0},"intercept":354558.1883998782,"coefficients":{"x":-318250.0000165849}},{"state":{"x":0.0},"intercept":357155.19103196025,"coefficients":{"x":-318250.00002266077}},{"state":{"x":0.0},"intercept":357990.49560985994,"coefficients":{"x":-318250.0000249221}},{"state":{"x":0.0},"intercept":358259.16447035177,"coefficients":{"x":-318250.00002568774}},{"state":{"x":1.004836793707554},"intercept":104502.32174045643,"coefficients":{"x":-101729.16675464461}},{"state":{"x":0.6698921986092845},"intercept":148532.27219942765,"coefficients":{"x":-349514.23575976526}},{"state":{"x":0.3349461006592544},"intercept":273330.5249344267,"coefficients":{"x":-349514.23612227093}},{"state":{"x":0.0},"intercept":392846.7328678744,"coefficients":{"x":-349514.236165854}},{"state":{"x":0.6698921986092845},"intercept":159485.0022909803,"coefficients":{"x":-349514.2357600201}},{"state":{"x":0.3349461006592544},"intercept":276798.88946347113,"coefficients":{"x":-349514.2361227015}},{"state":{"x":0.0},"intercept":393945.0483021176,"coefficients":{"x":-349514.2361662153}},{"state":{"x":0.0},"intercept":393969.6622715268,"coefficients":{"x":-349514.23616622365}},{"state":{"x":0.0},"intercept":393977.4566951732,"coefficients":{"x":-349514.23616622627}},{"state":{"x":0.3349461006592544},"intercept":276911.4944092662,"coefficients":{"x":-349514.2361227162}},{"state":{"x":0.0},"intercept":393980.7065349545,"coefficients":{"x":-349514.2361662274}},{"state":{"x":0.0},"intercept":393980.9540452587,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.6698921986092845},"intercept":159844.1723304849,"coefficients":{"x":-349514.2357600446}},{"state":{"x":0.3349461006592544},"intercept":276912.62664265267,"coefficients":{"x":-349514.23612271633}},{"state":{"x":0.0},"intercept":393981.0650755269,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.6698921986092845},"intercept":159844.20749007008,"coefficients":{"x":-349514.2357600446}},{"state":{"x":0.3349461006592544},"intercept":276912.6377765217,"coefficients":{"x":-349514.23612271633}},{"state":{"x":0.0},"intercept":393981.0686012518,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.0},"intercept":393981.06869958586,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.0},"intercept":393981.06873072503,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.0},"intercept":393981.0687405858,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.0},"intercept":393981.0687437084,"coefficients":{"x":-349514.2361662275}},{"state":{"x":0.0},"intercept":393981.0687446971,"coefficients":{"x":-349514.2361662275}},{"state":{"x":1.004836793707554},"intercept":114095.79477302845,"coefficients":{"x":-112262.84149603915}},{"state":{"x":0.6698921986092845},"intercept":159656.11709911487,"coefficients":{"x":-352849.8994007419}},{"state":{"x":0.3349461006592544},"intercept":278489.85457248666,"coefficients":{"x":-352849.8998073284}},{"state":{"x":0.0},"intercept":396880.7655017967,"coefficients":{"x":-352849.899832754}},{"state":{"x":0.3349461006592544},"intercept":278760.0515187867,"coefficients":{"x":-352849.89979030535}},{"state":{"x":0.0},"intercept":396966.3278663192,"coefficients":{"x":-352849.89983277896}},{"state":{"x":0.0},"intercept":396972.8443263268,"coefficients":{"x":-352849.8998327809}},{"state":{"x":0.0},"intercept":396974.9078719961,"coefficients":{"x":-352849.8998327815}},{"state":{"x":0.0},"intercept":396975.5613281245,"coefficients":{"x":-352849.8998327817}},{"state":{"x":0.6698921986092845},"intercept":160604.3730944301,"coefficients":{"x":-352849.8994007543}},{"state":{"x":0.3349461006592544},"intercept":278790.1356378552,"coefficients":{"x":-352849.89979030896}},{"state":{"x":0.0},"intercept":396975.85450402467,"coefficients":{"x":-352849.8998327818}},{"state":{"x":0.3349461006592544},"intercept":278790.1630361588,"coefficients":{"x":-352849.89979030896}},{"state":{"x":0.0},"intercept":396975.8631801542,"coefficients":{"x":-352849.8998327818}},{"state":{"x":0.0},"intercept":396975.8638423745,"coefficients":{"x":-352849.8998327818}},{"state":{"x":1.004836793707554},"intercept":114704.83901016395,"coefficients":{"x":-113319.13498685189}},{"state":{"x":0.6698921986092845},"intercept":160471.93261423847,"coefficients":{"x":-353184.3923256809}},{"state":{"x":0.3349461006592544},"intercept":278798.6538096404,"coefficients":{"x":-353184.3927458069}},{"state":{"x":0.0},"intercept":397105.5682765613,"coefficients":{"x":-353184.39279775607}},{"state":{"x":0.6698921986092845},"intercept":160513.00568440114,"coefficients":{"x":-353184.3923382999}},{"state":{"x":0.3349461006592544},"intercept":278811.6602844246,"coefficients":{"x":-353184.3927543283}},{"state":{"x":0.0},"intercept":397109.6869938252,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.77931566356,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.8085509142,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.81780874403,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.3349461006592544},"intercept":278812.0855761277,"coefficients":{"x":-353184.3927543284}},{"state":{"x":0.0},"intercept":397109.8216695387,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.8219629754,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.82205589715,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.82208532223,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.82209464005,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.3349461006592544},"intercept":278812.0869333281,"coefficients":{"x":-353184.3927543284}},{"state":{"x":0.0},"intercept":397109.82209931855,"coefficients":{"x":-353184.3928110058}},{"state":{"x":0.0},"intercept":397109.82210467744,"coefficients":{"x":-353184.39282431966}},{"state":{"x":0.004839783237610497},"intercept":395400.4862012116,"coefficients":{"x":-353184.39282317745}},{"state":{"x":2.6698936309934798},"intercept":22732.488087711827,"coefficients":{"x":-36834.39236015842}},{"state":{"x":5.3349460650066005},"intercept":18860.785165229132,"coefficients":{"x":-1583.3333154240474}},{"state":{"x":5.0048452488827815},"intercept":38720.289143969676,"coefficients":{"x":-1636.1111682942442}},{"state":{"x":7.669896832613536},"intercept":52872.06532339254,"coefficients":{"x":-1554.3056582579154}},{"state":{"x":10.334948416405053},"intercept":66342.15776906771,"coefficients":{"x":-1476.5904003870546}},{"state":{"x":1.3140744788089118},"intercept":129051.03581832969,"coefficients":{"x":-113153.38863197068}},{"state":{"x":0.614074569487701},"intercept":226256.72278861314,"coefficients":{"x":-318249.9999496664}},{"state":{"x":1.3140744807342495},"intercept":143835.21868050355,"coefficients":{"x":-102362.49979727317}},{"state":{"x":1.3140744797354362},"intercept":143960.8624891105,"coefficients":{"x":-102362.49980866902}},{"state":{"x":0.2490230439263425},"intercept":363004.4057368755,"coefficients":{"x":-349714.79158868716}},{"state":{"x":0.9490229551780073},"intercept":187843.10078724346,"coefficients":{"x":-112326.35070908452}},{"state":{"x":1.6490228670480749},"intercept":131257.302852938,"coefficients":{"x":-36987.598189618424}},{"state":{"x":1.3140744807344384},"intercept":152993.7609098988,"coefficients":{"x":-123089.089885349}},{"state":{"x":1.6490228670480749},"intercept":137011.05599667164,"coefficients":{"x":-51324.28452930077}},{"state":{"x":1.3140744807344384},"intercept":154202.04228826988,"coefficients":{"x":-51324.28502966446}},{"state":{"x":1.6490228670478857},"intercept":137011.05599699792,"coefficients":{"x":-51324.28450143214}},{"state":{"x":1.3140744807342495},"intercept":154202.0422884318,"coefficients":{"x":-51324.28503207567}},{"state":{"x":1.3140744807342495},"intercept":154202.04228843178,"coefficients":{"x":-51324.285032075684}},{"state":{"x":1.314074474455739},"intercept":154202.04261076736,"coefficients":{"x":-51324.28503451555}},{"state":{"x":0.9189198208138118},"intercept":201632.98844755237,"coefficients":{"x":-123089.09023612597}},{"state":{"x":1.6189197326842222},"intercept":138556.07785778868,"coefficients":{"x":-51324.2846288428}},{"state":{"x":1.2839713472996899},"intercept":156699.1282132047,"coefficients":{"x":-123089.0894500429}},{"state":{"x":1.9839712591642478},"intercept":121629.18802043647,"coefficients":{"x":-40561.54445920297}},{"state":{"x":1.6490228670478857},"intercept":136787.6719139076,"coefficients":{"x":-52456.03426575769}},{"state":{"x":1.3140744807342495},"intercept":154357.73596388218,"coefficients":{"x":-52456.03479760503}},{"state":{"x":1.3140744807342413},"intercept":154357.73596388265,"coefficients":{"x":-52456.03479760503}},{"state":{"x":1.300000088140661},"intercept":155096.02277795118,"coefficients":{"x":-52456.037191062824}},{"state":{"x":0.0},"intercept":462591.94952613197,"coefficients":{"x":-334227.7468600484}},{"state":{"x":0.0},"intercept":466550.4417396543,"coefficients":{"x":-334227.7468628674}},{"state":{"x":0.614074568488888},"intercept":263063.6018753948,"coefficients":{"x":-330461.1558957928}},{"state":{"x":1.3140744797354367},"intercept":161928.44073360594,"coefficients":{"x":-118123.8549125337}},{"state":{"x":0.24902305580807646},"intercept":384608.44973243424,"coefficients":{"x":-333869.35682064574}},{"state":{"x":0.9490229670597415},"intercept":205337.7420128627,"coefficients":{"x":-119203.11879265298}},{"state":{"x":1.64902287892981},"intercept":139134.2100602643,"coefficients":{"x":-51225.47665680049}},{"state":{"x":1.3140744926161738},"intercept":161928.43921153896,"coefficients":{"x":-118123.85499772345}},{"state":{"x":0.6000001768941124},"intercept":268487.8681393218,"coefficients":{"x":-333838.0674648761}},{"state":{"x":1.300000088140661},"intercept":163835.81720046283,"coefficients":{"x":-119193.21020419332}},{"state":{"x":0.0},"intercept":471281.0023550791,"coefficients":{"x":-355361.1831039943}},{"state":{"x":0.614074569487701},"intercept":264907.57214438845,"coefficients":{"x":-333838.06740618404}},{"state":{"x":1.3140744807342495},"intercept":162512.36463813818,"coefficients":{"x":-119193.21033136749}},{"state":{"x":1.3140744807342495},"intercept":162512.364638138,"coefficients":{"x":-119193.21032974064}},{"state":{"x":1.3140744807342497},"intercept":162512.36463814712,"coefficients":{"x":-119193.21024672968}},{"state":{"x":1.3140744788089118},"intercept":162512.36486763431,"coefficients":{"x":-119193.21024672968}},{"state":{"x":0.6140745694875681},"intercept":264907.5721442152,"coefficients":{"x":-333838.0674206987}},{"state":{"x":1.3140744807341167},"intercept":162512.36464456492,"coefficients":{"x":-119193.21025270822}},{"state":{"x":3.0048452039402527},"intercept":104807.11810002883,"coefficients":{"x":-14262.076163390537}},{"state":{"x":2.6698968024929477},"intercept":115436.02333439601,"coefficients":{"x":-21687.724976627484}},{"state":{"x":5.334948401387881},"intercept":93941.62975051024,"coefficients":{"x":-5933.911107793759}},{"state":{"x":10.205473512484808},"intercept":85391.00462297234,"coefficients":{"x":-3296.658792178857}},{"state":{"x":9.870525111905035},"intercept":103094.62932788879,"coefficients":{"x":-3131.8258720180775}},{"state":{"x":9.30000006512732},"intercept":120817.28434237142,"coefficients":{"x":-2975.2345986914065}},{"state":{"x":3.600001869547069},"intercept":152267.6317945382,"coefficients":{"x":-2834.3153149026894}},{"state":{"x":6.300001886729413},"intercept":158711.00835166418,"coefficients":{"x":-2692.5995831270147}},{"state":{"x":4.000001706044817},"intercept":177936.20115206842,"coefficients":{"x":-2557.9698273836993}},{"state":{"x":6.700001712238983},"intercept":183708.7151153544,"coefficients":{"x":-2430.071360560159}},{"state":{"x":7.400001469174623},"intercept":194093.24781929352,"coefficients":{"x":-2308.5678133036067}},{"state":{"x":5.100001141799806},"intercept":210576.4484435483,"coefficients":{"x":-2193.1394607694156}},{"state":{"x":2.8000011320096627},"intercept":226108.6967838704,"coefficients":{"x":-2338.988341180169}},{"state":{"x":5.500000722441809},"intercept":229957.99586697068,"coefficients":{"x":-2222.0389496064677}},{"state":{"x":3.2000006913641177},"intercept":244474.75291148556,"coefficients":{"x":-2357.291276121874}},{"state":{"x":3.900000720576761},"intercept":251844.03013117408,"coefficients":{"x":-2239.4267728549758}},{"state":{"x":6.600000729132857},"intercept":254627.25642725057,"coefficients":{"x":-2127.455452300632}},{"state":{"x":9.300000500852994},"intercept":257519.52499080304,"coefficients":{"x":-2021.082690785446}},{"state":{"x":5.499976634922586},"intercept":272983.2040353046,"coefficients":{"x":-1920.0285655863813}},{"state":{"x":8.199978652078354},"intercept":275268.17675709125,"coefficients":{"x":-1824.0271533046352}},{"state":{"x":10.899980525141737},"intercept":277472.4890626819,"coefficients":{"x":-1732.8258051558787}},{"state":{"x":8.599981661073231},"intercept":287829.4181120329,"coefficients":{"x":-1646.1845285707968}},{"state":{"x":9.299983533997588},"intercept":292595.6406538724,"coefficients":{"x":-1563.8753142208705}},{"state":{"x":6.999984669925197},"intercept":301453.00777988404,"coefficients":{"x":-1485.6815698651685}},{"state":{"x":9.699986614684226},"intercept":302466.4552110373,"coefficients":{"x":-1411.3975025796858}},{"state":{"x":10.39998845916633},"intercept":306136.8753891638,"coefficients":{"x":-1340.8276374865554}},{"state":{"x":13.09998959509798},"intercept":306966.8104423946,"coefficients":{"x":-1273.7862628550233}},{"state":{"x":15.799991393262314},"intercept":307778.7036219864,"coefficients":{"x":-1210.0969554117726}},{"state":{"x":18.499991767755073},"intercept":308572.2867162394,"coefficients":{"x":-1149.5921123520648}},{"state":{"x":16.19999352677947},"intercept":314807.9203860667,"coefficients":{"x":-1092.112512238083}},{"state":{"x":13.899995314049011},"intercept":320478.85459110216,"coefficients":{"x":-1037.5068932748356}},{"state":{"x":11.599997044979672},"intercept":325625.9702836521,"coefficients":{"x":-985.631557063283}},{"state":{"x":12.29999886407037},"intercept":327478.42191149376,"coefficients":{"x":-936.3499869934408}},{"state":{"x":10.334948357558652},"intercept":331532.50871194096,"coefficients":{"x":-889.5324976077011}},{"state":{"x":0.02294612147636601},"intercept":601239.5791263442,"coefficients":{"x":-318249.99989099125}},{"state":{"x":0.7229460327208838},"intercept":427424.67609494354,"coefficients":{"x":-102010.85231635123}},{"state":{"x":0.38799768602576623},"intercept":529750.5708623917,"coefficients":{"x":-318250.0000258104}},{"state":{"x":0.0},"intercept":676370.6600292979,"coefficients":{"x":-349603.4365545106}},{"state":{"x":0.3879976856941409},"intercept":551229.5890205081,"coefficients":{"x":-318250.0000238123}},{"state":{"x":2.3397934142373376},"intercept":342953.0300141731,"coefficients":{"x":-1865.0188238007213}},{"state":{"x":5.0048450610238175},"intercept":341871.8260692935,"coefficients":{"x":-1814.5119457556048}},{"state":{"x":7.669896707471607},"intercept":340809.5317166413,"coefficients":{"x":-1723.7863712560352}},{"state":{"x":7.33494835372553},"intercept":344741.87300721585,"coefficients":{"x":-1637.5970829338714}},{"state":{"x":10.334948426340079},"intercept":343071.0143169347,"coefficients":{"x":-1555.7172435313723}},{"state":{"x":1.6487514592489814},"intercept":375281.05678669625,"coefficients":{"x":-33746.08046039746}},{"state":{"x":1.3138030623186916},"intercept":399961.7762064385,"coefficients":{"x":-102362.49996138341}},{"state":{"x":0.013802974287132973},"intercept":694852.6614270125,"coefficients":{"x":-349714.7916689488}},{"state":{"x":0.7138028855369929},"intercept":471249.6712395425,"coefficients":{"x":-122062.6100634259}},{"state":{"x":1.4138027968165285},"intercept":397232.95597264887,"coefficients":{"x":-40236.49324307761}},{"state":{"x":0.713802845281302},"intercept":474488.9577155114,"coefficients":{"x":-124117.90746217078}},{"state":{"x":1.4138027565608382},"intercept":398393.68400619447,"coefficients":{"x":-40887.33745145729}},{"state":{"x":0.3487513345759198},"intercept":592449.6414375787,"coefficients":{"x":-330247.65690253524}},{"state":{"x":1.0487512458556645},"intercept":437758.0296532116,"coefficients":{"x":-118159.41482320408}},{"state":{"x":1.7487511578256576},"intercept":386239.23136576614,"coefficients":{"x":-39000.481342426865}},{"state":{"x":1.41380275748386},"intercept":399348.647543517,"coefficients":{"x":-39000.48170314647}},{"state":{"x":1.4138027574838603},"intercept":399349.3864917197,"coefficients":{"x":-39000.481703529775}},{"state":{"x":1.4138027968183595},"intercept":399349.3967596941,"coefficients":{"x":-39000.481703535464}},{"state":{"x":0.0},"intercept":711819.4957812838,"coefficients":{"x":-355033.81450459774}},{"state":{"x":0.6000001767516958},"intercept":513923.75686895335,"coefficients":{"x":-329650.1524984372}},{"state":{"x":1.300000088031347},"intercept":411115.919127597,"coefficients":{"x":-105972.54827526107}},{"state":{"x":0.0},"intercept":714112.4313472782,"coefficients":{"x":-351174.6402196134}},{"state":{"x":0.0},"intercept":714838.5275988701,"coefficients":{"x":-351174.6402223222}},{"state":{"x":0.0},"intercept":715068.4580785508,"coefficients":{"x":-351174.6402223823}},{"state":{"x":0.0},"intercept":715141.2693971179,"coefficients":{"x":-351174.6402223997}},{"state":{"x":0.013802934954465013},"intercept":710317.0855970501,"coefficients":{"x":-351174.6402062267}},{"state":{"x":0.7138028462043239},"intercept":480303.85779632046,"coefficients":{"x":-124188.7891428213}},{"state":{"x":1.41380275748386},"intercept":400289.43020662793,"coefficients":{"x":-40909.78376941772}},{"state":{"x":1.41380275748386},"intercept":400289.43020772125,"coefficients":{"x":-40909.78375586384}},{"state":{"x":1.41380275748386},"intercept":400289.4302077212,"coefficients":{"x":-40909.783755863835}},{"state":{"x":1.4138027574838596},"intercept":400289.43020570825,"coefficients":{"x":-40909.783734376775}},{"state":{"x":1.4138027573741583},"intercept":400289.43021458504,"coefficients":{"x":-40909.7837171513}},{"state":{"x":1.3836996542652553},"intercept":402246.0627973583,"coefficients":{"x":-105972.54738223554}},{"state":{"x":2.083699566229923},"intercept":374510.4584965237,"coefficients":{"x":-35141.305678328674}},{"state":{"x":1.7487511545171666},"intercept":386641.4648647261,"coefficients":{"x":-40909.78291210611}},{"state":{"x":1.4138027541753684},"intercept":400344.13131980115,"coefficients":{"x":-40909.78375171291}},{"state":{"x":2.0535964684262544},"intercept":375623.0216618838,"coefficients":{"x":-35141.306316772796}},{"state":{"x":1.7186480570892173},"intercept":387873.9500901606,"coefficients":{"x":-40909.783015618814}},{"state":{"x":1.3836996575737455},"intercept":402301.7374592957,"coefficients":{"x":-105972.54719660824}},{"state":{"x":2.0836995695384135},"intercept":374583.76380074234,"coefficients":{"x":-35141.305663047446}},{"state":{"x":1.748751157825658},"intercept":386642.77004946984,"coefficients":{"x":-40909.78305802272}},{"state":{"x":1.4138027574838596},"intercept":400345.4365037976,"coefficients":{"x":-40909.78377352187}},{"state":{"x":1.4138027570987173},"intercept":400345.4365216913,"coefficients":{"x":-40909.7837603498}},{"state":{"x":0.713802885507701},"intercept":480662.4865203752,"coefficients":{"x":-122966.71679166412}},{"state":{"x":1.4138027967872369},"intercept":400459.00220642315,"coefficients":{"x":-40522.794151635986}},{"state":{"x":1.0487512454618524},"intercept":440759.4734106782,"coefficients":{"x":-116150.62844410918}},{"state":{"x":1.7487511574318462},"intercept":387292.713122005,"coefficients":{"x":-38364.36568769827}},{"state":{"x":1.413802757090048},"intercept":400459.003808214,"coefficients":{"x":-40522.79409648087}},{"state":{"x":1.7487511578256576},"intercept":387292.7131149584,"coefficients":{"x":-38364.365638826705}},{"state":{"x":1.4138027574838592},"intercept":400459.0037972043,"coefficients":{"x":-40522.794083091845}},{"state":{"x":1.4138027570987175},"intercept":400459.0038128112,"coefficients":{"x":-40522.794083091845}},{"state":{"x":0.713802874462685},"intercept":480689.1344069189,"coefficients":{"x":-123987.35203822156}},{"state":{"x":1.4138027857422206},"intercept":400467.44071867876,"coefficients":{"x":-40845.99521813373}},{"state":{"x":0.34875133419077675},"intercept":598118.9177895344,"coefficients":{"x":-329448.7168065573}},{"state":{"x":1.0487512454705217},"intercept":441193.6049341625,"coefficients":{"x":-116086.84046216114}},{"state":{"x":1.7487511574405155},"intercept":387430.1880994154,"coefficients":{"x":-38344.16615679938}},{"state":{"x":1.413802757098717},"intercept":400467.441890101,"coefficients":{"x":-40845.99540578776}},{"state":{"x":0.7138028458191823},"intercept":480734.36710149504,"coefficients":{"x":-123980.9557159982}},{"state":{"x":1.4138027570987175},"intercept":400481.7644521145,"coefficients":{"x":-40843.96990249864}},{"state":{"x":0.713802846204324},"intercept":480734.36705374473,"coefficients":{"x":-123980.955716171}},{"state":{"x":1.4138027574838596},"intercept":400481.7644392922,"coefficients":{"x":-40843.9699015203}},{"state":{"x":1.4138027573741576},"intercept":400481.7644437729,"coefficients":{"x":-40843.96990152031}},{"state":{"x":1.383699657573747},"intercept":402757.5659447013,"coefficients":{"x":-105908.75886726669}},{"state":{"x":2.083699569538415},"intercept":374728.44077769393,"coefficients":{"x":-35121.106084282816}},{"state":{"x":1.7487511578256576},"intercept":387432.96598290314,"coefficients":{"x":-38344.1661638247}},{"state":{"x":1.41380275748386},"intercept":400484.54233579536,"coefficients":{"x":-40843.96988323572}},{"state":{"x":1.41380275748386},"intercept":400484.54233579495,"coefficients":{"x":-40843.96988323551}},{"state":{"x":1.4138027565608382},"intercept":400484.542380679,"coefficients":{"x":-40843.96986598735}},{"state":{"x":0.3487513345759198},"intercept":598163.3449182622,"coefficients":{"x":-329442.3212558633}},{"state":{"x":1.0487512458556645},"intercept":441255.8367966007,"coefficients":{"x":-116078.41865604097}},{"state":{"x":1.7487511578256576},"intercept":387452.6727535068,"coefficients":{"x":-38341.49925742224}},{"state":{"x":1.4138027574838599},"intercept":400484.5423357426,"coefficients":{"x":-40843.969881490004}},{"state":{"x":1.4138027569434342},"intercept":400484.54236524284,"coefficients":{"x":-40843.969864595085}},{"state":{"x":3.0048452358720716},"intercept":362035.1670299714,"coefficients":{"x":-12564.327492764143}},{"state":{"x":5.669896823956976},"intercept":353567.27872252837,"coefficients":{"x":-2075.9771292955184}},{"state":{"x":5.334948411946709},"intercept":357802.2397640563,"coefficients":{"x":-2264.7855483929875}}],"multi_cuts":[]}] \ No newline at end of file diff --git a/previews/PR826/tutorial/decision_hazard.ipynb b/previews/PR826/tutorial/decision_hazard.ipynb new file mode 100644 index 0000000000..91b2d43f69 --- /dev/null +++ b/previews/PR826/tutorial/decision_hazard.ipynb @@ -0,0 +1,357 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Here-and-now and hazard-decision" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP.jl assumes that the agent gets to make a decision _after_ observing the\n", + "realization of the random variable. This is called a _wait-and-see_ or\n", + "_hazard-decision_ model. In contrast, you might want your agent to make\n", + "decisions _before_ observing the random variable. This is called a\n", + "_here-and-now_ or _decision-hazard_ model." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> The terms decision-hazard and hazard-decision from the French _hasard_,\n", + "> meaning chance. It could also have been translated as uncertainty-decision\n", + "> and decision-uncertainty, but the community seems to have settled on the\n", + "> transliteration _hazard_ instead. We like the hazard-decision and\n", + "> decision-hazard terms because they clearly communicate the order of the\n", + "> decision and the uncertainty." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The purpose of this tutorial is to demonstrate how to model here-and-now\n", + "decisions in SDDP.jl." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial uses the following packages:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Hazard-decision formulation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As an example, we're going to build a standard hydro-thermal scheduling\n", + "model, with a single hydro-reservoir and a single thermal generation plant.\n", + "In each of the four stages, we need to choose some mix of `u_thermal` and\n", + "`u_hydro` to meet a demand of `9` units, where unmet demand is penalized at a\n", + "rate of \\$500/unit." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "hazard_decision = SDDP.LinearPolicyGraph(;\n", + " stages = 4,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, node\n", + " @variables(sp, begin\n", + " 0 <= x_storage <= 8, (SDDP.State, initial_value = 6)\n", + " u_thermal >= 0\n", + " u_hydro >= 0\n", + " u_unmet_demand >= 0\n", + " end)\n", + " @constraint(sp, u_thermal + u_hydro == 9 - u_unmet_demand)\n", + " @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)\n", + " SDDP.parameterize(sp, [2, 3]) do ω_inflow\n", + " return set_normalized_rhs(c_balance, ω_inflow)\n", + " end\n", + " @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal)\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Decision-hazard formulation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the wait-and-see formulation, we get to decide the generation variables\n", + "_after_ observing the realization of `ω_inflow`. However, a common modeling\n", + "situation is that we need to decide the level of thermal generation\n", + "`u_thermal` _before_ observing the inflow." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP.jl can model here-and-now decisions with a modeling trick: a wait-and-see\n", + "decision in stage _t-1_ is equivalent to a here-and-now decision in stage _t_." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In other words, we need to convert the `u_thermal` decision from a control\n", + "variable that is decided in stage `t`, to a state variable that is decided in\n", + "stage `t-1`. Here's our new model, with the three lines that have changed:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "decision_hazard = SDDP.LinearPolicyGraph(;\n", + " stages = 4,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, node\n", + " @variables(sp, begin\n", + " 0 <= x_storage <= 8, (SDDP.State, initial_value = 6)\n", + " u_thermal >= 0, (SDDP.State, initial_value = 0) # <-- changed\n", + " u_hydro >= 0\n", + " u_unmet_demand >= 0\n", + " end)\n", + " @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand) # <-- changed\n", + " @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)\n", + " SDDP.parameterize(sp, [2, 3]) do ω\n", + " return set_normalized_rhs(c_balance, ω)\n", + " end\n", + " @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in) # <-- changed\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Can you understand the reformulation? In each stage, we now use the value of\n", + "`u_thermal.in` instead of `u_thermal`, and the value of the outgoing\n", + "`u_thermal.out` is the here-and-how decision for stage `t+1`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "(If you can spot a \"mistake\" with this model, don't worry, we'll fix it below.\n", + "Presenting it like this simplifies the exposition.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Comparison" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Let's compare the cost of operating the two models:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function train_and_compute_cost(model)\n", + " SDDP.train(model; print_level = 0)\n", + " return println(\"Cost = \\$\", SDDP.calculate_bound(model))\n", + "end\n", + "\n", + "train_and_compute_cost(hazard_decision)" + ], + "metadata": {}, + "execution_count": null + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "train_and_compute_cost(decision_hazard)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This suggests that choosing the thermal generation before observing the inflow\n", + "adds a cost of \\$250. But does this make sense?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If we look carefully at our `decision_hazard` model, the incoming value of\n", + "`u_thermal.in` in the first stage is fixed to the `initial_value` of `0`.\n", + "Therefore, we must always meet the full demand with `u_hydro`, which we cannot\n", + "do without incurring unmet demand." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To allow the model to choose an optimal level of `u_thermal` in the\n", + "first-stage, we need to add an extra stage that is deterministic with no\n", + "stage objective." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Fixing the decision-hazard" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the following model, we now have five stages, so that stage `t+1` in\n", + "`decision_hazard_2` corresponds to stage `t` in `decision_hazard`. We've also\n", + "added an `if`-statement, which adds different constraints depending on the\n", + "node. Note that we need to add an `x_storage.out == x_storage.in` constraint\n", + "because the storage can't change in this new first-stage." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "decision_hazard_2 = SDDP.LinearPolicyGraph(;\n", + " stages = 5, # <-- changed\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, node\n", + " @variables(sp, begin\n", + " 0 <= x_storage <= 8, (SDDP.State, initial_value = 6)\n", + " u_thermal >= 0, (SDDP.State, initial_value = 0)\n", + " u_hydro >= 0\n", + " u_unmet_demand >= 0\n", + " end)\n", + " if node == 1 # <-- new\n", + " @constraint(sp, x_storage.out == x_storage.in) # <-- new\n", + " @stageobjective(sp, 0) # <-- new\n", + " else\n", + " @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand)\n", + " @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)\n", + " SDDP.parameterize(sp, [2, 3]) do ω\n", + " return set_normalized_rhs(c_balance, ω)\n", + " end\n", + " @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in)\n", + " end\n", + "end\n", + "\n", + "train_and_compute_cost(decision_hazard_2)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Now we find that the cost of choosing the thermal generation before observing\n", + "the inflow adds a much more reasonable cost of \\$10." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Summary" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To summarize, the difference between here-and-now and wait-and-see variables\n", + "is a modeling choice." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To create a here-and-now decision, add it as a state variable to the\n", + "previous stage" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In some cases, you'll need to add an additional \"first-stage\" problem to\n", + "enable the model to choose an optimal value for the here-and-now decision\n", + "variable. You do not need to do this if the first stage is deterministic. You\n", + "must make sure that the subproblem is feasible for all possible incoming\n", + "values of the here-and-now decision variable." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/decision_hazard.jl b/previews/PR826/tutorial/decision_hazard.jl new file mode 100644 index 0000000000..ad7168e383 --- /dev/null +++ b/previews/PR826/tutorial/decision_hazard.jl @@ -0,0 +1,176 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Here-and-now and hazard-decision + +# SDDP.jl assumes that the agent gets to make a decision _after_ observing the +# realization of the random variable. This is called a _wait-and-see_ or +# _hazard-decision_ model. In contrast, you might want your agent to make +# decisions _before_ observing the random variable. This is called a +# _here-and-now_ or _decision-hazard_ model. + +# !!! info +# The terms decision-hazard and hazard-decision from the French _hasard_, +# meaning chance. It could also have been translated as uncertainty-decision +# and decision-uncertainty, but the community seems to have settled on the +# transliteration _hazard_ instead. We like the hazard-decision and +# decision-hazard terms because they clearly communicate the order of the +# decision and the uncertainty. + +# The purpose of this tutorial is to demonstrate how to model here-and-now +# decisions in SDDP.jl. + +# This tutorial uses the following packages: + +using SDDP +import HiGHS + +# ## Hazard-decision formulation + +# As an example, we're going to build a standard hydro-thermal scheduling +# model, with a single hydro-reservoir and a single thermal generation plant. +# In each of the four stages, we need to choose some mix of `u_thermal` and +# `u_hydro` to meet a demand of `9` units, where unmet demand is penalized at a +# rate of \$500/unit. + +hazard_decision = SDDP.LinearPolicyGraph(; + stages = 4, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, node + @variables(sp, begin + 0 <= x_storage <= 8, (SDDP.State, initial_value = 6) + u_thermal >= 0 + u_hydro >= 0 + u_unmet_demand >= 0 + end) + @constraint(sp, u_thermal + u_hydro == 9 - u_unmet_demand) + @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0) + SDDP.parameterize(sp, [2, 3]) do ω_inflow + return set_normalized_rhs(c_balance, ω_inflow) + end + @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal) +end + +# ## Decision-hazard formulation + +# In the wait-and-see formulation, we get to decide the generation variables +# _after_ observing the realization of `ω_inflow`. However, a common modeling +# situation is that we need to decide the level of thermal generation +# `u_thermal` _before_ observing the inflow. + +# SDDP.jl can model here-and-now decisions with a modeling trick: a wait-and-see +# decision in stage _t-1_ is equivalent to a here-and-now decision in stage _t_. + +# In other words, we need to convert the `u_thermal` decision from a control +# variable that is decided in stage `t`, to a state variable that is decided in +# stage `t-1`. Here's our new model, with the three lines that have changed: + +decision_hazard = SDDP.LinearPolicyGraph(; + stages = 4, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, node + @variables(sp, begin + 0 <= x_storage <= 8, (SDDP.State, initial_value = 6) + u_thermal >= 0, (SDDP.State, initial_value = 0) # <-- changed + u_hydro >= 0 + u_unmet_demand >= 0 + end) + @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand) # <-- changed + @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0) + SDDP.parameterize(sp, [2, 3]) do ω + return set_normalized_rhs(c_balance, ω) + end + @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in) # <-- changed +end + +# Can you understand the reformulation? In each stage, we now use the value of +# `u_thermal.in` instead of `u_thermal`, and the value of the outgoing +# `u_thermal.out` is the here-and-how decision for stage `t+1`. + +# (If you can spot a "mistake" with this model, don't worry, we'll fix it below. +# Presenting it like this simplifies the exposition.) + +# ## Comparison + +# Let's compare the cost of operating the two models: + +function train_and_compute_cost(model) + SDDP.train(model; print_level = 0) + return println("Cost = \$", SDDP.calculate_bound(model)) +end + +train_and_compute_cost(hazard_decision) + +#- + +train_and_compute_cost(decision_hazard) + +# This suggests that choosing the thermal generation before observing the inflow +# adds a cost of \$250. But does this make sense? + +# If we look carefully at our `decision_hazard` model, the incoming value of +# `u_thermal.in` in the first stage is fixed to the `initial_value` of `0`. +# Therefore, we must always meet the full demand with `u_hydro`, which we cannot +# do without incurring unmet demand. + +# To allow the model to choose an optimal level of `u_thermal` in the +# first-stage, we need to add an extra stage that is deterministic with no +# stage objective. + +# ## Fixing the decision-hazard + +# In the following model, we now have five stages, so that stage `t+1` in +# `decision_hazard_2` corresponds to stage `t` in `decision_hazard`. We've also +# added an `if`-statement, which adds different constraints depending on the +# node. Note that we need to add an `x_storage.out == x_storage.in` constraint +# because the storage can't change in this new first-stage. + +decision_hazard_2 = SDDP.LinearPolicyGraph(; + stages = 5, # <-- changed + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, node + @variables(sp, begin + 0 <= x_storage <= 8, (SDDP.State, initial_value = 6) + u_thermal >= 0, (SDDP.State, initial_value = 0) + u_hydro >= 0 + u_unmet_demand >= 0 + end) + if node == 1 # <-- new + @constraint(sp, x_storage.out == x_storage.in) # <-- new + @stageobjective(sp, 0) # <-- new + else + @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand) + @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0) + SDDP.parameterize(sp, [2, 3]) do ω + return set_normalized_rhs(c_balance, ω) + end + @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in) + end +end + +train_and_compute_cost(decision_hazard_2) + +# Now we find that the cost of choosing the thermal generation before observing +# the inflow adds a much more reasonable cost of \$10. + +# ## Summary + +# To summarize, the difference between here-and-now and wait-and-see variables +# is a modeling choice. + +# To create a here-and-now decision, add it as a state variable to the +# previous stage + +# In some cases, you'll need to add an additional "first-stage" problem to +# enable the model to choose an optimal value for the here-and-now decision +# variable. You do not need to do this if the first stage is deterministic. You +# must make sure that the subproblem is feasible for all possible incoming +# values of the here-and-now decision variable. diff --git a/previews/PR826/tutorial/decision_hazard/index.html b/previews/PR826/tutorial/decision_hazard/index.html new file mode 100644 index 0000000000..76918aa1fe --- /dev/null +++ b/previews/PR826/tutorial/decision_hazard/index.html @@ -0,0 +1,77 @@ + +Here-and-now and hazard-decision · SDDP.jl

Here-and-now and hazard-decision

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

SDDP.jl assumes that the agent gets to make a decision after observing the realization of the random variable. This is called a wait-and-see or hazard-decision model. In contrast, you might want your agent to make decisions before observing the random variable. This is called a here-and-now or decision-hazard model.

Info

The terms decision-hazard and hazard-decision from the French hasard, meaning chance. It could also have been translated as uncertainty-decision and decision-uncertainty, but the community seems to have settled on the transliteration hazard instead. We like the hazard-decision and decision-hazard terms because they clearly communicate the order of the decision and the uncertainty.

The purpose of this tutorial is to demonstrate how to model here-and-now decisions in SDDP.jl.

This tutorial uses the following packages:

using SDDP
+import HiGHS

Hazard-decision formulation

As an example, we're going to build a standard hydro-thermal scheduling model, with a single hydro-reservoir and a single thermal generation plant. In each of the four stages, we need to choose some mix of u_thermal and u_hydro to meet a demand of 9 units, where unmet demand is penalized at a rate of $500/unit.

hazard_decision = SDDP.LinearPolicyGraph(;
+    stages = 4,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, node
+    @variables(sp, begin
+        0 <= x_storage <= 8, (SDDP.State, initial_value = 6)
+        u_thermal >= 0
+        u_hydro >= 0
+        u_unmet_demand >= 0
+    end)
+    @constraint(sp, u_thermal + u_hydro == 9 - u_unmet_demand)
+    @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)
+    SDDP.parameterize(sp, [2, 3]) do ω_inflow
+        return set_normalized_rhs(c_balance, ω_inflow)
+    end
+    @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal)
+end
A policy graph with 4 nodes.
+ Node indices: 1, 2, 3, 4
+

Decision-hazard formulation

In the wait-and-see formulation, we get to decide the generation variables after observing the realization of ω_inflow. However, a common modeling situation is that we need to decide the level of thermal generation u_thermal before observing the inflow.

SDDP.jl can model here-and-now decisions with a modeling trick: a wait-and-see decision in stage t-1 is equivalent to a here-and-now decision in stage t.

In other words, we need to convert the u_thermal decision from a control variable that is decided in stage t, to a state variable that is decided in stage t-1. Here's our new model, with the three lines that have changed:

decision_hazard = SDDP.LinearPolicyGraph(;
+    stages = 4,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, node
+    @variables(sp, begin
+        0 <= x_storage <= 8, (SDDP.State, initial_value = 6)
+        u_thermal >= 0, (SDDP.State, initial_value = 0)  # <-- changed
+        u_hydro >= 0
+        u_unmet_demand >= 0
+    end)
+    @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand)  # <-- changed
+    @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)
+    SDDP.parameterize(sp, [2, 3]) do ω
+        return set_normalized_rhs(c_balance, ω)
+    end
+    @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in) # <-- changed
+end
A policy graph with 4 nodes.
+ Node indices: 1, 2, 3, 4
+

Can you understand the reformulation? In each stage, we now use the value of u_thermal.in instead of u_thermal, and the value of the outgoing u_thermal.out is the here-and-how decision for stage t+1.

(If you can spot a "mistake" with this model, don't worry, we'll fix it below. Presenting it like this simplifies the exposition.)

Comparison

Let's compare the cost of operating the two models:

function train_and_compute_cost(model)
+    SDDP.train(model; print_level = 0)
+    return println("Cost = \$", SDDP.calculate_bound(model))
+end
+
+train_and_compute_cost(hazard_decision)
Cost = $400.0
train_and_compute_cost(decision_hazard)
Cost = $650.0

This suggests that choosing the thermal generation before observing the inflow adds a cost of $250. But does this make sense?

If we look carefully at our decision_hazard model, the incoming value of u_thermal.in in the first stage is fixed to the initial_value of 0. Therefore, we must always meet the full demand with u_hydro, which we cannot do without incurring unmet demand.

To allow the model to choose an optimal level of u_thermal in the first-stage, we need to add an extra stage that is deterministic with no stage objective.

Fixing the decision-hazard

In the following model, we now have five stages, so that stage t+1 in decision_hazard_2 corresponds to stage t in decision_hazard. We've also added an if-statement, which adds different constraints depending on the node. Note that we need to add an x_storage.out == x_storage.in constraint because the storage can't change in this new first-stage.

decision_hazard_2 = SDDP.LinearPolicyGraph(;
+    stages = 5,  # <-- changed
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, node
+    @variables(sp, begin
+        0 <= x_storage <= 8, (SDDP.State, initial_value = 6)
+        u_thermal >= 0, (SDDP.State, initial_value = 0)
+        u_hydro >= 0
+        u_unmet_demand >= 0
+    end)
+    if node == 1                                        # <-- new
+        @constraint(sp, x_storage.out == x_storage.in)  # <-- new
+        @stageobjective(sp, 0)                          # <-- new
+    else
+        @constraint(sp, u_thermal.in + u_hydro == 9 - u_unmet_demand)
+        @constraint(sp, c_balance, x_storage.out == x_storage.in - u_hydro + 0)
+        SDDP.parameterize(sp, [2, 3]) do ω
+            return set_normalized_rhs(c_balance, ω)
+        end
+        @stageobjective(sp, 500 * u_unmet_demand + 20 * u_thermal.in)
+    end
+end
+
+train_and_compute_cost(decision_hazard_2)
Cost = $410.0

Now we find that the cost of choosing the thermal generation before observing the inflow adds a much more reasonable cost of $10.

Summary

To summarize, the difference between here-and-now and wait-and-see variables is a modeling choice.

To create a here-and-now decision, add it as a state variable to the previous stage

In some cases, you'll need to add an additional "first-stage" problem to enable the model to choose an optimal value for the here-and-now decision variable. You do not need to do this if the first stage is deterministic. You must make sure that the subproblem is feasible for all possible incoming values of the here-and-now decision variable.

diff --git a/previews/PR826/tutorial/example_milk_producer.ipynb b/previews/PR826/tutorial/example_milk_producer.ipynb new file mode 100644 index 0000000000..d265264d15 --- /dev/null +++ b/previews/PR826/tutorial/example_milk_producer.ipynb @@ -0,0 +1,513 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Example: the milk producer" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The purpose of this tutorial is to demonstrate how to fit a Markovian policy\n", + "graph to a univariate stochastic process." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial uses the following packages:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import HiGHS\n", + "import Plots" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Background" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A company produces milk for sale on a spot market each month. The quantity of\n", + "milk they produce is uncertain, and so too is the price on the spot market.\n", + "The company can store unsold milk in a stockpile of dried milk powder." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The spot price is determined by an auction system, and so varies from month to\n", + "month, but demonstrates serial correlation. In each auction, there is\n", + "sufficient demand that the milk producer finds a buyer for all their\n", + "milk, regardless of the quantity they supply. Furthermore, the spot price\n", + "is independent of the milk producer (they are a small player in the market)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The spot price is highly volatile, and is the result of a process that is out\n", + "of the control of the company. To counteract their price risk, the company\n", + "engages in a forward contracting programme." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The forward contracting programme is a deal for physical milk four months in\n", + "the future." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The futures price is the current spot price, plus some forward contango (the\n", + "buyers gain certainty that they will receive the milk in the future)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In general, the milk company should forward contract (since they reduce\n", + "their price risk), however they also have production risk. Therefore, it may\n", + "be the case that they forward contract a fixed amount, but find that they do\n", + "not produce enough milk to meet the fixed demand. They are then forced to\n", + "buy additional milk on the spot market." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The goal of the milk company is to choose the extent to which they forward\n", + "contract in order to maximise (risk-adjusted) revenues, whilst managing their\n", + "production risk." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## A stochastic process for price" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "It is outside the scope of this tutorial, but assume that we have gone away\n", + "and analysed historical data to fit a stochastic process to the sequence of\n", + "monthly auction spot prices." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One plausible model is a multiplicative auto-regressive model of order one,\n", + "where the white noise term is modeled by a finite distribution of empirical\n", + "residuals. We can simulate this stochastic process as follows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function simulator()\n", + " residuals = [0.0987, 0.199, 0.303, 0.412, 0.530, 0.661, 0.814, 1.010, 1.290]\n", + " residuals = 0.1 * vcat(-residuals, 0.0, residuals)\n", + " scenario = zeros(12)\n", + " y, μ, α = 4.5, 6.0, 0.05\n", + " for t in 1:12\n", + " y = exp((1 - α) * log(y) + α * log(μ) + rand(residuals))\n", + " scenario[t] = clamp(y, 3.0, 9.0)\n", + " end\n", + " return scenario\n", + "end\n", + "\n", + "simulator()" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "It may be helpful to visualize a number of simulations of the price process:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plot = Plots.plot(\n", + " [simulator() for _ in 1:500];\n", + " color = \"gray\",\n", + " opacity = 0.2,\n", + " legend = false,\n", + " xlabel = \"Month\",\n", + " ylabel = \"Price [\\$/kg]\",\n", + " xlims = (1, 12),\n", + " ylims = (3, 9),\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The prices gradually revert to the mean of \\$6/kg, and there is high\n", + "volatility." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can't incorporate this price process directly into SDDP.jl, but we can fit\n", + "a `SDDP.MarkovianGraph` directly from the simulator:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "graph = SDDP.MarkovianGraph(simulator; budget = 30, scenarios = 10_000);\n", + "nothing # hide" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Here `budget` is the number of nodes in the policy graph, and `scenarios` is\n", + "the number of simulations to use when estimating the transition probabilities." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The graph contains too many nodes to be show, but we can plot it:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for ((t, price), edges) in graph.nodes\n", + " for ((t′, price′), probability) in edges\n", + " Plots.plot!(\n", + " plot,\n", + " [t, t′],\n", + " [price, price′];\n", + " color = \"red\",\n", + " width = 3 * probability,\n", + " )\n", + " end\n", + "end\n", + "\n", + "plot" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "That looks okay. Try changing `budget` and `scenarios` to see how different\n", + "Markovian policy graphs can be created." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now that we have a Markovian graph, we can build the model. See if you can\n", + "work out how we arrived at this formulation by reading the background\n", + "description. Do all the variables and constraints make sense?" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.PolicyGraph(\n", + " graph;\n", + " sense = :Max,\n", + " upper_bound = 1e2,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, node\n", + " # Decompose the node into the month (::Int) and spot price (::Float64)\n", + " t, price = node::Tuple{Int,Float64}\n", + " # Transactions on the futures market cost 0.01\n", + " c_transaction = 0.01\n", + " # It costs the company +50% to buy milk on the spot market and deliver to\n", + " # their customers\n", + " c_buy_premium = 1.5\n", + " # Buyer is willing to pay +5% for certainty\n", + " c_contango = 1.05\n", + " # Distribution of production\n", + " Ω_production = range(0.1, 0.2; length = 5)\n", + " c_max_production = 12 * maximum(Ω_production)\n", + " # x_stock: quantity of milk in stock pile\n", + " @variable(sp, 0 <= x_stock, SDDP.State, initial_value = 0)\n", + " # x_forward[i]: quantity of milk for delivery in i months\n", + " @variable(sp, 0 <= x_forward[1:4], SDDP.State, initial_value = 0)\n", + " # u_spot_sell: quantity of milk to sell on spot market\n", + " @variable(sp, 0 <= u_spot_sell <= c_max_production)\n", + " # u_spot_buy: quantity of milk to buy on spot market\n", + " @variable(sp, 0 <= u_spot_buy <= c_max_production)\n", + " # u_spot_buy: quantity of milk to sell on futures market\n", + " c_max_futures = t <= 8 ? c_max_production : 0.0\n", + " @variable(sp, 0 <= u_forward_sell <= c_max_futures)\n", + " # ω_production: production random variable\n", + " @variable(sp, ω_production)\n", + " # Forward contracting constraints:\n", + " @constraint(sp, [i in 1:3], x_forward[i].out == x_forward[i+1].in)\n", + " @constraint(sp, x_forward[4].out == u_forward_sell)\n", + " # Stockpile balance constraint\n", + " @constraint(\n", + " sp,\n", + " x_stock.out ==\n", + " x_stock.in + ω_production + u_spot_buy - x_forward[1].in - u_spot_sell\n", + " )\n", + " # The random variables. `price` comes from the Markov node\n", + " #\n", + " # !!! warning\n", + " # The elements in Ω MUST be a tuple with 1 or 2 values, where the first\n", + " # value is `price` and the second value is the random variable for the\n", + " # current node. If the node is deterministic, use Ω = [(price,)].\n", + " Ω = [(price, p) for p in Ω_production]\n", + " SDDP.parameterize(sp, Ω) do ω\n", + " # Fix the ω_production variable\n", + " fix(ω_production, ω[2])\n", + " @stageobjective(\n", + " sp,\n", + " # Sales on spot market\n", + " ω[1] * (u_spot_sell - c_buy_premium * u_spot_buy) +\n", + " # Sales on futures smarket\n", + " (ω[1] * c_contango - c_transaction) * u_forward_sell\n", + " )\n", + " return\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Training a policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we have a model, we train a policy. The `SDDP.SimulatorSamplingScheme`\n", + "is used in the forward pass. It generates an out-of-sample sequence of prices\n", + "using `simulator` and traverses the closest sequence of nodes in the policy\n", + "graph. When calling `SDDP.parameterize` for each subproblem, it uses\n", + "the new out-of-sample price instead of the price associated with the Markov\n", + "node." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(\n", + " model;\n", + " time_limit = 20,\n", + " risk_measure = 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25),\n", + " sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> We're intentionally terminating the training early so that the\n", + "> documentation doesn't take too long to build. If you run this example\n", + "> locally, increase the time limit." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Simulating the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "When simulating the policy, we can also use the\n", + "`SDDP.SimulatorSamplingScheme`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model,\n", + " 200,\n", + " Symbol[:x_stock, :u_forward_sell, :u_spot_sell, :u_spot_buy];\n", + " sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),\n", + ");\n", + "nothing # hide" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "To show how the sampling scheme uses the new out-of-sample price instead of\n", + "the price associated with the Markov node, compare the index of the Markov\n", + "state visited in stage 12 of the first simulation:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations[1][12][:node_index]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "to the realization of the noise `(price, ω)` passed to `SDDP.parameterize`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations[1][12][:noise_term]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Visualizing the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we can plot the policy to gain insight (although note that we\n", + "terminated the training early, so we should run the re-train the policy for\n", + "more iterations before making too many judgements)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plot = Plots.plot(\n", + " SDDP.publication_plot(simulations; title = \"x_stock.out\") do data\n", + " return data[:x_stock].out\n", + " end,\n", + " SDDP.publication_plot(simulations; title = \"u_forward_sell\") do data\n", + " return data[:u_forward_sell]\n", + " end,\n", + " SDDP.publication_plot(simulations; title = \"u_spot_buy\") do data\n", + " return data[:u_spot_buy]\n", + " end,\n", + " SDDP.publication_plot(simulations; title = \"u_spot_sell\") do data\n", + " return data[:u_spot_sell]\n", + " end;\n", + " layout = (2, 2),\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Next steps" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "* Train the policy for longer. What do you observe?\n", + "* Try creating different Markovian graphs. What happens if you add more nodes?\n", + "* Try different risk measures" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/example_milk_producer.jl b/previews/PR826/tutorial/example_milk_producer.jl new file mode 100644 index 0000000000..c43aecb755 --- /dev/null +++ b/previews/PR826/tutorial/example_milk_producer.jl @@ -0,0 +1,255 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Example: the milk producer + +# The purpose of this tutorial is to demonstrate how to fit a Markovian policy +# graph to a univariate stochastic process. + +# This tutorial uses the following packages: + +using SDDP +import HiGHS +import Plots + +# ## Background + +# A company produces milk for sale on a spot market each month. The quantity of +# milk they produce is uncertain, and so too is the price on the spot market. +# The company can store unsold milk in a stockpile of dried milk powder. + +# The spot price is determined by an auction system, and so varies from month to +# month, but demonstrates serial correlation. In each auction, there is +# sufficient demand that the milk producer finds a buyer for all their +# milk, regardless of the quantity they supply. Furthermore, the spot price +# is independent of the milk producer (they are a small player in the market). + +# The spot price is highly volatile, and is the result of a process that is out +# of the control of the company. To counteract their price risk, the company +# engages in a forward contracting programme. + +# The forward contracting programme is a deal for physical milk four months in +# the future. + +# The futures price is the current spot price, plus some forward contango (the +# buyers gain certainty that they will receive the milk in the future). + +# In general, the milk company should forward contract (since they reduce +# their price risk), however they also have production risk. Therefore, it may +# be the case that they forward contract a fixed amount, but find that they do +# not produce enough milk to meet the fixed demand. They are then forced to +# buy additional milk on the spot market. + +# The goal of the milk company is to choose the extent to which they forward +# contract in order to maximise (risk-adjusted) revenues, whilst managing their +# production risk. + +# ## A stochastic process for price + +# It is outside the scope of this tutorial, but assume that we have gone away +# and analysed historical data to fit a stochastic process to the sequence of +# monthly auction spot prices. + +# One plausible model is a multiplicative auto-regressive model of order one, +# where the white noise term is modeled by a finite distribution of empirical +# residuals. We can simulate this stochastic process as follows: + +function simulator() + residuals = [0.0987, 0.199, 0.303, 0.412, 0.530, 0.661, 0.814, 1.010, 1.290] + residuals = 0.1 * vcat(-residuals, 0.0, residuals) + scenario = zeros(12) + y, μ, α = 4.5, 6.0, 0.05 + for t in 1:12 + y = exp((1 - α) * log(y) + α * log(μ) + rand(residuals)) + scenario[t] = clamp(y, 3.0, 9.0) + end + return scenario +end + +simulator() + +# It may be helpful to visualize a number of simulations of the price process: + +plot = Plots.plot( + [simulator() for _ in 1:500]; + color = "gray", + opacity = 0.2, + legend = false, + xlabel = "Month", + ylabel = "Price [\$/kg]", + xlims = (1, 12), + ylims = (3, 9), +) + +# The prices gradually revert to the mean of \$6/kg, and there is high +# volatility. + +# We can't incorporate this price process directly into SDDP.jl, but we can fit +# a [`SDDP.MarkovianGraph`](@ref) directly from the simulator: + +graph = SDDP.MarkovianGraph(simulator; budget = 30, scenarios = 10_000); +nothing # hide + +# Here `budget` is the number of nodes in the policy graph, and `scenarios` is +# the number of simulations to use when estimating the transition probabilities. + +# The graph contains too many nodes to be show, but we can plot it: + +for ((t, price), edges) in graph.nodes + for ((t′, price′), probability) in edges + Plots.plot!( + plot, + [t, t′], + [price, price′]; + color = "red", + width = 3 * probability, + ) + end +end + +plot + +# That looks okay. Try changing `budget` and `scenarios` to see how different +# Markovian policy graphs can be created. + +# ## Model + +# Now that we have a Markovian graph, we can build the model. See if you can +# work out how we arrived at this formulation by reading the background +# description. Do all the variables and constraints make sense? + +model = SDDP.PolicyGraph( + graph; + sense = :Max, + upper_bound = 1e2, + optimizer = HiGHS.Optimizer, +) do sp, node + ## Decompose the node into the month (::Int) and spot price (::Float64) + t, price = node::Tuple{Int,Float64} + ## Transactions on the futures market cost 0.01 + c_transaction = 0.01 + ## It costs the company +50% to buy milk on the spot market and deliver to + ## their customers + c_buy_premium = 1.5 + ## Buyer is willing to pay +5% for certainty + c_contango = 1.05 + ## Distribution of production + Ω_production = range(0.1, 0.2; length = 5) + c_max_production = 12 * maximum(Ω_production) + ## x_stock: quantity of milk in stock pile + @variable(sp, 0 <= x_stock, SDDP.State, initial_value = 0) + ## x_forward[i]: quantity of milk for delivery in i months + @variable(sp, 0 <= x_forward[1:4], SDDP.State, initial_value = 0) + ## u_spot_sell: quantity of milk to sell on spot market + @variable(sp, 0 <= u_spot_sell <= c_max_production) + ## u_spot_buy: quantity of milk to buy on spot market + @variable(sp, 0 <= u_spot_buy <= c_max_production) + ## u_spot_buy: quantity of milk to sell on futures market + c_max_futures = t <= 8 ? c_max_production : 0.0 + @variable(sp, 0 <= u_forward_sell <= c_max_futures) + ## ω_production: production random variable + @variable(sp, ω_production) + ## Forward contracting constraints: + @constraint(sp, [i in 1:3], x_forward[i].out == x_forward[i+1].in) + @constraint(sp, x_forward[4].out == u_forward_sell) + ## Stockpile balance constraint + @constraint( + sp, + x_stock.out == + x_stock.in + ω_production + u_spot_buy - x_forward[1].in - u_spot_sell + ) + ## The random variables. `price` comes from the Markov node + ## + ## !!! warning + ## The elements in Ω MUST be a tuple with 1 or 2 values, where the first + ## value is `price` and the second value is the random variable for the + ## current node. If the node is deterministic, use Ω = [(price,)]. + Ω = [(price, p) for p in Ω_production] + SDDP.parameterize(sp, Ω) do ω + ## Fix the ω_production variable + fix(ω_production, ω[2]) + @stageobjective( + sp, + ## Sales on spot market + ω[1] * (u_spot_sell - c_buy_premium * u_spot_buy) + + ## Sales on futures smarket + (ω[1] * c_contango - c_transaction) * u_forward_sell + ) + return + end + return +end + +# ## Training a policy + +# Now we have a model, we train a policy. The [`SDDP.SimulatorSamplingScheme`](@ref) +# is used in the forward pass. It generates an out-of-sample sequence of prices +# using `simulator` and traverses the closest sequence of nodes in the policy +# graph. When calling [`SDDP.parameterize`](@ref) for each subproblem, it uses +# the new out-of-sample price instead of the price associated with the Markov +# node. + +SDDP.train( + model; + time_limit = 20, + risk_measure = 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25), + sampling_scheme = SDDP.SimulatorSamplingScheme(simulator), +) + +# !!! warning +# We're intentionally terminating the training early so that the +# documentation doesn't take too long to build. If you run this example +# locally, increase the time limit. + +# ## Simulating the policy + +# When simulating the policy, we can also use the +# [`SDDP.SimulatorSamplingScheme`](@ref). + +simulations = SDDP.simulate( + model, + 200, + Symbol[:x_stock, :u_forward_sell, :u_spot_sell, :u_spot_buy]; + sampling_scheme = SDDP.SimulatorSamplingScheme(simulator), +); +nothing # hide + +# To show how the sampling scheme uses the new out-of-sample price instead of +# the price associated with the Markov node, compare the index of the Markov +# state visited in stage 12 of the first simulation: + +simulations[1][12][:node_index] + +# to the realization of the noise `(price, ω)` passed to [`SDDP.parameterize`](@ref): + +simulations[1][12][:noise_term] + +# ## Visualizing the policy + +# Finally, we can plot the policy to gain insight (although note that we +# terminated the training early, so we should run the re-train the policy for +# more iterations before making too many judgements). + +plot = Plots.plot( + SDDP.publication_plot(simulations; title = "x_stock.out") do data + return data[:x_stock].out + end, + SDDP.publication_plot(simulations; title = "u_forward_sell") do data + return data[:u_forward_sell] + end, + SDDP.publication_plot(simulations; title = "u_spot_buy") do data + return data[:u_spot_buy] + end, + SDDP.publication_plot(simulations; title = "u_spot_sell") do data + return data[:u_spot_sell] + end; + layout = (2, 2), +) + +# ## Next steps + +# * Train the policy for longer. What do you observe? +# * Try creating different Markovian graphs. What happens if you add more nodes? +# * Try different risk measures diff --git a/previews/PR826/tutorial/example_milk_producer/058d9f82.svg b/previews/PR826/tutorial/example_milk_producer/058d9f82.svg new file mode 100644 index 0000000000..868f62cd9f --- /dev/null +++ b/previews/PR826/tutorial/example_milk_producer/058d9f82.svg @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_milk_producer/09925323.svg b/previews/PR826/tutorial/example_milk_producer/09925323.svg new file mode 100644 index 0000000000..5898345b95 --- /dev/null +++ b/previews/PR826/tutorial/example_milk_producer/09925323.svg @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_milk_producer/2ddc7ff5.svg b/previews/PR826/tutorial/example_milk_producer/2ddc7ff5.svg new file mode 100644 index 0000000000..2cf3fcb165 --- /dev/null +++ b/previews/PR826/tutorial/example_milk_producer/2ddc7ff5.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_milk_producer/index.html b/previews/PR826/tutorial/example_milk_producer/index.html new file mode 100644 index 0000000000..e7d0352cdb --- /dev/null +++ b/previews/PR826/tutorial/example_milk_producer/index.html @@ -0,0 +1,184 @@ + +Example: the milk producer · SDDP.jl

Example: the milk producer

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

The purpose of this tutorial is to demonstrate how to fit a Markovian policy graph to a univariate stochastic process.

This tutorial uses the following packages:

using SDDP
+import HiGHS
+import Plots

Background

A company produces milk for sale on a spot market each month. The quantity of milk they produce is uncertain, and so too is the price on the spot market. The company can store unsold milk in a stockpile of dried milk powder.

The spot price is determined by an auction system, and so varies from month to month, but demonstrates serial correlation. In each auction, there is sufficient demand that the milk producer finds a buyer for all their milk, regardless of the quantity they supply. Furthermore, the spot price is independent of the milk producer (they are a small player in the market).

The spot price is highly volatile, and is the result of a process that is out of the control of the company. To counteract their price risk, the company engages in a forward contracting programme.

The forward contracting programme is a deal for physical milk four months in the future.

The futures price is the current spot price, plus some forward contango (the buyers gain certainty that they will receive the milk in the future).

In general, the milk company should forward contract (since they reduce their price risk), however they also have production risk. Therefore, it may be the case that they forward contract a fixed amount, but find that they do not produce enough milk to meet the fixed demand. They are then forced to buy additional milk on the spot market.

The goal of the milk company is to choose the extent to which they forward contract in order to maximise (risk-adjusted) revenues, whilst managing their production risk.

A stochastic process for price

It is outside the scope of this tutorial, but assume that we have gone away and analysed historical data to fit a stochastic process to the sequence of monthly auction spot prices.

One plausible model is a multiplicative auto-regressive model of order one, where the white noise term is modeled by a finite distribution of empirical residuals. We can simulate this stochastic process as follows:

function simulator()
+    residuals = [0.0987, 0.199, 0.303, 0.412, 0.530, 0.661, 0.814, 1.010, 1.290]
+    residuals = 0.1 * vcat(-residuals, 0.0, residuals)
+    scenario = zeros(12)
+    y, μ, α = 4.5, 6.0, 0.05
+    for t in 1:12
+        y = exp((1 - α) * log(y) + α * log(μ) + rand(residuals))
+        scenario[t] = clamp(y, 3.0, 9.0)
+    end
+    return scenario
+end
+
+simulator()
12-element Vector{Float64}:
+ 5.19377867361595
+ 5.675033116405084
+ 6.29566028484143
+ 5.956335828886897
+ 5.6509331835737875
+ 6.2702588355397575
+ 6.318518253250099
+ 6.42886794338868
+ 5.905868481395218
+ 6.314427927129379
+ 6.492081980658026
+ 6.4665458039058255

It may be helpful to visualize a number of simulations of the price process:

plot = Plots.plot(
+    [simulator() for _ in 1:500];
+    color = "gray",
+    opacity = 0.2,
+    legend = false,
+    xlabel = "Month",
+    ylabel = "Price [\$/kg]",
+    xlims = (1, 12),
+    ylims = (3, 9),
+)
Example block output

The prices gradually revert to the mean of $6/kg, and there is high volatility.

We can't incorporate this price process directly into SDDP.jl, but we can fit a SDDP.MarkovianGraph directly from the simulator:

graph = SDDP.MarkovianGraph(simulator; budget = 30, scenarios = 10_000);

Here budget is the number of nodes in the policy graph, and scenarios is the number of simulations to use when estimating the transition probabilities.

The graph contains too many nodes to be show, but we can plot it:

for ((t, price), edges) in graph.nodes
+    for ((t′, price′), probability) in edges
+        Plots.plot!(
+            plot,
+            [t, t′],
+            [price, price′];
+            color = "red",
+            width = 3 * probability,
+        )
+    end
+end
+
+plot
Example block output

That looks okay. Try changing budget and scenarios to see how different Markovian policy graphs can be created.

Model

Now that we have a Markovian graph, we can build the model. See if you can work out how we arrived at this formulation by reading the background description. Do all the variables and constraints make sense?

model = SDDP.PolicyGraph(
+    graph;
+    sense = :Max,
+    upper_bound = 1e2,
+    optimizer = HiGHS.Optimizer,
+) do sp, node
+    # Decompose the node into the month (::Int) and spot price (::Float64)
+    t, price = node::Tuple{Int,Float64}
+    # Transactions on the futures market cost 0.01
+    c_transaction = 0.01
+    # It costs the company +50% to buy milk on the spot market and deliver to
+    # their customers
+    c_buy_premium = 1.5
+    # Buyer is willing to pay +5% for certainty
+    c_contango = 1.05
+    # Distribution of production
+    Ω_production = range(0.1, 0.2; length = 5)
+    c_max_production = 12 * maximum(Ω_production)
+    # x_stock: quantity of milk in stock pile
+    @variable(sp, 0 <= x_stock, SDDP.State, initial_value = 0)
+    # x_forward[i]: quantity of milk for delivery in i months
+    @variable(sp, 0 <= x_forward[1:4], SDDP.State, initial_value = 0)
+    # u_spot_sell: quantity of milk to sell on spot market
+    @variable(sp, 0 <= u_spot_sell <= c_max_production)
+    # u_spot_buy: quantity of milk to buy on spot market
+    @variable(sp, 0 <= u_spot_buy <= c_max_production)
+    # u_spot_buy: quantity of milk to sell on futures market
+    c_max_futures = t <= 8 ? c_max_production : 0.0
+    @variable(sp, 0 <= u_forward_sell <= c_max_futures)
+    # ω_production: production random variable
+    @variable(sp, ω_production)
+    # Forward contracting constraints:
+    @constraint(sp, [i in 1:3], x_forward[i].out == x_forward[i+1].in)
+    @constraint(sp, x_forward[4].out == u_forward_sell)
+    # Stockpile balance constraint
+    @constraint(
+        sp,
+        x_stock.out ==
+        x_stock.in + ω_production + u_spot_buy - x_forward[1].in - u_spot_sell
+    )
+    # The random variables. `price` comes from the Markov node
+    #
+    # !!! warning
+    #     The elements in Ω MUST be a tuple with 1 or 2 values, where the first
+    #     value is `price` and the second value is the random variable for the
+    #     current node. If the node is deterministic, use Ω = [(price,)].
+    Ω = [(price, p) for p in Ω_production]
+    SDDP.parameterize(sp, Ω) do ω
+        # Fix the ω_production variable
+        fix(ω_production, ω[2])
+        @stageobjective(
+            sp,
+            # Sales on spot market
+            ω[1] * (u_spot_sell - c_buy_premium * u_spot_buy) +
+            # Sales on futures smarket
+            (ω[1] * c_contango - c_transaction) * u_forward_sell
+        )
+        return
+    end
+    return
+end
A policy graph with 30 nodes.
+ Node indices: (1, 4.575608052376776), ..., (12, 7.606365515572084)
+

Training a policy

Now we have a model, we train a policy. The SDDP.SimulatorSamplingScheme is used in the forward pass. It generates an out-of-sample sequence of prices using simulator and traverses the closest sequence of nodes in the policy graph. When calling SDDP.parameterize for each subproblem, it uses the new out-of-sample price instead of the price associated with the Markov node.

SDDP.train(
+    model;
+    time_limit = 20,
+    risk_measure = 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25),
+    sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),
+)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 30
+  state variables : 5
+  scenarios       : 1.00586e+12
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : A convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25)
+  sampling scheme : SDDP.SimulatorSamplingScheme{typeof(Main.simulator)}
+subproblem structure
+  VariableRef                             : [15, 15]
+  AffExpr in MOI.EqualTo{Float64}         : [5, 5]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [8, 9]
+  VariableRef in MOI.LessThan{Float64}    : [4, 4]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+01]
+  bounds range     [2e+00, 1e+02]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1  -2.475708e+01  6.553769e+01  1.232314e+00       162   1
+        61   1.008092e+01  7.886870e+00  2.234701e+00      9882   1
+       114   9.037845e+00  7.875605e+00  3.247699e+00     18468   1
+       162   9.712452e+00  7.873005e+00  4.266038e+00     26244   1
+       203   9.189887e+00  7.871622e+00  5.276566e+00     32886   1
+       243   9.000019e+00  7.871388e+00  6.279205e+00     39366   1
+       280   8.267105e+00  7.871006e+00  7.280654e+00     45360   1
+       314   1.138611e+01  7.870940e+00  8.280932e+00     50868   1
+       348   9.957101e+00  7.870627e+00  9.298395e+00     56376   1
+       487   9.672940e+00  7.870582e+00  1.430990e+01     78894   1
+       594   1.020155e+01  7.870581e+00  1.931252e+01     96228   1
+       611   8.129233e+00  7.870444e+00  2.003531e+01     98982   1
+-------------------------------------------------------------------
+status         : time_limit
+total time (s) : 2.003531e+01
+total solves   : 98982
+best bound     :  7.870444e+00
+simulation ci  :  8.880022e+00 ± 3.094901e-01
+numeric issues : 0
+-------------------------------------------------------------------
Warning

We're intentionally terminating the training early so that the documentation doesn't take too long to build. If you run this example locally, increase the time limit.

Simulating the policy

When simulating the policy, we can also use the SDDP.SimulatorSamplingScheme.

simulations = SDDP.simulate(
+    model,
+    200,
+    Symbol[:x_stock, :u_forward_sell, :u_spot_sell, :u_spot_buy];
+    sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),
+);

To show how the sampling scheme uses the new out-of-sample price instead of the price associated with the Markov node, compare the index of the Markov state visited in stage 12 of the first simulation:

simulations[1][12][:node_index]
(12, 5.982908713384155)

to the realization of the noise (price, ω) passed to SDDP.parameterize:

simulations[1][12][:noise_term]
(6.426391595326291, 0.2)

Visualizing the policy

Finally, we can plot the policy to gain insight (although note that we terminated the training early, so we should run the re-train the policy for more iterations before making too many judgements).

plot = Plots.plot(
+    SDDP.publication_plot(simulations; title = "x_stock.out") do data
+        return data[:x_stock].out
+    end,
+    SDDP.publication_plot(simulations; title = "u_forward_sell") do data
+        return data[:u_forward_sell]
+    end,
+    SDDP.publication_plot(simulations; title = "u_spot_buy") do data
+        return data[:u_spot_buy]
+    end,
+    SDDP.publication_plot(simulations; title = "u_spot_sell") do data
+        return data[:u_spot_sell]
+    end;
+    layout = (2, 2),
+)
Example block output

Next steps

  • Train the policy for longer. What do you observe?
  • Try creating different Markovian graphs. What happens if you add more nodes?
  • Try different risk measures
diff --git a/previews/PR826/tutorial/example_newsvendor.ipynb b/previews/PR826/tutorial/example_newsvendor.ipynb new file mode 100644 index 0000000000..61dea85697 --- /dev/null +++ b/previews/PR826/tutorial/example_newsvendor.ipynb @@ -0,0 +1,948 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Example: two-stage newsvendor" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The purpose of this tutorial is to demonstrate how to model and solve a\n", + "two-stage stochastic program." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "It is based on the [Two stage stochastic programs](https://jump.dev/JuMP.jl/dev/tutorials/applications/two_stage_stochastic/)\n", + "tutorial in JuMP." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial uses the following packages" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using JuMP\n", + "using SDDP\n", + "import Distributions\n", + "import ForwardDiff\n", + "import HiGHS\n", + "import Plots\n", + "import StatsPlots\n", + "import Statistics" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Background" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The data for this problem is:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "D = Distributions.TriangularDist(150.0, 250.0, 200.0)\n", + "N = 100\n", + "d = sort!(rand(D, N));\n", + "Ω = 1:N\n", + "P = fill(1 / N, N);\n", + "StatsPlots.histogram(d; bins = 20, label = \"\", xlabel = \"Demand\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Kelley's cutting plane algorithm" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Kelley's cutting plane algorithm is an iterative method for maximizing concave\n", + "functions. Given a concave function $f(x)$, Kelley's constructs an\n", + "outer-approximation of the function at the minimum by a set of first-order\n", + "Taylor series approximations (called **cuts**) constructed at a set of points\n", + "$k = 1,\\ldots,K$:\n", + "$$\n", + "\\begin{aligned}\n", + "f^K = \\max\\limits_{\\theta \\in \\mathbb{R}, x \\in \\mathbb{R}^N} \\;\\; & \\theta\\\\\n", + "& \\theta \\le f(x_k) + \\nabla f(x_k)^\\top (x - x_k),\\quad k=1,\\ldots,K\\\\\n", + "& \\theta \\le M,\n", + "\\end{aligned}\n", + "$$\n", + "where $M$ is a sufficiently large number that is an upper bound for $f$ over\n", + "the domain of $x$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Kelley's cutting plane algorithm is a structured way of choosing points $x_k$\n", + "to visit, so that as more cuts are added:\n", + "$$\n", + "\\lim_{K \\rightarrow \\infty} f^K = \\max\\limits_{x \\in \\mathbb{R}^N} f(x)\n", + "$$\n", + "However, before we introduce the algorithm, we need to introduce some bounds." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "By convexity, $f(x) \\le f^K$ for all $x$. Thus, if $x^*$ is a maximizer of\n", + "$f$, then at any point in time we can construct an upper bound for $f(x^*)$ by\n", + "solving $f^K$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Moreover, we can use the primal solutions $x_k^*$ returned by solving $f^k$ to\n", + "evaluate $f(x_k^*)$ to generate a lower bound." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Therefore, $\\max\\limits_{k=1,\\ldots,K} f(x_k^*) \\le f(x^*) \\le f^K$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "When the lower bound is sufficiently close to the upper bound, we can\n", + "terminate the algorithm and declare that we have found an solution that is\n", + "close to optimal." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Implementation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is pseudo-code fo the Kelley algorithm:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "1. Take as input a convex function $f(x)$ and a iteration limit $K_{max}$.\n", + " Set $K = 1$, and initialize $f^{K-1}$. Set $lb = -\\infty$ and $ub = \\infty$.\n", + "2. Solve $f^{K-1}$ to obtain a candidate solution $x_{K}$.\n", + "3. Update $ub = f^{K-1}$ and $lb = \\max\\{lb, f(x_{K})\\}$.\n", + "4. Add a cut $\\theta \\ge f(x_{K}) + \\nabla f\\left(x_{K}\\right)^\\top (x - x_{K})$ to form $f^{K}$.\n", + "5. Increment $K$.\n", + "6. If $K > K_{max}$ or $|ub - lb| < \\epsilon$, STOP, otherwise, go to step 2." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "And here's a complete implementation:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function kelleys_cutting_plane(\n", + " # The function to be minimized.\n", + " f::Function,\n", + " # The gradient of `f`. By default, we use automatic differentiation to\n", + " # compute the gradient of f so the user doesn't have to!\n", + " ∇f::Function = x -> ForwardDiff.gradient(f, x);\n", + " # The number of arguments to `f`.\n", + " input_dimension::Int,\n", + " # An upper bound for the function `f` over its domain.\n", + " upper_bound::Float64,\n", + " # The number of iterations to run Kelley's algorithm for before stopping.\n", + " iteration_limit::Int,\n", + " # The absolute tolerance ϵ to use for convergence.\n", + " tolerance::Float64 = 1e-6,\n", + ")\n", + " # Step (1):\n", + " K = 1\n", + " model = JuMP.Model(HiGHS.Optimizer)\n", + " JuMP.set_silent(model)\n", + " JuMP.@variable(model, θ <= upper_bound)\n", + " JuMP.@variable(model, x[1:input_dimension])\n", + " JuMP.@objective(model, Max, θ)\n", + " x_k = fill(NaN, input_dimension)\n", + " lower_bound, upper_bound = -Inf, Inf\n", + " while true\n", + " # Step (2):\n", + " JuMP.optimize!(model)\n", + " x_k .= JuMP.value.(x)\n", + " # Step (3):\n", + " upper_bound = JuMP.objective_value(model)\n", + " lower_bound = min(upper_bound, f(x_k))\n", + " println(\"K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)\")\n", + " # Step (4):\n", + " JuMP.@constraint(model, θ <= f(x_k) + ∇f(x_k)' * (x .- x_k))\n", + " # Step (5):\n", + " K = K + 1\n", + " # Step (6):\n", + " if K > iteration_limit\n", + " println(\"-- Termination status: iteration limit --\")\n", + " break\n", + " elseif abs(upper_bound - lower_bound) < tolerance\n", + " println(\"-- Termination status: converged --\")\n", + " break\n", + " end\n", + " end\n", + " println(\"Found solution: x_K = \", x_k)\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's run our algorithm to see what happens:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "kelleys_cutting_plane(;\n", + " input_dimension = 2,\n", + " upper_bound = 10.0,\n", + " iteration_limit = 20,\n", + ") do x\n", + " return -(x[1] - 1)^2 + -(x[2] + 2)^2 + 1.0\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## L-Shaped theory" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The L-Shaped method is a way of solving two-stage stochastic programs by\n", + "Benders' decomposition. It takes the problem:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\begin{aligned}\n", + "V = \\max\\limits_{x,y_\\omega} \\;\\; & -2x + \\mathbb{E}_\\omega[5y_\\omega - 0.1(x - y_\\omega)] \\\\\n", + " & y_\\omega \\le x & \\quad \\forall \\omega \\in \\Omega \\\\\n", + " & 0 \\le y_\\omega \\le d_\\omega & \\quad \\forall \\omega \\in \\Omega \\\\\n", + " & x \\ge 0.\n", + "\\end{aligned}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "and decomposes it into a second-stage problem:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\begin{aligned}\n", + "V_2(\\bar{x}, d_\\omega) = \\max\\limits_{x,x^\\prime,y_\\omega} \\;\\; & 5y_\\omega - x^\\prime \\\\\n", + " & y_\\omega \\le x \\\\\n", + " & x^\\prime = x - y_\\omega \\\\\n", + " & 0 \\le y_\\omega \\le d_\\omega \\\\\n", + " & x = \\bar{x} & [\\lambda]\n", + "\\end{aligned}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "and a first-stage problem:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\begin{aligned}\n", + "V = \\max\\limits_{x,\\theta} \\;\\; & -2x + \\theta \\\\\n", + " & \\theta \\le \\mathbb{E}_\\omega[V_2(x, \\omega)] \\\\\n", + " & x \\ge 0\n", + "\\end{aligned}\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Then, because $V_2$ is convex with respect to $\\bar{x}$ for fixed $\\omega$,\n", + "we can use a set of feasible points $\\{x^k\\}$ construct an outer approximation:\n", + "$$\n", + "\\begin{aligned}\n", + "V^K = \\max\\limits_{x,\\theta} \\;\\; & -2x + \\theta \\\\\n", + " & \\theta \\le \\mathbb{E}_\\omega[V_2(x^k, \\omega) + \\nabla V_2(x^k, \\omega)^\\top(x - x^k)] & \\quad k = 1,\\ldots,K\\\\\n", + " & x \\ge 0 \\\\\n", + " & \\theta \\le M\n", + "\\end{aligned}\n", + "$$\n", + "where $M$ is an upper bound on possible values of $V_2$ so that the problem\n", + "has a bounded solution." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "It is also useful to see that because $\\bar{x}$ appears only on the right-hand\n", + "side of a linear program, $\\nabla V_2(x^k, \\omega) = \\lambda^k$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Ignoring how we choose $x^k$ for now, we can construct a lower and upper bound\n", + "on the optimal solution:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$-2x^K + \\mathbb{E}_\\omega[V_2(x^K, \\omega)] = \\underbar{V} \\le V \\le \\overline{V} = V^K$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Thus, we need some way of cleverly choosing a sequence of $x^k$ so that the\n", + "lower bound converges to the upper bound." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "1. Start with $K=1$\n", + "2. Solve $V^{K-1}$ to get $x^K$\n", + "3. Set $\\overline{V} = V^k$\n", + "4. Solve $V_2(x^K, \\omega)$ for all $\\omega$ and store the optimal objective\n", + " value and dual solution $\\lambda^K$\n", + "5. Set $\\underbar{V} = -2x^K + \\mathbb{E}_\\omega[V_2(x^k, \\omega)]$\n", + "6. If $\\underbar{V} \\approx \\overline{V}$, STOP\n", + "7. Add new constraint $\\theta \\le \\mathbb{E}_\\omega[V_2(x^K, \\omega) +\\lambda^K (x - x^K)]$\n", + "8. Increment $K$, GOTO 2" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The next section implements this algorithm in Julia." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## L-Shaped implementation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here's a function to compute the second-stage problem;" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function solve_second_stage(x̅, d_ω)\n", + " model = Model(HiGHS.Optimizer)\n", + " set_silent(model)\n", + " @variable(model, x_in)\n", + " @variable(model, x_out >= 0)\n", + " fix(x_in, x̅)\n", + " @variable(model, 0 <= u_sell <= d_ω)\n", + " @constraint(model, x_out == x_in - u_sell)\n", + " @constraint(model, u_sell <= x_in)\n", + " @objective(model, Max, 5 * u_sell - 0.1 * x_out)\n", + " optimize!(model)\n", + " return (\n", + " V = objective_value(model),\n", + " λ = reduced_cost(x_in),\n", + " x = value(x_out),\n", + " u = value(u_sell),\n", + " )\n", + "end\n", + "\n", + "solve_second_stage(200, 170)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Here's the first-stage subproblem:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = Model(HiGHS.Optimizer)\n", + "set_silent(model)\n", + "@variable(model, x_in == 0)\n", + "@variable(model, x_out >= 0)\n", + "@variable(model, u_make >= 0)\n", + "@constraint(model, x_out == x_in + u_make)\n", + "M = 5 * maximum(d)\n", + "@variable(model, θ <= M)\n", + "@objective(model, Max, -2 * u_make + θ)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Importantly, to ensure we have a bounded solution, we need to add an upper\n", + "bound to the variable `θ`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "kIterationLimit = 100\n", + "for k in 1:kIterationLimit\n", + " println(\"Solving iteration k = $k\")\n", + " # Step 2\n", + " optimize!(model)\n", + " xᵏ = value(x_out)\n", + " println(\" xᵏ = $xᵏ\")\n", + " # Step 3\n", + " ub = objective_value(model)\n", + " println(\" V̅ = $ub\")\n", + " # Step 4\n", + " ret = [solve_second_stage(xᵏ, d[ω]) for ω in Ω]\n", + " # Step 5\n", + " lb = value(-2 * u_make) + sum(p * r.V for (p, r) in zip(P, ret))\n", + " println(\" V̲ = $lb\")\n", + " # Step 6\n", + " if ub - lb < 1e-6\n", + " println(\"Terminating with near-optimal solution\")\n", + " break\n", + " end\n", + " # Step 7\n", + " c = @constraint(\n", + " model,\n", + " θ <= sum(p * (r.V + r.λ * (x_out - xᵏ)) for (p, r) in zip(P, ret)),\n", + " )\n", + " println(\" Added cut: $c\")\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "To get the first-stage solution, we do:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "optimize!(model)\n", + "xᵏ = value(x_out)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "To compute a second-stage solution, we do:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "solve_second_stage(xᵏ, 170.0)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Policy Graph" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now let's see how we can formulate and train a policy for the two-stage\n", + "newsvendor problem using `SDDP.jl`. Under the hood, `SDDP.jl` implements the\n", + "exact algorithm that we just wrote by hand." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " sense = :Max,\n", + " upper_bound = 5 * maximum(d), # The `M` in θ <= M\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem::JuMP.Model, stage::Int\n", + " @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)\n", + " if stage == 1\n", + " @variable(subproblem, u_make >= 0)\n", + " @constraint(subproblem, x.out == x.in + u_make)\n", + " @stageobjective(subproblem, -2 * u_make)\n", + " else\n", + " @variable(subproblem, u_sell >= 0)\n", + " @constraint(subproblem, u_sell <= x.in)\n", + " @constraint(subproblem, x.out == x.in - u_sell)\n", + " SDDP.parameterize(subproblem, d, P) do ω\n", + " set_upper_bound(u_sell, ω)\n", + " return\n", + " end\n", + " @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out)\n", + " end\n", + " return\n", + "end\n", + "\n", + "SDDP.train(model; log_every_iteration = true)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "One way to query the optimal policy is with `SDDP.DecisionRule`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "first_stage_rule = SDDP.DecisionRule(model; node = 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "solution_1 = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Here's the second stage:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "second_stage_rule = SDDP.DecisionRule(model; node = 2)\n", + "solution = SDDP.evaluate(\n", + " second_stage_rule;\n", + " incoming_state = Dict(:x => solution_1.outgoing_state[:x]),\n", + " noise = 170.0, # A value of d[ω], can be out-of-sample.\n", + " controls_to_record = [:u_sell],\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Simulation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Querying the decision rules is tedious. It's often more useful to simulate the\n", + "policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model,\n", + " 10, #= number of replications =#\n", + " [:x, :u_sell, :u_make]; #= variables to record =#\n", + " skip_undefined_variables = true,\n", + ");" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`simulations` is a vector with 10 elements" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "length(simulations)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "and each element is a vector with two elements (one for each stage)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "length(simulations[1])" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The first stage contains:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations[1][1]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The second stage contains:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations[1][2]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can compute aggregated statistics across the simulations:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "objectives = map(simulations) do simulation\n", + " return sum(data[:stage_objective] for data in simulation)\n", + "end\n", + "μ, t = SDDP.confidence_interval(objectives)\n", + "println(\"Simulation ci : $μ ± $t\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Risk aversion revisited" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP.jl contains a number of risk measures. One example is:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "0.5 * SDDP.Expectation() + 0.5 * SDDP.WorstCase()" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "You can construct a risk-averse policy by passing a risk measure to the\n", + "`risk_measure` keyword argument of `SDDP.train`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can explore how the optimal decision changes with risk by creating a\n", + "function:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function solve_newsvendor(risk_measure::SDDP.AbstractRiskMeasure)\n", + " model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " sense = :Max,\n", + " upper_bound = 5 * maximum(d),\n", + " optimizer = HiGHS.Optimizer,\n", + " ) do subproblem, node\n", + " @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)\n", + " if node == 1\n", + " @stageobjective(subproblem, -2 * x.out)\n", + " else\n", + " @variable(subproblem, u_sell >= 0)\n", + " @constraint(subproblem, u_sell <= x.in)\n", + " @constraint(subproblem, x.out == x.in - u_sell)\n", + " SDDP.parameterize(subproblem, d, P) do ω\n", + " set_upper_bound(u_sell, ω)\n", + " return\n", + " end\n", + " @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out)\n", + " end\n", + " return\n", + " end\n", + " SDDP.train(model; risk_measure = risk_measure, print_level = 0)\n", + " first_stage_rule = SDDP.DecisionRule(model; node = 1)\n", + " solution = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0))\n", + " return solution.outgoing_state[:x]\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Now we can see how many units a decision maker would order using `CVaR`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "solve_newsvendor(SDDP.CVaR(0.4))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "as well as a decision-maker who cares only about the worst-case outcome:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "solve_newsvendor(SDDP.WorstCase())" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "In general, the decision-maker will be somewhere between the two extremes.\n", + "The `SDDP.Entropic` risk measure is a risk measure that has a single\n", + "parameter that lets us explore the space of policies between the two extremes.\n", + "When the parameter is small, the measure acts like `SDDP.Expectation`,\n", + "and when it is large, it acts like `SDDP.WorstCase`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is what we get if we solve our problem multiple times for different\n", + "values of the risk aversion parameter $\\gamma$:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Γ = [10^i for i in -4:0.5:1]\n", + "buy = [solve_newsvendor(SDDP.Entropic(γ)) for γ in Γ]\n", + "Plots.plot(\n", + " Γ,\n", + " buy;\n", + " xaxis = :log,\n", + " xlabel = \"Risk aversion parameter γ\",\n", + " ylabel = \"Number of pies to make\",\n", + " legend = false,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Things to try" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are a number of things you can try next:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + " * Experiment with different buy and sales prices\n", + " * Experiment with different distributions of demand\n", + " * Explore how the optimal policy changes if you use a different risk measure\n", + " * What happens if you can only buy and sell integer numbers of newspapers?\n", + " Try this by adding `Int` to the variable definitions:\n", + " `@variable(subproblem, buy >= 0, Int)`\n", + " * What happens if you use a different upper bound? Try an invalid one like\n", + " `-100`, and a very large one like `1e12`." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/example_newsvendor.jl b/previews/PR826/tutorial/example_newsvendor.jl new file mode 100644 index 0000000000..21d45121d7 --- /dev/null +++ b/previews/PR826/tutorial/example_newsvendor.jl @@ -0,0 +1,462 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Example: two-stage newsvendor + +# The purpose of this tutorial is to demonstrate how to model and solve a +# two-stage stochastic program. + +# It is based on the [Two stage stochastic programs](https://jump.dev/JuMP.jl/dev/tutorials/applications/two_stage_stochastic/) +# tutorial in JuMP. + +# This tutorial uses the following packages + +using JuMP +using SDDP +import Distributions +import ForwardDiff +import HiGHS +import Plots +import StatsPlots +import Statistics + +# ## Background + +# The data for this problem is: + +D = Distributions.TriangularDist(150.0, 250.0, 200.0) +N = 100 +d = sort!(rand(D, N)); +Ω = 1:N +P = fill(1 / N, N); +StatsPlots.histogram(d; bins = 20, label = "", xlabel = "Demand") + +# ## Kelley's cutting plane algorithm + +# Kelley's cutting plane algorithm is an iterative method for maximizing concave +# functions. Given a concave function $f(x)$, Kelley's constructs an +# outer-approximation of the function at the minimum by a set of first-order +# Taylor series approximations (called **cuts**) constructed at a set of points +# $k = 1,\ldots,K$: +# ```math +# \begin{aligned} +# f^K = \max\limits_{\theta \in \mathbb{R}, x \in \mathbb{R}^N} \;\; & \theta\\ +# & \theta \le f(x_k) + \nabla f(x_k)^\top (x - x_k),\quad k=1,\ldots,K\\ +# & \theta \le M, +# \end{aligned} +# ``` +# where $M$ is a sufficiently large number that is an upper bound for $f$ over +# the domain of $x$. + +# Kelley's cutting plane algorithm is a structured way of choosing points $x_k$ +# to visit, so that as more cuts are added: +# ```math +# \lim_{K \rightarrow \infty} f^K = \max\limits_{x \in \mathbb{R}^N} f(x) +# ``` +# However, before we introduce the algorithm, we need to introduce some bounds. + +# ### Bounds + +# By convexity, $f(x) \le f^K$ for all $x$. Thus, if $x^*$ is a maximizer of +# $f$, then at any point in time we can construct an upper bound for $f(x^*)$ by +# solving $f^K$. + +# Moreover, we can use the primal solutions $x_k^*$ returned by solving $f^k$ to +# evaluate $f(x_k^*)$ to generate a lower bound. + +# Therefore, $\max\limits_{k=1,\ldots,K} f(x_k^*) \le f(x^*) \le f^K$. + +# When the lower bound is sufficiently close to the upper bound, we can +# terminate the algorithm and declare that we have found an solution that is +# close to optimal. + +# ### Implementation + +# Here is pseudo-code fo the Kelley algorithm: + +# 1. Take as input a convex function $f(x)$ and a iteration limit $K_{max}$. +# Set $K = 1$, and initialize $f^{K-1}$. Set $lb = -\infty$ and $ub = \infty$. +# 2. Solve $f^{K-1}$ to obtain a candidate solution $x_{K}$. +# 3. Update $ub = f^{K-1}$ and $lb = \max\{lb, f(x_{K})\}$. +# 4. Add a cut $\theta \ge f(x_{K}) + \nabla f\left(x_{K}\right)^\top (x - x_{K})$ to form $f^{K}$. +# 5. Increment $K$. +# 6. If $K > K_{max}$ or $|ub - lb| < \epsilon$, STOP, otherwise, go to step 2. + +# And here's a complete implementation: + +function kelleys_cutting_plane( + ## The function to be minimized. + f::Function, + ## The gradient of `f`. By default, we use automatic differentiation to + ## compute the gradient of f so the user doesn't have to! + ∇f::Function = x -> ForwardDiff.gradient(f, x); + ## The number of arguments to `f`. + input_dimension::Int, + ## An upper bound for the function `f` over its domain. + upper_bound::Float64, + ## The number of iterations to run Kelley's algorithm for before stopping. + iteration_limit::Int, + ## The absolute tolerance ϵ to use for convergence. + tolerance::Float64 = 1e-6, +) + ## Step (1): + K = 1 + model = JuMP.Model(HiGHS.Optimizer) + JuMP.set_silent(model) + JuMP.@variable(model, θ <= upper_bound) + JuMP.@variable(model, x[1:input_dimension]) + JuMP.@objective(model, Max, θ) + x_k = fill(NaN, input_dimension) + lower_bound, upper_bound = -Inf, Inf + while true + ## Step (2): + JuMP.optimize!(model) + x_k .= JuMP.value.(x) + ## Step (3): + upper_bound = JuMP.objective_value(model) + lower_bound = min(upper_bound, f(x_k)) + println("K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)") + ## Step (4): + JuMP.@constraint(model, θ <= f(x_k) + ∇f(x_k)' * (x .- x_k)) + ## Step (5): + K = K + 1 + ## Step (6): + if K > iteration_limit + println("-- Termination status: iteration limit --") + break + elseif abs(upper_bound - lower_bound) < tolerance + println("-- Termination status: converged --") + break + end + end + println("Found solution: x_K = ", x_k) + return +end + +# Let's run our algorithm to see what happens: + +kelleys_cutting_plane(; + input_dimension = 2, + upper_bound = 10.0, + iteration_limit = 20, +) do x + return -(x[1] - 1)^2 + -(x[2] + 2)^2 + 1.0 +end + +# ## L-Shaped theory + +# The L-Shaped method is a way of solving two-stage stochastic programs by +# Benders' decomposition. It takes the problem: + +# ```math +# \begin{aligned} +# V = \max\limits_{x,y_\omega} \;\; & -2x + \mathbb{E}_\omega[5y_\omega - 0.1(x - y_\omega)] \\ +# & y_\omega \le x & \quad \forall \omega \in \Omega \\ +# & 0 \le y_\omega \le d_\omega & \quad \forall \omega \in \Omega \\ +# & x \ge 0. +# \end{aligned} +# ``` + +# and decomposes it into a second-stage problem: + +# ```math +# \begin{aligned} +# V_2(\bar{x}, d_\omega) = \max\limits_{x,x^\prime,y_\omega} \;\; & 5y_\omega - x^\prime \\ +# & y_\omega \le x \\ +# & x^\prime = x - y_\omega \\ +# & 0 \le y_\omega \le d_\omega \\ +# & x = \bar{x} & [\lambda] +# \end{aligned} +# ``` + +# and a first-stage problem: + +# ```math +# \begin{aligned} +# V = \max\limits_{x,\theta} \;\; & -2x + \theta \\ +# & \theta \le \mathbb{E}_\omega[V_2(x, \omega)] \\ +# & x \ge 0 +# \end{aligned} +# ``` + +# Then, because $V_2$ is convex with respect to $\bar{x}$ for fixed $\omega$, +# we can use a set of feasible points $\{x^k\}$ construct an outer approximation: +# ```math +# \begin{aligned} +# V^K = \max\limits_{x,\theta} \;\; & -2x + \theta \\ +# & \theta \le \mathbb{E}_\omega[V_2(x^k, \omega) + \nabla V_2(x^k, \omega)^\top(x - x^k)] & \quad k = 1,\ldots,K\\ +# & x \ge 0 \\ +# & \theta \le M +# \end{aligned} +# ``` +# where $M$ is an upper bound on possible values of $V_2$ so that the problem +# has a bounded solution. + +# It is also useful to see that because $\bar{x}$ appears only on the right-hand +# side of a linear program, $\nabla V_2(x^k, \omega) = \lambda^k$. + +# Ignoring how we choose $x^k$ for now, we can construct a lower and upper bound +# on the optimal solution: + +# $$-2x^K + \mathbb{E}_\omega[V_2(x^K, \omega)] = \underbar{V} \le V \le \overline{V} = V^K$$ + +# Thus, we need some way of cleverly choosing a sequence of $x^k$ so that the +# lower bound converges to the upper bound. + +# 1. Start with $K=1$ +# 2. Solve $V^{K-1}$ to get $x^K$ +# 3. Set $\overline{V} = V^k$ +# 4. Solve $V_2(x^K, \omega)$ for all $\omega$ and store the optimal objective +# value and dual solution $\lambda^K$ +# 5. Set $\underbar{V} = -2x^K + \mathbb{E}_\omega[V_2(x^k, \omega)]$ +# 6. If $\underbar{V} \approx \overline{V}$, STOP +# 7. Add new constraint $\theta \le \mathbb{E}_\omega[V_2(x^K, \omega) +\lambda^K (x - x^K)]$ +# 8. Increment $K$, GOTO 2 + +# The next section implements this algorithm in Julia. + +# ## L-Shaped implementation + +# Here's a function to compute the second-stage problem; + +function solve_second_stage(x̅, d_ω) + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, x_in) + @variable(model, x_out >= 0) + fix(x_in, x̅) + @variable(model, 0 <= u_sell <= d_ω) + @constraint(model, x_out == x_in - u_sell) + @constraint(model, u_sell <= x_in) + @objective(model, Max, 5 * u_sell - 0.1 * x_out) + optimize!(model) + return ( + V = objective_value(model), + λ = reduced_cost(x_in), + x = value(x_out), + u = value(u_sell), + ) +end + +solve_second_stage(200, 170) + +# Here's the first-stage subproblem: + +model = Model(HiGHS.Optimizer) +set_silent(model) +@variable(model, x_in == 0) +@variable(model, x_out >= 0) +@variable(model, u_make >= 0) +@constraint(model, x_out == x_in + u_make) +M = 5 * maximum(d) +@variable(model, θ <= M) +@objective(model, Max, -2 * u_make + θ) + +# Importantly, to ensure we have a bounded solution, we need to add an upper +# bound to the variable `θ`. + +kIterationLimit = 100 +for k in 1:kIterationLimit + println("Solving iteration k = $k") + ## Step 2 + optimize!(model) + xᵏ = value(x_out) + println(" xᵏ = $xᵏ") + ## Step 3 + ub = objective_value(model) + println(" V̅ = $ub") + ## Step 4 + ret = [solve_second_stage(xᵏ, d[ω]) for ω in Ω] + ## Step 5 + lb = value(-2 * u_make) + sum(p * r.V for (p, r) in zip(P, ret)) + println(" V̲ = $lb") + ## Step 6 + if ub - lb < 1e-6 + println("Terminating with near-optimal solution") + break + end + ## Step 7 + c = @constraint( + model, + θ <= sum(p * (r.V + r.λ * (x_out - xᵏ)) for (p, r) in zip(P, ret)), + ) + println(" Added cut: $c") +end + +# To get the first-stage solution, we do: + +optimize!(model) +xᵏ = value(x_out) + +# To compute a second-stage solution, we do: + +solve_second_stage(xᵏ, 170.0) + +# ## Policy Graph + +# Now let's see how we can formulate and train a policy for the two-stage +# newsvendor problem using `SDDP.jl`. Under the hood, `SDDP.jl` implements the +# exact algorithm that we just wrote by hand. + +model = SDDP.LinearPolicyGraph(; + stages = 2, + sense = :Max, + upper_bound = 5 * maximum(d), # The `M` in θ <= M + optimizer = HiGHS.Optimizer, +) do subproblem::JuMP.Model, stage::Int + @variable(subproblem, x >= 0, SDDP.State, initial_value = 0) + if stage == 1 + @variable(subproblem, u_make >= 0) + @constraint(subproblem, x.out == x.in + u_make) + @stageobjective(subproblem, -2 * u_make) + else + @variable(subproblem, u_sell >= 0) + @constraint(subproblem, u_sell <= x.in) + @constraint(subproblem, x.out == x.in - u_sell) + SDDP.parameterize(subproblem, d, P) do ω + set_upper_bound(u_sell, ω) + return + end + @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out) + end + return +end + +SDDP.train(model; log_every_iteration = true) + +# One way to query the optimal policy is with [`SDDP.DecisionRule`](@ref): + +first_stage_rule = SDDP.DecisionRule(model; node = 1) + +#- + +solution_1 = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0)) + +# Here's the second stage: + +second_stage_rule = SDDP.DecisionRule(model; node = 2) +solution = SDDP.evaluate( + second_stage_rule; + incoming_state = Dict(:x => solution_1.outgoing_state[:x]), + noise = 170.0, # A value of d[ω], can be out-of-sample. + controls_to_record = [:u_sell], +) + +# ## Simulation + +# Querying the decision rules is tedious. It's often more useful to simulate the +# policy: + +simulations = SDDP.simulate( + model, + 10, #= number of replications =# + [:x, :u_sell, :u_make]; #= variables to record =# + skip_undefined_variables = true, +); + +# `simulations` is a vector with 10 elements + +length(simulations) + +# and each element is a vector with two elements (one for each stage) + +length(simulations[1]) + +# The first stage contains: + +simulations[1][1] + +# The second stage contains: + +simulations[1][2] + +# We can compute aggregated statistics across the simulations: + +objectives = map(simulations) do simulation + return sum(data[:stage_objective] for data in simulation) +end +μ, t = SDDP.confidence_interval(objectives) +println("Simulation ci : $μ ± $t") + +# ## Risk aversion revisited + +# SDDP.jl contains a number of risk measures. One example is: + +0.5 * SDDP.Expectation() + 0.5 * SDDP.WorstCase() + +# You can construct a risk-averse policy by passing a risk measure to the +# `risk_measure` keyword argument of [`SDDP.train`](@ref). + +# We can explore how the optimal decision changes with risk by creating a +# function: + +function solve_newsvendor(risk_measure::SDDP.AbstractRiskMeasure) + model = SDDP.LinearPolicyGraph(; + stages = 2, + sense = :Max, + upper_bound = 5 * maximum(d), + optimizer = HiGHS.Optimizer, + ) do subproblem, node + @variable(subproblem, x >= 0, SDDP.State, initial_value = 0) + if node == 1 + @stageobjective(subproblem, -2 * x.out) + else + @variable(subproblem, u_sell >= 0) + @constraint(subproblem, u_sell <= x.in) + @constraint(subproblem, x.out == x.in - u_sell) + SDDP.parameterize(subproblem, d, P) do ω + set_upper_bound(u_sell, ω) + return + end + @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out) + end + return + end + SDDP.train(model; risk_measure = risk_measure, print_level = 0) + first_stage_rule = SDDP.DecisionRule(model; node = 1) + solution = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0)) + return solution.outgoing_state[:x] +end + +# Now we can see how many units a decision maker would order using `CVaR`: + +solve_newsvendor(SDDP.CVaR(0.4)) + +# as well as a decision-maker who cares only about the worst-case outcome: + +solve_newsvendor(SDDP.WorstCase()) + +# In general, the decision-maker will be somewhere between the two extremes. +# The [`SDDP.Entropic`](@ref) risk measure is a risk measure that has a single +# parameter that lets us explore the space of policies between the two extremes. +# When the parameter is small, the measure acts like [`SDDP.Expectation`](@ref), +# and when it is large, it acts like [`SDDP.WorstCase`](@ref). + +# Here is what we get if we solve our problem multiple times for different +# values of the risk aversion parameter ``\gamma``: + +Γ = [10^i for i in -4:0.5:1] +buy = [solve_newsvendor(SDDP.Entropic(γ)) for γ in Γ] +Plots.plot( + Γ, + buy; + xaxis = :log, + xlabel = "Risk aversion parameter γ", + ylabel = "Number of pies to make", + legend = false, +) + +# ## Things to try + +# There are a number of things you can try next: + +# * Experiment with different buy and sales prices +# * Experiment with different distributions of demand +# * Explore how the optimal policy changes if you use a different risk measure +# * What happens if you can only buy and sell integer numbers of newspapers? +# Try this by adding `Int` to the variable definitions: +# `@variable(subproblem, buy >= 0, Int)` +# * What happens if you use a different upper bound? Try an invalid one like +# `-100`, and a very large one like `1e12`. diff --git a/previews/PR826/tutorial/example_newsvendor/01120dfc.svg b/previews/PR826/tutorial/example_newsvendor/01120dfc.svg new file mode 100644 index 0000000000..a3727d1e8c --- /dev/null +++ b/previews/PR826/tutorial/example_newsvendor/01120dfc.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_newsvendor/b847ac9f.svg b/previews/PR826/tutorial/example_newsvendor/b847ac9f.svg new file mode 100644 index 0000000000..d80c7ff92a --- /dev/null +++ b/previews/PR826/tutorial/example_newsvendor/b847ac9f.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_newsvendor/index.html b/previews/PR826/tutorial/example_newsvendor/index.html new file mode 100644 index 0000000000..276f5565f3 --- /dev/null +++ b/previews/PR826/tutorial/example_newsvendor/index.html @@ -0,0 +1,384 @@ + +Example: two-stage newsvendor · SDDP.jl

Example: two-stage newsvendor

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

The purpose of this tutorial is to demonstrate how to model and solve a two-stage stochastic program.

It is based on the Two stage stochastic programs tutorial in JuMP.

This tutorial uses the following packages

using JuMP
+using SDDP
+import Distributions
+import ForwardDiff
+import HiGHS
+import Plots
+import StatsPlots
+import Statistics

Background

The data for this problem is:

D = Distributions.TriangularDist(150.0, 250.0, 200.0)
+N = 100
+d = sort!(rand(D, N));
+Ω = 1:N
+P = fill(1 / N, N);
+StatsPlots.histogram(d; bins = 20, label = "", xlabel = "Demand")
Example block output

Kelley's cutting plane algorithm

Kelley's cutting plane algorithm is an iterative method for maximizing concave functions. Given a concave function $f(x)$, Kelley's constructs an outer-approximation of the function at the minimum by a set of first-order Taylor series approximations (called cuts) constructed at a set of points $k = 1,\ldots,K$:

\[\begin{aligned} +f^K = \max\limits_{\theta \in \mathbb{R}, x \in \mathbb{R}^N} \;\; & \theta\\ +& \theta \le f(x_k) + \nabla f(x_k)^\top (x - x_k),\quad k=1,\ldots,K\\ +& \theta \le M, +\end{aligned}\]

where $M$ is a sufficiently large number that is an upper bound for $f$ over the domain of $x$.

Kelley's cutting plane algorithm is a structured way of choosing points $x_k$ to visit, so that as more cuts are added:

\[\lim_{K \rightarrow \infty} f^K = \max\limits_{x \in \mathbb{R}^N} f(x)\]

However, before we introduce the algorithm, we need to introduce some bounds.

Bounds

By convexity, $f(x) \le f^K$ for all $x$. Thus, if $x^*$ is a maximizer of $f$, then at any point in time we can construct an upper bound for $f(x^*)$ by solving $f^K$.

Moreover, we can use the primal solutions $x_k^*$ returned by solving $f^k$ to evaluate $f(x_k^*)$ to generate a lower bound.

Therefore, $\max\limits_{k=1,\ldots,K} f(x_k^*) \le f(x^*) \le f^K$.

When the lower bound is sufficiently close to the upper bound, we can terminate the algorithm and declare that we have found an solution that is close to optimal.

Implementation

Here is pseudo-code fo the Kelley algorithm:

  1. Take as input a convex function $f(x)$ and a iteration limit $K_{max}$. Set $K = 1$, and initialize $f^{K-1}$. Set $lb = -\infty$ and $ub = \infty$.
  2. Solve $f^{K-1}$ to obtain a candidate solution $x_{K}$.
  3. Update $ub = f^{K-1}$ and $lb = \max\{lb, f(x_{K})\}$.
  4. Add a cut $\theta \ge f(x_{K}) + \nabla f\left(x_{K}\right)^\top (x - x_{K})$ to form $f^{K}$.
  5. Increment $K$.
  6. If $K > K_{max}$ or $|ub - lb| < \epsilon$, STOP, otherwise, go to step 2.

And here's a complete implementation:

function kelleys_cutting_plane(
+    # The function to be minimized.
+    f::Function,
+    # The gradient of `f`. By default, we use automatic differentiation to
+    # compute the gradient of f so the user doesn't have to!
+    ∇f::Function = x -> ForwardDiff.gradient(f, x);
+    # The number of arguments to `f`.
+    input_dimension::Int,
+    # An upper bound for the function `f` over its domain.
+    upper_bound::Float64,
+    # The number of iterations to run Kelley's algorithm for before stopping.
+    iteration_limit::Int,
+    # The absolute tolerance ϵ to use for convergence.
+    tolerance::Float64 = 1e-6,
+)
+    # Step (1):
+    K = 1
+    model = JuMP.Model(HiGHS.Optimizer)
+    JuMP.set_silent(model)
+    JuMP.@variable(model, θ <= upper_bound)
+    JuMP.@variable(model, x[1:input_dimension])
+    JuMP.@objective(model, Max, θ)
+    x_k = fill(NaN, input_dimension)
+    lower_bound, upper_bound = -Inf, Inf
+    while true
+        # Step (2):
+        JuMP.optimize!(model)
+        x_k .= JuMP.value.(x)
+        # Step (3):
+        upper_bound = JuMP.objective_value(model)
+        lower_bound = min(upper_bound, f(x_k))
+        println("K = $K : $(lower_bound) <= f(x*) <= $(upper_bound)")
+        # Step (4):
+        JuMP.@constraint(model, θ <= f(x_k) + ∇f(x_k)' * (x .- x_k))
+        # Step (5):
+        K = K + 1
+        # Step (6):
+        if K > iteration_limit
+            println("-- Termination status: iteration limit --")
+            break
+        elseif abs(upper_bound - lower_bound) < tolerance
+            println("-- Termination status: converged --")
+            break
+        end
+    end
+    println("Found solution: x_K = ", x_k)
+    return
+end
kelleys_cutting_plane (generic function with 2 methods)

Let's run our algorithm to see what happens:

kelleys_cutting_plane(;
+    input_dimension = 2,
+    upper_bound = 10.0,
+    iteration_limit = 20,
+) do x
+    return -(x[1] - 1)^2 + -(x[2] + 2)^2 + 1.0
+end
K = 1 : -4.0 <= f(x*) <= 10.0
+K = 2 : -2.25 <= f(x*) <= 10.0
+K = 3 : -5.3125 <= f(x*) <= 10.0
+K = 4 : 0.83984375 <= f(x*) <= 5.625
+K = 5 : -1.3438585069444455 <= f(x*) <= 1.9791666666666667
+K = 6 : 0.4532453748914933 <= f(x*) <= 1.7513020833333333
+K = 7 : -2.794810401068801 <= f(x*) <= 1.3444010416666663
+K = 8 : 0.19507712328139326 <= f(x*) <= 1.3179100884331594
+K = 9 : 0.9073862122310157 <= f(x*) <= 1.3022015061077878
+K = 10 : 0.7292616273896162 <= f(x*) <= 1.2835882279084943
+K = 11 : 0.9856775767620292 <= f(x*) <= 1.1542808575464905
+K = 12 : 0.9521967150117504 <= f(x*) <= 1.0538679846579115
+K = 13 : 0.9907765147617908 <= f(x*) <= 1.0341945633777465
+K = 14 : 0.990619313815891 <= f(x*) <= 1.0168012962055821
+K = 15 : 0.9997569528573889 <= f(x*) <= 1.010937796651451
+K = 16 : 0.9955736574995747 <= f(x*) <= 1.0023159378334365
+K = 17 : 0.9981907645826057 <= f(x*) <= 1.001070011161672
+K = 18 : 0.999293284088297 <= f(x*) <= 1.0010295293971427
+K = 19 : 0.9997619192401398 <= f(x*) <= 1.0005033714074143
+K = 20 : 0.9999234387181322 <= f(x*) <= 1.0003705497285347
+-- Termination status: iteration limit --
+Found solution: x_K = [1.0074056501552666, -1.9953397824465389]

L-Shaped theory

The L-Shaped method is a way of solving two-stage stochastic programs by Benders' decomposition. It takes the problem:

\[\begin{aligned} +V = \max\limits_{x,y_\omega} \;\; & -2x + \mathbb{E}_\omega[5y_\omega - 0.1(x - y_\omega)] \\ + & y_\omega \le x & \quad \forall \omega \in \Omega \\ + & 0 \le y_\omega \le d_\omega & \quad \forall \omega \in \Omega \\ + & x \ge 0. +\end{aligned}\]

and decomposes it into a second-stage problem:

\[\begin{aligned} +V_2(\bar{x}, d_\omega) = \max\limits_{x,x^\prime,y_\omega} \;\; & 5y_\omega - x^\prime \\ + & y_\omega \le x \\ + & x^\prime = x - y_\omega \\ + & 0 \le y_\omega \le d_\omega \\ + & x = \bar{x} & [\lambda] +\end{aligned}\]

and a first-stage problem:

\[\begin{aligned} +V = \max\limits_{x,\theta} \;\; & -2x + \theta \\ + & \theta \le \mathbb{E}_\omega[V_2(x, \omega)] \\ + & x \ge 0 +\end{aligned}\]

Then, because $V_2$ is convex with respect to $\bar{x}$ for fixed $\omega$, we can use a set of feasible points $\{x^k\}$ construct an outer approximation:

\[\begin{aligned} +V^K = \max\limits_{x,\theta} \;\; & -2x + \theta \\ + & \theta \le \mathbb{E}_\omega[V_2(x^k, \omega) + \nabla V_2(x^k, \omega)^\top(x - x^k)] & \quad k = 1,\ldots,K\\ + & x \ge 0 \\ + & \theta \le M +\end{aligned}\]

where $M$ is an upper bound on possible values of $V_2$ so that the problem has a bounded solution.

It is also useful to see that because $\bar{x}$ appears only on the right-hand side of a linear program, $\nabla V_2(x^k, \omega) = \lambda^k$.

Ignoring how we choose $x^k$ for now, we can construct a lower and upper bound on the optimal solution:

\[-2x^K + \mathbb{E}_\omega[V_2(x^K, \omega)] = \underbar{V} \le V \le \overline{V} = V^K\]

Thus, we need some way of cleverly choosing a sequence of $x^k$ so that the lower bound converges to the upper bound.

  1. Start with $K=1$
  2. Solve $V^{K-1}$ to get $x^K$
  3. Set $\overline{V} = V^k$
  4. Solve $V_2(x^K, \omega)$ for all $\omega$ and store the optimal objective value and dual solution $\lambda^K$
  5. Set $\underbar{V} = -2x^K + \mathbb{E}_\omega[V_2(x^k, \omega)]$
  6. If $\underbar{V} \approx \overline{V}$, STOP
  7. Add new constraint $\theta \le \mathbb{E}_\omega[V_2(x^K, \omega) +\lambda^K (x - x^K)]$
  8. Increment $K$, GOTO 2

The next section implements this algorithm in Julia.

L-Shaped implementation

Here's a function to compute the second-stage problem;

function solve_second_stage(x̅, d_ω)
+    model = Model(HiGHS.Optimizer)
+    set_silent(model)
+    @variable(model, x_in)
+    @variable(model, x_out >= 0)
+    fix(x_in, x̅)
+    @variable(model, 0 <= u_sell <= d_ω)
+    @constraint(model, x_out == x_in - u_sell)
+    @constraint(model, u_sell <= x_in)
+    @objective(model, Max, 5 * u_sell - 0.1 * x_out)
+    optimize!(model)
+    return (
+        V = objective_value(model),
+        λ = reduced_cost(x_in),
+        x = value(x_out),
+        u = value(u_sell),
+    )
+end
+
+solve_second_stage(200, 170)
(V = 847.0, λ = -0.1, x = 30.0, u = 170.0)

Here's the first-stage subproblem:

model = Model(HiGHS.Optimizer)
+set_silent(model)
+@variable(model, x_in == 0)
+@variable(model, x_out >= 0)
+@variable(model, u_make >= 0)
+@constraint(model, x_out == x_in + u_make)
+M = 5 * maximum(d)
+@variable(model, θ <= M)
+@objective(model, Max, -2 * u_make + θ)

\[ -2 u\_make + θ \]

Importantly, to ensure we have a bounded solution, we need to add an upper bound to the variable θ.

kIterationLimit = 100
+for k in 1:kIterationLimit
+    println("Solving iteration k = $k")
+    # Step 2
+    optimize!(model)
+    xᵏ = value(x_out)
+    println("  xᵏ = $xᵏ")
+    # Step 3
+    ub = objective_value(model)
+    println("  V̅ = $ub")
+    # Step 4
+    ret = [solve_second_stage(xᵏ, d[ω]) for ω in Ω]
+    # Step 5
+    lb = value(-2 * u_make) + sum(p * r.V for (p, r) in zip(P, ret))
+    println("  V̲ = $lb")
+    # Step 6
+    if ub - lb < 1e-6
+        println("Terminating with near-optimal solution")
+        break
+    end
+    # Step 7
+    c = @constraint(
+        model,
+        θ <= sum(p * (r.V + r.λ * (x_out - xᵏ)) for (p, r) in zip(P, ret)),
+    )
+    println("  Added cut: $c")
+end
Solving iteration k = 1
+  xᵏ = -0.0
+  V̅ = 1214.2515283970054
+  V̲ = 0.0
+  Added cut: -4.99999999999999 x_out + θ ≤ 0
+Solving iteration k = 2
+  xᵏ = 242.85030567940154
+  V̅ = 728.5509170382022
+  V̲ = 511.68221829384134
+  Added cut: 0.10000000000000007 x_out + θ ≤ 1021.6678602205853
+Solving iteration k = 3
+  xᵏ = 200.32703141580143
+  V̅ = 600.9810942474023
+  V̲ = 565.1096942735571
+  Added cut: -2.551999999999999 x_out + θ ≤ 454.5291729320332
+Solving iteration k = 4
+  xᵏ = 213.85320033504993
+  V̅ = 572.5761395169804
+  V̲ = 561.1215888832664
+  Added cut: -0.9200000000000003 x_out + θ ≤ 792.0830452451207
+Solving iteration k = 5
+  xᵏ = 206.83448058399986
+  V̅ = 568.7018062144009
+  V̲ = 565.747515882488
+  Added cut: -1.7360000000000009 x_out + θ ≤ 620.3518187566635
+Solving iteration k = 6
+  xᵏ = 203.2140267458709
+  V̅ = 566.7033156957539
+  V̲ = 566.0115663489057
+  Added cut: -2.093000000000001 x_out + θ ≤ 547.1126618615413
+Solving iteration k = 7
+  xᵏ = 205.15169998633698
+  V̅ = 566.1917699602707
+  V̲ = 566.0037426869833
+  Added cut: -1.940000000000001 x_out + θ ≤ 578.3128446861654
+Solving iteration k = 8
+  xᵏ = 203.92276355963565
+  V̅ = 566.0774788725876
+  V̲ = 566.0356930816718
+  Added cut: -1.9910000000000012 x_out + θ ≤ 567.8709979537094
+Solving iteration k = 9
+  xᵏ = 203.5130989428207
+  V̅ = 566.0393800632238
+  V̲ = 566.0255991703993
+  Added cut: -2.042000000000001 x_out + θ ≤ 557.4780490147989
+Solving iteration k = 10
+  xᵏ = 203.78331252764602
+  V̅ = 566.0369481409609
+  V̲ = 566.0369481409607
+Terminating with near-optimal solution

To get the first-stage solution, we do:

optimize!(model)
+xᵏ = value(x_out)
203.78331252764602

To compute a second-stage solution, we do:

solve_second_stage(xᵏ, 170.0)
(V = 846.6216687472354, λ = -0.1, x = 33.78331252764602, u = 170.0)

Policy Graph

Now let's see how we can formulate and train a policy for the two-stage newsvendor problem using SDDP.jl. Under the hood, SDDP.jl implements the exact algorithm that we just wrote by hand.

model = SDDP.LinearPolicyGraph(;
+    stages = 2,
+    sense = :Max,
+    upper_bound = 5 * maximum(d),  # The `M` in θ <= M
+    optimizer = HiGHS.Optimizer,
+) do subproblem::JuMP.Model, stage::Int
+    @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)
+    if stage == 1
+        @variable(subproblem, u_make >= 0)
+        @constraint(subproblem, x.out == x.in + u_make)
+        @stageobjective(subproblem, -2 * u_make)
+    else
+        @variable(subproblem, u_sell >= 0)
+        @constraint(subproblem, u_sell <= x.in)
+        @constraint(subproblem, x.out == x.in - u_sell)
+        SDDP.parameterize(subproblem, d, P) do ω
+            set_upper_bound(u_sell, ω)
+            return
+        end
+        @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out)
+    end
+    return
+end
+
+SDDP.train(model; log_every_iteration = true)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 1
+  scenarios       : 1.00000e+02
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [4, 4]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  AffExpr in MOI.LessThan{Float64}        : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [2, 3]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e-01, 5e+00]
+  bounds range     [2e+02, 1e+03]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   0.000000e+00  7.285509e+02  5.853176e-03       103   1
+         2   5.159109e+02  6.009811e+02  2.118611e-02       406   1
+         3   4.777421e+02  5.725761e+02  2.545404e-02       509   1
+         4   6.415596e+02  5.687018e+02  2.974415e-02       612   1
+         5   6.205034e+02  5.667033e+02  3.400421e-02       715   1
+         6   6.088708e+02  5.661918e+02  3.829598e-02       818   1
+         7   6.154551e+02  5.660775e+02  4.261398e-02       921   1
+         8   4.542303e+02  5.660394e+02  4.687405e-02      1024   1
+         9   5.518068e+02  5.660369e+02  5.112410e-02      1127   1
+        10   5.804826e+02  5.660369e+02  5.534601e-02      1230   1
+        11   5.439791e+02  5.660369e+02  5.985403e-02      1333   1
+        12   6.076753e+02  5.660369e+02  6.435800e-02      1436   1
+        13   4.545232e+02  5.660369e+02  6.880498e-02      1539   1
+        14   6.113499e+02  5.660369e+02  7.327199e-02      1642   1
+        15   5.063055e+02  5.660369e+02  7.771802e-02      1745   1
+        16   6.113499e+02  5.660369e+02  8.227921e-02      1848   1
+        17   4.822513e+02  5.660369e+02  8.678007e-02      1951   1
+        18   3.938110e+02  5.660369e+02  9.124207e-02      2054   1
+        19   4.881845e+02  5.660369e+02  9.571409e-02      2157   1
+        20   5.722176e+02  5.660369e+02  1.002121e-01      2260   1
+        21   5.661409e+02  5.660369e+02  1.183491e-01      2563   1
+        22   5.313244e+02  5.660369e+02  1.229300e-01      2666   1
+        23   6.113499e+02  5.660369e+02  1.275401e-01      2769   1
+        24   4.107931e+02  5.660369e+02  1.321032e-01      2872   1
+        25   6.113499e+02  5.660369e+02  1.366980e-01      2975   1
+        26   4.927614e+02  5.660369e+02  1.413081e-01      3078   1
+        27   4.378365e+02  5.660369e+02  1.459022e-01      3181   1
+        28   6.113499e+02  5.660369e+02  1.505282e-01      3284   1
+        29   4.704839e+02  5.660369e+02  1.550400e-01      3387   1
+        30   5.685328e+02  5.660369e+02  1.596131e-01      3490   1
+        31   4.542352e+02  5.660369e+02  1.641421e-01      3593   1
+        32   5.304119e+02  5.660369e+02  1.686690e-01      3696   1
+        33   5.027909e+02  5.660369e+02  1.731472e-01      3799   1
+        34   4.927614e+02  5.660369e+02  1.776941e-01      3902   1
+        35   6.113499e+02  5.660369e+02  1.822882e-01      4005   1
+        36   6.113499e+02  5.660369e+02  1.868992e-01      4108   1
+        37   4.525856e+02  5.660369e+02  1.914592e-01      4211   1
+        38   6.113499e+02  5.660369e+02  1.960070e-01      4314   1
+        39   5.063055e+02  5.660369e+02  2.006261e-01      4417   1
+        40   6.113499e+02  5.660369e+02  2.052431e-01      4520   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 2.052431e-01
+total solves   : 4520
+best bound     :  5.660369e+02
+simulation ci  :  5.258656e+02 ± 3.392366e+01
+numeric issues : 0
+-------------------------------------------------------------------

One way to query the optimal policy is with SDDP.DecisionRule:

first_stage_rule = SDDP.DecisionRule(model; node = 1)
A decision rule for node 1
solution_1 = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0))
(stage_objective = -407.56662505535576, outgoing_state = Dict(:x => 203.78331252767788), controls = Dict{Any, Any}())

Here's the second stage:

second_stage_rule = SDDP.DecisionRule(model; node = 2)
+solution = SDDP.evaluate(
+    second_stage_rule;
+    incoming_state = Dict(:x => solution_1.outgoing_state[:x]),
+    noise = 170.0,  # A value of d[ω], can be out-of-sample.
+    controls_to_record = [:u_sell],
+)
(stage_objective = 846.6216687472322, outgoing_state = Dict(:x => 33.78331252767788), controls = Dict(:u_sell => 170.0))

Simulation

Querying the decision rules is tedious. It's often more useful to simulate the policy:

simulations = SDDP.simulate(
+    model,
+    10,  #= number of replications =#
+    [:x, :u_sell, :u_make];  #= variables to record =#
+    skip_undefined_variables = true,
+);

simulations is a vector with 10 elements

length(simulations)
10

and each element is a vector with two elements (one for each stage)

length(simulations[1])
2

The first stage contains:

simulations[1][1]
Dict{Symbol, Any} with 9 entries:
+  :u_make          => 203.783
+  :bellman_term    => 973.604
+  :noise_term      => nothing
+  :node_index      => 1
+  :stage_objective => -407.567
+  :objective_state => nothing
+  :u_sell          => NaN
+  :belief          => Dict(1=>1.0)
+  :x               => State{Float64}(0.0, 203.783)

The second stage contains:

simulations[1][2]
Dict{Symbol, Any} with 9 entries:
+  :u_make          => NaN
+  :bellman_term    => 0.0
+  :noise_term      => 205.507
+  :node_index      => 2
+  :stage_objective => 1018.92
+  :objective_state => nothing
+  :u_sell          => 203.783
+  :belief          => Dict(2=>1.0)
+  :x               => State{Float64}(203.783, 0.0)

We can compute aggregated statistics across the simulations:

objectives = map(simulations) do simulation
+    return sum(data[:stage_objective] for data in simulation)
+end
+μ, t = SDDP.confidence_interval(objectives)
+println("Simulation ci : $μ ± $t")
Simulation ci : 571.2416946764831 ± 40.81682134226482

Risk aversion revisited

SDDP.jl contains a number of risk measures. One example is:

0.5 * SDDP.Expectation() + 0.5 * SDDP.WorstCase()
A convex combination of 0.5 * SDDP.Expectation() + 0.5 * SDDP.WorstCase()

You can construct a risk-averse policy by passing a risk measure to the risk_measure keyword argument of SDDP.train.

We can explore how the optimal decision changes with risk by creating a function:

function solve_newsvendor(risk_measure::SDDP.AbstractRiskMeasure)
+    model = SDDP.LinearPolicyGraph(;
+        stages = 2,
+        sense = :Max,
+        upper_bound = 5 * maximum(d),
+        optimizer = HiGHS.Optimizer,
+    ) do subproblem, node
+        @variable(subproblem, x >= 0, SDDP.State, initial_value = 0)
+        if node == 1
+            @stageobjective(subproblem, -2 * x.out)
+        else
+            @variable(subproblem, u_sell >= 0)
+            @constraint(subproblem, u_sell <= x.in)
+            @constraint(subproblem, x.out == x.in - u_sell)
+            SDDP.parameterize(subproblem, d, P) do ω
+                set_upper_bound(u_sell, ω)
+                return
+            end
+            @stageobjective(subproblem, 5 * u_sell - 0.1 * x.out)
+        end
+        return
+    end
+    SDDP.train(model; risk_measure = risk_measure, print_level = 0)
+    first_stage_rule = SDDP.DecisionRule(model; node = 1)
+    solution = SDDP.evaluate(first_stage_rule; incoming_state = Dict(:x => 0.0))
+    return solution.outgoing_state[:x]
+end
solve_newsvendor (generic function with 1 method)

Now we can see how many units a decision maker would order using CVaR:

solve_newsvendor(SDDP.CVaR(0.4))
187.9131018386334

as well as a decision-maker who cares only about the worst-case outcome:

solve_newsvendor(SDDP.WorstCase())
161.12861521172456

In general, the decision-maker will be somewhere between the two extremes. The SDDP.Entropic risk measure is a risk measure that has a single parameter that lets us explore the space of policies between the two extremes. When the parameter is small, the measure acts like SDDP.Expectation, and when it is large, it acts like SDDP.WorstCase.

Here is what we get if we solve our problem multiple times for different values of the risk aversion parameter $\gamma$:

Γ = [10^i for i in -4:0.5:1]
+buy = [solve_newsvendor(SDDP.Entropic(γ)) for γ in Γ]
+Plots.plot(
+    Γ,
+    buy;
+    xaxis = :log,
+    xlabel = "Risk aversion parameter γ",
+    ylabel = "Number of pies to make",
+    legend = false,
+)
Example block output

Things to try

There are a number of things you can try next:

  • Experiment with different buy and sales prices
  • Experiment with different distributions of demand
  • Explore how the optimal policy changes if you use a different risk measure
  • What happens if you can only buy and sell integer numbers of newspapers? Try this by adding Int to the variable definitions: @variable(subproblem, buy >= 0, Int)
  • What happens if you use a different upper bound? Try an invalid one like -100, and a very large one like 1e12.
diff --git a/previews/PR826/tutorial/example_reservoir.ipynb b/previews/PR826/tutorial/example_reservoir.ipynb new file mode 100644 index 0000000000..fd2f67350e --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir.ipynb @@ -0,0 +1,1020 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Example: deterministic to stochastic" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The purpose of this tutorial is to explain how we can go from a deterministic\n", + "time-staged optimal control model in JuMP to a multistage stochastic\n", + "optimization model in `SDDP.jl`. As a motivating problem, we consider the\n", + "hydro-thermal problem with a single reservoir." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Packages" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial requires the following packages:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using JuMP\n", + "using SDDP\n", + "import CSV\n", + "import DataFrames\n", + "import HiGHS\n", + "import Plots" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Data" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, we need some data for the problem. For this tutorial, we'll write CSV\n", + "files to a temporary directory from Julia. If you have an existing file, you\n", + "could change the filename to point to that instead." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "dir = mktempdir()\n", + "filename = joinpath(dir, \"example_reservoir.csv\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Here is the data" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "csv_data = \"\"\"\n", + "week,inflow,demand,cost\n", + "1,3,7,10.2\\n2,2,7.1,10.4\\n3,3,7.2,10.6\\n4,2,7.3,10.9\\n5,3,7.4,11.2\\n\n", + "6,2,7.6,11.5\\n7,3,7.8,11.9\\n8,2,8.1,12.3\\n9,3,8.3,12.7\\n10,2,8.6,13.1\\n\n", + "11,3,8.9,13.6\\n12,2,9.2,14\\n13,3,9.5,14.5\\n14,2,9.8,14.9\\n15,3,10.1,15.3\\n\n", + "16,2,10.4,15.8\\n17,3,10.7,16.2\\n18,2,10.9,16.6\\n19,3,11.2,17\\n20,3,11.4,17.4\\n\n", + "21,3,11.6,17.7\\n22,2,11.7,18\\n23,3,11.8,18.3\\n24,2,11.9,18.5\\n25,3,12,18.7\\n\n", + "26,2,12,18.9\\n27,3,12,19\\n28,2,11.9,19.1\\n29,3,11.8,19.2\\n30,2,11.7,19.2\\n\n", + "31,3,11.6,19.2\\n32,2,11.4,19.2\\n33,3,11.2,19.1\\n34,2,10.9,19\\n35,3,10.7,18.9\\n\n", + "36,2,10.4,18.8\\n37,3,10.1,18.6\\n38,2,9.8,18.5\\n39,3,9.5,18.4\\n40,3,9.2,18.2\\n\n", + "41,2,8.9,18.1\\n42,3,8.6,17.9\\n43,2,8.3,17.8\\n44,3,8.1,17.7\\n45,2,7.8,17.6\\n\n", + "46,3,7.6,17.5\\n47,2,7.4,17.5\\n48,3,7.3,17.5\\n49,2,7.2,17.5\\n50,3,7.1,17.6\\n\n", + "51,3,7,17.7\\n52,3,7,17.8\\n\n", + "\"\"\"\n", + "write(filename, csv_data);" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "And here we read it into a DataFrame:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "data = CSV.read(filename, DataFrames.DataFrame)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "It's easier to visualize the data if we plot it:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Plots.plot(\n", + " Plots.plot(data[!, :inflow]; ylabel = \"Inflow\"),\n", + " Plots.plot(data[!, :demand]; ylabel = \"Demand\"),\n", + " Plots.plot(data[!, :cost]; ylabel = \"Cost\", xlabel = \"Week\");\n", + " layout = (3, 1),\n", + " legend = false,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The number of weeks will be useful later:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "T = size(data, 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Deterministic JuMP model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To start, we construct a deterministic model in pure JuMP." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Create a JuMP model, using `HiGHS` as the optimizer:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = Model(HiGHS.Optimizer)\n", + "set_silent(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`x_storage[t]`: the amount of water in the reservoir at the start of stage `t`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "reservoir_max = 320.0\n", + "@variable(model, 0 <= x_storage[1:T+1] <= reservoir_max)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We need an initial condition for `x_storage[1]`. Fix it to 300 units:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "reservoir_initial = 300\n", + "fix(x_storage[1], reservoir_initial; force = true)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`u_flow[t]`: the amount of water to flow through the turbine in stage `t`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "flow_max = 12\n", + "@variable(model, 0 <= u_flow[1:T] <= flow_max)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`u_spill[t]`: the amount of water to spill from the reservoir in stage `t`,\n", + "bypassing the turbine:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@variable(model, 0 <= u_spill[1:T])" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`u_thermal[t]`: the amount of thermal generation in stage `t`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@variable(model, 0 <= u_thermal[1:T])" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`ω_inflow[t]`: the amount of inflow to the reservoir in stage `t`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@variable(model, ω_inflow[1:T])" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "For this model, our inflow is fixed, so we fix it to the data we have:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for t in 1:T\n", + " fix(ω_inflow[t], data[t, :inflow])\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The water balance constraint says that the water in the reservoir at the start\n", + "of stage `t+1` is the water in the reservoir at the start of stage `t`, less\n", + "the amount flowed through the turbine, `u_flow[t]`, less the amount spilled,\n", + "`u_spill[t]`, plus the amount of inflow, `ω_inflow[t]`, into the reservoir:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@constraint(\n", + " model,\n", + " [t in 1:T],\n", + " x_storage[t+1] == x_storage[t] - u_flow[t] - u_spill[t] + ω_inflow[t],\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We also need a `supply = demand` constraint. In practice, the units of this\n", + "would be in MWh, and there would be a conversion factor between the amount of\n", + "water flowing through the turbine and the power output. To simplify, we assume\n", + "that power and water have the same units, so that one \"unit\" of demand is\n", + "equal to one \"unit\" of the reservoir `x_storage[t]`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@constraint(model, [t in 1:T], u_flow[t] + u_thermal[t] == data[t, :demand])" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Our objective is to minimize the cost of thermal generation:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "@objective(model, Min, sum(data[t, :cost] * u_thermal[t] for t in 1:T))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's optimize and check the solution" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "optimize!(model)\n", + "solution_summary(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The total cost is:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "objective_value(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Here's a plot of demand and generation:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Plots.plot(data[!, :demand]; label = \"Demand\", xlabel = \"Week\")\n", + "Plots.plot!(value.(u_thermal); label = \"Thermal\")\n", + "Plots.plot!(value.(u_flow); label = \"Hydro\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "And here's the storage over time:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Plots.plot(value.(x_storage); label = \"Storage\", xlabel = \"Week\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Deterministic SDDP model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For the next step, we show how to decompose our JuMP model into SDDP.jl. It\n", + "should obtain the same solution." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = T,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(\n", + " sp,\n", + " 0 <= x_storage <= reservoir_max,\n", + " SDDP.State,\n", + " initial_value = reservoir_initial,\n", + " )\n", + " @variable(sp, 0 <= u_flow <= flow_max)\n", + " @variable(sp, 0 <= u_thermal)\n", + " @variable(sp, 0 <= u_spill)\n", + " @variable(sp, ω_inflow)\n", + " fix(ω_inflow, data[t, :inflow])\n", + " @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)\n", + " @constraint(sp, u_flow + u_thermal == data[t, :demand])\n", + " @stageobjective(sp, data[t, :cost] * u_thermal)\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Can you see how the JuMP model maps to this syntax? We have created a\n", + "`SDDP.LinearPolicyGraph` with `T` stages, we're minimizing, and we're\n", + "using `HiGHS.Optimizer` as the optimizer." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A few bits might be non-obvious:\n", + "\n", + "* We need to provide a lower bound for the objective function. Since our costs\n", + " are always positive, a valid lower bound for the total cost is `0.0`.\n", + "* We define `x_storage` as a state variable using `SDDP.State`. A state\n", + " variable is any variable that flows through time, and for which we need to\n", + " know the value of it in stage `t-1` to compute the best action in stage `t`.\n", + " The state variable `x_storage` is actually two decision variables,\n", + " `x_storage.in` and `x_storage.out`, which represent `x_storage[t]` and\n", + " `x_storage[t+1]` respectively.\n", + "* We need to use `@stageobjective` instead of `@objective`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Instead of calling `JuMP.optimize!`, SDDP.jl uses a `train` method. With our\n", + "machine learning hat on, you can think of SDDP.jl as training a function for\n", + "each stage that accepts the current reservoir state as input and returns the\n", + "optimal actions as output. It is also an iterative algorithm, so we need to\n", + "specify when it should terminate:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; iteration_limit = 10)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "As a quick sanity check, did we get the same cost as our JuMP model?" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.calculate_bound(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "That's good. Next, to check the value of the decision variables. This isn't as\n", + "straight forward as our JuMP model. Instead, we need to _simulate_ the policy,\n", + "and then extract the values of the decision variables from the results of the\n", + "simulation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Since our model is deterministic, we need only 1 replication of the\n", + "simulation, and we want to record the values of the `x_storage`, `u_flow`, and\n", + "`u_thermal` variables:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model,\n", + " 1, # Number of replications\n", + " [:x_storage, :u_flow, :u_thermal],\n", + ");" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The `simulations` vector is too big to show. But it contains one element for\n", + "each replication, and each replication contains one dictionary for each stage." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For example, the data corresponding to the tenth stage in the first\n", + "replication is:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations[1][10]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's grab the trace of the `u_thermal` and `u_flow` variables in the first\n", + "replication, and then plot them:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "r_sim = [sim[:u_thermal] for sim in simulations[1]]\n", + "u_sim = [sim[:u_flow] for sim in simulations[1]]\n", + "\n", + "Plots.plot(data[!, :demand]; label = \"Demand\", xlabel = \"Week\")\n", + "Plots.plot!(r_sim; label = \"Thermal\")\n", + "Plots.plot!(u_sim; label = \"Hydro\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Perfect. That's the same as we got before." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now let's look at `x_storage`. This is a little more complicated, because we\n", + "need to grab the outgoing value of the state variable in each stage:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "x_sim = [sim[:x_storage].out for sim in simulations[1]]\n", + "\n", + "Plots.plot(x_sim; label = \"Storage\", xlabel = \"Week\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Stochastic SDDP model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we add some randomness to our model. In each stage, we assume that the\n", + "inflow could be: 2 units lower, with 30% probability; the same as before, with\n", + "40% probability; or 5 units higher, with 30% probability." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = T,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(\n", + " sp,\n", + " 0 <= x_storage <= reservoir_max,\n", + " SDDP.State,\n", + " initial_value = reservoir_initial,\n", + " )\n", + " @variable(sp, 0 <= u_flow <= flow_max)\n", + " @variable(sp, 0 <= u_thermal)\n", + " @variable(sp, 0 <= u_spill)\n", + " @variable(sp, ω_inflow)\n", + " # <--- This bit is new\n", + " Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3]\n", + " SDDP.parameterize(sp, Ω, P) do ω\n", + " fix(ω_inflow, data[t, :inflow] + ω)\n", + " return\n", + " end\n", + " # --->\n", + " @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)\n", + " @constraint(sp, u_flow + u_thermal == data[t, :demand])\n", + " @stageobjective(sp, data[t, :cost] * u_thermal)\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Can you see the differences?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Let's train our new model. We need more iterations because of the\n", + "stochasticity:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; iteration_limit = 100)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Now simulate the policy. This time we do 100 replications because the policy\n", + "is now stochastic instead of deterministic:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations =\n", + " SDDP.simulate(model, 100, [:x_storage, :u_flow, :u_thermal, :ω_inflow]);" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "And let's plot the use of thermal generation in each replication:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plot = Plots.plot(data[!, :demand]; label = \"Demand\", xlabel = \"Week\")\n", + "for simulation in simulations\n", + " Plots.plot!(plot, [sim[:u_thermal] for sim in simulation]; label = \"\")\n", + "end\n", + "plot" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Viewing an interpreting static plots like this is difficult, particularly as\n", + "the number of simulations grows. SDDP.jl includes an interactive\n", + "`SpaghettiPlot` that makes things easier:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plot = SDDP.SpaghettiPlot(simulations)\n", + "SDDP.add_spaghetti(plot; title = \"Storage\") do sim\n", + " return sim[:x_storage].out\n", + "end\n", + "SDDP.add_spaghetti(plot; title = \"Hydro\") do sim\n", + " return sim[:u_flow]\n", + "end\n", + "SDDP.add_spaghetti(plot; title = \"Inflow\") do sim\n", + " return sim[:ω_inflow]\n", + "end\n", + "SDDP.plot(\n", + " plot,\n", + " \"spaghetti_plot.html\";\n", + " # We need this to build the documentation. Set to true if running locally.\n", + " open = false,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> If you have trouble viewing the plot, you can\n", + "> [open it in a new window](spaghetti_plot.html)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Cyclic graphs" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One major problem with our model is that the reservoir is empty at the end of\n", + "the time horizon. This is because our model does not consider the cost of\n", + "future years after the `T` weeks." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can fix this using a cyclic policy graph. One way to construct a graph is\n", + "with the `SDDP.UnicyclicGraph` constructor:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.UnicyclicGraph(0.7; num_nodes = 2)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This graph has two nodes, and a loop from node 2 back to node 1 with\n", + "probability 0.7." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can construct a cyclic policy graph as follows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "graph = SDDP.UnicyclicGraph(0.95; num_nodes = T)\n", + "model = SDDP.PolicyGraph(\n", + " graph;\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(\n", + " sp,\n", + " 0 <= x_storage <= reservoir_max,\n", + " SDDP.State,\n", + " initial_value = reservoir_initial,\n", + " )\n", + " @variable(sp, 0 <= u_flow <= flow_max)\n", + " @variable(sp, 0 <= u_thermal)\n", + " @variable(sp, 0 <= u_spill)\n", + " @variable(sp, ω_inflow)\n", + " Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3]\n", + " SDDP.parameterize(sp, Ω, P) do ω\n", + " fix(ω_inflow, data[t, :inflow] + ω)\n", + " return\n", + " end\n", + " @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)\n", + " @constraint(sp, u_flow + u_thermal == data[t, :demand])\n", + " @stageobjective(sp, data[t, :cost] * u_thermal)\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Notice how the only thing that has changed is our graph; the subproblems\n", + "remain the same." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Let's train a policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; iteration_limit = 100)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "When we simulate now, each trajectory will be a different length, because\n", + "each cycle has a 95% probability of continuing and a 5% probability of\n", + "stopping." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(model, 3);\n", + "length.(simulations)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can simulate a fixed number of cycles by passing a `sampling_scheme`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model,\n", + " 100,\n", + " [:x_storage, :u_flow];\n", + " sampling_scheme = SDDP.InSampleMonteCarlo(;\n", + " max_depth = 5 * T,\n", + " terminate_on_dummy_leaf = false,\n", + " ),\n", + ");\n", + "length.(simulations)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Let's visualize the policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Plots.plot(\n", + " SDDP.publication_plot(simulations; ylabel = \"Storage\") do sim\n", + " return sim[:x_storage].out\n", + " end,\n", + " SDDP.publication_plot(simulations; ylabel = \"Hydro\") do sim\n", + " return sim[:u_flow]\n", + " end;\n", + " layout = (2, 1),\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Next steps" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Our model is very basic. There are many aspects that we could improve:\n", + "\n", + "* Can you add a second reservoir to make a river chain?\n", + "\n", + "* Can you modify the problem and data to use proper units, including a\n", + " conversion between the volume of water flowing through the turbine and the\n", + " electrical power output?" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/example_reservoir.jl b/previews/PR826/tutorial/example_reservoir.jl new file mode 100644 index 0000000000..6e1b8425c4 --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir.jl @@ -0,0 +1,429 @@ + +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Example: deterministic to stochastic + +# The purpose of this tutorial is to explain how we can go from a deterministic +# time-staged optimal control model in JuMP to a multistage stochastic +# optimization model in `SDDP.jl`. As a motivating problem, we consider the +# hydro-thermal problem with a single reservoir. + +# ## Packages + +# This tutorial requires the following packages: + +using JuMP +using SDDP +import CSV +import DataFrames +import HiGHS +import Plots + +# ## Data + +# First, we need some data for the problem. For this tutorial, we'll write CSV +# files to a temporary directory from Julia. If you have an existing file, you +# could change the filename to point to that instead. + +dir = mktempdir() +filename = joinpath(dir, "example_reservoir.csv") + +# Here is the data + +csv_data = """ +week,inflow,demand,cost +1,3,7,10.2\n2,2,7.1,10.4\n3,3,7.2,10.6\n4,2,7.3,10.9\n5,3,7.4,11.2\n +6,2,7.6,11.5\n7,3,7.8,11.9\n8,2,8.1,12.3\n9,3,8.3,12.7\n10,2,8.6,13.1\n +11,3,8.9,13.6\n12,2,9.2,14\n13,3,9.5,14.5\n14,2,9.8,14.9\n15,3,10.1,15.3\n +16,2,10.4,15.8\n17,3,10.7,16.2\n18,2,10.9,16.6\n19,3,11.2,17\n20,3,11.4,17.4\n +21,3,11.6,17.7\n22,2,11.7,18\n23,3,11.8,18.3\n24,2,11.9,18.5\n25,3,12,18.7\n +26,2,12,18.9\n27,3,12,19\n28,2,11.9,19.1\n29,3,11.8,19.2\n30,2,11.7,19.2\n +31,3,11.6,19.2\n32,2,11.4,19.2\n33,3,11.2,19.1\n34,2,10.9,19\n35,3,10.7,18.9\n +36,2,10.4,18.8\n37,3,10.1,18.6\n38,2,9.8,18.5\n39,3,9.5,18.4\n40,3,9.2,18.2\n +41,2,8.9,18.1\n42,3,8.6,17.9\n43,2,8.3,17.8\n44,3,8.1,17.7\n45,2,7.8,17.6\n +46,3,7.6,17.5\n47,2,7.4,17.5\n48,3,7.3,17.5\n49,2,7.2,17.5\n50,3,7.1,17.6\n +51,3,7,17.7\n52,3,7,17.8\n +""" +write(filename, csv_data); + +# And here we read it into a DataFrame: + +data = CSV.read(filename, DataFrames.DataFrame) + +# It's easier to visualize the data if we plot it: + +Plots.plot( + Plots.plot(data[!, :inflow]; ylabel = "Inflow"), + Plots.plot(data[!, :demand]; ylabel = "Demand"), + Plots.plot(data[!, :cost]; ylabel = "Cost", xlabel = "Week"); + layout = (3, 1), + legend = false, +) + +# The number of weeks will be useful later: + +T = size(data, 1) + +# ## Deterministic JuMP model + +# To start, we construct a deterministic model in pure JuMP. + +# Create a JuMP model, using `HiGHS` as the optimizer: + +model = Model(HiGHS.Optimizer) +set_silent(model) + +# `x_storage[t]`: the amount of water in the reservoir at the start of stage `t`: + +reservoir_max = 320.0 +@variable(model, 0 <= x_storage[1:T+1] <= reservoir_max) + +# We need an initial condition for `x_storage[1]`. Fix it to 300 units: + +reservoir_initial = 300 +fix(x_storage[1], reservoir_initial; force = true) + +# `u_flow[t]`: the amount of water to flow through the turbine in stage `t`: + +flow_max = 12 +@variable(model, 0 <= u_flow[1:T] <= flow_max) + +# `u_spill[t]`: the amount of water to spill from the reservoir in stage `t`, +# bypassing the turbine: + +@variable(model, 0 <= u_spill[1:T]) + +# `u_thermal[t]`: the amount of thermal generation in stage `t`: + +@variable(model, 0 <= u_thermal[1:T]) + +# `ω_inflow[t]`: the amount of inflow to the reservoir in stage `t`: + +@variable(model, ω_inflow[1:T]) + +# For this model, our inflow is fixed, so we fix it to the data we have: + +for t in 1:T + fix(ω_inflow[t], data[t, :inflow]) +end + +# The water balance constraint says that the water in the reservoir at the start +# of stage `t+1` is the water in the reservoir at the start of stage `t`, less +# the amount flowed through the turbine, `u_flow[t]`, less the amount spilled, +# `u_spill[t]`, plus the amount of inflow, `ω_inflow[t]`, into the reservoir: + +@constraint( + model, + [t in 1:T], + x_storage[t+1] == x_storage[t] - u_flow[t] - u_spill[t] + ω_inflow[t], +) + +# We also need a `supply = demand` constraint. In practice, the units of this +# would be in MWh, and there would be a conversion factor between the amount of +# water flowing through the turbine and the power output. To simplify, we assume +# that power and water have the same units, so that one "unit" of demand is +# equal to one "unit" of the reservoir `x_storage[t]`: + +@constraint(model, [t in 1:T], u_flow[t] + u_thermal[t] == data[t, :demand]) + +# Our objective is to minimize the cost of thermal generation: + +@objective(model, Min, sum(data[t, :cost] * u_thermal[t] for t in 1:T)) + +# Let's optimize and check the solution + +optimize!(model) +solution_summary(model) + +# The total cost is: + +objective_value(model) + +# Here's a plot of demand and generation: + +Plots.plot(data[!, :demand]; label = "Demand", xlabel = "Week") +Plots.plot!(value.(u_thermal); label = "Thermal") +Plots.plot!(value.(u_flow); label = "Hydro") + +# And here's the storage over time: + +Plots.plot(value.(x_storage); label = "Storage", xlabel = "Week") + +# ## Deterministic SDDP model + +# For the next step, we show how to decompose our JuMP model into SDDP.jl. It +# should obtain the same solution. + +model = SDDP.LinearPolicyGraph(; + stages = T, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable( + sp, + 0 <= x_storage <= reservoir_max, + SDDP.State, + initial_value = reservoir_initial, + ) + @variable(sp, 0 <= u_flow <= flow_max) + @variable(sp, 0 <= u_thermal) + @variable(sp, 0 <= u_spill) + @variable(sp, ω_inflow) + fix(ω_inflow, data[t, :inflow]) + @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow) + @constraint(sp, u_flow + u_thermal == data[t, :demand]) + @stageobjective(sp, data[t, :cost] * u_thermal) + return +end + +# Can you see how the JuMP model maps to this syntax? We have created a +# [`SDDP.LinearPolicyGraph`](@ref) with `T` stages, we're minimizing, and we're +# using `HiGHS.Optimizer` as the optimizer. + +# A few bits might be non-obvious: +# +# * We need to provide a lower bound for the objective function. Since our costs +# are always positive, a valid lower bound for the total cost is `0.0`. +# * We define `x_storage` as a state variable using `SDDP.State`. A state +# variable is any variable that flows through time, and for which we need to +# know the value of it in stage `t-1` to compute the best action in stage `t`. +# The state variable `x_storage` is actually two decision variables, +# `x_storage.in` and `x_storage.out`, which represent `x_storage[t]` and +# `x_storage[t+1]` respectively. +# * We need to use `@stageobjective` instead of `@objective`. + +# Instead of calling `JuMP.optimize!`, SDDP.jl uses a `train` method. With our +# machine learning hat on, you can think of SDDP.jl as training a function for +# each stage that accepts the current reservoir state as input and returns the +# optimal actions as output. It is also an iterative algorithm, so we need to +# specify when it should terminate: + +SDDP.train(model; iteration_limit = 10) + +# As a quick sanity check, did we get the same cost as our JuMP model? + +SDDP.calculate_bound(model) + +# That's good. Next, to check the value of the decision variables. This isn't as +# straight forward as our JuMP model. Instead, we need to _simulate_ the policy, +# and then extract the values of the decision variables from the results of the +# simulation. + +# Since our model is deterministic, we need only 1 replication of the +# simulation, and we want to record the values of the `x_storage`, `u_flow`, and +# `u_thermal` variables: + +simulations = SDDP.simulate( + model, + 1, # Number of replications + [:x_storage, :u_flow, :u_thermal], +); + +# The `simulations` vector is too big to show. But it contains one element for +# each replication, and each replication contains one dictionary for each stage. + +# For example, the data corresponding to the tenth stage in the first +# replication is: + +simulations[1][10] + +# Let's grab the trace of the `u_thermal` and `u_flow` variables in the first +# replication, and then plot them: + +r_sim = [sim[:u_thermal] for sim in simulations[1]] +u_sim = [sim[:u_flow] for sim in simulations[1]] + +Plots.plot(data[!, :demand]; label = "Demand", xlabel = "Week") +Plots.plot!(r_sim; label = "Thermal") +Plots.plot!(u_sim; label = "Hydro") + +# Perfect. That's the same as we got before. + +# Now let's look at `x_storage`. This is a little more complicated, because we +# need to grab the outgoing value of the state variable in each stage: + +x_sim = [sim[:x_storage].out for sim in simulations[1]] + +Plots.plot(x_sim; label = "Storage", xlabel = "Week") + +# ## Stochastic SDDP model + +# Now we add some randomness to our model. In each stage, we assume that the +# inflow could be: 2 units lower, with 30% probability; the same as before, with +# 40% probability; or 5 units higher, with 30% probability. + +model = SDDP.LinearPolicyGraph(; + stages = T, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable( + sp, + 0 <= x_storage <= reservoir_max, + SDDP.State, + initial_value = reservoir_initial, + ) + @variable(sp, 0 <= u_flow <= flow_max) + @variable(sp, 0 <= u_thermal) + @variable(sp, 0 <= u_spill) + @variable(sp, ω_inflow) + ## <--- This bit is new + Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3] + SDDP.parameterize(sp, Ω, P) do ω + fix(ω_inflow, data[t, :inflow] + ω) + return + end + ## ---> + @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow) + @constraint(sp, u_flow + u_thermal == data[t, :demand]) + @stageobjective(sp, data[t, :cost] * u_thermal) + return +end + +# Can you see the differences? + +# Let's train our new model. We need more iterations because of the +# stochasticity: + +SDDP.train(model; iteration_limit = 100) + +# Now simulate the policy. This time we do 100 replications because the policy +# is now stochastic instead of deterministic: + +simulations = + SDDP.simulate(model, 100, [:x_storage, :u_flow, :u_thermal, :ω_inflow]); + +# And let's plot the use of thermal generation in each replication: + +plot = Plots.plot(data[!, :demand]; label = "Demand", xlabel = "Week") +for simulation in simulations + Plots.plot!(plot, [sim[:u_thermal] for sim in simulation]; label = "") +end +plot + +# Viewing an interpreting static plots like this is difficult, particularly as +# the number of simulations grows. SDDP.jl includes an interactive +# `SpaghettiPlot` that makes things easier: + +plot = SDDP.SpaghettiPlot(simulations) +SDDP.add_spaghetti(plot; title = "Storage") do sim + return sim[:x_storage].out +end +SDDP.add_spaghetti(plot; title = "Hydro") do sim + return sim[:u_flow] +end +SDDP.add_spaghetti(plot; title = "Inflow") do sim + return sim[:ω_inflow] +end +SDDP.plot( + plot, + "spaghetti_plot.html"; + ## We need this to build the documentation. Set to true if running locally. + open = false, +) + +# ```@raw html +# +# ``` + +# !!! info +# If you have trouble viewing the plot, you can +# [open it in a new window](spaghetti_plot.html). + +# ## Cyclic graphs + +# One major problem with our model is that the reservoir is empty at the end of +# the time horizon. This is because our model does not consider the cost of +# future years after the `T` weeks. + +# We can fix this using a cyclic policy graph. One way to construct a graph is +# with the [`SDDP.UnicyclicGraph`](@ref) constructor: + +SDDP.UnicyclicGraph(0.7; num_nodes = 2) + +# This graph has two nodes, and a loop from node 2 back to node 1 with +# probability 0.7. + +# We can construct a cyclic policy graph as follows: + +graph = SDDP.UnicyclicGraph(0.95; num_nodes = T) +model = SDDP.PolicyGraph( + graph; + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable( + sp, + 0 <= x_storage <= reservoir_max, + SDDP.State, + initial_value = reservoir_initial, + ) + @variable(sp, 0 <= u_flow <= flow_max) + @variable(sp, 0 <= u_thermal) + @variable(sp, 0 <= u_spill) + @variable(sp, ω_inflow) + Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3] + SDDP.parameterize(sp, Ω, P) do ω + fix(ω_inflow, data[t, :inflow] + ω) + return + end + @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow) + @constraint(sp, u_flow + u_thermal == data[t, :demand]) + @stageobjective(sp, data[t, :cost] * u_thermal) + return +end + +# Notice how the only thing that has changed is our graph; the subproblems +# remain the same. + +# Let's train a policy: + +SDDP.train(model; iteration_limit = 100) + +# When we simulate now, each trajectory will be a different length, because +# each cycle has a 95% probability of continuing and a 5% probability of +# stopping. + +simulations = SDDP.simulate(model, 3); +length.(simulations) + +# We can simulate a fixed number of cycles by passing a `sampling_scheme`: + +simulations = SDDP.simulate( + model, + 100, + [:x_storage, :u_flow]; + sampling_scheme = SDDP.InSampleMonteCarlo(; + max_depth = 5 * T, + terminate_on_dummy_leaf = false, + ), +); +length.(simulations) + +# Let's visualize the policy: + +Plots.plot( + SDDP.publication_plot(simulations; ylabel = "Storage") do sim + return sim[:x_storage].out + end, + SDDP.publication_plot(simulations; ylabel = "Hydro") do sim + return sim[:u_flow] + end; + layout = (2, 1), +) + +# ## Next steps + +# Our model is very basic. There are many aspects that we could improve: +# +# * Can you add a second reservoir to make a river chain? +# +# * Can you modify the problem and data to use proper units, including a +# conversion between the volume of water flowing through the turbine and the +# electrical power output? diff --git a/previews/PR826/tutorial/example_reservoir/0ecd7287.svg b/previews/PR826/tutorial/example_reservoir/0ecd7287.svg new file mode 100644 index 0000000000..3cba5b8072 --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/0ecd7287.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/1b42dc01.svg b/previews/PR826/tutorial/example_reservoir/1b42dc01.svg new file mode 100644 index 0000000000..d83d674189 --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/1b42dc01.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/452ae0d0.svg b/previews/PR826/tutorial/example_reservoir/452ae0d0.svg new file mode 100644 index 0000000000..c654c307f1 --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/452ae0d0.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/51620718.svg b/previews/PR826/tutorial/example_reservoir/51620718.svg new file mode 100644 index 0000000000..a2af24c32f --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/51620718.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/570c16ca.svg b/previews/PR826/tutorial/example_reservoir/570c16ca.svg new file mode 100644 index 0000000000..77312c3b98 --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/570c16ca.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/8ee90425.svg b/previews/PR826/tutorial/example_reservoir/8ee90425.svg new file mode 100644 index 0000000000..2594611e68 --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/8ee90425.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/92ce3a4c.svg b/previews/PR826/tutorial/example_reservoir/92ce3a4c.svg new file mode 100644 index 0000000000..96223bae8e --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/92ce3a4c.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/example_reservoir/index.html b/previews/PR826/tutorial/example_reservoir/index.html new file mode 100644 index 0000000000..d0c139416c --- /dev/null +++ b/previews/PR826/tutorial/example_reservoir/index.html @@ -0,0 +1,504 @@ + +Example: deterministic to stochastic · SDDP.jl

Example: deterministic to stochastic

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

The purpose of this tutorial is to explain how we can go from a deterministic time-staged optimal control model in JuMP to a multistage stochastic optimization model in SDDP.jl. As a motivating problem, we consider the hydro-thermal problem with a single reservoir.

Packages

This tutorial requires the following packages:

using JuMP
+using SDDP
+import CSV
+import DataFrames
+import HiGHS
+import Plots

Data

First, we need some data for the problem. For this tutorial, we'll write CSV files to a temporary directory from Julia. If you have an existing file, you could change the filename to point to that instead.

dir = mktempdir()
+filename = joinpath(dir, "example_reservoir.csv")
"/tmp/jl_k4w77U/example_reservoir.csv"

Here is the data

csv_data = """
+week,inflow,demand,cost
+1,3,7,10.2\n2,2,7.1,10.4\n3,3,7.2,10.6\n4,2,7.3,10.9\n5,3,7.4,11.2\n
+6,2,7.6,11.5\n7,3,7.8,11.9\n8,2,8.1,12.3\n9,3,8.3,12.7\n10,2,8.6,13.1\n
+11,3,8.9,13.6\n12,2,9.2,14\n13,3,9.5,14.5\n14,2,9.8,14.9\n15,3,10.1,15.3\n
+16,2,10.4,15.8\n17,3,10.7,16.2\n18,2,10.9,16.6\n19,3,11.2,17\n20,3,11.4,17.4\n
+21,3,11.6,17.7\n22,2,11.7,18\n23,3,11.8,18.3\n24,2,11.9,18.5\n25,3,12,18.7\n
+26,2,12,18.9\n27,3,12,19\n28,2,11.9,19.1\n29,3,11.8,19.2\n30,2,11.7,19.2\n
+31,3,11.6,19.2\n32,2,11.4,19.2\n33,3,11.2,19.1\n34,2,10.9,19\n35,3,10.7,18.9\n
+36,2,10.4,18.8\n37,3,10.1,18.6\n38,2,9.8,18.5\n39,3,9.5,18.4\n40,3,9.2,18.2\n
+41,2,8.9,18.1\n42,3,8.6,17.9\n43,2,8.3,17.8\n44,3,8.1,17.7\n45,2,7.8,17.6\n
+46,3,7.6,17.5\n47,2,7.4,17.5\n48,3,7.3,17.5\n49,2,7.2,17.5\n50,3,7.1,17.6\n
+51,3,7,17.7\n52,3,7,17.8\n
+"""
+write(filename, csv_data);

And here we read it into a DataFrame:

data = CSV.read(filename, DataFrames.DataFrame)
52×4 DataFrame
Rowweekinflowdemandcost
Int64Int64Float64Float64
1137.010.2
2227.110.4
3337.210.6
4427.310.9
5537.411.2
6627.611.5
7737.811.9
8828.112.3
9938.312.7
101028.613.1
111138.913.6
121229.214.0
131339.514.5
141429.814.9
1515310.115.3
1616210.415.8
1717310.716.2
1818210.916.6
1919311.217.0
2020311.417.4
2121311.617.7
2222211.718.0
2323311.818.3
2424211.918.5
2525312.018.7
2626212.018.9
2727312.019.0
2828211.919.1
2929311.819.2
3030211.719.2
3131311.619.2
3232211.419.2
3333311.219.1
3434210.919.0
3535310.718.9
3636210.418.8
3737310.118.6
383829.818.5
393939.518.4
404039.218.2
414128.918.1
424238.617.9
434328.317.8
444438.117.7
454527.817.6
464637.617.5
474727.417.5
484837.317.5
494927.217.5
505037.117.6
515137.017.7
525237.017.8

It's easier to visualize the data if we plot it:

Plots.plot(
+    Plots.plot(data[!, :inflow]; ylabel = "Inflow"),
+    Plots.plot(data[!, :demand]; ylabel = "Demand"),
+    Plots.plot(data[!, :cost]; ylabel = "Cost", xlabel = "Week");
+    layout = (3, 1),
+    legend = false,
+)
Example block output

The number of weeks will be useful later:

T = size(data, 1)
52

Deterministic JuMP model

To start, we construct a deterministic model in pure JuMP.

Create a JuMP model, using HiGHS as the optimizer:

model = Model(HiGHS.Optimizer)
+set_silent(model)

x_storage[t]: the amount of water in the reservoir at the start of stage t:

reservoir_max = 320.0
+@variable(model, 0 <= x_storage[1:T+1] <= reservoir_max)
53-element Vector{VariableRef}:
+ x_storage[1]
+ x_storage[2]
+ x_storage[3]
+ x_storage[4]
+ x_storage[5]
+ x_storage[6]
+ x_storage[7]
+ x_storage[8]
+ x_storage[9]
+ x_storage[10]
+ ⋮
+ x_storage[45]
+ x_storage[46]
+ x_storage[47]
+ x_storage[48]
+ x_storage[49]
+ x_storage[50]
+ x_storage[51]
+ x_storage[52]
+ x_storage[53]

We need an initial condition for x_storage[1]. Fix it to 300 units:

reservoir_initial = 300
+fix(x_storage[1], reservoir_initial; force = true)

u_flow[t]: the amount of water to flow through the turbine in stage t:

flow_max = 12
+@variable(model, 0 <= u_flow[1:T] <= flow_max)
52-element Vector{VariableRef}:
+ u_flow[1]
+ u_flow[2]
+ u_flow[3]
+ u_flow[4]
+ u_flow[5]
+ u_flow[6]
+ u_flow[7]
+ u_flow[8]
+ u_flow[9]
+ u_flow[10]
+ ⋮
+ u_flow[44]
+ u_flow[45]
+ u_flow[46]
+ u_flow[47]
+ u_flow[48]
+ u_flow[49]
+ u_flow[50]
+ u_flow[51]
+ u_flow[52]

u_spill[t]: the amount of water to spill from the reservoir in stage t, bypassing the turbine:

@variable(model, 0 <= u_spill[1:T])
52-element Vector{VariableRef}:
+ u_spill[1]
+ u_spill[2]
+ u_spill[3]
+ u_spill[4]
+ u_spill[5]
+ u_spill[6]
+ u_spill[7]
+ u_spill[8]
+ u_spill[9]
+ u_spill[10]
+ ⋮
+ u_spill[44]
+ u_spill[45]
+ u_spill[46]
+ u_spill[47]
+ u_spill[48]
+ u_spill[49]
+ u_spill[50]
+ u_spill[51]
+ u_spill[52]

u_thermal[t]: the amount of thermal generation in stage t:

@variable(model, 0 <= u_thermal[1:T])
52-element Vector{VariableRef}:
+ u_thermal[1]
+ u_thermal[2]
+ u_thermal[3]
+ u_thermal[4]
+ u_thermal[5]
+ u_thermal[6]
+ u_thermal[7]
+ u_thermal[8]
+ u_thermal[9]
+ u_thermal[10]
+ ⋮
+ u_thermal[44]
+ u_thermal[45]
+ u_thermal[46]
+ u_thermal[47]
+ u_thermal[48]
+ u_thermal[49]
+ u_thermal[50]
+ u_thermal[51]
+ u_thermal[52]

ω_inflow[t]: the amount of inflow to the reservoir in stage t:

@variable(model, ω_inflow[1:T])
52-element Vector{VariableRef}:
+ ω_inflow[1]
+ ω_inflow[2]
+ ω_inflow[3]
+ ω_inflow[4]
+ ω_inflow[5]
+ ω_inflow[6]
+ ω_inflow[7]
+ ω_inflow[8]
+ ω_inflow[9]
+ ω_inflow[10]
+ ⋮
+ ω_inflow[44]
+ ω_inflow[45]
+ ω_inflow[46]
+ ω_inflow[47]
+ ω_inflow[48]
+ ω_inflow[49]
+ ω_inflow[50]
+ ω_inflow[51]
+ ω_inflow[52]

For this model, our inflow is fixed, so we fix it to the data we have:

for t in 1:T
+    fix(ω_inflow[t], data[t, :inflow])
+end

The water balance constraint says that the water in the reservoir at the start of stage t+1 is the water in the reservoir at the start of stage t, less the amount flowed through the turbine, u_flow[t], less the amount spilled, u_spill[t], plus the amount of inflow, ω_inflow[t], into the reservoir:

@constraint(
+    model,
+    [t in 1:T],
+    x_storage[t+1] == x_storage[t] - u_flow[t] - u_spill[t] + ω_inflow[t],
+)
52-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
+ -x_storage[1] + x_storage[2] + u_flow[1] + u_spill[1] - ω_inflow[1] = 0
+ -x_storage[2] + x_storage[3] + u_flow[2] + u_spill[2] - ω_inflow[2] = 0
+ -x_storage[3] + x_storage[4] + u_flow[3] + u_spill[3] - ω_inflow[3] = 0
+ -x_storage[4] + x_storage[5] + u_flow[4] + u_spill[4] - ω_inflow[4] = 0
+ -x_storage[5] + x_storage[6] + u_flow[5] + u_spill[5] - ω_inflow[5] = 0
+ -x_storage[6] + x_storage[7] + u_flow[6] + u_spill[6] - ω_inflow[6] = 0
+ -x_storage[7] + x_storage[8] + u_flow[7] + u_spill[7] - ω_inflow[7] = 0
+ -x_storage[8] + x_storage[9] + u_flow[8] + u_spill[8] - ω_inflow[8] = 0
+ -x_storage[9] + x_storage[10] + u_flow[9] + u_spill[9] - ω_inflow[9] = 0
+ -x_storage[10] + x_storage[11] + u_flow[10] + u_spill[10] - ω_inflow[10] = 0
+ ⋮
+ -x_storage[44] + x_storage[45] + u_flow[44] + u_spill[44] - ω_inflow[44] = 0
+ -x_storage[45] + x_storage[46] + u_flow[45] + u_spill[45] - ω_inflow[45] = 0
+ -x_storage[46] + x_storage[47] + u_flow[46] + u_spill[46] - ω_inflow[46] = 0
+ -x_storage[47] + x_storage[48] + u_flow[47] + u_spill[47] - ω_inflow[47] = 0
+ -x_storage[48] + x_storage[49] + u_flow[48] + u_spill[48] - ω_inflow[48] = 0
+ -x_storage[49] + x_storage[50] + u_flow[49] + u_spill[49] - ω_inflow[49] = 0
+ -x_storage[50] + x_storage[51] + u_flow[50] + u_spill[50] - ω_inflow[50] = 0
+ -x_storage[51] + x_storage[52] + u_flow[51] + u_spill[51] - ω_inflow[51] = 0
+ -x_storage[52] + x_storage[53] + u_flow[52] + u_spill[52] - ω_inflow[52] = 0

We also need a supply = demand constraint. In practice, the units of this would be in MWh, and there would be a conversion factor between the amount of water flowing through the turbine and the power output. To simplify, we assume that power and water have the same units, so that one "unit" of demand is equal to one "unit" of the reservoir x_storage[t]:

@constraint(model, [t in 1:T], u_flow[t] + u_thermal[t] == data[t, :demand])
52-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
+ u_flow[1] + u_thermal[1] = 7
+ u_flow[2] + u_thermal[2] = 7.1
+ u_flow[3] + u_thermal[3] = 7.2
+ u_flow[4] + u_thermal[4] = 7.3
+ u_flow[5] + u_thermal[5] = 7.4
+ u_flow[6] + u_thermal[6] = 7.6
+ u_flow[7] + u_thermal[7] = 7.8
+ u_flow[8] + u_thermal[8] = 8.1
+ u_flow[9] + u_thermal[9] = 8.3
+ u_flow[10] + u_thermal[10] = 8.6
+ ⋮
+ u_flow[44] + u_thermal[44] = 8.1
+ u_flow[45] + u_thermal[45] = 7.8
+ u_flow[46] + u_thermal[46] = 7.6
+ u_flow[47] + u_thermal[47] = 7.4
+ u_flow[48] + u_thermal[48] = 7.3
+ u_flow[49] + u_thermal[49] = 7.2
+ u_flow[50] + u_thermal[50] = 7.1
+ u_flow[51] + u_thermal[51] = 7
+ u_flow[52] + u_thermal[52] = 7

Our objective is to minimize the cost of thermal generation:

@objective(model, Min, sum(data[t, :cost] * u_thermal[t] for t in 1:T))

\[ 10.2 u\_thermal_{1} + 10.4 u\_thermal_{2} + 10.6 u\_thermal_{3} + 10.9 u\_thermal_{4} + 11.2 u\_thermal_{5} + 11.5 u\_thermal_{6} + 11.9 u\_thermal_{7} + 12.3 u\_thermal_{8} + 12.7 u\_thermal_{9} + 13.1 u\_thermal_{10} + 13.6 u\_thermal_{11} + 14 u\_thermal_{12} + 14.5 u\_thermal_{13} + 14.9 u\_thermal_{14} + 15.3 u\_thermal_{15} + 15.8 u\_thermal_{16} + 16.2 u\_thermal_{17} + 16.6 u\_thermal_{18} + 17 u\_thermal_{19} + 17.4 u\_thermal_{20} + 17.7 u\_thermal_{21} + 18 u\_thermal_{22} + 18.3 u\_thermal_{23} + 18.5 u\_thermal_{24} + 18.7 u\_thermal_{25} + 18.9 u\_thermal_{26} + 19 u\_thermal_{27} + 19.1 u\_thermal_{28} + 19.2 u\_thermal_{29} + 19.2 u\_thermal_{30} + 19.2 u\_thermal_{31} + 19.2 u\_thermal_{32} + 19.1 u\_thermal_{33} + 19 u\_thermal_{34} + 18.9 u\_thermal_{35} + 18.8 u\_thermal_{36} + 18.6 u\_thermal_{37} + 18.5 u\_thermal_{38} + 18.4 u\_thermal_{39} + 18.2 u\_thermal_{40} + 18.1 u\_thermal_{41} + 17.9 u\_thermal_{42} + 17.8 u\_thermal_{43} + 17.7 u\_thermal_{44} + 17.6 u\_thermal_{45} + 17.5 u\_thermal_{46} + 17.5 u\_thermal_{47} + 17.5 u\_thermal_{48} + 17.5 u\_thermal_{49} + 17.6 u\_thermal_{50} + 17.7 u\_thermal_{51} + 17.8 u\_thermal_{52} \]

Let's optimize and check the solution

optimize!(model)
+solution_summary(model)
* Solver : HiGHS
+
+* Status
+  Result count       : 1
+  Termination status : OPTIMAL
+  Message from the solver:
+  "kHighsModelStatusOptimal"
+
+* Candidate solution (result #1)
+  Primal status      : FEASIBLE_POINT
+  Dual status        : FEASIBLE_POINT
+  Objective value    : 6.82910e+02
+  Objective bound    : 6.82910e+02
+  Relative gap       : 5.32717e-15
+  Dual objective value : 6.82910e+02
+
+* Work counters
+  Solve time (sec)   : 9.05037e-04
+  Simplex iterations : 53
+  Barrier iterations : 0
+  Node count         : -1
+

The total cost is:

objective_value(model)
682.9099999999999

Here's a plot of demand and generation:

Plots.plot(data[!, :demand]; label = "Demand", xlabel = "Week")
+Plots.plot!(value.(u_thermal); label = "Thermal")
+Plots.plot!(value.(u_flow); label = "Hydro")
Example block output

And here's the storage over time:

Plots.plot(value.(x_storage); label = "Storage", xlabel = "Week")
Example block output

Deterministic SDDP model

For the next step, we show how to decompose our JuMP model into SDDP.jl. It should obtain the same solution.

model = SDDP.LinearPolicyGraph(;
+    stages = T,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(
+        sp,
+        0 <= x_storage <= reservoir_max,
+        SDDP.State,
+        initial_value = reservoir_initial,
+    )
+    @variable(sp, 0 <= u_flow <= flow_max)
+    @variable(sp, 0 <= u_thermal)
+    @variable(sp, 0 <= u_spill)
+    @variable(sp, ω_inflow)
+    fix(ω_inflow, data[t, :inflow])
+    @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)
+    @constraint(sp, u_flow + u_thermal == data[t, :demand])
+    @stageobjective(sp, data[t, :cost] * u_thermal)
+    return
+end
A policy graph with 52 nodes.
+ Node indices: 1, ..., 52
+

Can you see how the JuMP model maps to this syntax? We have created a SDDP.LinearPolicyGraph with T stages, we're minimizing, and we're using HiGHS.Optimizer as the optimizer.

A few bits might be non-obvious:

  • We need to provide a lower bound for the objective function. Since our costs are always positive, a valid lower bound for the total cost is 0.0.
  • We define x_storage as a state variable using SDDP.State. A state variable is any variable that flows through time, and for which we need to know the value of it in stage t-1 to compute the best action in stage t. The state variable x_storage is actually two decision variables, x_storage.in and x_storage.out, which represent x_storage[t] and x_storage[t+1] respectively.
  • We need to use @stageobjective instead of @objective.

Instead of calling JuMP.optimize!, SDDP.jl uses a train method. With our machine learning hat on, you can think of SDDP.jl as training a function for each stage that accepts the current reservoir state as input and returns the optimal actions as output. It is also an iterative algorithm, so we need to specify when it should terminate:

SDDP.train(model; iteration_limit = 10)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 52
+  state variables : 1
+  scenarios       : 1.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [2, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+01]
+  bounds range     [1e+01, 3e+02]
+  rhs range        [7e+00, 1e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.079600e+03  3.157700e+02  4.043794e-02       104   1
+        10   6.829100e+02  6.829100e+02  1.332049e-01      1040   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 1.332049e-01
+total solves   : 1040
+best bound     :  6.829100e+02
+simulation ci  :  7.289889e+02 ± 7.726064e+01
+numeric issues : 0
+-------------------------------------------------------------------

As a quick sanity check, did we get the same cost as our JuMP model?

SDDP.calculate_bound(model)
682.910000000047

That's good. Next, to check the value of the decision variables. This isn't as straight forward as our JuMP model. Instead, we need to simulate the policy, and then extract the values of the decision variables from the results of the simulation.

Since our model is deterministic, we need only 1 replication of the simulation, and we want to record the values of the x_storage, u_flow, and u_thermal variables:

simulations = SDDP.simulate(
+    model,
+    1,  # Number of replications
+    [:x_storage, :u_flow, :u_thermal],
+);

The simulations vector is too big to show. But it contains one element for each replication, and each replication contains one dictionary for each stage.

For example, the data corresponding to the tenth stage in the first replication is:

simulations[1][10]
Dict{Symbol, Any} with 9 entries:
+  :u_flow          => 8.6
+  :bellman_term    => 0.0
+  :noise_term      => nothing
+  :node_index      => 10
+  :stage_objective => 0.0
+  :objective_state => nothing
+  :x_storage       => State{Float64}(316.2, 309.6)
+  :u_thermal       => 0.0
+  :belief          => Dict(10=>1.0)

Let's grab the trace of the u_thermal and u_flow variables in the first replication, and then plot them:

r_sim = [sim[:u_thermal] for sim in simulations[1]]
+u_sim = [sim[:u_flow] for sim in simulations[1]]
+
+Plots.plot(data[!, :demand]; label = "Demand", xlabel = "Week")
+Plots.plot!(r_sim; label = "Thermal")
+Plots.plot!(u_sim; label = "Hydro")
Example block output

Perfect. That's the same as we got before.

Now let's look at x_storage. This is a little more complicated, because we need to grab the outgoing value of the state variable in each stage:

x_sim = [sim[:x_storage].out for sim in simulations[1]]
+
+Plots.plot(x_sim; label = "Storage", xlabel = "Week")
Example block output

Stochastic SDDP model

Now we add some randomness to our model. In each stage, we assume that the inflow could be: 2 units lower, with 30% probability; the same as before, with 40% probability; or 5 units higher, with 30% probability.

model = SDDP.LinearPolicyGraph(;
+    stages = T,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(
+        sp,
+        0 <= x_storage <= reservoir_max,
+        SDDP.State,
+        initial_value = reservoir_initial,
+    )
+    @variable(sp, 0 <= u_flow <= flow_max)
+    @variable(sp, 0 <= u_thermal)
+    @variable(sp, 0 <= u_spill)
+    @variable(sp, ω_inflow)
+    # <--- This bit is new
+    Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3]
+    SDDP.parameterize(sp, Ω, P) do ω
+        fix(ω_inflow, data[t, :inflow] + ω)
+        return
+    end
+    # --->
+    @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)
+    @constraint(sp, u_flow + u_thermal == data[t, :demand])
+    @stageobjective(sp, data[t, :cost] * u_thermal)
+    return
+end
A policy graph with 52 nodes.
+ Node indices: 1, ..., 52
+

Can you see the differences?

Let's train our new model. We need more iterations because of the stochasticity:

SDDP.train(model; iteration_limit = 100)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 52
+  state variables : 1
+  scenarios       : 6.46108e+24
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [2, 3]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+01]
+  bounds range     [1e+01, 3e+02]
+  rhs range        [7e+00, 1e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   5.280100e+02  9.139270e+01  4.416203e-02       208   1
+        48   1.323019e+02  2.485841e+02  1.048049e+00      9984   1
+        86   2.048329e+02  2.659575e+02  2.052619e+00     17888   1
+       100   3.091833e+01  2.685173e+02  2.415835e+00     20800   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 2.415835e+00
+total solves   : 20800
+best bound     :  2.685173e+02
+simulation ci  :  3.077718e+02 ± 4.613924e+01
+numeric issues : 0
+-------------------------------------------------------------------

Now simulate the policy. This time we do 100 replications because the policy is now stochastic instead of deterministic:

simulations =
+    SDDP.simulate(model, 100, [:x_storage, :u_flow, :u_thermal, :ω_inflow]);

And let's plot the use of thermal generation in each replication:

plot = Plots.plot(data[!, :demand]; label = "Demand", xlabel = "Week")
+for simulation in simulations
+    Plots.plot!(plot, [sim[:u_thermal] for sim in simulation]; label = "")
+end
+plot
Example block output

Viewing an interpreting static plots like this is difficult, particularly as the number of simulations grows. SDDP.jl includes an interactive SpaghettiPlot that makes things easier:

plot = SDDP.SpaghettiPlot(simulations)
+SDDP.add_spaghetti(plot; title = "Storage") do sim
+    return sim[:x_storage].out
+end
+SDDP.add_spaghetti(plot; title = "Hydro") do sim
+    return sim[:u_flow]
+end
+SDDP.add_spaghetti(plot; title = "Inflow") do sim
+    return sim[:ω_inflow]
+end
+SDDP.plot(
+    plot,
+    "spaghetti_plot.html";
+    # We need this to build the documentation. Set to true if running locally.
+    open = false,
+)
Info

If you have trouble viewing the plot, you can open it in a new window.

Cyclic graphs

One major problem with our model is that the reservoir is empty at the end of the time horizon. This is because our model does not consider the cost of future years after the T weeks.

We can fix this using a cyclic policy graph. One way to construct a graph is with the SDDP.UnicyclicGraph constructor:

SDDP.UnicyclicGraph(0.7; num_nodes = 2)
Root
+ 0
+Nodes
+ 1
+ 2
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 1 w.p. 0.7

This graph has two nodes, and a loop from node 2 back to node 1 with probability 0.7.

We can construct a cyclic policy graph as follows:

graph = SDDP.UnicyclicGraph(0.95; num_nodes = T)
+model = SDDP.PolicyGraph(
+    graph;
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(
+        sp,
+        0 <= x_storage <= reservoir_max,
+        SDDP.State,
+        initial_value = reservoir_initial,
+    )
+    @variable(sp, 0 <= u_flow <= flow_max)
+    @variable(sp, 0 <= u_thermal)
+    @variable(sp, 0 <= u_spill)
+    @variable(sp, ω_inflow)
+    Ω, P = [-2, 0, 5], [0.3, 0.4, 0.3]
+    SDDP.parameterize(sp, Ω, P) do ω
+        fix(ω_inflow, data[t, :inflow] + ω)
+        return
+    end
+    @constraint(sp, x_storage.out == x_storage.in - u_flow - u_spill + ω_inflow)
+    @constraint(sp, u_flow + u_thermal == data[t, :demand])
+    @stageobjective(sp, data[t, :cost] * u_thermal)
+    return
+end
A policy graph with 52 nodes.
+ Node indices: 1, ..., 52
+

Notice how the only thing that has changed is our graph; the subproblems remain the same.

Let's train a policy:

SDDP.train(model; iteration_limit = 100)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 52
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [2, 2]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+01]
+  bounds range     [1e+01, 3e+02]
+  rhs range        [7e+00, 1e+01]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.678376e+04  2.751676e+04  1.518590e-01      1667   1
+         5   3.604433e+04  8.652705e+04  1.233367e+00     14575   1
+         8   1.690164e+05  9.035063e+04  2.378571e+00     26648   1
+        12   1.891482e+05  9.253085e+04  3.772616e+00     40388   1
+        14   1.791072e+05  9.302733e+04  5.044939e+00     51210   1
+        15   2.934068e+05  9.323453e+04  6.732738e+00     63901   1
+        17   2.261693e+05  9.331255e+04  8.266854e+00     74515   1
+        19   4.024393e+04  9.334521e+04  9.281785e+00     81177   1
+        26   9.668378e+04  9.336095e+04  1.502346e+01    115102   1
+        41   3.634169e+05  9.337154e+04  2.300425e+01    153419   1
+        47   6.305561e+04  9.337393e+04  2.812257e+01    175485   1
+        50   1.441756e+05  9.337725e+04  3.462104e+01    200662   1
+        55   5.266733e+03  9.338192e+04  3.972483e+01    219189   1
+        60   1.273406e+05  9.338272e+04  4.535771e+01    238340   1
+        65   3.348033e+05  9.338388e+04  5.219400e+01    259987   1
+        73   1.033069e+05  9.338631e+04  5.726585e+01    275403   1
+        75   4.509049e+05  9.338659e+04  6.591720e+01    299953   1
+        78   1.306847e+05  9.338672e+04  7.112395e+01    314314   1
+        83   1.617152e+05  9.338762e+04  7.764913e+01    331801   1
+        89   3.303921e+05  9.338846e+04  8.663575e+01    354491   1
+        92   2.115573e+05  9.338912e+04  9.491650e+01    374468   1
+        96   1.860085e+05  9.339135e+04  1.026589e+02    392368   1
+        99   3.774492e+04  9.339221e+04  1.082910e+02    403817   1
+       100   2.990582e+04  9.339229e+04  1.089285e+02    405276   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 1.089285e+02
+total solves   : 405276
+best bound     :  9.339229e+04
+simulation ci  :  9.110187e+04 ± 1.846262e+04
+numeric issues : 0
+-------------------------------------------------------------------

When we simulate now, each trajectory will be a different length, because each cycle has a 95% probability of continuing and a 5% probability of stopping.

simulations = SDDP.simulate(model, 3);
+length.(simulations)
3-element Vector{Int64}:
+  988
+ 2184
+  728

We can simulate a fixed number of cycles by passing a sampling_scheme:

simulations = SDDP.simulate(
+    model,
+    100,
+    [:x_storage, :u_flow];
+    sampling_scheme = SDDP.InSampleMonteCarlo(;
+        max_depth = 5 * T,
+        terminate_on_dummy_leaf = false,
+    ),
+);
+length.(simulations)
100-element Vector{Int64}:
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+   ⋮
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260
+ 260

Let's visualize the policy:

Plots.plot(
+    SDDP.publication_plot(simulations; ylabel = "Storage") do sim
+        return sim[:x_storage].out
+    end,
+    SDDP.publication_plot(simulations; ylabel = "Hydro") do sim
+        return sim[:u_flow]
+    end;
+    layout = (2, 1),
+)
Example block output

Next steps

Our model is very basic. There are many aspects that we could improve:

  • Can you add a second reservoir to make a river chain?

  • Can you modify the problem and data to use proper units, including a conversion between the volume of water flowing through the turbine and the electrical power output?

diff --git a/previews/PR826/tutorial/first_steps.ipynb b/previews/PR826/tutorial/first_steps.ipynb new file mode 100644 index 0000000000..c2db657986 --- /dev/null +++ b/previews/PR826/tutorial/first_steps.ipynb @@ -0,0 +1,1751 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# An introduction to SDDP.jl" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP.jl is a solver for multistage stochastic optimization problems. By\n", + "**multistage**, we mean problems in which an agent makes a sequence of\n", + "decisions over time. By **stochastic**, we mean that the agent is making\n", + "decisions in the presence of uncertainty that is gradually revealed over the\n", + "multiple stages." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> Multistage stochastic programming has a lot in common with fields like\n", + "> stochastic optimal control, approximate dynamic programming, Markov\n", + "> decision processes, and reinforcement learning. If it helps, you can think\n", + "> of SDDP as Q-learning in which we approximate the value function using\n", + "> linear programming duality." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial is in two parts. First, it is an introduction to the background\n", + "notation and theory we need, and second, it solves a simple multistage\n", + "stochastic programming problem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## What is a node?" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A common feature of multistage stochastic optimization problems is that they\n", + "model an agent controlling a system over time. To simplify things initially,\n", + "we're going to start by describing what happens at an instant in time at which\n", + "the agent makes a decision. Only after this will we extend our problem to\n", + "multiple stages and the notion of time." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A **node** is a place at which the agent makes a decision." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> For readers with a stochastic programming background, \"node\" is synonymous\n", + "> with \"stage\" in this section. However, for reasons that will become clear\n", + "> shortly, there can be more than one \"node\" per instant in time, which is\n", + "> why we prefer the term \"node\" over \"stage.\"" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### States, controls, and random variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The system that we are modeling can be described by three types of variables." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "1. **State** variables track a property of the system over time." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + " Each node has an associated _incoming_ state variable (the value of the\n", + " state at the start of the node), and an _outgoing_ state variable (the\n", + " value of the state at the end of the node).\n", + "\n", + " Examples of state variables include the volume of water in a reservoir, the\n", + " number of units of inventory in a warehouse, or the spatial position of a\n", + " moving vehicle.\n", + "\n", + " Because state variables track the system over time, each node must have the\n", + " same set of state variables.\n", + "\n", + " We denote state variables by the letter $x$ for the incoming state variable\n", + " and $x^\\prime$ for the outgoing state variable.\n", + "\n", + "2. **Control** variables are actions taken (implicitly or explicitly) by the\n", + " agent within a node which modify the state variables.\n", + "\n", + " Examples of control variables include releases of water from the reservoir,\n", + " sales or purchasing decisions, and acceleration or braking of the vehicle.\n", + "\n", + " Control variables are local to a node $i$, and they can differ between\n", + " nodes. For example, some control variables may be available within certain\n", + " nodes.\n", + "\n", + " We denote control variables by the letter $u$.\n", + "\n", + "3. **Random** variables are finite, discrete, exogenous random variables that\n", + " the agent observes at the start of a node, before the control variables are\n", + " decided.\n", + "\n", + " Examples of random variables include rainfall inflow into a reservoir,\n", + " probabilistic perishing of inventory, and steering errors in a vehicle.\n", + "\n", + " Random variables are local to a node $i$, and they can differ between\n", + " nodes. For example, some nodes may have random variables, and some nodes\n", + " may not.\n", + "\n", + " We denote random variables by the Greek letter $\\omega$ and the sample\n", + " space from which they are drawn by $\\Omega_i$. The probability of sampling\n", + " $\\omega$ is denoted $p_{\\omega}$ for simplicity.\n", + "\n", + " Importantly, the random variable associated with node $i$ is independent of\n", + " the random variables in all other nodes." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Dynamics" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In a node $i$, the three variables are related by a **transition function**,\n", + "which maps the incoming state, the controls, and the random variables to the\n", + "outgoing state as follows: $x^\\prime = T_i(x, u, \\omega)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As a result of entering a node $i$ with the incoming state $x$, observing\n", + "random variable $\\omega$, and choosing control $u$, the agent incurs a cost\n", + "$C_i(x, u, \\omega)$. (If the agent is a maximizer, this can be a profit, or a\n", + "negative cost.) We call $C_i$ the **stage objective**." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To choose their control variables in node $i$, the agent uses a **decision**\n", + "**rule** $u = \\pi_i(x, \\omega)$, which is a function that maps the incoming\n", + "state variable and observation of the random variable to a control $u$. This\n", + "control must satisfy some feasibility requirements $u \\in U_i(x, \\omega)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is a schematic which we can use to visualize a single node:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![Hazard-decision node](../assets/hazard_decision.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Policy graphs" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now that we have a node, we need to connect multiple nodes together to form a\n", + "multistage stochastic program. We call the graph created by connecting nodes\n", + "together a **policy graph**." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The simplest type of policy graph is a **linear policy graph**. Here's a\n", + "linear policy graph with three nodes:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![Linear policy graph](../assets/stochastic_linear_policy_graph.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here we have dropped the notations inside each node and replaced them by a\n", + "label (1, 2, and 3) to represent nodes `i=1`, `i=2`, and `i=3`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In addition to nodes 1, 2, and 3, there is also a root node (the circle), and\n", + "three arcs. Each arc has an origin node and a destination node, like `1 => 2`,\n", + "and a corresponding probability of transitioning from the origin to the\n", + "destination. Unless specified, we assume that the arc probabilities are\n", + "uniform over the number of outgoing arcs. Thus, in this picture the arc\n", + "probabilities are all 1.0." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "State variables flow long the arcs of the graph. Thus, the outgoing state\n", + "variable $x^\\prime$ from node 1 becomes the incoming state variable $x$ to\n", + "node 2, and so on." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We denote the set of nodes by $\\mathcal{N}$, the root node by $R$, and the\n", + "probability of transitioning from node $i$ to node $j$ by $p_{ij}$. (If no arc\n", + "exists, then $p_{ij} = 0$.) We define the set of successors of node $i$ as\n", + "$i^+ = \\{j \\in \\mathcal{N} | p_{ij} > 0\\}$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Each node in the graph corresponds to a place at which the agent makes a\n", + "decision, and we call moments in time at which the agent makes a decision\n", + "**stages**. By convention, we try to draw policy graphs from left-to-right,\n", + "with the stages as columns. There can be more than one node in a stage! Here's\n", + "an example of a structure we call **Markovian policy graphs**:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![Markovian policy graph](../assets/enso_markovian.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here each column represents a moment in time, the squiggly lines represent\n", + "stochastic rainfall, and the rows represent the world in two discrete states:\n", + "El Niño and La Niña. In the El Niño states, the distribution of the rainfall\n", + "random variable is different to the distribution of the rainfall random\n", + "variable in the La Niña states, and there is some switching probability\n", + "between the two states that can be modelled by a Markov chain." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Moreover, policy graphs can have cycles! This allows them to model infinite\n", + "horizon problems. Here's another example, taken from the paper\n", + "[Dowson (2020)](https://doi.org/10.1002/net.21932):" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "![POWDer policy graph](../assets/powder_policy_graph.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The columns represent time, and the rows represent different states of the\n", + "world. In this case, the rows represent different prices that milk can be sold\n", + "for at the end of each year. The squiggly lines denote a multivariate random\n", + "variable that models the weekly amount of rainfall that occurs." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> The sum of probabilities on the outgoing arcs of node $i$ can be less than\n", + "> 1, i.e., $\\sum\\limits_{j\\in i^+} p_{ij} \\le 1$. What does this mean?\n", + "> One interpretation is that the probability is a [discount factor](https://en.wikipedia.org/wiki/Discounting).\n", + "> Another interpretation is that there is an implicit \"zero\" node that we\n", + "> have not modeled, with $p_{i0} = 1 - \\sum\\limits_{j\\in i^+} p_{ij}$.\n", + "> This zero node has $C_0(x, u, \\omega) = 0$, and $0^+ = \\varnothing$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## More notation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Recall that each node $i$ has a **decision rule** $u = \\pi_i(x, \\omega)$,\n", + "which is a function that maps the incoming state variable and observation of\n", + "the random variable to a control $u$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The set of decision rules, with one element for each node in the policy graph,\n", + "is called a **policy**." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The goal of the agent is to find a policy that minimizes the expected cost of\n", + "starting at the root node with some initial condition $x_R$, and proceeding\n", + "from node to node along the probabilistic arcs until they reach a node with no\n", + "outgoing arcs (or it reaches an implicit \"zero\" node)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\min_{\\pi} \\mathbb{E}_{i \\in R^+, \\omega \\in \\Omega_i}[V_i^\\pi(x_R, \\omega)],\n", + "$$\n", + "where\n", + "$$\n", + "V_i^\\pi(x, \\omega) = C_i(x, u, \\omega) + \\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)],\n", + "$$\n", + "where $u = \\pi_i(x, \\omega) \\in U_i(x, \\omega)$, and\n", + "$x^\\prime = T_i(x, u, \\omega)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The expectations are a bit complicated, but they are equivalent to:\n", + "$$\n", + "\\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)] = \\sum\\limits_{j \\in i^+} p_{ij} \\sum\\limits_{\\varphi \\in \\Omega_j} p_{\\varphi}V_j(x^\\prime, \\varphi).\n", + "$$" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "An optimal policy is the set of decision rules that the agent can use to make\n", + "decisions and achieve the smallest expected cost." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Assumptions" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> This section is important!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The space of problems you can model with this framework is very large. Too\n", + "large, in fact, for us to form tractable solution algorithms for! Stochastic\n", + "dual dynamic programming requires the following assumptions in order to work:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "**Assumption 1: finite nodes**\n", + "\n", + "There is a finite number of nodes in $\\mathcal{N}$.\n", + "\n", + "**Assumption 2: finite random variables**\n", + "\n", + "The sample space $\\Omega_i$ is finite and discrete for each node\n", + "$i\\in\\mathcal{N}$.\n", + "\n", + "**Assumption 3: convex problems**\n", + "\n", + "Given fixed $\\omega$, $C_i(x, u, \\omega)$ is a convex function,\n", + "$T_i(x, u, \\omega)$ is linear, and $U_i(x, u, \\omega)$ is a non-empty,\n", + "bounded convex set with respect to $x$ and $u$.\n", + "\n", + "**Assumption 4: no infinite loops**\n", + "\n", + "For all loops in the policy graph, the product of the arc transition\n", + "probabilities around the loop is strictly less than 1.\n", + "\n", + "**Assumption 5: relatively complete recourse**\n", + "\n", + "This is a technical but important assumption. See Relatively complete recourse\n", + "for more details." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> SDDP.jl relaxes assumption (3) to allow for integer state and control\n", + "> variables, but we won't go into the details here. Assumption (4)\n", + "> essentially means that we obtain a discounted-cost solution for\n", + "> infinite-horizon problems, instead of an average-cost solution; see\n", + "> [Dowson (2020)](https://doi.org/10.1002/net.21932) for details." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Dynamic programming and subproblems" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now that we have formulated our problem, we need some ways of computing\n", + "optimal decision rules. One way is to just use a heuristic like \"choose a\n", + "control randomly from the set of feasible controls.\" However, such a policy\n", + "is unlikely to be optimal." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A better way of obtaining an optimal policy is to use [Bellman's principle of\n", + "optimality](https://en.wikipedia.org/wiki/Bellman_equation#Bellman's_principle_of_optimality),\n", + "a.k.a Dynamic Programming, and define a recursive **subproblem** as follows:\n", + "$$\n", + "\\begin{aligned}\n", + "V_i(x, \\omega) = \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) + \\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]\\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega) \\\\\n", + "& \\bar{x} = x.\n", + "\\end{aligned}\n", + "$$\n", + "Our decision rule, $\\pi_i(x, \\omega)$, solves this optimization problem and\n", + "returns a $u^*$ corresponding to an optimal solution.\n", + "\n", + "> **Note**\n", + ">\n", + "> We add $\\bar{x}$ as a decision variable, along with the fishing constraint\n", + "> $\\bar{x} = x$ for two reasons: it makes it obvious that formulating a\n", + "> problem with $x \\times u$ results in a bilinear program instead of a\n", + "> linear program (see Assumption 3), and it simplifies the implementation of\n", + "> the SDDP algorithm." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "These subproblems are very difficult to solve exactly, because they involve\n", + "recursive optimization problems with lots of nested expectations." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Therefore, instead of solving them exactly, SDDP.jl works by iteratively\n", + "approximating the expectation term of each subproblem, which is also called\n", + "the cost-to-go term. For now, you don't need to understand the details, other\n", + "than that there is a nasty cost-to-go term that we deal with\n", + "behind-the-scenes." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The subproblem view of a multistage stochastic program is also important,\n", + "because it provides a convenient way of communicating the different parts of\n", + "the broader problem, and it is how we will communicate the problem to SDDP.jl.\n", + "All we need to do is drop the cost-to-go term and fishing constraint, and\n", + "define a new subproblem `SP` as:\n", + "$$\n", + "\\begin{aligned}\n", + "\\texttt{SP}_i(x, \\omega) : \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, u, \\omega) \\\\\n", + "& x^\\prime = T_i(\\bar{x}, u, \\omega) \\\\\n", + "& u \\in U_i(\\bar{x}, \\omega).\n", + "\\end{aligned}\n", + "$$\n", + "> **Note**\n", + ">\n", + "> When we talk about formulating a **subproblem** with SDDP.jl, this is the\n", + "> formulation we mean." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We've retained the transition function and uncertainty set because they help\n", + "to motivate the different components of the subproblem. However, in general,\n", + "the subproblem can be more general. A better (less restrictive) representation\n", + "might be:\n", + "$$\n", + "\\begin{aligned}\n", + "\\texttt{SP}_i(x, \\omega) : \\min\\limits_{\\bar{x}, x^\\prime, u} \\;\\; & C_i(\\bar{x}, x^\\prime, u, \\omega) \\\\\n", + "& (\\bar{x}, x^\\prime, u) \\in \\mathcal{X}_i(\\omega).\n", + "\\end{aligned}\n", + "$$\n", + "Note that the outgoing state variable can appear in the objective, and we can\n", + "add constraints involving the incoming and outgoing state variables. It\n", + "should be obvious how to map between the two representations." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Example: hydro-thermal scheduling" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Hydrothermal scheduling is the most common application of stochastic dual\n", + "dynamic programming. To illustrate some of the basic functionality of\n", + "`SDDP.jl`, we implement a very simple model of the hydrothermal scheduling\n", + "problem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Problem statement" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We consider the problem of scheduling electrical generation over three weeks\n", + "in order to meet a known demand of 150 MWh in each week.\n", + "\n", + "There are two generators: a thermal generator, and a hydro generator. In each\n", + "week, the agent needs to decide how much energy to generate from thermal, and\n", + "how much energy to generate from hydro.\n", + "\n", + "The thermal generator has a short-run marginal cost of \\$50/MWh in the first\n", + "stage, \\$100/MWh in the second stage, and \\$150/MWh in the third stage.\n", + "\n", + "The hydro generator has a short-run marginal cost of \\$0/MWh.\n", + "\n", + "The hydro generator draws water from a reservoir which has a maximum capacity\n", + "of 200 MWh. (Although water is usually measured in m³, we measure it in the\n", + "energy-equivalent MWh to simplify things. In practice, there is a conversion\n", + "function between m³ flowing throw the turbine and MWh.) At the start of the\n", + "first time period, the reservoir is full.\n", + "\n", + "In addition to the ability to generate electricity by passing water through\n", + "the hydroelectric turbine, the hydro generator can also spill water down a\n", + "spillway (bypassing the turbine) in order to prevent the water from\n", + "over-topping the dam. We assume that there is no cost of spillage." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In addition to water leaving the reservoir, water that flows into the reservoir\n", + "through rainfall or rivers are referred to as inflows. These inflows are\n", + "uncertain, and are the cause of the main trade-off in hydro-thermal\n", + "scheduling: the desire to use water now to generate cheap electricity, against\n", + "the risk that future inflows will be low, leading to blackouts or expensive\n", + "thermal generation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For our simple model, we assume that the inflows can be modelled by a discrete\n", + "distribution with the three outcomes given in the following table:\n", + "\n", + "| ω | 0 | 50 | 100 |\n", + "| ---- | --- | --- | --- |\n", + "| P(ω) | 1/3 | 1/3 | 1/3 |" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The value of the noise (the random variable) is observed by the agent at the\n", + "start of each stage. This makes the problem a _wait-and-see_ or\n", + "_hazard-decision_ formulation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The goal of the agent is to minimize the expected cost of generation over the\n", + "three weeks." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Formulating the problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Before going further, we need to load SDDP.jl:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "#### Graph structure" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, we need to identify the structure of the policy graph. From the problem\n", + "statement, we want to model the problem over three weeks in weekly stages.\n", + "Therefore, the policy graph is a linear graph with three stages:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "graph = SDDP.LinearGraph(3)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "#### Building the subproblem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Next, we need to construct the associated subproblem for each node in `graph`.\n", + "To do so, we need to provide SDDP.jl a function which takes two arguments. The\n", + "first is `subproblem::Model`, which is an empty JuMP model. The second is\n", + "`node`, which is the name of each node in the policy graph. If the graph is\n", + "linear, SDDP defaults to naming the nodes using the integers in `1:T`. Here's\n", + "an example that we are going to flesh out over the next few paragraphs:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # ... stuff to go here ...\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> If you use a different type of graph, `node` may be a type different to\n", + "> `Int`. For example, in `SDDP.MarkovianGraph`, `node` is a\n", + "> `Tuple{Int,Int}`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "#### State variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The first part of the subproblem we need to identify are the state variables.\n", + "Since we only have one reservoir, there is only one state variable, `volume`,\n", + "the volume of water in the reservoir [MWh].\n", + "\n", + "The volume had bounds of `[0, 200]`, and the reservoir was full at the start\n", + "of time, so $x_R = 200$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We add state variables to our `subproblem` using JuMP's `@variable` macro.\n", + "However, in addition to the usual syntax, we also pass `SDDP.State`, and we\n", + "need to provide the initial value ($x_R$) using the `initial_value` keyword." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The syntax for adding a state variable is a little obtuse, because `volume` is\n", + "not single JuMP variable. Instead, `volume` is a struct with two fields, `.in`\n", + "and `.out`, corresponding to the incoming and outgoing state variables\n", + "respectively." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> We don't need to add the fishing constraint $\\bar{x} = x$; SDDP.jl does\n", + "> this automatically." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "#### Control variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The next part of the subproblem we need to identify are the control variables.\n", + "The control variables for our problem are:\n", + " - `thermal_generation`: the quantity of energy generated from thermal\n", + " [MWh/week]\n", + " - `hydro_generation`: the quantity of energy generated from hydro [MWh/week]\n", + " - `hydro_spill`: the volume of water spilled from the reservoir in each week\n", + " [MWh/week]\n", + "Each of these variables is non-negative." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We add control variables to our `subproblem` as normal JuMP variables, using\n", + "`@variable` or `@variables`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Control variables\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " end)\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> Modeling is an art, and a tricky part of that art is figuring out which\n", + "> variables are state variables, and which are control variables. A good\n", + "> rule is: if you need a value of a control variable in some future node to\n", + "> make a decision, it is a state variable instead." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "#### Random variables" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The next step is to identify any random variables. In our example, we had\n", + " - `inflow`: the quantity of water that flows into the reservoir each week\n", + " [MWh/week]" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To add an uncertain variable to the model, we create a new JuMP variable\n", + "`inflow`, and then call the function `SDDP.parameterize`. The\n", + "`SDDP.parameterize` function takes three arguments: the subproblem, a\n", + "vector of realizations, and a corresponding vector of probabilities." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Control variables\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " end)\n", + " # Random variables\n", + " @variable(subproblem, inflow)\n", + " Ω = [0.0, 50.0, 100.0]\n", + " P = [1 / 3, 1 / 3, 1 / 3]\n", + " SDDP.parameterize(subproblem, Ω, P) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Note how we use the JuMP function\n", + "[`JuMP.fix`](https://jump.dev/JuMP.jl/stable/reference/variables/#JuMP.fix)\n", + "to set the value of the `inflow` variable to `ω`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> `SDDP.parameterize` can only be called once in each subproblem\n", + "> definition! If your random variable is multi-variate, read\n", + "> Add multi-dimensional noise terms." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "#### Transition function and constraints" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now that we've identified our variables, we can define the transition function\n", + "and the constraints." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For our problem, the state variable is the volume of water in the reservoir.\n", + "The volume of water decreases in response to water being used for hydro\n", + "generation and spillage. So the transition function is:\n", + "`volume.out = volume.in - hydro_generation - hydro_spill + inflow`. (Note how\n", + "we use `volume.in` and `volume.out` to refer to the incoming and outgoing\n", + "state variables.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There is also a constraint that the total generation must sum to 150 MWh." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Both the transition function and any additional constraint are added using\n", + "JuMP's `@constraint` and `@constraints` macro." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Control variables\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " end)\n", + " # Random variables\n", + " @variable(subproblem, inflow)\n", + " Ω = [0.0, 50.0, 100.0]\n", + " P = [1 / 3, 1 / 3, 1 / 3]\n", + " SDDP.parameterize(subproblem, Ω, P) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " # Transition function and constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in - hydro_generation - hydro_spill + inflow\n", + " demand_constraint, hydro_generation + thermal_generation == 150\n", + " end\n", + " )\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "#### Objective function" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we need to add an objective function using `@stageobjective`. The\n", + "objective of the agent is to minimize the cost of thermal generation. This is\n", + "complicated by a fuel cost that depends on the `node`.\n", + "\n", + "One possibility is to use an `if` statement on `node` to define the correct\n", + "objective:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Control variables\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " end)\n", + " # Random variables\n", + " @variable(subproblem, inflow)\n", + " Ω = [0.0, 50.0, 100.0]\n", + " P = [1 / 3, 1 / 3, 1 / 3]\n", + " SDDP.parameterize(subproblem, Ω, P) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " # Transition function and constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in - hydro_generation - hydro_spill + inflow\n", + " demand_constraint, hydro_generation + thermal_generation == 150\n", + " end\n", + " )\n", + " # Stage-objective\n", + " if node == 1\n", + " @stageobjective(subproblem, 50 * thermal_generation)\n", + " elseif node == 2\n", + " @stageobjective(subproblem, 100 * thermal_generation)\n", + " else\n", + " @assert node == 3\n", + " @stageobjective(subproblem, 150 * thermal_generation)\n", + " end\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "A second possibility is to use an array of fuel costs, and use `node` to index\n", + "the correct value:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function subproblem_builder(subproblem::Model, node::Int)\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Control variables\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " end)\n", + " # Random variables\n", + " @variable(subproblem, inflow)\n", + " Ω = [0.0, 50.0, 100.0]\n", + " P = [1 / 3, 1 / 3, 1 / 3]\n", + " SDDP.parameterize(subproblem, Ω, P) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " # Transition function and constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in - hydro_generation - hydro_spill + inflow\n", + " demand_constraint, hydro_generation + thermal_generation == 150\n", + " end\n", + " )\n", + " # Stage-objective\n", + " fuel_cost = [50, 100, 150]\n", + " @stageobjective(subproblem, fuel_cost[node] * thermal_generation)\n", + " return subproblem\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "### Constructing the model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now that we've written our subproblem, we need to construct the full model.\n", + "For that, we're going to need a linear solver. Let's choose HiGHS:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using HiGHS" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> In larger problems, you should use a more robust commercial LP solver like\n", + "> Gurobi. Read Words of warning for more details." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Then, we can create a full model using `SDDP.PolicyGraph`, passing our\n", + "`subproblem_builder` function as the first argument, and our `graph` as the\n", + "second:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.PolicyGraph(\n", + " subproblem_builder,\n", + " graph;\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "* `sense`: the optimization sense. Must be `:Min` or `:Max`.\n", + "* `lower_bound`: you _must_ supply a valid bound on the objective. For our\n", + " problem, we know that we cannot incur a negative cost so \\$0 is a valid\n", + " lower bound.\n", + "* `optimizer`: This is borrowed directly from JuMP's `Model` constructor:\n", + " `Model(HiGHS.Optimizer)`" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Because linear policy graphs are the most commonly used structure, we can use\n", + "`SDDP.LinearPolicyGraph` instead of passing `SDDP.LinearGraph(3)` to\n", + "`SDDP.PolicyGraph`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(\n", + " subproblem_builder;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "There is also the option is to use Julia's `do` syntax to avoid needing to\n", + "define a `subproblem_builder` function separately:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, node\n", + " # State variables\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Control variables\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " end)\n", + " # Random variables\n", + " @variable(subproblem, inflow)\n", + " Ω = [0.0, 50.0, 100.0]\n", + " P = [1 / 3, 1 / 3, 1 / 3]\n", + " SDDP.parameterize(subproblem, Ω, P) do ω\n", + " return JuMP.fix(inflow, ω)\n", + " end\n", + " # Transition function and constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in - hydro_generation - hydro_spill + inflow\n", + " demand_constraint, hydro_generation + thermal_generation == 150\n", + " end\n", + " )\n", + " # Stage-objective\n", + " if node == 1\n", + " @stageobjective(subproblem, 50 * thermal_generation)\n", + " elseif node == 2\n", + " @stageobjective(subproblem, 100 * thermal_generation)\n", + " else\n", + " @assert node == 3\n", + " @stageobjective(subproblem, 150 * thermal_generation)\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> Julia's `do` syntax is just a different way of passing an anonymous\n", + "> function `inner` to some function `outer` which takes `inner` as the first\n", + "> argument. For example, given:\n", + "> ```julia\n", + "> outer(inner::Function, x, y) = inner(x, y)\n", + "> ```\n", + "> then\n", + "> ```julia\n", + "> outer(1, 2) do x, y\n", + "> return x^2 + y^2\n", + "> end\n", + "> ```\n", + "> is equivalent to:\n", + "> ```julia\n", + "> outer((x, y) -> x^2 + y^2, 1, 2)\n", + "> ```\n", + "> For our purpose, `inner` is `subproblem_builder`, and `outer` is\n", + "> `SDDP.PolicyGraph`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Training a policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Now we have a model, which is a description of the policy graph, we need to\n", + "train a policy. Models can be trained using the `SDDP.train` function.\n", + "It accepts a number of keyword arguments. `iteration_limit` terminates the\n", + "training after the provided number of iterations." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; iteration_limit = 10)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "There's a lot going on in this printout! Let's break it down." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The first section, \"problem,\" gives some problem statistics. In this example\n", + "there are 3 nodes, 1 state variable, and 27 scenarios ($3^3$). We haven't\n", + "solved this problem before so there are no existing cuts." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The \"options\" section lists some options we are using to solve the problem.\n", + "For more information on the numerical stability report, read the\n", + "Numerical stability report section." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The \"subproblem structure\" section also needs explaining. This looks at all of\n", + "the nodes in the policy graph and reports the minimum and maximum number of\n", + "variables and each constraint type in the corresponding subproblem. In this\n", + "case each subproblem has 7 variables and various numbers of different\n", + "constraint types. Note that the exact numbers may not correspond to the\n", + "formulation as you wrote it, because SDDP.jl adds some extra variables for the\n", + "cost-to-go function." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Then comes the iteration log, which is the main part of the printout. It has\n", + "the following columns:\n", + " - `iteration`: the SDDP iteration\n", + " - `simulation`: the cost of the single forward pass simulation for that\n", + " iteration. This value is stochastic and is not guaranteed to improve over\n", + " time. However, it's useful to check that the units are reasonable, and that\n", + " it is not deterministic if you intended for the problem to be stochastic,\n", + " etc.\n", + " - `bound`: this is a lower bound (upper if maximizing) for the value of the\n", + " optimal policy. This bound should be monotonically improving (increasing if\n", + " minimizing, decreasing if maximizing), but in some cases it can temporarily\n", + " worsen due to cut selection, especially in the early iterations of the\n", + " algorithm.\n", + " - `time (s)`: the total number of seconds spent solving so far\n", + " - `solves`: the total number of subproblem solves to date. This can be very\n", + " large!\n", + " - `pid`: the ID of the processor used to solve that iteration. This\n", + " should be 1 unless you are using parallel computation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In addition, if the first character of a line is `†`, then SDDP.jl experienced\n", + "numerical issues during the solve, but successfully recovered." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The printout finishes with some summary statistics:\n", + "\n", + " - `status`: why did the solver stop?\n", + " - `total time (s)`, `best bound`, and `total solves` are the values from the\n", + " last iteration of the solve.\n", + " - `simulation ci`: a confidence interval that estimates the quality of the\n", + " policy from the `Simulation` column.\n", + " - `numeric issues`: the number of iterations that experienced numerical\n", + " issues." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> The `simulation ci` result can be misleading if you run a small number of\n", + "> iterations, or if the initial simulations are very bad. On a more\n", + "> technical note, it is an _in-sample simulation_, which may not reflect the\n", + "> true performance of the policy. See Obtaining bounds for more\n", + "> details." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Obtaining the decision rule" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "After training a policy, we can create a decision rule using\n", + "`SDDP.DecisionRule`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "rule = SDDP.DecisionRule(model; node = 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Then, to evaluate the decision rule, we use `SDDP.evaluate`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "solution = SDDP.evaluate(\n", + " rule;\n", + " incoming_state = Dict(:volume => 150.0),\n", + " noise = 50.0,\n", + " controls_to_record = [:hydro_generation, :thermal_generation],\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Simulating the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Once you have a trained policy, you can also simulate it using\n", + "`SDDP.simulate`. The return value from `simulate` is a vector with one\n", + "element for each replication. Each element is itself a vector, with one\n", + "element for each stage. Each element, corresponding to a particular stage in a\n", + "particular replication, is a dictionary that records information from the\n", + "simulation." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " # The trained model to simulate.\n", + " model,\n", + " # The number of replications.\n", + " 100,\n", + " # A list of names to record the values of.\n", + " [:volume, :thermal_generation, :hydro_generation, :hydro_spill],\n", + ")\n", + "\n", + "replication = 1\n", + "stage = 2\n", + "simulations[replication][stage]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Ignore many of the entries for now; they will be relevant later." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + " One element of interest is `:volume`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "outgoing_volume = map(simulations[1]) do node\n", + " return node[:volume].out\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + " Another is `:thermal_generation`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "thermal_generation = map(simulations[1]) do node\n", + " return node[:thermal_generation]\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Obtaining bounds" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Because the optimal policy is stochastic, one common approach to quantify the\n", + "quality of the policy is to construct a confidence interval for the expected\n", + "cost by summing the stage objectives along each simulation." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "objectives = map(simulations) do simulation\n", + " return sum(stage[:stage_objective] for stage in simulation)\n", + "end\n", + "\n", + "μ, ci = SDDP.confidence_interval(objectives)\n", + "println(\"Confidence interval: \", μ, \" ± \", ci)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This confidence interval is an estimate for an upper bound of the policy's\n", + "quality. We can calculate the lower bound using `SDDP.calculate_bound`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "println(\"Lower bound: \", SDDP.calculate_bound(model))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> The upper- and lower-bounds are reversed if maximizing, i.e., `SDDP.calculate_bound`.\n", + "> returns an upper bound." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Custom recorders" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In addition to simulating the primal values of variables, we can also pass\n", + "custom recorder functions. Each of these functions takes one argument, the\n", + "JuMP subproblem corresponding to each node. This function gets called after we\n", + "have solved each node as we traverse the policy graph in the simulation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For example, the dual of the demand constraint (which we named\n", + "`demand_constraint`) corresponds to the price we should charge for\n", + "electricity, since it represents the cost of each additional unit of demand.\n", + "To calculate this, we can go:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model,\n", + " 1; ## Perform a single simulation\n", + " custom_recorders = Dict{Symbol,Function}(\n", + " :price => (sp::JuMP.Model) -> JuMP.dual(sp[:demand_constraint]),\n", + " ),\n", + ")\n", + "\n", + "prices = map(simulations[1]) do node\n", + " return node[:price]\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Extracting the marginal water values" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, we can use `SDDP.ValueFunction` and `SDDP.evaluate`\n", + "to obtain and evaluate the value function at different points in the\n", + "state-space." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> By \"value function\" we mean $\\mathbb{E}_{j \\in i^+, \\varphi \\in \\Omega_j}[V_j(x^\\prime, \\varphi)]$,\n", + "> not the function $V_i(x, \\omega)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "First, we construct a value function from the first subproblem:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "V = SDDP.ValueFunction(model; node = 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Then we can evaluate `V` at a point:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "cost, price = SDDP.evaluate(V, Dict(\"volume\" => 10))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This returns the cost-to-go (`cost`), and the gradient of the cost-to-go\n", + "function with respect to each state variable. Note that since we are\n", + "minimizing, the price has a negative sign: each additional unit of water leads\n", + "to a decrease in the expected long-run cost." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/first_steps.jl b/previews/PR826/tutorial/first_steps.jl new file mode 100644 index 0000000000..61c1ac37c4 --- /dev/null +++ b/previews/PR826/tutorial/first_steps.jl @@ -0,0 +1,912 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # An introduction to SDDP.jl + +# SDDP.jl is a solver for multistage stochastic optimization problems. By +# **multistage**, we mean problems in which an agent makes a sequence of +# decisions over time. By **stochastic**, we mean that the agent is making +# decisions in the presence of uncertainty that is gradually revealed over the +# multiple stages. + +# !!! tip +# Multistage stochastic programming has a lot in common with fields like +# stochastic optimal control, approximate dynamic programming, Markov +# decision processes, and reinforcement learning. If it helps, you can think +# of SDDP as Q-learning in which we approximate the value function using +# linear programming duality. + +# This tutorial is in two parts. First, it is an introduction to the background +# notation and theory we need, and second, it solves a simple multistage +# stochastic programming problem. + +# ## What is a node? + +# A common feature of multistage stochastic optimization problems is that they +# model an agent controlling a system over time. To simplify things initially, +# we're going to start by describing what happens at an instant in time at which +# the agent makes a decision. Only after this will we extend our problem to +# multiple stages and the notion of time. + +# A **node** is a place at which the agent makes a decision. + +# !!! tip +# For readers with a stochastic programming background, "node" is synonymous +# with "stage" in this section. However, for reasons that will become clear +# shortly, there can be more than one "node" per instant in time, which is +# why we prefer the term "node" over "stage." + +# ### States, controls, and random variables + +# The system that we are modeling can be described by three types of variables. + +# 1. **State** variables track a property of the system over time. + +# Each node has an associated _incoming_ state variable (the value of the +# state at the start of the node), and an _outgoing_ state variable (the +# value of the state at the end of the node). +# +# Examples of state variables include the volume of water in a reservoir, the +# number of units of inventory in a warehouse, or the spatial position of a +# moving vehicle. +# +# Because state variables track the system over time, each node must have the +# same set of state variables. +# +# We denote state variables by the letter $x$ for the incoming state variable +# and $x^\prime$ for the outgoing state variable. +# +# 2. **Control** variables are actions taken (implicitly or explicitly) by the +# agent within a node which modify the state variables. +# +# Examples of control variables include releases of water from the reservoir, +# sales or purchasing decisions, and acceleration or braking of the vehicle. +# +# Control variables are local to a node $i$, and they can differ between +# nodes. For example, some control variables may be available within certain +# nodes. +# +# We denote control variables by the letter $u$. +# +# 3. **Random** variables are finite, discrete, exogenous random variables that +# the agent observes at the start of a node, before the control variables are +# decided. +# +# Examples of random variables include rainfall inflow into a reservoir, +# probabilistic perishing of inventory, and steering errors in a vehicle. +# +# Random variables are local to a node $i$, and they can differ between +# nodes. For example, some nodes may have random variables, and some nodes +# may not. +# +# We denote random variables by the Greek letter $\omega$ and the sample +# space from which they are drawn by $\Omega_i$. The probability of sampling +# $\omega$ is denoted $p_{\omega}$ for simplicity. +# +# Importantly, the random variable associated with node $i$ is independent of +# the random variables in all other nodes. + +# ### Dynamics + +# In a node $i$, the three variables are related by a **transition function**, +# which maps the incoming state, the controls, and the random variables to the +# outgoing state as follows: $x^\prime = T_i(x, u, \omega)$. + +# As a result of entering a node $i$ with the incoming state $x$, observing +# random variable $\omega$, and choosing control $u$, the agent incurs a cost +# $C_i(x, u, \omega)$. (If the agent is a maximizer, this can be a profit, or a +# negative cost.) We call $C_i$ the **stage objective**. + +# To choose their control variables in node $i$, the agent uses a **decision** +# **rule** $u = \pi_i(x, \omega)$, which is a function that maps the incoming +# state variable and observation of the random variable to a control $u$. This +# control must satisfy some feasibility requirements $u \in U_i(x, \omega)$. + +# Here is a schematic which we can use to visualize a single node: + +# ![Hazard-decision node](../assets/hazard_decision.png) + +# ## Policy graphs + +# Now that we have a node, we need to connect multiple nodes together to form a +# multistage stochastic program. We call the graph created by connecting nodes +# together a **policy graph**. + +# The simplest type of policy graph is a **linear policy graph**. Here's a +# linear policy graph with three nodes: + +# ![Linear policy graph](../assets/stochastic_linear_policy_graph.png) + +# Here we have dropped the notations inside each node and replaced them by a +# label (1, 2, and 3) to represent nodes `i=1`, `i=2`, and `i=3`. + +# In addition to nodes 1, 2, and 3, there is also a root node (the circle), and +# three arcs. Each arc has an origin node and a destination node, like `1 => 2`, +# and a corresponding probability of transitioning from the origin to the +# destination. Unless specified, we assume that the arc probabilities are +# uniform over the number of outgoing arcs. Thus, in this picture the arc +# probabilities are all 1.0. + +# State variables flow long the arcs of the graph. Thus, the outgoing state +# variable $x^\prime$ from node 1 becomes the incoming state variable $x$ to +# node 2, and so on. + +# We denote the set of nodes by $\mathcal{N}$, the root node by $R$, and the +# probability of transitioning from node $i$ to node $j$ by $p_{ij}$. (If no arc +# exists, then $p_{ij} = 0$.) We define the set of successors of node $i$ as +# $i^+ = \{j \in \mathcal{N} | p_{ij} > 0\}$. + +# Each node in the graph corresponds to a place at which the agent makes a +# decision, and we call moments in time at which the agent makes a decision +# **stages**. By convention, we try to draw policy graphs from left-to-right, +# with the stages as columns. There can be more than one node in a stage! Here's +# an example of a structure we call **Markovian policy graphs**: + +# ![Markovian policy graph](../assets/enso_markovian.png) + +# Here each column represents a moment in time, the squiggly lines represent +# stochastic rainfall, and the rows represent the world in two discrete states: +# El Niño and La Niña. In the El Niño states, the distribution of the rainfall +# random variable is different to the distribution of the rainfall random +# variable in the La Niña states, and there is some switching probability +# between the two states that can be modelled by a Markov chain. + +# Moreover, policy graphs can have cycles! This allows them to model infinite +# horizon problems. Here's another example, taken from the paper +# [Dowson (2020)](https://doi.org/10.1002/net.21932): + +# ![POWDer policy graph](../assets/powder_policy_graph.png) + +# The columns represent time, and the rows represent different states of the +# world. In this case, the rows represent different prices that milk can be sold +# for at the end of each year. The squiggly lines denote a multivariate random +# variable that models the weekly amount of rainfall that occurs. + +# !!! note +# The sum of probabilities on the outgoing arcs of node $i$ can be less than +# 1, i.e., $\sum\limits_{j\in i^+} p_{ij} \le 1$. What does this mean? +# One interpretation is that the probability is a [discount factor](https://en.wikipedia.org/wiki/Discounting). +# Another interpretation is that there is an implicit "zero" node that we +# have not modeled, with $p_{i0} = 1 - \sum\limits_{j\in i^+} p_{ij}$. +# This zero node has $C_0(x, u, \omega) = 0$, and $0^+ = \varnothing$. + +# ## More notation + +# Recall that each node $i$ has a **decision rule** $u = \pi_i(x, \omega)$, +# which is a function that maps the incoming state variable and observation of +# the random variable to a control $u$. + +# The set of decision rules, with one element for each node in the policy graph, +# is called a **policy**. + +# The goal of the agent is to find a policy that minimizes the expected cost of +# starting at the root node with some initial condition $x_R$, and proceeding +# from node to node along the probabilistic arcs until they reach a node with no +# outgoing arcs (or it reaches an implicit "zero" node). + +# ```math +# \min_{\pi} \mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^\pi(x_R, \omega)], +# ``` +# where +# ```math +# V_i^\pi(x, \omega) = C_i(x, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)], +# ``` +# where $u = \pi_i(x, \omega) \in U_i(x, \omega)$, and +# $x^\prime = T_i(x, u, \omega)$. + +# The expectations are a bit complicated, but they are equivalent to: +# ```math +# \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)] = \sum\limits_{j \in i^+} p_{ij} \sum\limits_{\varphi \in \Omega_j} p_{\varphi}V_j(x^\prime, \varphi). +# ``` + +# An optimal policy is the set of decision rules that the agent can use to make +# decisions and achieve the smallest expected cost. + +# ## Assumptions + +# !!! warning +# This section is important! + +# The space of problems you can model with this framework is very large. Too +# large, in fact, for us to form tractable solution algorithms for! Stochastic +# dual dynamic programming requires the following assumptions in order to work: + +# **Assumption 1: finite nodes** +# +# There is a finite number of nodes in $\mathcal{N}$. +# +# **Assumption 2: finite random variables** +# +# The sample space $\Omega_i$ is finite and discrete for each node +# $i\in\mathcal{N}$. +# +# **Assumption 3: convex problems** +# +# Given fixed $\omega$, $C_i(x, u, \omega)$ is a convex function, +# $T_i(x, u, \omega)$ is linear, and $U_i(x, u, \omega)$ is a non-empty, +# bounded convex set with respect to $x$ and $u$. +# +# **Assumption 4: no infinite loops** +# +# For all loops in the policy graph, the product of the arc transition +# probabilities around the loop is strictly less than 1. +# +# **Assumption 5: relatively complete recourse** +# +# This is a technical but important assumption. See [Relatively complete recourse](@ref) +# for more details. + +# !!! note +# SDDP.jl relaxes assumption (3) to allow for integer state and control +# variables, but we won't go into the details here. Assumption (4) +# essentially means that we obtain a discounted-cost solution for +# infinite-horizon problems, instead of an average-cost solution; see +# [Dowson (2020)](https://doi.org/10.1002/net.21932) for details. + +# ## Dynamic programming and subproblems + +# Now that we have formulated our problem, we need some ways of computing +# optimal decision rules. One way is to just use a heuristic like "choose a +# control randomly from the set of feasible controls." However, such a policy +# is unlikely to be optimal. + +# A better way of obtaining an optimal policy is to use [Bellman's principle of +# optimality](https://en.wikipedia.org/wiki/Bellman_equation#Bellman's_principle_of_optimality), +# a.k.a Dynamic Programming, and define a recursive **subproblem** as follows: +# ```math +# \begin{aligned} +# V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega) \\ +# & \bar{x} = x. +# \end{aligned} +# ``` +# Our decision rule, $\pi_i(x, \omega)$, solves this optimization problem and +# returns a $u^*$ corresponding to an optimal solution. +# +# !!! note +# We add $\bar{x}$ as a decision variable, along with the fishing constraint +# $\bar{x} = x$ for two reasons: it makes it obvious that formulating a +# problem with $x \times u$ results in a bilinear program instead of a +# linear program (see Assumption 3), and it simplifies the implementation of +# the SDDP algorithm. + +# These subproblems are very difficult to solve exactly, because they involve +# recursive optimization problems with lots of nested expectations. + +# Therefore, instead of solving them exactly, SDDP.jl works by iteratively +# approximating the expectation term of each subproblem, which is also called +# the cost-to-go term. For now, you don't need to understand the details, other +# than that there is a nasty cost-to-go term that we deal with +# behind-the-scenes. + +# The subproblem view of a multistage stochastic program is also important, +# because it provides a convenient way of communicating the different parts of +# the broader problem, and it is how we will communicate the problem to SDDP.jl. +# All we need to do is drop the cost-to-go term and fishing constraint, and +# define a new subproblem `SP` as: +# ```math +# \begin{aligned} +# \texttt{SP}_i(x, \omega) : \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) \\ +# & x^\prime = T_i(\bar{x}, u, \omega) \\ +# & u \in U_i(\bar{x}, \omega). +# \end{aligned} +# ``` +# !!! note +# When we talk about formulating a **subproblem** with SDDP.jl, this is the +# formulation we mean. + +# We've retained the transition function and uncertainty set because they help +# to motivate the different components of the subproblem. However, in general, +# the subproblem can be more general. A better (less restrictive) representation +# might be: +# ```math +# \begin{aligned} +# \texttt{SP}_i(x, \omega) : \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, x^\prime, u, \omega) \\ +# & (\bar{x}, x^\prime, u) \in \mathcal{X}_i(\omega). +# \end{aligned} +# ``` +# Note that the outgoing state variable can appear in the objective, and we can +# add constraints involving the incoming and outgoing state variables. It +# should be obvious how to map between the two representations. + +# ## Example: hydro-thermal scheduling + +# Hydrothermal scheduling is the most common application of stochastic dual +# dynamic programming. To illustrate some of the basic functionality of +# `SDDP.jl`, we implement a very simple model of the hydrothermal scheduling +# problem. + +# ### Problem statement + +# We consider the problem of scheduling electrical generation over three weeks +# in order to meet a known demand of 150 MWh in each week. +# +# There are two generators: a thermal generator, and a hydro generator. In each +# week, the agent needs to decide how much energy to generate from thermal, and +# how much energy to generate from hydro. +# +# The thermal generator has a short-run marginal cost of \$50/MWh in the first +# stage, \$100/MWh in the second stage, and \$150/MWh in the third stage. +# +# The hydro generator has a short-run marginal cost of \$0/MWh. +# +# The hydro generator draws water from a reservoir which has a maximum capacity +# of 200 MWh. (Although water is usually measured in m³, we measure it in the +# energy-equivalent MWh to simplify things. In practice, there is a conversion +# function between m³ flowing throw the turbine and MWh.) At the start of the +# first time period, the reservoir is full. +# +# In addition to the ability to generate electricity by passing water through +# the hydroelectric turbine, the hydro generator can also spill water down a +# spillway (bypassing the turbine) in order to prevent the water from +# over-topping the dam. We assume that there is no cost of spillage. + +# In addition to water leaving the reservoir, water that flows into the reservoir +# through rainfall or rivers are referred to as inflows. These inflows are +# uncertain, and are the cause of the main trade-off in hydro-thermal +# scheduling: the desire to use water now to generate cheap electricity, against +# the risk that future inflows will be low, leading to blackouts or expensive +# thermal generation. + +# For our simple model, we assume that the inflows can be modelled by a discrete +# distribution with the three outcomes given in the following table: +# +# | ω | 0 | 50 | 100 | +# | ---- | --- | --- | --- | +# | P(ω) | 1/3 | 1/3 | 1/3 | + +# The value of the noise (the random variable) is observed by the agent at the +# start of each stage. This makes the problem a _wait-and-see_ or +# _hazard-decision_ formulation. + +# The goal of the agent is to minimize the expected cost of generation over the +# three weeks. + +# ### Formulating the problem + +# Before going further, we need to load SDDP.jl: + +using SDDP + +# #### Graph structure + +# First, we need to identify the structure of the policy graph. From the problem +# statement, we want to model the problem over three weeks in weekly stages. +# Therefore, the policy graph is a linear graph with three stages: + +graph = SDDP.LinearGraph(3) + +# #### Building the subproblem + +# Next, we need to construct the associated subproblem for each node in `graph`. +# To do so, we need to provide SDDP.jl a function which takes two arguments. The +# first is `subproblem::Model`, which is an empty JuMP model. The second is +# `node`, which is the name of each node in the policy graph. If the graph is +# linear, SDDP defaults to naming the nodes using the integers in `1:T`. Here's +# an example that we are going to flesh out over the next few paragraphs: + +function subproblem_builder(subproblem::Model, node::Int) + ## ... stuff to go here ... + return subproblem +end + +# !!! warning +# If you use a different type of graph, `node` may be a type different to +# `Int`. For example, in [`SDDP.MarkovianGraph`](@ref), `node` is a +# `Tuple{Int,Int}`. + +# #### State variables + +# The first part of the subproblem we need to identify are the state variables. +# Since we only have one reservoir, there is only one state variable, `volume`, +# the volume of water in the reservoir [MWh]. +# +# The volume had bounds of `[0, 200]`, and the reservoir was full at the start +# of time, so $x_R = 200$. + +# We add state variables to our `subproblem` using JuMP's `@variable` macro. +# However, in addition to the usual syntax, we also pass `SDDP.State`, and we +# need to provide the initial value ($x_R$) using the `initial_value` keyword. + +function subproblem_builder(subproblem::Model, node::Int) + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + return subproblem +end + +# The syntax for adding a state variable is a little obtuse, because `volume` is +# not single JuMP variable. Instead, `volume` is a struct with two fields, `.in` +# and `.out`, corresponding to the incoming and outgoing state variables +# respectively. + +# !!! note +# We don't need to add the fishing constraint $\bar{x} = x$; SDDP.jl does +# this automatically. + +# #### Control variables + +# The next part of the subproblem we need to identify are the control variables. +# The control variables for our problem are: +# - `thermal_generation`: the quantity of energy generated from thermal +# [MWh/week] +# - `hydro_generation`: the quantity of energy generated from hydro [MWh/week] +# - `hydro_spill`: the volume of water spilled from the reservoir in each week +# [MWh/week] +# Each of these variables is non-negative. + +# We add control variables to our `subproblem` as normal JuMP variables, using +# `@variable` or `@variables`: + +function subproblem_builder(subproblem::Model, node::Int) + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Control variables + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + end) + return subproblem +end + +# !!! tip +# Modeling is an art, and a tricky part of that art is figuring out which +# variables are state variables, and which are control variables. A good +# rule is: if you need a value of a control variable in some future node to +# make a decision, it is a state variable instead. + +# #### Random variables + +# The next step is to identify any random variables. In our example, we had +# - `inflow`: the quantity of water that flows into the reservoir each week +# [MWh/week] + +# To add an uncertain variable to the model, we create a new JuMP variable +# `inflow`, and then call the function [`SDDP.parameterize`](@ref). The +# [`SDDP.parameterize`](@ref) function takes three arguments: the subproblem, a +# vector of realizations, and a corresponding vector of probabilities. + +function subproblem_builder(subproblem::Model, node::Int) + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Control variables + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + end) + ## Random variables + @variable(subproblem, inflow) + Ω = [0.0, 50.0, 100.0] + P = [1 / 3, 1 / 3, 1 / 3] + SDDP.parameterize(subproblem, Ω, P) do ω + return JuMP.fix(inflow, ω) + end + return subproblem +end + +# Note how we use the JuMP function +# [`JuMP.fix`](https://jump.dev/JuMP.jl/stable/reference/variables/#JuMP.fix) +# to set the value of the `inflow` variable to `ω`. + +# !!! warning +# [`SDDP.parameterize`](@ref) can only be called once in each subproblem +# definition! If your random variable is multi-variate, read +# [Add multi-dimensional noise terms](@ref). + +# #### Transition function and constraints + +# Now that we've identified our variables, we can define the transition function +# and the constraints. + +# For our problem, the state variable is the volume of water in the reservoir. +# The volume of water decreases in response to water being used for hydro +# generation and spillage. So the transition function is: +# `volume.out = volume.in - hydro_generation - hydro_spill + inflow`. (Note how +# we use `volume.in` and `volume.out` to refer to the incoming and outgoing +# state variables.) + +# There is also a constraint that the total generation must sum to 150 MWh. + +# Both the transition function and any additional constraint are added using +# JuMP's `@constraint` and `@constraints` macro. + +function subproblem_builder(subproblem::Model, node::Int) + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Control variables + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + end) + ## Random variables + @variable(subproblem, inflow) + Ω = [0.0, 50.0, 100.0] + P = [1 / 3, 1 / 3, 1 / 3] + SDDP.parameterize(subproblem, Ω, P) do ω + return JuMP.fix(inflow, ω) + end + ## Transition function and constraints + @constraints( + subproblem, + begin + volume.out == volume.in - hydro_generation - hydro_spill + inflow + demand_constraint, hydro_generation + thermal_generation == 150 + end + ) + return subproblem +end + +# #### Objective function + +# Finally, we need to add an objective function using `@stageobjective`. The +# objective of the agent is to minimize the cost of thermal generation. This is +# complicated by a fuel cost that depends on the `node`. +# +# One possibility is to use an `if` statement on `node` to define the correct +# objective: + +function subproblem_builder(subproblem::Model, node::Int) + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Control variables + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + end) + ## Random variables + @variable(subproblem, inflow) + Ω = [0.0, 50.0, 100.0] + P = [1 / 3, 1 / 3, 1 / 3] + SDDP.parameterize(subproblem, Ω, P) do ω + return JuMP.fix(inflow, ω) + end + ## Transition function and constraints + @constraints( + subproblem, + begin + volume.out == volume.in - hydro_generation - hydro_spill + inflow + demand_constraint, hydro_generation + thermal_generation == 150 + end + ) + ## Stage-objective + if node == 1 + @stageobjective(subproblem, 50 * thermal_generation) + elseif node == 2 + @stageobjective(subproblem, 100 * thermal_generation) + else + @assert node == 3 + @stageobjective(subproblem, 150 * thermal_generation) + end + return subproblem +end + +# A second possibility is to use an array of fuel costs, and use `node` to index +# the correct value: + +function subproblem_builder(subproblem::Model, node::Int) + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Control variables + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + end) + ## Random variables + @variable(subproblem, inflow) + Ω = [0.0, 50.0, 100.0] + P = [1 / 3, 1 / 3, 1 / 3] + SDDP.parameterize(subproblem, Ω, P) do ω + return JuMP.fix(inflow, ω) + end + ## Transition function and constraints + @constraints( + subproblem, + begin + volume.out == volume.in - hydro_generation - hydro_spill + inflow + demand_constraint, hydro_generation + thermal_generation == 150 + end + ) + ## Stage-objective + fuel_cost = [50, 100, 150] + @stageobjective(subproblem, fuel_cost[node] * thermal_generation) + return subproblem +end + +# ### Constructing the model + +# Now that we've written our subproblem, we need to construct the full model. +# For that, we're going to need a linear solver. Let's choose HiGHS: + +using HiGHS + +# !!! warning +# In larger problems, you should use a more robust commercial LP solver like +# Gurobi. Read [Words of warning](@ref) for more details. + +# Then, we can create a full model using [`SDDP.PolicyGraph`](@ref), passing our +# `subproblem_builder` function as the first argument, and our `graph` as the +# second: + +model = SDDP.PolicyGraph( + subproblem_builder, + graph; + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) + +# * `sense`: the optimization sense. Must be `:Min` or `:Max`. +# * `lower_bound`: you _must_ supply a valid bound on the objective. For our +# problem, we know that we cannot incur a negative cost so \$0 is a valid +# lower bound. +# * `optimizer`: This is borrowed directly from JuMP's `Model` constructor: +# `Model(HiGHS.Optimizer)` + +# Because linear policy graphs are the most commonly used structure, we can use +# [`SDDP.LinearPolicyGraph`](@ref) instead of passing `SDDP.LinearGraph(3)` to +# [`SDDP.PolicyGraph`](@ref). + +model = SDDP.LinearPolicyGraph( + subproblem_builder; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) + +# There is also the option is to use Julia's `do` syntax to avoid needing to +# define a `subproblem_builder` function separately: + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, node + ## State variables + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Control variables + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + end) + ## Random variables + @variable(subproblem, inflow) + Ω = [0.0, 50.0, 100.0] + P = [1 / 3, 1 / 3, 1 / 3] + SDDP.parameterize(subproblem, Ω, P) do ω + return JuMP.fix(inflow, ω) + end + ## Transition function and constraints + @constraints( + subproblem, + begin + volume.out == volume.in - hydro_generation - hydro_spill + inflow + demand_constraint, hydro_generation + thermal_generation == 150 + end + ) + ## Stage-objective + if node == 1 + @stageobjective(subproblem, 50 * thermal_generation) + elseif node == 2 + @stageobjective(subproblem, 100 * thermal_generation) + else + @assert node == 3 + @stageobjective(subproblem, 150 * thermal_generation) + end +end + +# !!! info +# Julia's `do` syntax is just a different way of passing an anonymous +# function `inner` to some function `outer` which takes `inner` as the first +# argument. For example, given: +# ```julia +# outer(inner::Function, x, y) = inner(x, y) +# ``` +# then +# ```julia +# outer(1, 2) do x, y +# return x^2 + y^2 +# end +# ``` +# is equivalent to: +# ```julia +# outer((x, y) -> x^2 + y^2, 1, 2) +# ``` +# For our purpose, `inner` is `subproblem_builder`, and `outer` is +# [`SDDP.PolicyGraph`](@ref). + +# ## Training a policy + +# Now we have a model, which is a description of the policy graph, we need to +# train a policy. Models can be trained using the [`SDDP.train`](@ref) function. +# It accepts a number of keyword arguments. `iteration_limit` terminates the +# training after the provided number of iterations. + +SDDP.train(model; iteration_limit = 10) + +# There's a lot going on in this printout! Let's break it down. + +# The first section, "problem," gives some problem statistics. In this example +# there are 3 nodes, 1 state variable, and 27 scenarios ($3^3$). We haven't +# solved this problem before so there are no existing cuts. + +# The "options" section lists some options we are using to solve the problem. +# For more information on the numerical stability report, read the +# [Numerical stability report](@ref) section. + +# The "subproblem structure" section also needs explaining. This looks at all of +# the nodes in the policy graph and reports the minimum and maximum number of +# variables and each constraint type in the corresponding subproblem. In this +# case each subproblem has 7 variables and various numbers of different +# constraint types. Note that the exact numbers may not correspond to the +# formulation as you wrote it, because SDDP.jl adds some extra variables for the +# cost-to-go function. + +# Then comes the iteration log, which is the main part of the printout. It has +# the following columns: +# - `iteration`: the SDDP iteration +# - `simulation`: the cost of the single forward pass simulation for that +# iteration. This value is stochastic and is not guaranteed to improve over +# time. However, it's useful to check that the units are reasonable, and that +# it is not deterministic if you intended for the problem to be stochastic, +# etc. +# - `bound`: this is a lower bound (upper if maximizing) for the value of the +# optimal policy. This bound should be monotonically improving (increasing if +# minimizing, decreasing if maximizing), but in some cases it can temporarily +# worsen due to cut selection, especially in the early iterations of the +# algorithm. +# - `time (s)`: the total number of seconds spent solving so far +# - `solves`: the total number of subproblem solves to date. This can be very +# large! +# - `pid`: the ID of the processor used to solve that iteration. This +# should be 1 unless you are using parallel computation. + +# In addition, if the first character of a line is `†`, then SDDP.jl experienced +# numerical issues during the solve, but successfully recovered. + +# The printout finishes with some summary statistics: +# +# - `status`: why did the solver stop? +# - `total time (s)`, `best bound`, and `total solves` are the values from the +# last iteration of the solve. +# - `simulation ci`: a confidence interval that estimates the quality of the +# policy from the `Simulation` column. +# - `numeric issues`: the number of iterations that experienced numerical +# issues. + +# !!! warning +# The `simulation ci` result can be misleading if you run a small number of +# iterations, or if the initial simulations are very bad. On a more +# technical note, it is an _in-sample simulation_, which may not reflect the +# true performance of the policy. See [Obtaining bounds](@ref) for more +# details. + +# ## Obtaining the decision rule + +# After training a policy, we can create a decision rule using +# [`SDDP.DecisionRule`](@ref): + +rule = SDDP.DecisionRule(model; node = 1) + +# Then, to evaluate the decision rule, we use [`SDDP.evaluate`](@ref): + +solution = SDDP.evaluate( + rule; + incoming_state = Dict(:volume => 150.0), + noise = 50.0, + controls_to_record = [:hydro_generation, :thermal_generation], +) + +# ## Simulating the policy + +# Once you have a trained policy, you can also simulate it using +# [`SDDP.simulate`](@ref). The return value from `simulate` is a vector with one +# element for each replication. Each element is itself a vector, with one +# element for each stage. Each element, corresponding to a particular stage in a +# particular replication, is a dictionary that records information from the +# simulation. + +simulations = SDDP.simulate( + ## The trained model to simulate. + model, + ## The number of replications. + 100, + ## A list of names to record the values of. + [:volume, :thermal_generation, :hydro_generation, :hydro_spill], +) + +replication = 1 +stage = 2 +simulations[replication][stage] + +# Ignore many of the entries for now; they will be relevant later. + +# One element of interest is `:volume`. + +outgoing_volume = map(simulations[1]) do node + return node[:volume].out +end + +# Another is `:thermal_generation`. + +thermal_generation = map(simulations[1]) do node + return node[:thermal_generation] +end + +# ## Obtaining bounds + +# Because the optimal policy is stochastic, one common approach to quantify the +# quality of the policy is to construct a confidence interval for the expected +# cost by summing the stage objectives along each simulation. + +objectives = map(simulations) do simulation + return sum(stage[:stage_objective] for stage in simulation) +end + +μ, ci = SDDP.confidence_interval(objectives) +println("Confidence interval: ", μ, " ± ", ci) + +# This confidence interval is an estimate for an upper bound of the policy's +# quality. We can calculate the lower bound using [`SDDP.calculate_bound`](@ref). + +println("Lower bound: ", SDDP.calculate_bound(model)) + +# !!! tip +# The upper- and lower-bounds are reversed if maximizing, i.e., [`SDDP.calculate_bound`](@ref). +# returns an upper bound. + +# ## Custom recorders + +# In addition to simulating the primal values of variables, we can also pass +# custom recorder functions. Each of these functions takes one argument, the +# JuMP subproblem corresponding to each node. This function gets called after we +# have solved each node as we traverse the policy graph in the simulation. + +# For example, the dual of the demand constraint (which we named +# `demand_constraint`) corresponds to the price we should charge for +# electricity, since it represents the cost of each additional unit of demand. +# To calculate this, we can go: + +simulations = SDDP.simulate( + model, + 1; ## Perform a single simulation + custom_recorders = Dict{Symbol,Function}( + :price => (sp::JuMP.Model) -> JuMP.dual(sp[:demand_constraint]), + ), +) + +prices = map(simulations[1]) do node + return node[:price] +end + +# ## Extracting the marginal water values + +# Finally, we can use [`SDDP.ValueFunction`](@ref) and [`SDDP.evaluate`](@ref) +# to obtain and evaluate the value function at different points in the +# state-space. + +# !!! note +# By "value function" we mean $\mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]$, +# not the function $V_i(x, \omega)$. + +# First, we construct a value function from the first subproblem: + +V = SDDP.ValueFunction(model; node = 1) + +# Then we can evaluate `V` at a point: + +cost, price = SDDP.evaluate(V, Dict("volume" => 10)) + +# This returns the cost-to-go (`cost`), and the gradient of the cost-to-go +# function with respect to each state variable. Note that since we are +# minimizing, the price has a negative sign: each additional unit of water leads +# to a decrease in the expected long-run cost. diff --git a/previews/PR826/tutorial/first_steps/index.html b/previews/PR826/tutorial/first_steps/index.html new file mode 100644 index 0000000000..e3498e9dff --- /dev/null +++ b/previews/PR826/tutorial/first_steps/index.html @@ -0,0 +1,295 @@ + +An introduction to SDDP.jl · SDDP.jl

An introduction to SDDP.jl

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

SDDP.jl is a solver for multistage stochastic optimization problems. By multistage, we mean problems in which an agent makes a sequence of decisions over time. By stochastic, we mean that the agent is making decisions in the presence of uncertainty that is gradually revealed over the multiple stages.

Tip

Multistage stochastic programming has a lot in common with fields like stochastic optimal control, approximate dynamic programming, Markov decision processes, and reinforcement learning. If it helps, you can think of SDDP as Q-learning in which we approximate the value function using linear programming duality.

This tutorial is in two parts. First, it is an introduction to the background notation and theory we need, and second, it solves a simple multistage stochastic programming problem.

What is a node?

A common feature of multistage stochastic optimization problems is that they model an agent controlling a system over time. To simplify things initially, we're going to start by describing what happens at an instant in time at which the agent makes a decision. Only after this will we extend our problem to multiple stages and the notion of time.

A node is a place at which the agent makes a decision.

Tip

For readers with a stochastic programming background, "node" is synonymous with "stage" in this section. However, for reasons that will become clear shortly, there can be more than one "node" per instant in time, which is why we prefer the term "node" over "stage."

States, controls, and random variables

The system that we are modeling can be described by three types of variables.

  1. State variables track a property of the system over time.

    Each node has an associated incoming state variable (the value of the state at the start of the node), and an outgoing state variable (the value of the state at the end of the node).

    Examples of state variables include the volume of water in a reservoir, the number of units of inventory in a warehouse, or the spatial position of a moving vehicle.

    Because state variables track the system over time, each node must have the same set of state variables.

    We denote state variables by the letter $x$ for the incoming state variable and $x^\prime$ for the outgoing state variable.

  2. Control variables are actions taken (implicitly or explicitly) by the agent within a node which modify the state variables.

    Examples of control variables include releases of water from the reservoir, sales or purchasing decisions, and acceleration or braking of the vehicle.

    Control variables are local to a node $i$, and they can differ between nodes. For example, some control variables may be available within certain nodes.

    We denote control variables by the letter $u$.

  3. Random variables are finite, discrete, exogenous random variables that the agent observes at the start of a node, before the control variables are decided.

    Examples of random variables include rainfall inflow into a reservoir, probabilistic perishing of inventory, and steering errors in a vehicle.

    Random variables are local to a node $i$, and they can differ between nodes. For example, some nodes may have random variables, and some nodes may not.

    We denote random variables by the Greek letter $\omega$ and the sample space from which they are drawn by $\Omega_i$. The probability of sampling $\omega$ is denoted $p_{\omega}$ for simplicity.

    Importantly, the random variable associated with node $i$ is independent of the random variables in all other nodes.

Dynamics

In a node $i$, the three variables are related by a transition function, which maps the incoming state, the controls, and the random variables to the outgoing state as follows: $x^\prime = T_i(x, u, \omega)$.

As a result of entering a node $i$ with the incoming state $x$, observing random variable $\omega$, and choosing control $u$, the agent incurs a cost $C_i(x, u, \omega)$. (If the agent is a maximizer, this can be a profit, or a negative cost.) We call $C_i$ the stage objective.

To choose their control variables in node $i$, the agent uses a decision rule $u = \pi_i(x, \omega)$, which is a function that maps the incoming state variable and observation of the random variable to a control $u$. This control must satisfy some feasibility requirements $u \in U_i(x, \omega)$.

Here is a schematic which we can use to visualize a single node:

Hazard-decision node

Policy graphs

Now that we have a node, we need to connect multiple nodes together to form a multistage stochastic program. We call the graph created by connecting nodes together a policy graph.

The simplest type of policy graph is a linear policy graph. Here's a linear policy graph with three nodes:

Linear policy graph

Here we have dropped the notations inside each node and replaced them by a label (1, 2, and 3) to represent nodes i=1, i=2, and i=3.

In addition to nodes 1, 2, and 3, there is also a root node (the circle), and three arcs. Each arc has an origin node and a destination node, like 1 => 2, and a corresponding probability of transitioning from the origin to the destination. Unless specified, we assume that the arc probabilities are uniform over the number of outgoing arcs. Thus, in this picture the arc probabilities are all 1.0.

State variables flow long the arcs of the graph. Thus, the outgoing state variable $x^\prime$ from node 1 becomes the incoming state variable $x$ to node 2, and so on.

We denote the set of nodes by $\mathcal{N}$, the root node by $R$, and the probability of transitioning from node $i$ to node $j$ by $p_{ij}$. (If no arc exists, then $p_{ij} = 0$.) We define the set of successors of node $i$ as $i^+ = \{j \in \mathcal{N} | p_{ij} > 0\}$.

Each node in the graph corresponds to a place at which the agent makes a decision, and we call moments in time at which the agent makes a decision stages. By convention, we try to draw policy graphs from left-to-right, with the stages as columns. There can be more than one node in a stage! Here's an example of a structure we call Markovian policy graphs:

Markovian policy graph

Here each column represents a moment in time, the squiggly lines represent stochastic rainfall, and the rows represent the world in two discrete states: El Niño and La Niña. In the El Niño states, the distribution of the rainfall random variable is different to the distribution of the rainfall random variable in the La Niña states, and there is some switching probability between the two states that can be modelled by a Markov chain.

Moreover, policy graphs can have cycles! This allows them to model infinite horizon problems. Here's another example, taken from the paper Dowson (2020):

POWDer policy graph

The columns represent time, and the rows represent different states of the world. In this case, the rows represent different prices that milk can be sold for at the end of each year. The squiggly lines denote a multivariate random variable that models the weekly amount of rainfall that occurs.

Note

The sum of probabilities on the outgoing arcs of node $i$ can be less than 1, i.e., $\sum\limits_{j\in i^+} p_{ij} \le 1$. What does this mean? One interpretation is that the probability is a discount factor. Another interpretation is that there is an implicit "zero" node that we have not modeled, with $p_{i0} = 1 - \sum\limits_{j\in i^+} p_{ij}$. This zero node has $C_0(x, u, \omega) = 0$, and $0^+ = \varnothing$.

More notation

Recall that each node $i$ has a decision rule $u = \pi_i(x, \omega)$, which is a function that maps the incoming state variable and observation of the random variable to a control $u$.

The set of decision rules, with one element for each node in the policy graph, is called a policy.

The goal of the agent is to find a policy that minimizes the expected cost of starting at the root node with some initial condition $x_R$, and proceeding from node to node along the probabilistic arcs until they reach a node with no outgoing arcs (or it reaches an implicit "zero" node).

\[\min_{\pi} \mathbb{E}_{i \in R^+, \omega \in \Omega_i}[V_i^\pi(x_R, \omega)],\]

where

\[V_i^\pi(x, \omega) = C_i(x, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)],\]

where $u = \pi_i(x, \omega) \in U_i(x, \omega)$, and $x^\prime = T_i(x, u, \omega)$.

The expectations are a bit complicated, but they are equivalent to:

\[\mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)] = \sum\limits_{j \in i^+} p_{ij} \sum\limits_{\varphi \in \Omega_j} p_{\varphi}V_j(x^\prime, \varphi).\]

An optimal policy is the set of decision rules that the agent can use to make decisions and achieve the smallest expected cost.

Assumptions

Warning

This section is important!

The space of problems you can model with this framework is very large. Too large, in fact, for us to form tractable solution algorithms for! Stochastic dual dynamic programming requires the following assumptions in order to work:

Assumption 1: finite nodes

There is a finite number of nodes in $\mathcal{N}$.

Assumption 2: finite random variables

The sample space $\Omega_i$ is finite and discrete for each node $i\in\mathcal{N}$.

Assumption 3: convex problems

Given fixed $\omega$, $C_i(x, u, \omega)$ is a convex function, $T_i(x, u, \omega)$ is linear, and $U_i(x, u, \omega)$ is a non-empty, bounded convex set with respect to $x$ and $u$.

Assumption 4: no infinite loops

For all loops in the policy graph, the product of the arc transition probabilities around the loop is strictly less than 1.

Assumption 5: relatively complete recourse

This is a technical but important assumption. See Relatively complete recourse for more details.

Note

SDDP.jl relaxes assumption (3) to allow for integer state and control variables, but we won't go into the details here. Assumption (4) essentially means that we obtain a discounted-cost solution for infinite-horizon problems, instead of an average-cost solution; see Dowson (2020) for details.

Dynamic programming and subproblems

Now that we have formulated our problem, we need some ways of computing optimal decision rules. One way is to just use a heuristic like "choose a control randomly from the set of feasible controls." However, such a policy is unlikely to be optimal.

A better way of obtaining an optimal policy is to use Bellman's principle of optimality, a.k.a Dynamic Programming, and define a recursive subproblem as follows:

\[\begin{aligned} +V_i(x, \omega) = \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) + \mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]\\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega) \\ +& \bar{x} = x. +\end{aligned}\]

Our decision rule, $\pi_i(x, \omega)$, solves this optimization problem and returns a $u^*$ corresponding to an optimal solution.

Note

We add $\bar{x}$ as a decision variable, along with the fishing constraint $\bar{x} = x$ for two reasons: it makes it obvious that formulating a problem with $x \times u$ results in a bilinear program instead of a linear program (see Assumption 3), and it simplifies the implementation of the SDDP algorithm.

These subproblems are very difficult to solve exactly, because they involve recursive optimization problems with lots of nested expectations.

Therefore, instead of solving them exactly, SDDP.jl works by iteratively approximating the expectation term of each subproblem, which is also called the cost-to-go term. For now, you don't need to understand the details, other than that there is a nasty cost-to-go term that we deal with behind-the-scenes.

The subproblem view of a multistage stochastic program is also important, because it provides a convenient way of communicating the different parts of the broader problem, and it is how we will communicate the problem to SDDP.jl. All we need to do is drop the cost-to-go term and fishing constraint, and define a new subproblem SP as:

\[\begin{aligned} +\texttt{SP}_i(x, \omega) : \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, u, \omega) \\ +& x^\prime = T_i(\bar{x}, u, \omega) \\ +& u \in U_i(\bar{x}, \omega). +\end{aligned}\]

Note

When we talk about formulating a subproblem with SDDP.jl, this is the formulation we mean.

We've retained the transition function and uncertainty set because they help to motivate the different components of the subproblem. However, in general, the subproblem can be more general. A better (less restrictive) representation might be:

\[\begin{aligned} +\texttt{SP}_i(x, \omega) : \min\limits_{\bar{x}, x^\prime, u} \;\; & C_i(\bar{x}, x^\prime, u, \omega) \\ +& (\bar{x}, x^\prime, u) \in \mathcal{X}_i(\omega). +\end{aligned}\]

Note that the outgoing state variable can appear in the objective, and we can add constraints involving the incoming and outgoing state variables. It should be obvious how to map between the two representations.

Example: hydro-thermal scheduling

Hydrothermal scheduling is the most common application of stochastic dual dynamic programming. To illustrate some of the basic functionality of SDDP.jl, we implement a very simple model of the hydrothermal scheduling problem.

Problem statement

We consider the problem of scheduling electrical generation over three weeks in order to meet a known demand of 150 MWh in each week.

There are two generators: a thermal generator, and a hydro generator. In each week, the agent needs to decide how much energy to generate from thermal, and how much energy to generate from hydro.

The thermal generator has a short-run marginal cost of $50/MWh in the first stage, $100/MWh in the second stage, and $150/MWh in the third stage.

The hydro generator has a short-run marginal cost of $0/MWh.

The hydro generator draws water from a reservoir which has a maximum capacity of 200 MWh. (Although water is usually measured in m³, we measure it in the energy-equivalent MWh to simplify things. In practice, there is a conversion function between m³ flowing throw the turbine and MWh.) At the start of the first time period, the reservoir is full.

In addition to the ability to generate electricity by passing water through the hydroelectric turbine, the hydro generator can also spill water down a spillway (bypassing the turbine) in order to prevent the water from over-topping the dam. We assume that there is no cost of spillage.

In addition to water leaving the reservoir, water that flows into the reservoir through rainfall or rivers are referred to as inflows. These inflows are uncertain, and are the cause of the main trade-off in hydro-thermal scheduling: the desire to use water now to generate cheap electricity, against the risk that future inflows will be low, leading to blackouts or expensive thermal generation.

For our simple model, we assume that the inflows can be modelled by a discrete distribution with the three outcomes given in the following table:

ω050100
P(ω)1/31/31/3

The value of the noise (the random variable) is observed by the agent at the start of each stage. This makes the problem a wait-and-see or hazard-decision formulation.

The goal of the agent is to minimize the expected cost of generation over the three weeks.

Formulating the problem

Before going further, we need to load SDDP.jl:

using SDDP

Graph structure

First, we need to identify the structure of the policy graph. From the problem statement, we want to model the problem over three weeks in weekly stages. Therefore, the policy graph is a linear graph with three stages:

graph = SDDP.LinearGraph(3)
Root
+ 0
+Nodes
+ 1
+ 2
+ 3
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 3 w.p. 1.0

Building the subproblem

Next, we need to construct the associated subproblem for each node in graph. To do so, we need to provide SDDP.jl a function which takes two arguments. The first is subproblem::Model, which is an empty JuMP model. The second is node, which is the name of each node in the policy graph. If the graph is linear, SDDP defaults to naming the nodes using the integers in 1:T. Here's an example that we are going to flesh out over the next few paragraphs:

function subproblem_builder(subproblem::Model, node::Int)
+    # ... stuff to go here ...
+    return subproblem
+end
subproblem_builder (generic function with 1 method)
Warning

If you use a different type of graph, node may be a type different to Int. For example, in SDDP.MarkovianGraph, node is a Tuple{Int,Int}.

State variables

The first part of the subproblem we need to identify are the state variables. Since we only have one reservoir, there is only one state variable, volume, the volume of water in the reservoir [MWh].

The volume had bounds of [0, 200], and the reservoir was full at the start of time, so $x_R = 200$.

We add state variables to our subproblem using JuMP's @variable macro. However, in addition to the usual syntax, we also pass SDDP.State, and we need to provide the initial value ($x_R$) using the initial_value keyword.

function subproblem_builder(subproblem::Model, node::Int)
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    return subproblem
+end
subproblem_builder (generic function with 1 method)

The syntax for adding a state variable is a little obtuse, because volume is not single JuMP variable. Instead, volume is a struct with two fields, .in and .out, corresponding to the incoming and outgoing state variables respectively.

Note

We don't need to add the fishing constraint $\bar{x} = x$; SDDP.jl does this automatically.

Control variables

The next part of the subproblem we need to identify are the control variables. The control variables for our problem are:

  • thermal_generation: the quantity of energy generated from thermal [MWh/week]
  • hydro_generation: the quantity of energy generated from hydro [MWh/week]
  • hydro_spill: the volume of water spilled from the reservoir in each week [MWh/week]

Each of these variables is non-negative.

We add control variables to our subproblem as normal JuMP variables, using @variable or @variables:

function subproblem_builder(subproblem::Model, node::Int)
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Control variables
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+    end)
+    return subproblem
+end
subproblem_builder (generic function with 1 method)
Tip

Modeling is an art, and a tricky part of that art is figuring out which variables are state variables, and which are control variables. A good rule is: if you need a value of a control variable in some future node to make a decision, it is a state variable instead.

Random variables

The next step is to identify any random variables. In our example, we had

  • inflow: the quantity of water that flows into the reservoir each week [MWh/week]

To add an uncertain variable to the model, we create a new JuMP variable inflow, and then call the function SDDP.parameterize. The SDDP.parameterize function takes three arguments: the subproblem, a vector of realizations, and a corresponding vector of probabilities.

function subproblem_builder(subproblem::Model, node::Int)
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Control variables
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+    end)
+    # Random variables
+    @variable(subproblem, inflow)
+    Ω = [0.0, 50.0, 100.0]
+    P = [1 / 3, 1 / 3, 1 / 3]
+    SDDP.parameterize(subproblem, Ω, P) do ω
+        return JuMP.fix(inflow, ω)
+    end
+    return subproblem
+end
subproblem_builder (generic function with 1 method)

Note how we use the JuMP function JuMP.fix to set the value of the inflow variable to ω.

Warning

SDDP.parameterize can only be called once in each subproblem definition! If your random variable is multi-variate, read Add multi-dimensional noise terms.

Transition function and constraints

Now that we've identified our variables, we can define the transition function and the constraints.

For our problem, the state variable is the volume of water in the reservoir. The volume of water decreases in response to water being used for hydro generation and spillage. So the transition function is: volume.out = volume.in - hydro_generation - hydro_spill + inflow. (Note how we use volume.in and volume.out to refer to the incoming and outgoing state variables.)

There is also a constraint that the total generation must sum to 150 MWh.

Both the transition function and any additional constraint are added using JuMP's @constraint and @constraints macro.

function subproblem_builder(subproblem::Model, node::Int)
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Control variables
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+    end)
+    # Random variables
+    @variable(subproblem, inflow)
+    Ω = [0.0, 50.0, 100.0]
+    P = [1 / 3, 1 / 3, 1 / 3]
+    SDDP.parameterize(subproblem, Ω, P) do ω
+        return JuMP.fix(inflow, ω)
+    end
+    # Transition function and constraints
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in - hydro_generation - hydro_spill + inflow
+            demand_constraint, hydro_generation + thermal_generation == 150
+        end
+    )
+    return subproblem
+end
subproblem_builder (generic function with 1 method)

Objective function

Finally, we need to add an objective function using @stageobjective. The objective of the agent is to minimize the cost of thermal generation. This is complicated by a fuel cost that depends on the node.

One possibility is to use an if statement on node to define the correct objective:

function subproblem_builder(subproblem::Model, node::Int)
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Control variables
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+    end)
+    # Random variables
+    @variable(subproblem, inflow)
+    Ω = [0.0, 50.0, 100.0]
+    P = [1 / 3, 1 / 3, 1 / 3]
+    SDDP.parameterize(subproblem, Ω, P) do ω
+        return JuMP.fix(inflow, ω)
+    end
+    # Transition function and constraints
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in - hydro_generation - hydro_spill + inflow
+            demand_constraint, hydro_generation + thermal_generation == 150
+        end
+    )
+    # Stage-objective
+    if node == 1
+        @stageobjective(subproblem, 50 * thermal_generation)
+    elseif node == 2
+        @stageobjective(subproblem, 100 * thermal_generation)
+    else
+        @assert node == 3
+        @stageobjective(subproblem, 150 * thermal_generation)
+    end
+    return subproblem
+end
subproblem_builder (generic function with 1 method)

A second possibility is to use an array of fuel costs, and use node to index the correct value:

function subproblem_builder(subproblem::Model, node::Int)
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Control variables
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+    end)
+    # Random variables
+    @variable(subproblem, inflow)
+    Ω = [0.0, 50.0, 100.0]
+    P = [1 / 3, 1 / 3, 1 / 3]
+    SDDP.parameterize(subproblem, Ω, P) do ω
+        return JuMP.fix(inflow, ω)
+    end
+    # Transition function and constraints
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in - hydro_generation - hydro_spill + inflow
+            demand_constraint, hydro_generation + thermal_generation == 150
+        end
+    )
+    # Stage-objective
+    fuel_cost = [50, 100, 150]
+    @stageobjective(subproblem, fuel_cost[node] * thermal_generation)
+    return subproblem
+end
subproblem_builder (generic function with 1 method)

Constructing the model

Now that we've written our subproblem, we need to construct the full model. For that, we're going to need a linear solver. Let's choose HiGHS:

using HiGHS
Warning

In larger problems, you should use a more robust commercial LP solver like Gurobi. Read Words of warning for more details.

Then, we can create a full model using SDDP.PolicyGraph, passing our subproblem_builder function as the first argument, and our graph as the second:

model = SDDP.PolicyGraph(
+    subproblem_builder,
+    graph;
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+)
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+
  • sense: the optimization sense. Must be :Min or :Max.
  • lower_bound: you must supply a valid bound on the objective. For our problem, we know that we cannot incur a negative cost so $0 is a valid lower bound.
  • optimizer: This is borrowed directly from JuMP's Model constructor: Model(HiGHS.Optimizer)

Because linear policy graphs are the most commonly used structure, we can use SDDP.LinearPolicyGraph instead of passing SDDP.LinearGraph(3) to SDDP.PolicyGraph.

model = SDDP.LinearPolicyGraph(
+    subproblem_builder;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+)
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+

There is also the option is to use Julia's do syntax to avoid needing to define a subproblem_builder function separately:

model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, node
+    # State variables
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Control variables
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+    end)
+    # Random variables
+    @variable(subproblem, inflow)
+    Ω = [0.0, 50.0, 100.0]
+    P = [1 / 3, 1 / 3, 1 / 3]
+    SDDP.parameterize(subproblem, Ω, P) do ω
+        return JuMP.fix(inflow, ω)
+    end
+    # Transition function and constraints
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in - hydro_generation - hydro_spill + inflow
+            demand_constraint, hydro_generation + thermal_generation == 150
+        end
+    )
+    # Stage-objective
+    if node == 1
+        @stageobjective(subproblem, 50 * thermal_generation)
+    elseif node == 2
+        @stageobjective(subproblem, 100 * thermal_generation)
+    else
+        @assert node == 3
+        @stageobjective(subproblem, 150 * thermal_generation)
+    end
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+
Info

Julia's do syntax is just a different way of passing an anonymous function inner to some function outer which takes inner as the first argument. For example, given:

outer(inner::Function, x, y) = inner(x, y)

then

outer(1, 2) do x, y
+    return x^2 + y^2
+end

is equivalent to:

outer((x, y) -> x^2 + y^2, 1, 2)

For our purpose, inner is subproblem_builder, and outer is SDDP.PolicyGraph.

Training a policy

Now we have a model, which is a description of the policy graph, we need to train a policy. Models can be trained using the SDDP.train function. It accepts a number of keyword arguments. iteration_limit terminates the training after the provided number of iterations.

SDDP.train(model; iteration_limit = 10)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 2.70000e+01
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+02]
+  bounds range     [2e+02, 2e+02]
+  rhs range        [2e+02, 2e+02]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.750000e+04  2.500000e+03  3.611088e-03        12   1
+        10   1.250000e+04  8.333333e+03  1.414299e-02       120   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 1.414299e-02
+total solves   : 120
+best bound     :  8.333333e+03
+simulation ci  :  1.187500e+04 ± 6.125000e+03
+numeric issues : 0
+-------------------------------------------------------------------

There's a lot going on in this printout! Let's break it down.

The first section, "problem," gives some problem statistics. In this example there are 3 nodes, 1 state variable, and 27 scenarios ($3^3$). We haven't solved this problem before so there are no existing cuts.

The "options" section lists some options we are using to solve the problem. For more information on the numerical stability report, read the Numerical stability report section.

The "subproblem structure" section also needs explaining. This looks at all of the nodes in the policy graph and reports the minimum and maximum number of variables and each constraint type in the corresponding subproblem. In this case each subproblem has 7 variables and various numbers of different constraint types. Note that the exact numbers may not correspond to the formulation as you wrote it, because SDDP.jl adds some extra variables for the cost-to-go function.

Then comes the iteration log, which is the main part of the printout. It has the following columns:

  • iteration: the SDDP iteration
  • simulation: the cost of the single forward pass simulation for that iteration. This value is stochastic and is not guaranteed to improve over time. However, it's useful to check that the units are reasonable, and that it is not deterministic if you intended for the problem to be stochastic, etc.
  • bound: this is a lower bound (upper if maximizing) for the value of the optimal policy. This bound should be monotonically improving (increasing if minimizing, decreasing if maximizing), but in some cases it can temporarily worsen due to cut selection, especially in the early iterations of the algorithm.
  • time (s): the total number of seconds spent solving so far
  • solves: the total number of subproblem solves to date. This can be very large!
  • pid: the ID of the processor used to solve that iteration. This should be 1 unless you are using parallel computation.

In addition, if the first character of a line is , then SDDP.jl experienced numerical issues during the solve, but successfully recovered.

The printout finishes with some summary statistics:

  • status: why did the solver stop?
  • total time (s), best bound, and total solves are the values from the last iteration of the solve.
  • simulation ci: a confidence interval that estimates the quality of the policy from the Simulation column.
  • numeric issues: the number of iterations that experienced numerical issues.
Warning

The simulation ci result can be misleading if you run a small number of iterations, or if the initial simulations are very bad. On a more technical note, it is an in-sample simulation, which may not reflect the true performance of the policy. See Obtaining bounds for more details.

Obtaining the decision rule

After training a policy, we can create a decision rule using SDDP.DecisionRule:

rule = SDDP.DecisionRule(model; node = 1)
A decision rule for node 1

Then, to evaluate the decision rule, we use SDDP.evaluate:

solution = SDDP.evaluate(
+    rule;
+    incoming_state = Dict(:volume => 150.0),
+    noise = 50.0,
+    controls_to_record = [:hydro_generation, :thermal_generation],
+)
(stage_objective = 7500.0, outgoing_state = Dict(:volume => 200.0), controls = Dict(:thermal_generation => 150.0, :hydro_generation => -0.0))

Simulating the policy

Once you have a trained policy, you can also simulate it using SDDP.simulate. The return value from simulate is a vector with one element for each replication. Each element is itself a vector, with one element for each stage. Each element, corresponding to a particular stage in a particular replication, is a dictionary that records information from the simulation.

simulations = SDDP.simulate(
+    # The trained model to simulate.
+    model,
+    # The number of replications.
+    100,
+    # A list of names to record the values of.
+    [:volume, :thermal_generation, :hydro_generation, :hydro_spill],
+)
+
+replication = 1
+stage = 2
+simulations[replication][stage]
Dict{Symbol, Any} with 10 entries:
+  :volume             => State{Float64}(200.0, 100.0)
+  :hydro_spill        => 0.0
+  :bellman_term       => 2500.0
+  :noise_term         => 50.0
+  :node_index         => 2
+  :stage_objective    => 0.0
+  :objective_state    => nothing
+  :thermal_generation => 0.0
+  :hydro_generation   => 150.0
+  :belief             => Dict(2=>1.0)

Ignore many of the entries for now; they will be relevant later.

One element of interest is :volume.

outgoing_volume = map(simulations[1]) do node
+    return node[:volume].out
+end
3-element Vector{Float64}:
+ 200.0
+ 100.0
+  -0.0

Another is :thermal_generation.

thermal_generation = map(simulations[1]) do node
+    return node[:thermal_generation]
+end
3-element Vector{Float64}:
+ 150.0
+   0.0
+   0.0

Obtaining bounds

Because the optimal policy is stochastic, one common approach to quantify the quality of the policy is to construct a confidence interval for the expected cost by summing the stage objectives along each simulation.

objectives = map(simulations) do simulation
+    return sum(stage[:stage_objective] for stage in simulation)
+end
+
+μ, ci = SDDP.confidence_interval(objectives)
+println("Confidence interval: ", μ, " ± ", ci)
Confidence interval: 8250.0 ± 917.3672254133708

This confidence interval is an estimate for an upper bound of the policy's quality. We can calculate the lower bound using SDDP.calculate_bound.

println("Lower bound: ", SDDP.calculate_bound(model))
Lower bound: 8333.333333333332
Tip

The upper- and lower-bounds are reversed if maximizing, i.e., SDDP.calculate_bound. returns an upper bound.

Custom recorders

In addition to simulating the primal values of variables, we can also pass custom recorder functions. Each of these functions takes one argument, the JuMP subproblem corresponding to each node. This function gets called after we have solved each node as we traverse the policy graph in the simulation.

For example, the dual of the demand constraint (which we named demand_constraint) corresponds to the price we should charge for electricity, since it represents the cost of each additional unit of demand. To calculate this, we can go:

simulations = SDDP.simulate(
+    model,
+    1;  ## Perform a single simulation
+    custom_recorders = Dict{Symbol,Function}(
+        :price => (sp::JuMP.Model) -> JuMP.dual(sp[:demand_constraint]),
+    ),
+)
+
+prices = map(simulations[1]) do node
+    return node[:price]
+end
3-element Vector{Float64}:
+  50.0
+ 100.0
+  -0.0

Extracting the marginal water values

Finally, we can use SDDP.ValueFunction and SDDP.evaluate to obtain and evaluate the value function at different points in the state-space.

Note

By "value function" we mean $\mathbb{E}_{j \in i^+, \varphi \in \Omega_j}[V_j(x^\prime, \varphi)]$, not the function $V_i(x, \omega)$.

First, we construct a value function from the first subproblem:

V = SDDP.ValueFunction(model; node = 1)
A value function for node 1

Then we can evaluate V at a point:

cost, price = SDDP.evaluate(V, Dict("volume" => 10))
(21499.999999999996, Dict(:volume => -99.99999999999999))

This returns the cost-to-go (cost), and the gradient of the cost-to-go function with respect to each state variable. Note that since we are minimizing, the price has a negative sign: each additional unit of water leads to a decrease in the expected long-run cost.

diff --git a/previews/PR826/tutorial/inventory.ipynb b/previews/PR826/tutorial/inventory.ipynb new file mode 100644 index 0000000000..f076d67bb2 --- /dev/null +++ b/previews/PR826/tutorial/inventory.ipynb @@ -0,0 +1,375 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Example: inventory management" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The purpose of this tutorial is to demonstrate a well-known inventory\n", + "management problem with a finite- and infinite-horizon policy." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Required packages" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This tutorial requires the following packages:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import Distributions\n", + "import HiGHS\n", + "import Plots\n", + "import Statistics" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Background" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Consider a periodic review inventory problem involving a single product. The\n", + "initial inventory is denoted by $x_0 \\geq 0$, and a decision-maker can place\n", + "an order at the start of each stage. The objective is to minimize expected\n", + "costs over the planning horizon. The following parameters define the cost\n", + "structure:\n", + "\n", + " * `c` is the unit cost for purchasing each unit\n", + " * `h` is the holding cost per unit remaining at the end of each stage\n", + " * `p` is the shortage cost per unit of unsatisfied demand at the end of each\n", + " stage\n", + "\n", + "There are no fixed ordering costs, and the demand at each stage is assumed to\n", + "follow an independent and identically distributed random variable with\n", + "cumulative distribution function (CDF) $\\Phi(\\cdot)$. Any unsatisfied demand\n", + "is backlogged and carried forward to the next stage." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "At each stage, an agent must decide how many items to order. The per-stage\n", + "costs are the sum of the order costs, shortage and holding costs incurred at\n", + "the end of the stage, after demand is realized." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Following Chapter 19 of Introduction to Operations Research by Hillier and\n", + "Lieberman (7th edition), we use the following parameters: $c=15, h=1, p=15$." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "x_0 = 10 # initial inventory\n", + "c = 35 # unit inventory cost\n", + "h = 1 # unit inventory holding cost\n", + "p = 15 # unit order cost" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Demand follows a continuous uniform distribution between 0 and 800. We\n", + "construct a sample average approximation with 20 scenarios:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Ω = range(0, 800; length = 20);" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This is a well-known inventory problem with a closed-form solution. The\n", + "optimal policy is a simple order-up-to policy: if the inventory level is\n", + "below a certain number of units, the decision-maker orders up to that number\n", + "of units. Otherwise, no order is placed. For a detailed analysis, refer\n", + "to Foundations of Stochastic Inventory Theory by Evan Porteus (Stanford\n", + "Business Books, 2002)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Finite horizon" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For a finite horizon of length $T$, the problem is to minimize the total\n", + "expected cost over all stages." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the last stage, the decision-maker can recover the unit cost `c` for each\n", + "leftover item, or buy out any remaining backlog, also at the unit cost `c`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "T = 10 # number of stages\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = T + 1,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0)\n", + " @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0)\n", + " # u_buy is a Decision-Hazard control variable. We decide u.out for use in\n", + " # the next stage\n", + " @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0)\n", + " @variable(sp, u_sell >= 0)\n", + " @variable(sp, w_demand == 0)\n", + " @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell)\n", + " @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell)\n", + " if t == 1\n", + " fix(u_sell, 0; force = true)\n", + " @stageobjective(sp, c * u_buy.out)\n", + " elseif t == T + 1\n", + " fix(u_buy.out, 0; force = true)\n", + " @stageobjective(sp, -c * x_inventory.out + c * x_demand.out)\n", + " SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)\n", + " else\n", + " @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out)\n", + " SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Train and simulate the policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model)\n", + "simulations = SDDP.simulate(model, 200, [:x_inventory, :u_buy])\n", + "objective_values = [sum(t[:stage_objective] for t in s) for s in simulations]\n", + "μ, ci = round.(SDDP.confidence_interval(objective_values, 1.96); digits = 2)\n", + "lower_bound = round(SDDP.calculate_bound(model); digits = 2)\n", + "println(\"Confidence interval: \", μ, \" ± \", ci)\n", + "println(\"Lower bound: \", lower_bound)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Plot the optimal inventory levels:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plt = SDDP.publication_plot(\n", + " simulations;\n", + " title = \"x_inventory.out + u_buy.out\",\n", + " xlabel = \"Stage\",\n", + " ylabel = \"Quantity\",\n", + " ylims = (0, 1_000),\n", + ") do data\n", + " return data[:x_inventory].out + data[:u_buy].out\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "In the early stages, we indeed recover an order-up-to policy. However,\n", + "there are end-of-horizon effects as the agent tries to optimize their\n", + "decision making knowing that they have 10 realizations of demand." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Infinite horizon" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can remove the end-of-horizonn effects by considering an infinite\n", + "horizon model. We assume a discount factor $\\alpha=0.95$:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "α = 0.95\n", + "graph = SDDP.LinearGraph(2)\n", + "SDDP.add_edge(graph, 2 => 2, α)\n", + "graph" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The objective in this case is to minimize the discounted expected costs over\n", + "an infinite planning horizon." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.PolicyGraph(\n", + " graph;\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0)\n", + " @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0)\n", + " # u_buy is a Decision-Hazard control variable. We decide u.out for use in\n", + " # the next stage\n", + " @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0)\n", + " @variable(sp, u_sell >= 0)\n", + " @variable(sp, w_demand == 0)\n", + " @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell)\n", + " @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell)\n", + " if t == 1\n", + " fix(u_sell, 0; force = true)\n", + " @stageobjective(sp, c * u_buy.out)\n", + " else\n", + " @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out)\n", + " SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)\n", + " end\n", + " return\n", + "end\n", + "\n", + "SDDP.train(model; iteration_limit = 400)\n", + "simulations = SDDP.simulate(\n", + " model,\n", + " 200,\n", + " [:x_inventory, :u_buy];\n", + " sampling_scheme = SDDP.InSampleMonteCarlo(;\n", + " max_depth = 50,\n", + " terminate_on_dummy_leaf = false,\n", + " ),\n", + ");" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Plot the optimal inventory levels:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plt = SDDP.publication_plot(\n", + " simulations;\n", + " title = \"x_inventory.out + u_buy.out\",\n", + " xlabel = \"Stage\",\n", + " ylabel = \"Quantity\",\n", + " ylims = (0, 1_000),\n", + ") do data\n", + " return data[:x_inventory].out + data[:u_buy].out\n", + "end\n", + "Plots.hline!(plt, [662]; label = \"Analytic solution\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We again recover an order-up-to policy. The analytic solution is to\n", + "order-up-to 662 units. We do not precisely recover this solution because\n", + "we used a sample average approximation of 20 elements. If we increased the\n", + "number of samples, our solution would approach the analytic solution." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/inventory.jl b/previews/PR826/tutorial/inventory.jl new file mode 100644 index 0000000000..3b62540e0f --- /dev/null +++ b/previews/PR826/tutorial/inventory.jl @@ -0,0 +1,192 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Example: inventory management + +# The purpose of this tutorial is to demonstrate a well-known inventory +# management problem with a finite- and infinite-horizon policy. + +# ## Required packages + +# This tutorial requires the following packages: + +using SDDP +import Distributions +import HiGHS +import Plots +import Statistics + +# ## Background + +# Consider a periodic review inventory problem involving a single product. The +# initial inventory is denoted by $x_0 \geq 0$, and a decision-maker can place +# an order at the start of each stage. The objective is to minimize expected +# costs over the planning horizon. The following parameters define the cost +# structure: +# +# * `c` is the unit cost for purchasing each unit +# * `h` is the holding cost per unit remaining at the end of each stage +# * `p` is the shortage cost per unit of unsatisfied demand at the end of each +# stage +# +# There are no fixed ordering costs, and the demand at each stage is assumed to +# follow an independent and identically distributed random variable with +# cumulative distribution function (CDF) $\Phi(\cdot)$. Any unsatisfied demand +# is backlogged and carried forward to the next stage. + +# At each stage, an agent must decide how many items to order. The per-stage +# costs are the sum of the order costs, shortage and holding costs incurred at +# the end of the stage, after demand is realized. + +# Following Chapter 19 of Introduction to Operations Research by Hillier and +# Lieberman (7th edition), we use the following parameters: $c=15, h=1, p=15$. + +x_0 = 10 # initial inventory +c = 35 # unit inventory cost +h = 1 # unit inventory holding cost +p = 15 # unit order cost + +# Demand follows a continuous uniform distribution between 0 and 800. We +# construct a sample average approximation with 20 scenarios: + +Ω = range(0, 800; length = 20); + +# This is a well-known inventory problem with a closed-form solution. The +# optimal policy is a simple order-up-to policy: if the inventory level is +# below a certain number of units, the decision-maker orders up to that number +# of units. Otherwise, no order is placed. For a detailed analysis, refer +# to Foundations of Stochastic Inventory Theory by Evan Porteus (Stanford +# Business Books, 2002). + +# ## Finite horizon + +# For a finite horizon of length $T$, the problem is to minimize the total +# expected cost over all stages. + +# In the last stage, the decision-maker can recover the unit cost `c` for each +# leftover item, or buy out any remaining backlog, also at the unit cost `c`. + +T = 10 # number of stages +model = SDDP.LinearPolicyGraph(; + stages = T + 1, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0) + @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0) + ## u_buy is a Decision-Hazard control variable. We decide u.out for use in + ## the next stage + @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0) + @variable(sp, u_sell >= 0) + @variable(sp, w_demand == 0) + @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell) + @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell) + if t == 1 + fix(u_sell, 0; force = true) + @stageobjective(sp, c * u_buy.out) + elseif t == T + 1 + fix(u_buy.out, 0; force = true) + @stageobjective(sp, -c * x_inventory.out + c * x_demand.out) + SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω) + else + @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out) + SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω) + end + return +end + +# Train and simulate the policy: + +SDDP.train(model) +simulations = SDDP.simulate(model, 200, [:x_inventory, :u_buy]) +objective_values = [sum(t[:stage_objective] for t in s) for s in simulations] +μ, ci = round.(SDDP.confidence_interval(objective_values, 1.96); digits = 2) +lower_bound = round(SDDP.calculate_bound(model); digits = 2) +println("Confidence interval: ", μ, " ± ", ci) +println("Lower bound: ", lower_bound) + +# Plot the optimal inventory levels: + +plt = SDDP.publication_plot( + simulations; + title = "x_inventory.out + u_buy.out", + xlabel = "Stage", + ylabel = "Quantity", + ylims = (0, 1_000), +) do data + return data[:x_inventory].out + data[:u_buy].out +end + +# In the early stages, we indeed recover an order-up-to policy. However, +# there are end-of-horizon effects as the agent tries to optimize their +# decision making knowing that they have 10 realizations of demand. + +# ## Infinite horizon + +# We can remove the end-of-horizonn effects by considering an infinite +# horizon model. We assume a discount factor $\alpha=0.95$: + +α = 0.95 +graph = SDDP.LinearGraph(2) +SDDP.add_edge(graph, 2 => 2, α) +graph + +# The objective in this case is to minimize the discounted expected costs over +# an infinite planning horizon. + +model = SDDP.PolicyGraph( + graph; + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0) + @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0) + ## u_buy is a Decision-Hazard control variable. We decide u.out for use in + ## the next stage + @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0) + @variable(sp, u_sell >= 0) + @variable(sp, w_demand == 0) + @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell) + @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell) + if t == 1 + fix(u_sell, 0; force = true) + @stageobjective(sp, c * u_buy.out) + else + @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out) + SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω) + end + return +end + +SDDP.train(model; iteration_limit = 400) +simulations = SDDP.simulate( + model, + 200, + [:x_inventory, :u_buy]; + sampling_scheme = SDDP.InSampleMonteCarlo(; + max_depth = 50, + terminate_on_dummy_leaf = false, + ), +); + +# Plot the optimal inventory levels: + +plt = SDDP.publication_plot( + simulations; + title = "x_inventory.out + u_buy.out", + xlabel = "Stage", + ylabel = "Quantity", + ylims = (0, 1_000), +) do data + return data[:x_inventory].out + data[:u_buy].out +end +Plots.hline!(plt, [662]; label = "Analytic solution") + +# We again recover an order-up-to policy. The analytic solution is to +# order-up-to 662 units. We do not precisely recover this solution because +# we used a sample average approximation of 20 elements. If we increased the +# number of samples, our solution would approach the analytic solution. diff --git a/previews/PR826/tutorial/inventory/084e6905.svg b/previews/PR826/tutorial/inventory/084e6905.svg new file mode 100644 index 0000000000..45f4984df3 --- /dev/null +++ b/previews/PR826/tutorial/inventory/084e6905.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/inventory/2a021f08.svg b/previews/PR826/tutorial/inventory/2a021f08.svg new file mode 100644 index 0000000000..c2a305b7cb --- /dev/null +++ b/previews/PR826/tutorial/inventory/2a021f08.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/inventory/index.html b/previews/PR826/tutorial/inventory/index.html new file mode 100644 index 0000000000..8a5e93a840 --- /dev/null +++ b/previews/PR826/tutorial/inventory/index.html @@ -0,0 +1,203 @@ + +Example: inventory management · SDDP.jl

Example: inventory management

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

The purpose of this tutorial is to demonstrate a well-known inventory management problem with a finite- and infinite-horizon policy.

Required packages

This tutorial requires the following packages:

using SDDP
+import Distributions
+import HiGHS
+import Plots
+import Statistics

Background

Consider a periodic review inventory problem involving a single product. The initial inventory is denoted by $x_0 \geq 0$, and a decision-maker can place an order at the start of each stage. The objective is to minimize expected costs over the planning horizon. The following parameters define the cost structure:

  • c is the unit cost for purchasing each unit
  • h is the holding cost per unit remaining at the end of each stage
  • p is the shortage cost per unit of unsatisfied demand at the end of each stage

There are no fixed ordering costs, and the demand at each stage is assumed to follow an independent and identically distributed random variable with cumulative distribution function (CDF) $\Phi(\cdot)$. Any unsatisfied demand is backlogged and carried forward to the next stage.

At each stage, an agent must decide how many items to order. The per-stage costs are the sum of the order costs, shortage and holding costs incurred at the end of the stage, after demand is realized.

Following Chapter 19 of Introduction to Operations Research by Hillier and Lieberman (7th edition), we use the following parameters: $c=15, h=1, p=15$.

x_0 = 10        # initial inventory
+c = 35          # unit inventory cost
+h = 1           # unit inventory holding cost
+p = 15          # unit order cost
15

Demand follows a continuous uniform distribution between 0 and 800. We construct a sample average approximation with 20 scenarios:

Ω = range(0, 800; length = 20);

This is a well-known inventory problem with a closed-form solution. The optimal policy is a simple order-up-to policy: if the inventory level is below a certain number of units, the decision-maker orders up to that number of units. Otherwise, no order is placed. For a detailed analysis, refer to Foundations of Stochastic Inventory Theory by Evan Porteus (Stanford Business Books, 2002).

Finite horizon

For a finite horizon of length $T$, the problem is to minimize the total expected cost over all stages.

In the last stage, the decision-maker can recover the unit cost c for each leftover item, or buy out any remaining backlog, also at the unit cost c.

T = 10 # number of stages
+model = SDDP.LinearPolicyGraph(;
+    stages = T + 1,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0)
+    @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0)
+    # u_buy is a Decision-Hazard control variable. We decide u.out for use in
+    # the next stage
+    @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0)
+    @variable(sp, u_sell >= 0)
+    @variable(sp, w_demand == 0)
+    @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell)
+    @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell)
+    if t == 1
+        fix(u_sell, 0; force = true)
+        @stageobjective(sp, c * u_buy.out)
+    elseif t == T + 1
+        fix(u_buy.out, 0; force = true)
+        @stageobjective(sp, -c * x_inventory.out + c * x_demand.out)
+        SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)
+    else
+        @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out)
+        SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)
+    end
+    return
+end
A policy graph with 11 nodes.
+ Node indices: 1, ..., 11
+

Train and simulate the policy:

SDDP.train(model)
+simulations = SDDP.simulate(model, 200, [:x_inventory, :u_buy])
+objective_values = [sum(t[:stage_objective] for t in s) for s in simulations]
+μ, ci = round.(SDDP.confidence_interval(objective_values, 1.96); digits = 2)
+lower_bound = round(SDDP.calculate_bound(model); digits = 2)
+println("Confidence interval: ", μ, " ± ", ci)
+println("Lower bound: ", lower_bound)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 11
+  state variables : 3
+  scenarios       : 1.02400e+13
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 4e+01]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.886158e+05  4.573582e+04  1.809502e-02       212   1
+        52   1.007447e+05  1.443365e+05  1.032124e+00     14324   1
+       111   1.574605e+05  1.443373e+05  2.047157e+00     29032   1
+       176   7.539737e+04  1.443373e+05  3.061774e+00     42812   1
+       224   1.980079e+05  1.443373e+05  4.071919e+00     54088   1
+       277   1.612500e+05  1.443373e+05  5.074022e+00     65324   1
+       286   1.260500e+05  1.443373e+05  5.250725e+00     67232   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 5.250725e+00
+total solves   : 67232
+best bound     :  1.443373e+05
+simulation ci  :  1.446033e+05 ± 3.621723e+03
+numeric issues : 0
+-------------------------------------------------------------------
+
+Confidence interval: 142817.79 ± 3734.74
+Lower bound: 144337.34

Plot the optimal inventory levels:

plt = SDDP.publication_plot(
+    simulations;
+    title = "x_inventory.out + u_buy.out",
+    xlabel = "Stage",
+    ylabel = "Quantity",
+    ylims = (0, 1_000),
+) do data
+    return data[:x_inventory].out + data[:u_buy].out
+end
Example block output

In the early stages, we indeed recover an order-up-to policy. However, there are end-of-horizon effects as the agent tries to optimize their decision making knowing that they have 10 realizations of demand.

Infinite horizon

We can remove the end-of-horizonn effects by considering an infinite horizon model. We assume a discount factor $\alpha=0.95$:

α = 0.95
+graph = SDDP.LinearGraph(2)
+SDDP.add_edge(graph, 2 => 2, α)
+graph
Root
+ 0
+Nodes
+ 1
+ 2
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 2 w.p. 1.0
+ 2 => 2 w.p. 0.95

The objective in this case is to minimize the discounted expected costs over an infinite planning horizon.

model = SDDP.PolicyGraph(
+    graph;
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(sp, x_inventory >= 0, SDDP.State, initial_value = x_0)
+    @variable(sp, x_demand >= 0, SDDP.State, initial_value = 0)
+    # u_buy is a Decision-Hazard control variable. We decide u.out for use in
+    # the next stage
+    @variable(sp, u_buy >= 0, SDDP.State, initial_value = 0)
+    @variable(sp, u_sell >= 0)
+    @variable(sp, w_demand == 0)
+    @constraint(sp, x_inventory.out == x_inventory.in + u_buy.in - u_sell)
+    @constraint(sp, x_demand.out == x_demand.in + w_demand - u_sell)
+    if t == 1
+        fix(u_sell, 0; force = true)
+        @stageobjective(sp, c * u_buy.out)
+    else
+        @stageobjective(sp, c * u_buy.out + h * x_inventory.out + p * x_demand.out)
+        SDDP.parameterize(ω -> JuMP.fix(w_demand, ω), sp, Ω)
+    end
+    return
+end
+
+SDDP.train(model; iteration_limit = 400)
+simulations = SDDP.simulate(
+    model,
+    200,
+    [:x_inventory, :u_buy];
+    sampling_scheme = SDDP.InSampleMonteCarlo(;
+        max_depth = 50,
+        terminate_on_dummy_leaf = false,
+    ),
+);
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 2
+  state variables : 3
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 5]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 4e+01]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.976053e+04  3.345593e+04  6.365776e-03        85   1
+        31   1.542236e+05  3.083482e+05  1.021843e+00     14605   1
+        67   3.600012e+05  3.125402e+05  2.044755e+00     27157   1
+        87   1.688336e+05  3.126470e+05  3.048394e+00     37803   1
+       107   4.105309e+05  3.126622e+05  4.107988e+00     47210   1
+       129   4.259734e+04  3.126646e+05  5.110773e+00     55947   1
+       156   9.569557e+05  3.126650e+05  6.282574e+00     65193   1
+       172   1.536492e+06  3.126650e+05  7.344901e+00     72979   1
+       183   9.826052e+04  3.126650e+05  8.354020e+00     79731   1
+       204   3.062605e+05  3.126650e+05  9.407581e+00     86535   1
+       270   1.845763e+05  3.126650e+05  1.445588e+01    113145   1
+       317   5.972079e+05  3.126650e+05  1.965487e+01    130727   1
+       343   6.835237e+05  3.126650e+05  2.510433e+01    143164   1
+       360   1.253763e+05  3.126650e+05  3.011826e+01    152631   1
+       376   3.714395e+05  3.126650e+05  3.544222e+01    160774   1
+       399   4.517763e+05  3.126650e+05  4.100058e+01    168588   1
+       400   3.821342e+05  3.126650e+05  4.137211e+01    169114   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 4.137211e+01
+total solves   : 169114
+best bound     :  3.126650e+05
+simulation ci  :  3.018209e+05 ± 2.740583e+04
+numeric issues : 0
+-------------------------------------------------------------------

Plot the optimal inventory levels:

plt = SDDP.publication_plot(
+    simulations;
+    title = "x_inventory.out + u_buy.out",
+    xlabel = "Stage",
+    ylabel = "Quantity",
+    ylims = (0, 1_000),
+) do data
+    return data[:x_inventory].out + data[:u_buy].out
+end
+Plots.hline!(plt, [662]; label = "Analytic solution")
Example block output

We again recover an order-up-to policy. The analytic solution is to order-up-to 662 units. We do not precisely recover this solution because we used a sample average approximation of 20 elements. If we increased the number of samples, our solution would approach the analytic solution.

diff --git a/previews/PR826/tutorial/markov_uncertainty.ipynb b/previews/PR826/tutorial/markov_uncertainty.ipynb new file mode 100644 index 0000000000..a718b0f878 --- /dev/null +++ b/previews/PR826/tutorial/markov_uncertainty.ipynb @@ -0,0 +1,250 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Markovian policy graphs" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In our previous tutorials (An introduction to SDDP.jl and\n", + "Uncertainty in the objective function), we formulated a simple\n", + "hydrothermal scheduling problem with stagewise-independent random variables in\n", + "the right-hand side of the constraints and in the objective function.\n", + "Now, in this tutorial, we introduce some *stagewise-dependent* uncertainty\n", + "using a Markov chain." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Formulating the problem" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In this tutorial we consider a Markov chain with two *climate* states: wet and\n", + "dry. Each Markov state is associated with an integer, in this case the wet\n", + "climate state is Markov state `1` and the dry climate state is Markov state\n", + "`2`. In the wet climate state, the probability of the high inflow increases to\n", + "50%, and the probability of the low inflow decreases to 1/6. In the dry\n", + "climate state, the converse happens. There is also persistence in the climate\n", + "state: the probability of remaining in the current state is 75%, and the\n", + "probability of transitioning to the other climate state is 25%. We assume that\n", + "the first stage starts in the wet climate state." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is a picture of the model we're going to implement.\n", + "\n", + "![Markovian policy graph](../assets/stochastic_markovian_policy_graph.png)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are five nodes in our graph. Each node is named by a tuple `(t, i)`,\n", + "where `t` is the stage for `t=1,2,3`, and `i` is the Markov state for `i=1,2`.\n", + "As before, the wavy lines denote the stagewise-independent random variable." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For each stage, we need to provide a Markov transition matrix. This is an\n", + "`M`x`N` matrix, where the element `A[i, j]` gives the probability of\n", + "transitioning from Markov state `i` in the previous stage to Markov state `j`\n", + "in the current stage. The first stage is special because we assume there is a\n", + "\"zero'th\" stage which has one Markov state (the round node in the graph\n", + "above). Furthermore, the number of columns in the transition matrix of a stage\n", + "(i.e. the number of Markov states) must equal the number of rows in the next\n", + "stage's transition matrix. For our example, the vector of Markov transition\n", + "matrices is given by:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "T = Array{Float64,2}[[1.0]', [0.75 0.25], [0.75 0.25; 0.25 0.75]]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Note**\n", + ">\n", + "> Make sure to add the `'` after the first transition matrix so Julia can\n", + "> distinguish between a vector and a matrix." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Creating a model" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "Ω = [\n", + " (inflow = 0.0, fuel_multiplier = 1.5),\n", + " (inflow = 50.0, fuel_multiplier = 1.0),\n", + " (inflow = 100.0, fuel_multiplier = 0.75),\n", + "]\n", + "\n", + "model = SDDP.MarkovianPolicyGraph(;\n", + " transition_matrices = Array{Float64,2}[\n", + " [1.0]',\n", + " [0.75 0.25],\n", + " [0.75 0.25; 0.25 0.75],\n", + " ],\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, node\n", + " # Unpack the stage and Markov index.\n", + " t, markov_state = node\n", + " # Define the state variable.\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Define the control variables.\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " # Define the constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in + inflow - hydro_generation - hydro_spill\n", + " thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + " # Note how we can use `markov_state` to dispatch an `if` statement.\n", + " probability = if markov_state == 1 # wet climate state\n", + " [1 / 6, 1 / 3, 1 / 2]\n", + " else # dry climate state\n", + " [1 / 2, 1 / 3, 1 / 6]\n", + " end\n", + "\n", + " fuel_cost = [50.0, 100.0, 150.0]\n", + " SDDP.parameterize(subproblem, Ω, probability) do ω\n", + " JuMP.fix(inflow, ω.inflow)\n", + " @stageobjective(\n", + " subproblem,\n", + " ω.fuel_multiplier * fuel_cost[t] * thermal_generation\n", + " )\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> For more information on `SDDP.MarkovianPolicyGraph`s, read\n", + "> Create a general policy graph." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Training and simulating the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As in the previous three tutorials, we train the policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Instead of performing a Monte Carlo simulation like the previous tutorials, we\n", + "may want to simulate one particular sequence of noise realizations. This\n", + "_historical_ simulation can also be conducted by passing a\n", + "`SDDP.Historical` sampling scheme to the `sampling_scheme` keyword of\n", + "the `SDDP.simulate` function." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can confirm that the historical sequence of nodes was visited by querying\n", + "the `:node_index` key of the simulation results." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model;\n", + " sampling_scheme = SDDP.Historical([\n", + " ((1, 1), Ω[1]),\n", + " ((2, 2), Ω[3]),\n", + " ((3, 1), Ω[2]),\n", + " ]),\n", + ")\n", + "\n", + "[stage[:node_index] for stage in simulations[1]]" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/markov_uncertainty.jl b/previews/PR826/tutorial/markov_uncertainty.jl new file mode 100644 index 0000000000..cc49067762 --- /dev/null +++ b/previews/PR826/tutorial/markov_uncertainty.jl @@ -0,0 +1,135 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Markovian policy graphs + +# In our previous tutorials ([An introduction to SDDP.jl](@ref) and +# [Uncertainty in the objective function](@ref)), we formulated a simple +# hydrothermal scheduling problem with stagewise-independent random variables in +# the right-hand side of the constraints and in the objective function. +# Now, in this tutorial, we introduce some *stagewise-dependent* uncertainty +# using a Markov chain. + +# ## Formulating the problem + +# In this tutorial we consider a Markov chain with two *climate* states: wet and +# dry. Each Markov state is associated with an integer, in this case the wet +# climate state is Markov state `1` and the dry climate state is Markov state +# `2`. In the wet climate state, the probability of the high inflow increases to +# 50%, and the probability of the low inflow decreases to 1/6. In the dry +# climate state, the converse happens. There is also persistence in the climate +# state: the probability of remaining in the current state is 75%, and the +# probability of transitioning to the other climate state is 25%. We assume that +# the first stage starts in the wet climate state. + +# Here is a picture of the model we're going to implement. +# +# ![Markovian policy graph](../assets/stochastic_markovian_policy_graph.png) + +# There are five nodes in our graph. Each node is named by a tuple `(t, i)`, +# where `t` is the stage for `t=1,2,3`, and `i` is the Markov state for `i=1,2`. +# As before, the wavy lines denote the stagewise-independent random variable. + +# For each stage, we need to provide a Markov transition matrix. This is an +# `M`x`N` matrix, where the element `A[i, j]` gives the probability of +# transitioning from Markov state `i` in the previous stage to Markov state `j` +# in the current stage. The first stage is special because we assume there is a +# "zero'th" stage which has one Markov state (the round node in the graph +# above). Furthermore, the number of columns in the transition matrix of a stage +# (i.e. the number of Markov states) must equal the number of rows in the next +# stage's transition matrix. For our example, the vector of Markov transition +# matrices is given by: + +T = Array{Float64,2}[[1.0]', [0.75 0.25], [0.75 0.25; 0.25 0.75]] + +# !!! note +# Make sure to add the `'` after the first transition matrix so Julia can +# distinguish between a vector and a matrix. + +# ## Creating a model + +using SDDP, HiGHS + +Ω = [ + (inflow = 0.0, fuel_multiplier = 1.5), + (inflow = 50.0, fuel_multiplier = 1.0), + (inflow = 100.0, fuel_multiplier = 0.75), +] + +model = SDDP.MarkovianPolicyGraph(; + transition_matrices = Array{Float64,2}[ + [1.0]', + [0.75 0.25], + [0.75 0.25; 0.25 0.75], + ], + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, node + ## Unpack the stage and Markov index. + t, markov_state = node + ## Define the state variable. + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Define the control variables. + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + ## Define the constraints + @constraints( + subproblem, + begin + volume.out == volume.in + inflow - hydro_generation - hydro_spill + thermal_generation + hydro_generation == 150.0 + end + ) + ## Note how we can use `markov_state` to dispatch an `if` statement. + probability = if markov_state == 1 # wet climate state + [1 / 6, 1 / 3, 1 / 2] + else # dry climate state + [1 / 2, 1 / 3, 1 / 6] + end + + fuel_cost = [50.0, 100.0, 150.0] + SDDP.parameterize(subproblem, Ω, probability) do ω + JuMP.fix(inflow, ω.inflow) + @stageobjective( + subproblem, + ω.fuel_multiplier * fuel_cost[t] * thermal_generation + ) + end +end + +# !!! tip +# For more information on [`SDDP.MarkovianPolicyGraph`](@ref)s, read +# [Create a general policy graph](@ref). + +# ## Training and simulating the policy + +# As in the previous three tutorials, we train the policy: + +SDDP.train(model) + +# Instead of performing a Monte Carlo simulation like the previous tutorials, we +# may want to simulate one particular sequence of noise realizations. This +# _historical_ simulation can also be conducted by passing a +# [`SDDP.Historical`](@ref) sampling scheme to the `sampling_scheme` keyword of +# the [`SDDP.simulate`](@ref) function. + +# We can confirm that the historical sequence of nodes was visited by querying +# the `:node_index` key of the simulation results. + +simulations = SDDP.simulate( + model; + sampling_scheme = SDDP.Historical([ + ((1, 1), Ω[1]), + ((2, 2), Ω[3]), + ((3, 1), Ω[2]), + ]), +) + +[stage[:node_index] for stage in simulations[1]] diff --git a/previews/PR826/tutorial/markov_uncertainty/index.html b/previews/PR826/tutorial/markov_uncertainty/index.html new file mode 100644 index 0000000000..0a4d399c38 --- /dev/null +++ b/previews/PR826/tutorial/markov_uncertainty/index.html @@ -0,0 +1,110 @@ + +Markovian policy graphs · SDDP.jl

Markovian policy graphs

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

In our previous tutorials (An introduction to SDDP.jl and Uncertainty in the objective function), we formulated a simple hydrothermal scheduling problem with stagewise-independent random variables in the right-hand side of the constraints and in the objective function. Now, in this tutorial, we introduce some stagewise-dependent uncertainty using a Markov chain.

Formulating the problem

In this tutorial we consider a Markov chain with two climate states: wet and dry. Each Markov state is associated with an integer, in this case the wet climate state is Markov state 1 and the dry climate state is Markov state 2. In the wet climate state, the probability of the high inflow increases to 50%, and the probability of the low inflow decreases to 1/6. In the dry climate state, the converse happens. There is also persistence in the climate state: the probability of remaining in the current state is 75%, and the probability of transitioning to the other climate state is 25%. We assume that the first stage starts in the wet climate state.

Here is a picture of the model we're going to implement.

Markovian policy graph

There are five nodes in our graph. Each node is named by a tuple (t, i), where t is the stage for t=1,2,3, and i is the Markov state for i=1,2. As before, the wavy lines denote the stagewise-independent random variable.

For each stage, we need to provide a Markov transition matrix. This is an MxN matrix, where the element A[i, j] gives the probability of transitioning from Markov state i in the previous stage to Markov state j in the current stage. The first stage is special because we assume there is a "zero'th" stage which has one Markov state (the round node in the graph above). Furthermore, the number of columns in the transition matrix of a stage (i.e. the number of Markov states) must equal the number of rows in the next stage's transition matrix. For our example, the vector of Markov transition matrices is given by:

T = Array{Float64,2}[[1.0]', [0.75 0.25], [0.75 0.25; 0.25 0.75]]
3-element Vector{Matrix{Float64}}:
+ [1.0;;]
+ [0.75 0.25]
+ [0.75 0.25; 0.25 0.75]
Note

Make sure to add the ' after the first transition matrix so Julia can distinguish between a vector and a matrix.

Creating a model

using SDDP, HiGHS
+
+Ω = [
+    (inflow = 0.0, fuel_multiplier = 1.5),
+    (inflow = 50.0, fuel_multiplier = 1.0),
+    (inflow = 100.0, fuel_multiplier = 0.75),
+]
+
+model = SDDP.MarkovianPolicyGraph(;
+    transition_matrices = Array{Float64,2}[
+        [1.0]',
+        [0.75 0.25],
+        [0.75 0.25; 0.25 0.75],
+    ],
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, node
+    # Unpack the stage and Markov index.
+    t, markov_state = node
+    # Define the state variable.
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Define the control variables.
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    # Define the constraints
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in + inflow - hydro_generation - hydro_spill
+            thermal_generation + hydro_generation == 150.0
+        end
+    )
+    # Note how we can use `markov_state` to dispatch an `if` statement.
+    probability = if markov_state == 1  # wet climate state
+        [1 / 6, 1 / 3, 1 / 2]
+    else  # dry climate state
+        [1 / 2, 1 / 3, 1 / 6]
+    end
+
+    fuel_cost = [50.0, 100.0, 150.0]
+    SDDP.parameterize(subproblem, Ω, probability) do ω
+        JuMP.fix(inflow, ω.inflow)
+        @stageobjective(
+            subproblem,
+            ω.fuel_multiplier * fuel_cost[t] * thermal_generation
+        )
+    end
+end
A policy graph with 5 nodes.
+ Node indices: (1, 1), (2, 1), (2, 2), (3, 1), (3, 2)
+
Tip

Training and simulating the policy

As in the previous three tutorials, we train the policy:

SDDP.train(model)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 5
+  state variables : 1
+  scenarios       : 1.08000e+02
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+02]
+  bounds range     [2e+02, 2e+02]
+  rhs range        [2e+02, 2e+02]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   9.375000e+03  1.991887e+03  4.940033e-03        18   1
+        40   1.875000e+03  8.072917e+03  1.270659e-01      1320   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.270659e-01
+total solves   : 1320
+best bound     :  8.072917e+03
+simulation ci  :  5.917822e+03 ± 1.372472e+03
+numeric issues : 0
+-------------------------------------------------------------------

Instead of performing a Monte Carlo simulation like the previous tutorials, we may want to simulate one particular sequence of noise realizations. This historical simulation can also be conducted by passing a SDDP.Historical sampling scheme to the sampling_scheme keyword of the SDDP.simulate function.

We can confirm that the historical sequence of nodes was visited by querying the :node_index key of the simulation results.

simulations = SDDP.simulate(
+    model;
+    sampling_scheme = SDDP.Historical([
+        ((1, 1), Ω[1]),
+        ((2, 2), Ω[3]),
+        ((3, 1), Ω[2]),
+    ]),
+)
+
+[stage[:node_index] for stage in simulations[1]]
3-element Vector{Tuple{Int64, Int64}}:
+ (1, 1)
+ (2, 2)
+ (3, 1)
diff --git a/previews/PR826/tutorial/mdps.ipynb b/previews/PR826/tutorial/mdps.ipynb new file mode 100644 index 0000000000..2c3f21740d --- /dev/null +++ b/previews/PR826/tutorial/mdps.ipynb @@ -0,0 +1,427 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Example: Markov Decision Processes" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "`SDDP.jl` can be used to solve a variety of Markov Decision processes. If the\n", + "problem has continuous state and control spaces, and the objective and\n", + "transition function are convex, then SDDP.jl can find a globally optimal\n", + "policy. In other cases, SDDP.jl will find a locally optimal policy." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## A simple example" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "A simple demonstration of this is the example taken from page 98 of the book\n", + "\"Markov Decision Processes: Discrete stochastic Dynamic Programming\", by\n", + "Martin L. Putterman." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The example, as described in Section 4.6.3 of the book, is to minimize a sum\n", + "of squares of `N` non-negative variables, subject to a budget constraint that\n", + "the variable values add up to `M`. Put mathematically, that is:\n", + "$$\n", + "\\begin{aligned}\n", + "\\min \\;\\; & \\sum\\limits_{i=1}^N x_i^2 \\\\\n", + "s.t. \\;\\; & \\sum\\limits_{i=1}^N x_i = M \\\\\n", + " & x_i \\ge 0, \\quad i \\in 1,\\ldots,N\n", + "\\end{aligned}\n", + "$$\n", + "The optimal objective value is $M^2/N$, and the optimal solution is\n", + "$x_i = M / N$, which can be shown by induction." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This can be reformulated as a Markov Decision Process by introducing a state\n", + "variable, $s$, which tracks the un-spent budget over $N$ stages." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "$$\n", + "\\begin{aligned}\n", + "V_t(s) = \\min \\;\\; & x^2 + V_{t+1}(s^\\prime) \\\\\n", + "s.t. \\;\\; & s^\\prime = s - x \\\\\n", + " & x \\le s \\\\\n", + " & x \\ge 0 \\\\\n", + " & s \\ge 0\n", + "\\end{aligned}\n", + "$$\n", + "and in the last stage $V_N$, there is an additional constraint that\n", + "$s^\\prime = 0$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The budget of $M$ is computed by solving for $V_1(M)$." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> Since everything here is continuous and convex, SDDP.jl will find the\n", + "> globally optimal policy." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If the reformulation from the single problem into the recursive form of the\n", + "Markov Decision Process is not obvious, consult Putterman's book." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can model and solve this problem using SDDP.jl as follows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import Ipopt\n", + "\n", + "M, N = 5, 3\n", + "\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = N,\n", + " lower_bound = 0.0,\n", + " optimizer = Ipopt.Optimizer,\n", + ") do subproblem, node\n", + " @variable(subproblem, s >= 0, SDDP.State, initial_value = M)\n", + " @variable(subproblem, x >= 0)\n", + " @stageobjective(subproblem, x^2)\n", + " @constraint(subproblem, x <= s.in)\n", + " @constraint(subproblem, s.out == s.in - x)\n", + " if node == N\n", + " fix(s.out, 0.0; force = true)\n", + " end\n", + " return\n", + "end\n", + "\n", + "SDDP.train(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Check that we got the theoretical optimum:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.calculate_bound(model), M^2 / N" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "And check that we found the theoretical value for each $x_i$:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(model, 1, [:x])\n", + "for data in simulations[1]\n", + " println(\"x_$(data[:node_index]) = $(data[:x])\")\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Close enough! We don't get exactly 5/3 because of numerical tolerances within\n", + "our choice of optimization solver (in this case, Ipopt)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## A more complicated policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP.jl is also capable of finding policies for other types of Markov Decision\n", + "Processes. A classic example of a Markov Decision Process is the problem of\n", + "finding a path through a maze." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here's one example of a maze. Try changing the parameters to explore different\n", + "mazes:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "M, N = 3, 4\n", + "initial_square = (1, 1)\n", + "reward, illegal_squares, penalties = (3, 4), [(2, 2)], [(3, 1), (2, 4)]\n", + "path = fill(\"⋅\", M, N)\n", + "path[initial_square...] = \"1\"\n", + "for (k, v) in (illegal_squares => \"▩\", penalties => \"†\", [reward] => \"*\")\n", + " for (i, j) in k\n", + " path[i, j] = v\n", + " end\n", + "end\n", + "print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\\n'))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Our goal is to get from square `1` to square `*`. If we step on a `†`, we\n", + "incur a penalty of `1`. Squares with `▩` are blocked; we cannot move there." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are a variety of ways that we can solve this problem. We're going to\n", + "solve it using a stationary binary stochastic programming formulation." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Our state variable will be a matrix of binary variables $x_{i,j}$, where\n", + "each element is $1$ if the agent is in the square and $0$ otherwise. In\n", + "each period, we incur a reward of $1$ if we are in the `reward` square and a\n", + "penalty of $-1$ if we are in a `penalties` square. We cannot move to the\n", + "`illegal_squares`, so those $x_{i,j} = 0$. Feasibility between moves is\n", + "modelled by constraints of the form:\n", + "$$\n", + "x^\\prime_{i,j} \\le \\sum\\limits_{(a,b)\\in P} x_{a,b}\n", + "$$\n", + "where $P$ is the set of squares from which it is valid to move from `(a, b)`\n", + "to `(i, j)`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Because we are looking for a stationary policy, we need a unicyclic graph with\n", + "a discount factor:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "discount_factor = 0.9\n", + "graph = SDDP.UnicyclicGraph(discount_factor)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Then we can formulate our full model:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "import HiGHS\n", + "\n", + "model = SDDP.PolicyGraph(\n", + " graph;\n", + " sense = :Max,\n", + " upper_bound = 1 / (1 - discount_factor),\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, _\n", + " # Our state is a binary variable for each square\n", + " @variable(\n", + " sp,\n", + " x[i = 1:M, j = 1:N],\n", + " Bin,\n", + " SDDP.State,\n", + " initial_value = (i, j) == initial_square,\n", + " )\n", + " # Can only be in one square at a time\n", + " @constraint(sp, sum(x[i, j].out for i in 1:M, j in 1:N) == 1)\n", + " # Incur rewards and penalties\n", + " @stageobjective(\n", + " sp,\n", + " x[reward...].out - sum(x[i, j].out for (i, j) in penalties)\n", + " )\n", + " # Some squares are illegal\n", + " @constraint(sp, [(i, j) in illegal_squares], x[i, j].out <= 0)\n", + " # Constraints on valid moves\n", + " for i in 1:M, j in 1:N\n", + " moves = [(i - 1, j), (i + 1, j), (i, j), (i, j + 1), (i, j - 1)]\n", + " filter!(v -> 1 <= v[1] <= M && 1 <= v[2] <= N, moves)\n", + " @constraint(sp, x[i, j].out <= sum(x[a, b].in for (a, b) in moves))\n", + " end\n", + " return\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The upper bound is obtained by assuming that we reach the reward square in one\n", + "move and stay there." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> Since there are discrete decisions here, SDDP.jl is not guaranteed to find\n", + "> the globally optimal policy." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Simulating a cyclic policy graph requires an explicit `sampling_scheme` that\n", + "does not terminate early based on the cycle probability:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "simulations = SDDP.simulate(\n", + " model,\n", + " 1,\n", + " [:x];\n", + " sampling_scheme = SDDP.InSampleMonteCarlo(;\n", + " max_depth = 5,\n", + " terminate_on_dummy_leaf = false,\n", + " ),\n", + ");" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Fill in the `path` with the time-step in which we visit the square:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "for (t, data) in enumerate(simulations[1]), i in 1:M, j in 1:N\n", + " if data[:x][i, j].in > 0.5\n", + " path[i, j] = \"$t\"\n", + " end\n", + "end\n", + "\n", + "print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\\n'))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> This formulation will likely struggle as the number of cells in the maze\n", + "> increases. Can you think of an equivalent formulation that uses fewer\n", + "> state variables?" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/mdps.jl b/previews/PR826/tutorial/mdps.jl new file mode 100644 index 0000000000..35a21a2215 --- /dev/null +++ b/previews/PR826/tutorial/mdps.jl @@ -0,0 +1,211 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Example: Markov Decision Processes + +# `SDDP.jl` can be used to solve a variety of Markov Decision processes. If the +# problem has continuous state and control spaces, and the objective and +# transition function are convex, then SDDP.jl can find a globally optimal +# policy. In other cases, SDDP.jl will find a locally optimal policy. + +# ## A simple example + +# A simple demonstration of this is the example taken from page 98 of the book +# "Markov Decision Processes: Discrete stochastic Dynamic Programming", by +# Martin L. Putterman. + +# The example, as described in Section 4.6.3 of the book, is to minimize a sum +# of squares of `N` non-negative variables, subject to a budget constraint that +# the variable values add up to `M`. Put mathematically, that is: +# ```math +# \begin{aligned} +# \min \;\; & \sum\limits_{i=1}^N x_i^2 \\ +# s.t. \;\; & \sum\limits_{i=1}^N x_i = M \\ +# & x_i \ge 0, \quad i \in 1,\ldots,N +# \end{aligned} +# ``` +# The optimal objective value is ``M^2/N``, and the optimal solution is +# ``x_i = M / N``, which can be shown by induction. + +# This can be reformulated as a Markov Decision Process by introducing a state +# variable, ``s``, which tracks the un-spent budget over ``N`` stages. + +# ```math +# \begin{aligned} +# V_t(s) = \min \;\; & x^2 + V_{t+1}(s^\prime) \\ +# s.t. \;\; & s^\prime = s - x \\ +# & x \le s \\ +# & x \ge 0 \\ +# & s \ge 0 +# \end{aligned} +# ``` +# and in the last stage ``V_N``, there is an additional constraint that +# ``s^\prime = 0``. + +# The budget of ``M`` is computed by solving for ``V_1(M)``. + +# !!! info +# Since everything here is continuous and convex, SDDP.jl will find the +# globally optimal policy. + +# If the reformulation from the single problem into the recursive form of the +# Markov Decision Process is not obvious, consult Putterman's book. + +# We can model and solve this problem using SDDP.jl as follows: + +using SDDP +import Ipopt + +M, N = 5, 3 + +model = SDDP.LinearPolicyGraph(; + stages = N, + lower_bound = 0.0, + optimizer = Ipopt.Optimizer, +) do subproblem, node + @variable(subproblem, s >= 0, SDDP.State, initial_value = M) + @variable(subproblem, x >= 0) + @stageobjective(subproblem, x^2) + @constraint(subproblem, x <= s.in) + @constraint(subproblem, s.out == s.in - x) + if node == N + fix(s.out, 0.0; force = true) + end + return +end + +SDDP.train(model) + +# Check that we got the theoretical optimum: + +SDDP.calculate_bound(model), M^2 / N + +# And check that we found the theoretical value for each ``x_i``: + +simulations = SDDP.simulate(model, 1, [:x]) +for data in simulations[1] + println("x_$(data[:node_index]) = $(data[:x])") +end + +# Close enough! We don't get exactly 5/3 because of numerical tolerances within +# our choice of optimization solver (in this case, Ipopt). + +# ## A more complicated policy + +# SDDP.jl is also capable of finding policies for other types of Markov Decision +# Processes. A classic example of a Markov Decision Process is the problem of +# finding a path through a maze. + +# Here's one example of a maze. Try changing the parameters to explore different +# mazes: + +M, N = 3, 4 +initial_square = (1, 1) +reward, illegal_squares, penalties = (3, 4), [(2, 2)], [(3, 1), (2, 4)] +path = fill("⋅", M, N) +path[initial_square...] = "1" +for (k, v) in (illegal_squares => "▩", penalties => "†", [reward] => "*") + for (i, j) in k + path[i, j] = v + end +end +print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\n')) + +# Our goal is to get from square `1` to square `*`. If we step on a `†`, we +# incur a penalty of `1`. Squares with `▩` are blocked; we cannot move there. + +# There are a variety of ways that we can solve this problem. We're going to +# solve it using a stationary binary stochastic programming formulation. + +# Our state variable will be a matrix of binary variables ``x_{i,j}``, where +# each element is ``1`` if the agent is in the square and ``0`` otherwise. In +# each period, we incur a reward of ``1`` if we are in the `reward` square and a +# penalty of ``-1`` if we are in a `penalties` square. We cannot move to the +# `illegal_squares`, so those ``x_{i,j} = 0``. Feasibility between moves is +# modelled by constraints of the form: +# ```math +# x^\prime_{i,j} \le \sum\limits_{(a,b)\in P} x_{a,b} +# ``` +# where ``P`` is the set of squares from which it is valid to move from `(a, b)` +# to `(i, j)`. + +# Because we are looking for a stationary policy, we need a unicyclic graph with +# a discount factor: + +discount_factor = 0.9 +graph = SDDP.UnicyclicGraph(discount_factor) + +# Then we can formulate our full model: + +import HiGHS + +model = SDDP.PolicyGraph( + graph; + sense = :Max, + upper_bound = 1 / (1 - discount_factor), + optimizer = HiGHS.Optimizer, +) do sp, _ + ## Our state is a binary variable for each square + @variable( + sp, + x[i = 1:M, j = 1:N], + Bin, + SDDP.State, + initial_value = (i, j) == initial_square, + ) + ## Can only be in one square at a time + @constraint(sp, sum(x[i, j].out for i in 1:M, j in 1:N) == 1) + ## Incur rewards and penalties + @stageobjective( + sp, + x[reward...].out - sum(x[i, j].out for (i, j) in penalties) + ) + ## Some squares are illegal + @constraint(sp, [(i, j) in illegal_squares], x[i, j].out <= 0) + ## Constraints on valid moves + for i in 1:M, j in 1:N + moves = [(i - 1, j), (i + 1, j), (i, j), (i, j + 1), (i, j - 1)] + filter!(v -> 1 <= v[1] <= M && 1 <= v[2] <= N, moves) + @constraint(sp, x[i, j].out <= sum(x[a, b].in for (a, b) in moves)) + end + return +end + +# The upper bound is obtained by assuming that we reach the reward square in one +# move and stay there. + +# !!! warning +# Since there are discrete decisions here, SDDP.jl is not guaranteed to find +# the globally optimal policy. + +SDDP.train(model) + +# Simulating a cyclic policy graph requires an explicit `sampling_scheme` that +# does not terminate early based on the cycle probability: + +simulations = SDDP.simulate( + model, + 1, + [:x]; + sampling_scheme = SDDP.InSampleMonteCarlo(; + max_depth = 5, + terminate_on_dummy_leaf = false, + ), +); + +# Fill in the `path` with the time-step in which we visit the square: + +for (t, data) in enumerate(simulations[1]), i in 1:M, j in 1:N + if data[:x][i, j].in > 0.5 + path[i, j] = "$t" + end +end + +print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\n')) + +# !!! tip +# This formulation will likely struggle as the number of cells in the maze +# increases. Can you think of an equivalent formulation that uses fewer +# state variables? diff --git a/previews/PR826/tutorial/mdps/index.html b/previews/PR826/tutorial/mdps/index.html new file mode 100644 index 0000000000..8454faec7f --- /dev/null +++ b/previews/PR826/tutorial/mdps/index.html @@ -0,0 +1,182 @@ + +Example: Markov Decision Processes · SDDP.jl

Example: Markov Decision Processes

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

SDDP.jl can be used to solve a variety of Markov Decision processes. If the problem has continuous state and control spaces, and the objective and transition function are convex, then SDDP.jl can find a globally optimal policy. In other cases, SDDP.jl will find a locally optimal policy.

A simple example

A simple demonstration of this is the example taken from page 98 of the book "Markov Decision Processes: Discrete stochastic Dynamic Programming", by Martin L. Putterman.

The example, as described in Section 4.6.3 of the book, is to minimize a sum of squares of N non-negative variables, subject to a budget constraint that the variable values add up to M. Put mathematically, that is:

\[\begin{aligned} +\min \;\; & \sum\limits_{i=1}^N x_i^2 \\ +s.t. \;\; & \sum\limits_{i=1}^N x_i = M \\ + & x_i \ge 0, \quad i \in 1,\ldots,N +\end{aligned}\]

The optimal objective value is $M^2/N$, and the optimal solution is $x_i = M / N$, which can be shown by induction.

This can be reformulated as a Markov Decision Process by introducing a state variable, $s$, which tracks the un-spent budget over $N$ stages.

\[\begin{aligned} +V_t(s) = \min \;\; & x^2 + V_{t+1}(s^\prime) \\ +s.t. \;\; & s^\prime = s - x \\ + & x \le s \\ + & x \ge 0 \\ + & s \ge 0 +\end{aligned}\]

and in the last stage $V_N$, there is an additional constraint that $s^\prime = 0$.

The budget of $M$ is computed by solving for $V_1(M)$.

Info

Since everything here is continuous and convex, SDDP.jl will find the globally optimal policy.

If the reformulation from the single problem into the recursive form of the Markov Decision Process is not obvious, consult Putterman's book.

We can model and solve this problem using SDDP.jl as follows:

using SDDP
+import Ipopt
+
+M, N = 5, 3
+
+model = SDDP.LinearPolicyGraph(;
+    stages = N,
+    lower_bound = 0.0,
+    optimizer = Ipopt.Optimizer,
+) do subproblem, node
+    @variable(subproblem, s >= 0, SDDP.State, initial_value = M)
+    @variable(subproblem, x >= 0)
+    @stageobjective(subproblem, x^2)
+    @constraint(subproblem, x <= s.in)
+    @constraint(subproblem, s.out == s.in - x)
+    if node == N
+        fix(s.out, 0.0; force = true)
+    end
+    return
+end
+
+SDDP.train(model)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [4, 4]
+  AffExpr in MOI.EqualTo{Float64}         : [1, 1]
+  AffExpr in MOI.LessThan{Float64}        : [1, 1]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [2, 3]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [0e+00, 0e+00]
+  bounds range     [0e+00, 0e+00]
+  rhs range        [0e+00, 0e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   2.499895e+01  1.562631e+00  1.639104e-02         6   1
+        40   8.333333e+00  8.333333e+00  6.995511e-01       246   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 6.995511e-01
+total solves   : 246
+best bound     :  8.333333e+00
+simulation ci  :  8.810723e+00 ± 8.167195e-01
+numeric issues : 0
+-------------------------------------------------------------------

Check that we got the theoretical optimum:

SDDP.calculate_bound(model), M^2 / N
(8.333333297449974, 8.333333333333334)

And check that we found the theoretical value for each $x_i$:

simulations = SDDP.simulate(model, 1, [:x])
+for data in simulations[1]
+    println("x_$(data[:node_index]) = $(data[:x])")
+end
x_1 = 1.666665732482549
+x_2 = 1.6666672479151778
+x_3 = 1.666667013035603

Close enough! We don't get exactly 5/3 because of numerical tolerances within our choice of optimization solver (in this case, Ipopt).

A more complicated policy

SDDP.jl is also capable of finding policies for other types of Markov Decision Processes. A classic example of a Markov Decision Process is the problem of finding a path through a maze.

Here's one example of a maze. Try changing the parameters to explore different mazes:

M, N = 3, 4
+initial_square = (1, 1)
+reward, illegal_squares, penalties = (3, 4), [(2, 2)], [(3, 1), (2, 4)]
+path = fill("⋅", M, N)
+path[initial_square...] = "1"
+for (k, v) in (illegal_squares => "▩", penalties => "†", [reward] => "*")
+    for (i, j) in k
+        path[i, j] = v
+    end
+end
+print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\n'))
1 ⋅ ⋅ ⋅
+⋅ ▩ ⋅ †
+† ⋅ ⋅ *

Our goal is to get from square 1 to square *. If we step on a , we incur a penalty of 1. Squares with are blocked; we cannot move there.

There are a variety of ways that we can solve this problem. We're going to solve it using a stationary binary stochastic programming formulation.

Our state variable will be a matrix of binary variables $x_{i,j}$, where each element is $1$ if the agent is in the square and $0$ otherwise. In each period, we incur a reward of $1$ if we are in the reward square and a penalty of $-1$ if we are in a penalties square. We cannot move to the illegal_squares, so those $x_{i,j} = 0$. Feasibility between moves is modelled by constraints of the form:

\[x^\prime_{i,j} \le \sum\limits_{(a,b)\in P} x_{a,b}\]

where $P$ is the set of squares from which it is valid to move from (a, b) to (i, j).

Because we are looking for a stationary policy, we need a unicyclic graph with a discount factor:

discount_factor = 0.9
+graph = SDDP.UnicyclicGraph(discount_factor)
Root
+ 0
+Nodes
+ 1
+Arcs
+ 0 => 1 w.p. 1.0
+ 1 => 1 w.p. 0.9

Then we can formulate our full model:

import HiGHS
+
+model = SDDP.PolicyGraph(
+    graph;
+    sense = :Max,
+    upper_bound = 1 / (1 - discount_factor),
+    optimizer = HiGHS.Optimizer,
+) do sp, _
+    # Our state is a binary variable for each square
+    @variable(
+        sp,
+        x[i = 1:M, j = 1:N],
+        Bin,
+        SDDP.State,
+        initial_value = (i, j) == initial_square,
+    )
+    # Can only be in one square at a time
+    @constraint(sp, sum(x[i, j].out for i in 1:M, j in 1:N) == 1)
+    # Incur rewards and penalties
+    @stageobjective(
+        sp,
+        x[reward...].out - sum(x[i, j].out for (i, j) in penalties)
+    )
+    # Some squares are illegal
+    @constraint(sp, [(i, j) in illegal_squares], x[i, j].out <= 0)
+    # Constraints on valid moves
+    for i in 1:M, j in 1:N
+        moves = [(i - 1, j), (i + 1, j), (i, j), (i, j + 1), (i, j - 1)]
+        filter!(v -> 1 <= v[1] <= M && 1 <= v[2] <= N, moves)
+        @constraint(sp, x[i, j].out <= sum(x[a, b].in for (a, b) in moves))
+    end
+    return
+end
A policy graph with 1 nodes.
+ Node indices: 1
+

The upper bound is obtained by assuming that we reach the reward square in one move and stay there.

Warning

Since there are discrete decisions here, SDDP.jl is not guaranteed to find the globally optimal policy.

SDDP.train(model)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 12
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                          : [25, 25]
+  AffExpr in MOI.EqualTo{Float64}      : [1, 1]
+  AffExpr in MOI.LessThan{Float64}     : [13, 13]
+  VariableRef in MOI.LessThan{Float64} : [1, 1]
+  VariableRef in MOI.ZeroOne           : [12, 12]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 1e+00]
+  bounds range     [1e+01, 1e+01]
+  rhs range        [1e+00, 1e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   0.000000e+00  7.217100e+00  4.740000e-03        13   1
+        40   2.500000e+01  6.561000e+00  8.170640e-01      3144   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 8.170640e-01
+total solves   : 3144
+best bound     :  6.561000e+00
+simulation ci  :  8.075000e+00 ± 2.944509e+00
+numeric issues : 0
+-------------------------------------------------------------------

Simulating a cyclic policy graph requires an explicit sampling_scheme that does not terminate early based on the cycle probability:

simulations = SDDP.simulate(
+    model,
+    1,
+    [:x];
+    sampling_scheme = SDDP.InSampleMonteCarlo(;
+        max_depth = 5,
+        terminate_on_dummy_leaf = false,
+    ),
+);

Fill in the path with the time-step in which we visit the square:

for (t, data) in enumerate(simulations[1]), i in 1:M, j in 1:N
+    if data[:x][i, j].in > 0.5
+        path[i, j] = "$t"
+    end
+end
+
+print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\n'))
1 2 3 ⋅
+⋅ ▩ 4 †
+† ⋅ 5 *
Tip

This formulation will likely struggle as the number of cells in the maze increases. Can you think of an equivalent formulation that uses fewer state variables?

diff --git a/previews/PR826/tutorial/model_infeasible_node_2.cuts.json b/previews/PR826/tutorial/model_infeasible_node_2.cuts.json new file mode 100644 index 0000000000..418145e584 --- /dev/null +++ b/previews/PR826/tutorial/model_infeasible_node_2.cuts.json @@ -0,0 +1 @@ +[{"risk_set_cuts":[],"node":"2","single_cuts":[],"multi_cuts":[]},{"risk_set_cuts":[],"node":"1","single_cuts":[],"multi_cuts":[]}] \ No newline at end of file diff --git a/previews/PR826/tutorial/objective_states.ipynb b/previews/PR826/tutorial/objective_states.ipynb new file mode 100644 index 0000000000..6e432f858f --- /dev/null +++ b/previews/PR826/tutorial/objective_states.ipynb @@ -0,0 +1,441 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Objective states" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are many applications in which we want to model a price process that\n", + "follows some auto-regressive process. Common examples include stock prices on\n", + "financial exchanges and spot-prices in energy markets." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "However, it is well known that these cannot be incorporated in to SDDP because\n", + "they result in cost-to-go functions that are convex with respect to some state\n", + "variables (e.g., the reservoir levels) and concave with respect to other state\n", + "variables (e.g., the spot price in the current stage)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To overcome this problem, the approach in the literature has been to\n", + "discretize the price process in order to model it using a Markovian policy\n", + "graph like those discussed in Markovian policy graphs." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "However, recent work offers a way to include stagewise-dependent objective\n", + "uncertainty into the objective function of SDDP subproblems. Readers are\n", + "directed to the following works for an introduction:" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + " - Downward, A., Dowson, O., and Baucke, R. (2017). Stochastic dual dynamic\n", + " programming with stagewise dependent objective uncertainty. Optimization\n", + " Online. [link](http://www.optimization-online.org/DB_HTML/2018/02/6454.html)\n", + "\n", + " - Dowson, O. PhD Thesis. University of Auckland, 2018. [link](https://researchspace.auckland.ac.nz/handle/2292/37700)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The method discussed in the above works introduces the concept of an\n", + "_objective state_ into SDDP. Unlike normal state variables in SDDP (e.g., the\n", + "volume of water in the reservoir), the cost-to-go function is _concave_ with\n", + "respect to the objective states. Thus, the method builds an outer\n", + "approximation of the cost-to-go function in the normal state-space, and an\n", + "inner approximation of the cost-to-go function in the objective state-space." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> Support for objective states in `SDDP.jl` is experimental. Models are\n", + "> considerably more computational intensive, the interface is less\n", + "> user-friendly, and there are subtle gotchas to be aware of.\n", + "> Only use this if you have read and understood the theory behind the method." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## One-dimensional objective states" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Let's assume that the fuel cost is not fixed, but instead evolves according to\n", + "a multiplicative auto-regressive process: `fuel_cost[t] = ω * fuel_cost[t-1]`,\n", + "where `ω` is drawn from the sample space `[0.75, 0.9, 1.1, 1.25]` with equal\n", + "probability." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "An objective state can be added to a subproblem using the\n", + "`SDDP.add_objective_state` function. This can only be called once per\n", + "subproblem. If you want to add a multi-dimensional objective state, read\n", + "Multi-dimensional objective states. `SDDP.add_objective_state`\n", + "takes a number of keyword arguments. The two required ones are" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + " - `initial_value`: the value of the objective state at the root node of the\n", + " policy graph (i.e., identical to the `initial_value` when defining normal\n", + " state variables.\n", + "\n", + " - `lipschitz`: the Lipschitz constant of the cost-to-go function with respect\n", + " to the objective state. In other words, this value is the maximum change in\n", + " the cost-to-go function _at any point in the state space_, given a one-unit\n", + " change in the objective state." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are also two optional keyword arguments: `lower_bound` and\n", + "`upper_bound`, which give SDDP.jl hints (importantly, not constraints) about\n", + "the domain of the objective state. Setting these bounds appropriately can\n", + "improve the speed of convergence." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Finally, `SDDP.add_objective_state` requires an update function. This\n", + "function takes two arguments. The first is the incoming value of the objective\n", + "state, and the second is the realization of the stagewise-independent noise\n", + "term (set using `SDDP.parameterize`). The function should return the\n", + "value of the objective state to be used in the current subproblem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This connection with the stagewise-independent noise term means that\n", + "`SDDP.parameterize` _must_ be called in a subproblem that defines an\n", + "objective state. Inside `SDDP.parameterize`, the value of the\n", + "objective state to be used in the current subproblem (i.e., after the update\n", + "function), can be queried using `SDDP.objective_state`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is the full model with the objective state." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, t\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in + inflow - hydro_generation - hydro_spill\n", + " demand_constraint, thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + "\n", + " # Add an objective state. ω will be the same value that is called in\n", + " # `SDDP.parameterize`.\n", + "\n", + " SDDP.add_objective_state(\n", + " subproblem;\n", + " initial_value = 50.0,\n", + " lipschitz = 10_000.0,\n", + " lower_bound = 50.0,\n", + " upper_bound = 150.0,\n", + " ) do fuel_cost, ω\n", + " return ω.fuel * fuel_cost\n", + " end\n", + "\n", + " # Create the cartesian product of a multi-dimensional random variable.\n", + "\n", + " Ω = [\n", + " (fuel = f, inflow = w) for f in [0.75, 0.9, 1.1, 1.25] for\n", + " w in [0.0, 50.0, 100.0]\n", + " ]\n", + "\n", + " SDDP.parameterize(subproblem, Ω) do ω\n", + " # Query the current fuel cost.\n", + " fuel_cost = SDDP.objective_state(subproblem)\n", + " @stageobjective(subproblem, fuel_cost * thermal_generation)\n", + " return JuMP.fix(inflow, ω.inflow)\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "After creating our model, we can train and simulate as usual." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model; run_numerical_stability_report = false)\n", + "\n", + "simulations = SDDP.simulate(model, 1)\n", + "\n", + "print(\"Finished training and simulating.\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "To demonstrate how the objective states are updated, consider the sequence of\n", + "noise observations:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "[stage[:noise_term] for stage in simulations[1]]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This, the fuel cost in the first stage should be `0.75 * 50 = 37.5`. The fuel\n", + "cost in the second stage should be `1.1 * 37.5 = 41.25`. The fuel cost in the\n", + "third stage should be `0.75 * 41.25 = 30.9375`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To confirm this, the values of the objective state in a simulation can be\n", + "queried using the `:objective_state` key." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "[stage[:objective_state] for stage in simulations[1]]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Multi-dimensional objective states" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "You can construct multi-dimensional price processes using `NTuple`s. Just\n", + "replace every scalar value associated with the objective state by a tuple. For\n", + "example, `initial_value = 1.0` becomes `initial_value = (1.0, 2.0)`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Here is an example:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, t\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in + inflow - hydro_generation - hydro_spill\n", + " demand_constraint, thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + "\n", + " SDDP.add_objective_state(\n", + " subproblem;\n", + " initial_value = (50.0, 50.0),\n", + " lipschitz = (10_000.0, 10_000.0),\n", + " lower_bound = (50.0, 50.0),\n", + " upper_bound = (150.0, 150.0),\n", + " ) do fuel_cost, ω\n", + " # fuel_cost is a tuple, containing the (fuel_cost[t-1], fuel_cost[t-2])\n", + " # This function returns a new tuple containing\n", + " # (fuel_cost[t], fuel_cost[t-1]). Thus, we need to compute the new\n", + " # cost:\n", + " new_cost = fuel_cost[1] + 0.5 * (fuel_cost[1] - fuel_cost[2]) + ω.fuel\n", + " # And then return the appropriate tuple:\n", + " return (new_cost, fuel_cost[1])\n", + " end\n", + "\n", + " Ω = [\n", + " (fuel = f, inflow = w) for f in [-10.0, -5.0, 5.0, 10.0] for\n", + " w in [0.0, 50.0, 100.0]\n", + " ]\n", + "\n", + " SDDP.parameterize(subproblem, Ω) do ω\n", + " fuel_cost, _ = SDDP.objective_state(subproblem)\n", + " @stageobjective(subproblem, fuel_cost * thermal_generation)\n", + " return JuMP.fix(inflow, ω.inflow)\n", + " end\n", + "end\n", + "\n", + "SDDP.train(model; run_numerical_stability_report = false)\n", + "\n", + "simulations = SDDP.simulate(model, 1)\n", + "\n", + "print(\"Finished training and simulating.\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "This time, since our objective state is two-dimensional, the objective states\n", + "are tuples with two elements:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "[stage[:objective_state] for stage in simulations[1]]" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Warnings" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "There are number of things to be aware of when using objective states.\n", + "\n", + " - The key assumption is that price is independent of the states and actions in\n", + " the model.\n", + "\n", + " That means that the price cannot appear in any `@constraint`s. Nor can you\n", + " use any `@variable`s in the update function.\n", + "\n", + " - Choosing an appropriate Lipschitz constant is difficult.\n", + "\n", + " The points discussed in Choosing an initial bound are relevant.\n", + " The Lipschitz constant should not be chosen as large as possible (since\n", + " this will help with convergence and the numerical issues discussed above),\n", + " but if chosen to small, it may cut of the feasible region and lead to a\n", + " sub-optimal solution.\n", + "\n", + " - You need to ensure that the cost-to-go function is concave with respect to\n", + " the objective state _before_ the update.\n", + "\n", + " If the update function is linear, this is always the case. In some\n", + " situations, the update function can be nonlinear (e.g., multiplicative as\n", + " we have above). In general, placing constraints on the price (e.g.,\n", + " `clamp(price, 0, 1)`) will destroy concavity. [Caveat\n", + " emptor](https://en.wikipedia.org/wiki/Caveat_emptor). It's up to you if\n", + " this is a problem. If it isn't you'll get a good heuristic with no\n", + " guarantee of global optimality." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/objective_states.jl b/previews/PR826/tutorial/objective_states.jl new file mode 100644 index 0000000000..029e034310 --- /dev/null +++ b/previews/PR826/tutorial/objective_states.jl @@ -0,0 +1,253 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Objective states + +# There are many applications in which we want to model a price process that +# follows some auto-regressive process. Common examples include stock prices on +# financial exchanges and spot-prices in energy markets. + +# However, it is well known that these cannot be incorporated in to SDDP because +# they result in cost-to-go functions that are convex with respect to some state +# variables (e.g., the reservoir levels) and concave with respect to other state +# variables (e.g., the spot price in the current stage). + +# To overcome this problem, the approach in the literature has been to +# discretize the price process in order to model it using a Markovian policy +# graph like those discussed in [Markovian policy graphs](@ref). + +# However, recent work offers a way to include stagewise-dependent objective +# uncertainty into the objective function of SDDP subproblems. Readers are +# directed to the following works for an introduction: + +# - Downward, A., Dowson, O., and Baucke, R. (2017). Stochastic dual dynamic +# programming with stagewise dependent objective uncertainty. Optimization +# Online. [link](http://www.optimization-online.org/DB_HTML/2018/02/6454.html) +# +# - Dowson, O. PhD Thesis. University of Auckland, 2018. [link](https://researchspace.auckland.ac.nz/handle/2292/37700) + +# The method discussed in the above works introduces the concept of an +# _objective state_ into SDDP. Unlike normal state variables in SDDP (e.g., the +# volume of water in the reservoir), the cost-to-go function is _concave_ with +# respect to the objective states. Thus, the method builds an outer +# approximation of the cost-to-go function in the normal state-space, and an +# inner approximation of the cost-to-go function in the objective state-space. + +# !!! warning +# Support for objective states in `SDDP.jl` is experimental. Models are +# considerably more computational intensive, the interface is less +# user-friendly, and there are [subtle gotchas to be aware of](@ref objective_state_warnings). +# Only use this if you have read and understood the theory behind the method. + +# ## One-dimensional objective states + +# Let's assume that the fuel cost is not fixed, but instead evolves according to +# a multiplicative auto-regressive process: `fuel_cost[t] = ω * fuel_cost[t-1]`, +# where `ω` is drawn from the sample space `[0.75, 0.9, 1.1, 1.25]` with equal +# probability. + +# An objective state can be added to a subproblem using the +# [`SDDP.add_objective_state`](@ref) function. This can only be called once per +# subproblem. If you want to add a multi-dimensional objective state, read +# [Multi-dimensional objective states](@ref). [`SDDP.add_objective_state`](@ref) +# takes a number of keyword arguments. The two required ones are + +# - `initial_value`: the value of the objective state at the root node of the +# policy graph (i.e., identical to the `initial_value` when defining normal +# state variables. +# +# - `lipschitz`: the Lipschitz constant of the cost-to-go function with respect +# to the objective state. In other words, this value is the maximum change in +# the cost-to-go function _at any point in the state space_, given a one-unit +# change in the objective state. + +# There are also two optional keyword arguments: `lower_bound` and +# `upper_bound`, which give SDDP.jl hints (importantly, not constraints) about +# the domain of the objective state. Setting these bounds appropriately can +# improve the speed of convergence. + +# Finally, [`SDDP.add_objective_state`](@ref) requires an update function. This +# function takes two arguments. The first is the incoming value of the objective +# state, and the second is the realization of the stagewise-independent noise +# term (set using [`SDDP.parameterize`](@ref)). The function should return the +# value of the objective state to be used in the current subproblem. + +# This connection with the stagewise-independent noise term means that +# [`SDDP.parameterize`](@ref) _must_ be called in a subproblem that defines an +# objective state. Inside [`SDDP.parameterize`](@ref), the value of the +# objective state to be used in the current subproblem (i.e., after the update +# function), can be queried using [`SDDP.objective_state`](@ref). + +# Here is the full model with the objective state. + +using SDDP, HiGHS + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, t + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + @constraints( + subproblem, + begin + volume.out == volume.in + inflow - hydro_generation - hydro_spill + demand_constraint, thermal_generation + hydro_generation == 150.0 + end + ) + + ## Add an objective state. ω will be the same value that is called in + ## `SDDP.parameterize`. + + SDDP.add_objective_state( + subproblem; + initial_value = 50.0, + lipschitz = 10_000.0, + lower_bound = 50.0, + upper_bound = 150.0, + ) do fuel_cost, ω + return ω.fuel * fuel_cost + end + + ## Create the cartesian product of a multi-dimensional random variable. + + Ω = [ + (fuel = f, inflow = w) for f in [0.75, 0.9, 1.1, 1.25] for + w in [0.0, 50.0, 100.0] + ] + + SDDP.parameterize(subproblem, Ω) do ω + ## Query the current fuel cost. + fuel_cost = SDDP.objective_state(subproblem) + @stageobjective(subproblem, fuel_cost * thermal_generation) + return JuMP.fix(inflow, ω.inflow) + end +end + +# After creating our model, we can train and simulate as usual. + +SDDP.train(model; run_numerical_stability_report = false) + +simulations = SDDP.simulate(model, 1) + +print("Finished training and simulating.") + +# To demonstrate how the objective states are updated, consider the sequence of +# noise observations: + +[stage[:noise_term] for stage in simulations[1]] + +# This, the fuel cost in the first stage should be `0.75 * 50 = 37.5`. The fuel +# cost in the second stage should be `1.1 * 37.5 = 41.25`. The fuel cost in the +# third stage should be `0.75 * 41.25 = 30.9375`. + +# To confirm this, the values of the objective state in a simulation can be +# queried using the `:objective_state` key. + +[stage[:objective_state] for stage in simulations[1]] + +# ## Multi-dimensional objective states + +# You can construct multi-dimensional price processes using `NTuple`s. Just +# replace every scalar value associated with the objective state by a tuple. For +# example, `initial_value = 1.0` becomes `initial_value = (1.0, 2.0)`. + +# Here is an example: + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, t + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + @constraints( + subproblem, + begin + volume.out == volume.in + inflow - hydro_generation - hydro_spill + demand_constraint, thermal_generation + hydro_generation == 150.0 + end + ) + + SDDP.add_objective_state( + subproblem; + initial_value = (50.0, 50.0), + lipschitz = (10_000.0, 10_000.0), + lower_bound = (50.0, 50.0), + upper_bound = (150.0, 150.0), + ) do fuel_cost, ω + ## fuel_cost is a tuple, containing the (fuel_cost[t-1], fuel_cost[t-2]) + ## This function returns a new tuple containing + ## (fuel_cost[t], fuel_cost[t-1]). Thus, we need to compute the new + ## cost: + new_cost = fuel_cost[1] + 0.5 * (fuel_cost[1] - fuel_cost[2]) + ω.fuel + ## And then return the appropriate tuple: + return (new_cost, fuel_cost[1]) + end + + Ω = [ + (fuel = f, inflow = w) for f in [-10.0, -5.0, 5.0, 10.0] for + w in [0.0, 50.0, 100.0] + ] + + SDDP.parameterize(subproblem, Ω) do ω + fuel_cost, _ = SDDP.objective_state(subproblem) + @stageobjective(subproblem, fuel_cost * thermal_generation) + return JuMP.fix(inflow, ω.inflow) + end +end + +SDDP.train(model; run_numerical_stability_report = false) + +simulations = SDDP.simulate(model, 1) + +print("Finished training and simulating.") + +# This time, since our objective state is two-dimensional, the objective states +# are tuples with two elements: + +[stage[:objective_state] for stage in simulations[1]] + +# ## [Warnings](@id objective_state_warnings) + +# There are number of things to be aware of when using objective states. +# +# - The key assumption is that price is independent of the states and actions in +# the model. +# +# That means that the price cannot appear in any `@constraint`s. Nor can you +# use any `@variable`s in the update function. +# +# - Choosing an appropriate Lipschitz constant is difficult. +# +# The points discussed in [Choosing an initial bound](@ref) are relevant. +# The Lipschitz constant should not be chosen as large as possible (since +# this will help with convergence and the numerical issues discussed above), +# but if chosen to small, it may cut of the feasible region and lead to a +# sub-optimal solution. +# +# - You need to ensure that the cost-to-go function is concave with respect to +# the objective state _before_ the update. +# +# If the update function is linear, this is always the case. In some +# situations, the update function can be nonlinear (e.g., multiplicative as +# we have above). In general, placing constraints on the price (e.g., +# `clamp(price, 0, 1)`) will destroy concavity. [Caveat +# emptor](https://en.wikipedia.org/wiki/Caveat_emptor). It's up to you if +# this is a problem. If it isn't you'll get a good heuristic with no +# guarantee of global optimality. diff --git a/previews/PR826/tutorial/objective_states/index.html b/previews/PR826/tutorial/objective_states/index.html new file mode 100644 index 0000000000..b5503bdce5 --- /dev/null +++ b/previews/PR826/tutorial/objective_states/index.html @@ -0,0 +1,190 @@ + +Objective states · SDDP.jl

Objective states

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

There are many applications in which we want to model a price process that follows some auto-regressive process. Common examples include stock prices on financial exchanges and spot-prices in energy markets.

However, it is well known that these cannot be incorporated in to SDDP because they result in cost-to-go functions that are convex with respect to some state variables (e.g., the reservoir levels) and concave with respect to other state variables (e.g., the spot price in the current stage).

To overcome this problem, the approach in the literature has been to discretize the price process in order to model it using a Markovian policy graph like those discussed in Markovian policy graphs.

However, recent work offers a way to include stagewise-dependent objective uncertainty into the objective function of SDDP subproblems. Readers are directed to the following works for an introduction:

  • Downward, A., Dowson, O., and Baucke, R. (2017). Stochastic dual dynamic programming with stagewise dependent objective uncertainty. Optimization Online. link

  • Dowson, O. PhD Thesis. University of Auckland, 2018. link

The method discussed in the above works introduces the concept of an objective state into SDDP. Unlike normal state variables in SDDP (e.g., the volume of water in the reservoir), the cost-to-go function is concave with respect to the objective states. Thus, the method builds an outer approximation of the cost-to-go function in the normal state-space, and an inner approximation of the cost-to-go function in the objective state-space.

Warning

Support for objective states in SDDP.jl is experimental. Models are considerably more computational intensive, the interface is less user-friendly, and there are subtle gotchas to be aware of. Only use this if you have read and understood the theory behind the method.

One-dimensional objective states

Let's assume that the fuel cost is not fixed, but instead evolves according to a multiplicative auto-regressive process: fuel_cost[t] = ω * fuel_cost[t-1], where ω is drawn from the sample space [0.75, 0.9, 1.1, 1.25] with equal probability.

An objective state can be added to a subproblem using the SDDP.add_objective_state function. This can only be called once per subproblem. If you want to add a multi-dimensional objective state, read Multi-dimensional objective states. SDDP.add_objective_state takes a number of keyword arguments. The two required ones are

  • initial_value: the value of the objective state at the root node of the policy graph (i.e., identical to the initial_value when defining normal state variables.

  • lipschitz: the Lipschitz constant of the cost-to-go function with respect to the objective state. In other words, this value is the maximum change in the cost-to-go function at any point in the state space, given a one-unit change in the objective state.

There are also two optional keyword arguments: lower_bound and upper_bound, which give SDDP.jl hints (importantly, not constraints) about the domain of the objective state. Setting these bounds appropriately can improve the speed of convergence.

Finally, SDDP.add_objective_state requires an update function. This function takes two arguments. The first is the incoming value of the objective state, and the second is the realization of the stagewise-independent noise term (set using SDDP.parameterize). The function should return the value of the objective state to be used in the current subproblem.

This connection with the stagewise-independent noise term means that SDDP.parameterize must be called in a subproblem that defines an objective state. Inside SDDP.parameterize, the value of the objective state to be used in the current subproblem (i.e., after the update function), can be queried using SDDP.objective_state.

Here is the full model with the objective state.

using SDDP, HiGHS
+
+model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, t
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in + inflow - hydro_generation - hydro_spill
+            demand_constraint, thermal_generation + hydro_generation == 150.0
+        end
+    )
+
+    # Add an objective state. ω will be the same value that is called in
+    # `SDDP.parameterize`.
+
+    SDDP.add_objective_state(
+        subproblem;
+        initial_value = 50.0,
+        lipschitz = 10_000.0,
+        lower_bound = 50.0,
+        upper_bound = 150.0,
+    ) do fuel_cost, ω
+        return ω.fuel * fuel_cost
+    end
+
+    # Create the cartesian product of a multi-dimensional random variable.
+
+    Ω = [
+        (fuel = f, inflow = w) for f in [0.75, 0.9, 1.1, 1.25] for
+        w in [0.0, 50.0, 100.0]
+    ]
+
+    SDDP.parameterize(subproblem, Ω) do ω
+        # Query the current fuel cost.
+        fuel_cost = SDDP.objective_state(subproblem)
+        @stageobjective(subproblem, fuel_cost * thermal_generation)
+        return JuMP.fix(inflow, ω.inflow)
+    end
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+

After creating our model, we can train and simulate as usual.

SDDP.train(model; run_numerical_stability_report = false)
+
+simulations = SDDP.simulate(model, 1)
+
+print("Finished training and simulating.")
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.72800e+03
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [8, 8]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 4]
+  AffExpr in MOI.GreaterThan{Float64}     : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [6, 6]
+  VariableRef in MOI.LessThan{Float64}    : [2, 3]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   6.806250e+03  4.408308e+03  8.273125e-03        39   1
+       182   7.218750e+03  5.092593e+03  7.173290e-01      8598   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 7.173290e-01
+total solves   : 8598
+best bound     :  5.092593e+03
+simulation ci  :  4.992895e+03 ± 5.635857e+02
+numeric issues : 0
+-------------------------------------------------------------------
+
+Finished training and simulating.

To demonstrate how the objective states are updated, consider the sequence of noise observations:

[stage[:noise_term] for stage in simulations[1]]
3-element Vector{@NamedTuple{fuel::Float64, inflow::Float64}}:
+ (fuel = 1.1, inflow = 50.0)
+ (fuel = 1.25, inflow = 100.0)
+ (fuel = 1.1, inflow = 50.0)

This, the fuel cost in the first stage should be 0.75 * 50 = 37.5. The fuel cost in the second stage should be 1.1 * 37.5 = 41.25. The fuel cost in the third stage should be 0.75 * 41.25 = 30.9375.

To confirm this, the values of the objective state in a simulation can be queried using the :objective_state key.

[stage[:objective_state] for stage in simulations[1]]
3-element Vector{Float64}:
+ 55.00000000000001
+ 68.75000000000001
+ 75.62500000000003

Multi-dimensional objective states

You can construct multi-dimensional price processes using NTuples. Just replace every scalar value associated with the objective state by a tuple. For example, initial_value = 1.0 becomes initial_value = (1.0, 2.0).

Here is an example:

model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, t
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in + inflow - hydro_generation - hydro_spill
+            demand_constraint, thermal_generation + hydro_generation == 150.0
+        end
+    )
+
+    SDDP.add_objective_state(
+        subproblem;
+        initial_value = (50.0, 50.0),
+        lipschitz = (10_000.0, 10_000.0),
+        lower_bound = (50.0, 50.0),
+        upper_bound = (150.0, 150.0),
+    ) do fuel_cost, ω
+        # fuel_cost is a tuple, containing the (fuel_cost[t-1], fuel_cost[t-2])
+        # This function returns a new tuple containing
+        # (fuel_cost[t], fuel_cost[t-1]). Thus, we need to compute the new
+        # cost:
+        new_cost = fuel_cost[1] + 0.5 * (fuel_cost[1] - fuel_cost[2]) + ω.fuel
+        # And then return the appropriate tuple:
+        return (new_cost, fuel_cost[1])
+    end
+
+    Ω = [
+        (fuel = f, inflow = w) for f in [-10.0, -5.0, 5.0, 10.0] for
+        w in [0.0, 50.0, 100.0]
+    ]
+
+    SDDP.parameterize(subproblem, Ω) do ω
+        fuel_cost, _ = SDDP.objective_state(subproblem)
+        @stageobjective(subproblem, fuel_cost * thermal_generation)
+        return JuMP.fix(inflow, ω.inflow)
+    end
+end
+
+SDDP.train(model; run_numerical_stability_report = false)
+
+simulations = SDDP.simulate(model, 1)
+
+print("Finished training and simulating.")
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.72800e+03
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [9, 9]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 10]
+  AffExpr in MOI.GreaterThan{Float64}     : [4, 4]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [7, 7]
+  VariableRef in MOI.LessThan{Float64}    : [3, 4]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   7.250000e+03  3.529412e+03  9.008884e-03        39   1
+       184   1.875000e+03  5.135984e+03  1.009576e+00      8676   1
+       290   1.150000e+04  5.135984e+03  1.490031e+00     13110   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.490031e+00
+total solves   : 13110
+best bound     :  5.135984e+03
+simulation ci  :  5.362165e+03 ± 4.590779e+02
+numeric issues : 0
+-------------------------------------------------------------------
+
+Finished training and simulating.

This time, since our objective state is two-dimensional, the objective states are tuples with two elements:

[stage[:objective_state] for stage in simulations[1]]
3-element Vector{Tuple{Float64, Float64}}:
+ (55.0, 50.0)
+ (62.5, 55.0)
+ (71.25, 62.5)

Warnings

There are number of things to be aware of when using objective states.

  • The key assumption is that price is independent of the states and actions in the model.

    That means that the price cannot appear in any @constraints. Nor can you use any @variables in the update function.

  • Choosing an appropriate Lipschitz constant is difficult.

    The points discussed in Choosing an initial bound are relevant. The Lipschitz constant should not be chosen as large as possible (since this will help with convergence and the numerical issues discussed above), but if chosen to small, it may cut of the feasible region and lead to a sub-optimal solution.

  • You need to ensure that the cost-to-go function is concave with respect to the objective state before the update.

    If the update function is linear, this is always the case. In some situations, the update function can be nonlinear (e.g., multiplicative as we have above). In general, placing constraints on the price (e.g., clamp(price, 0, 1)) will destroy concavity. Caveat emptor. It's up to you if this is a problem. If it isn't you'll get a good heuristic with no guarantee of global optimality.

diff --git a/previews/PR826/tutorial/objective_uncertainty.ipynb b/previews/PR826/tutorial/objective_uncertainty.ipynb new file mode 100644 index 0000000000..d9af076bc6 --- /dev/null +++ b/previews/PR826/tutorial/objective_uncertainty.ipynb @@ -0,0 +1,166 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Uncertainty in the objective function" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In the previous tutorial, An introduction to SDDP.jl, we created a\n", + "stochastic hydro-thermal scheduling model. In this tutorial, we extend the\n", + "problem by adding uncertainty to the fuel costs." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Previously, we assumed that the fuel cost was deterministic: \\$50/MWh in the\n", + "first stage, \\$100/MWh in the second stage, and \\$150/MWh in the third\n", + "stage. For this tutorial, we assume that in addition to these base costs, the\n", + "actual fuel cost is correlated with the inflows." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Our new model for the uncertainty is given by the following table:\n", + "\n", + "| ω | 1 | 2 | 3 |\n", + "| ---- | --- | --- | ---- |\n", + "| P(ω) | 1/3 | 1/3 | 1/3 |\n", + "| inflow | 0 | 50 | 100 |\n", + "| fuel multiplier | 1.5 | 1.0 | 0.75 |" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In stage `t`, the objective is now to minimize:\n", + "\n", + "`fuel_multiplier * fuel_cost[t] * thermal_generation`" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Creating a model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To add an uncertain objective, we can simply call `@stageobjective`\n", + "from inside the `SDDP.parameterize` function." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, t\n", + " # Define the state variable.\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " # Define the control variables.\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " # Define the constraints\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in + inflow - hydro_generation - hydro_spill\n", + " thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + " fuel_cost = [50.0, 100.0, 150.0]\n", + " # Parameterize the subproblem.\n", + " Ω = [\n", + " (inflow = 0.0, fuel_multiplier = 1.5),\n", + " (inflow = 50.0, fuel_multiplier = 1.0),\n", + " (inflow = 100.0, fuel_multiplier = 0.75),\n", + " ]\n", + " SDDP.parameterize(subproblem, Ω, [1 / 3, 1 / 3, 1 / 3]) do ω\n", + " JuMP.fix(inflow, ω.inflow)\n", + " @stageobjective(\n", + " subproblem,\n", + " ω.fuel_multiplier * fuel_cost[t] * thermal_generation\n", + " )\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Training and simulating the policy" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As in the previous two tutorials, we train and simulate the policy:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(model)\n", + "\n", + "simulations = SDDP.simulate(model, 500)\n", + "\n", + "objective_values =\n", + " [sum(stage[:stage_objective] for stage in sim) for sim in simulations]\n", + "\n", + "using Statistics\n", + "\n", + "μ = round(mean(objective_values); digits = 2)\n", + "ci = round(1.96 * std(objective_values) / sqrt(500); digits = 2)\n", + "\n", + "println(\"Confidence interval: \", μ, \" ± \", ci)\n", + "println(\"Lower bound: \", round(SDDP.calculate_bound(model); digits = 2))" + ], + "metadata": {}, + "execution_count": null + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/objective_uncertainty.jl b/previews/PR826/tutorial/objective_uncertainty.jl new file mode 100644 index 0000000000..adba0d9fa6 --- /dev/null +++ b/previews/PR826/tutorial/objective_uncertainty.jl @@ -0,0 +1,92 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Uncertainty in the objective function + +# In the previous tutorial, [An introduction to SDDP.jl](@ref), we created a +# stochastic hydro-thermal scheduling model. In this tutorial, we extend the +# problem by adding uncertainty to the fuel costs. + +# Previously, we assumed that the fuel cost was deterministic: \$50/MWh in the +# first stage, \$100/MWh in the second stage, and \$150/MWh in the third +# stage. For this tutorial, we assume that in addition to these base costs, the +# actual fuel cost is correlated with the inflows. + +# Our new model for the uncertainty is given by the following table: +# +# | ω | 1 | 2 | 3 | +# | ---- | --- | --- | ---- | +# | P(ω) | 1/3 | 1/3 | 1/3 | +# | inflow | 0 | 50 | 100 | +# | fuel multiplier | 1.5 | 1.0 | 0.75 | + +# In stage `t`, the objective is now to minimize: +# +# `fuel_multiplier * fuel_cost[t] * thermal_generation` + +# ## Creating a model + +# To add an uncertain objective, we can simply call [`@stageobjective`](@ref) +# from inside the [`SDDP.parameterize`](@ref) function. + +using SDDP, HiGHS + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, t + ## Define the state variable. + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + ## Define the control variables. + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + ## Define the constraints + @constraints( + subproblem, + begin + volume.out == volume.in + inflow - hydro_generation - hydro_spill + thermal_generation + hydro_generation == 150.0 + end + ) + fuel_cost = [50.0, 100.0, 150.0] + ## Parameterize the subproblem. + Ω = [ + (inflow = 0.0, fuel_multiplier = 1.5), + (inflow = 50.0, fuel_multiplier = 1.0), + (inflow = 100.0, fuel_multiplier = 0.75), + ] + SDDP.parameterize(subproblem, Ω, [1 / 3, 1 / 3, 1 / 3]) do ω + JuMP.fix(inflow, ω.inflow) + @stageobjective( + subproblem, + ω.fuel_multiplier * fuel_cost[t] * thermal_generation + ) + end +end + +# ## Training and simulating the policy + +# As in the previous two tutorials, we train and simulate the policy: + +SDDP.train(model) + +simulations = SDDP.simulate(model, 500) + +objective_values = + [sum(stage[:stage_objective] for stage in sim) for sim in simulations] + +using Statistics + +μ = round(mean(objective_values); digits = 2) +ci = round(1.96 * std(objective_values) / sqrt(500); digits = 2) + +println("Confidence interval: ", μ, " ± ", ci) +println("Lower bound: ", round(SDDP.calculate_bound(model); digits = 2)) diff --git a/previews/PR826/tutorial/objective_uncertainty/index.html b/previews/PR826/tutorial/objective_uncertainty/index.html new file mode 100644 index 0000000000..7d3e2686e3 --- /dev/null +++ b/previews/PR826/tutorial/objective_uncertainty/index.html @@ -0,0 +1,98 @@ + +Uncertainty in the objective function · SDDP.jl

Uncertainty in the objective function

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

In the previous tutorial, An introduction to SDDP.jl, we created a stochastic hydro-thermal scheduling model. In this tutorial, we extend the problem by adding uncertainty to the fuel costs.

Previously, we assumed that the fuel cost was deterministic: $50/MWh in the first stage, $100/MWh in the second stage, and $150/MWh in the third stage. For this tutorial, we assume that in addition to these base costs, the actual fuel cost is correlated with the inflows.

Our new model for the uncertainty is given by the following table:

ω123
P(ω)1/31/31/3
inflow050100
fuel multiplier1.51.00.75

In stage t, the objective is now to minimize:

fuel_multiplier * fuel_cost[t] * thermal_generation

Creating a model

To add an uncertain objective, we can simply call @stageobjective from inside the SDDP.parameterize function.

using SDDP, HiGHS
+
+model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, t
+    # Define the state variable.
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    # Define the control variables.
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    # Define the constraints
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in + inflow - hydro_generation - hydro_spill
+            thermal_generation + hydro_generation == 150.0
+        end
+    )
+    fuel_cost = [50.0, 100.0, 150.0]
+    # Parameterize the subproblem.
+    Ω = [
+        (inflow = 0.0, fuel_multiplier = 1.5),
+        (inflow = 50.0, fuel_multiplier = 1.0),
+        (inflow = 100.0, fuel_multiplier = 0.75),
+    ]
+    SDDP.parameterize(subproblem, Ω, [1 / 3, 1 / 3, 1 / 3]) do ω
+        JuMP.fix(inflow, ω.inflow)
+        @stageobjective(
+            subproblem,
+            ω.fuel_multiplier * fuel_cost[t] * thermal_generation
+        )
+    end
+end
A policy graph with 3 nodes.
+ Node indices: 1, 2, 3
+

Training and simulating the policy

As in the previous two tutorials, we train and simulate the policy:

SDDP.train(model)
+
+simulations = SDDP.simulate(model, 500)
+
+objective_values =
+    [sum(stage[:stage_objective] for stage in sim) for sim in simulations]
+
+using Statistics
+
+μ = round(mean(objective_values); digits = 2)
+ci = round(1.96 * std(objective_values) / sqrt(500); digits = 2)
+
+println("Confidence interval: ", μ, " ± ", ci)
+println("Lower bound: ", round(SDDP.calculate_bound(model); digits = 2))
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 2.70000e+01
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+numerical stability report
+  matrix range     [1e+00, 1e+00]
+  objective range  [1e+00, 2e+02]
+  bounds range     [2e+02, 2e+02]
+  rhs range        [2e+02, 2e+02]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   3.750000e+04  3.958333e+03  3.351927e-03        12   1
+        60   1.125000e+04  1.062500e+04  1.046178e-01       963   1
+-------------------------------------------------------------------
+status         : simulation_stopping
+total time (s) : 1.046178e-01
+total solves   : 963
+best bound     :  1.062500e+04
+simulation ci  :  1.142388e+04 ± 2.185147e+03
+numeric issues : 0
+-------------------------------------------------------------------
+
+Confidence interval: 10605.0 ± 707.79
+Lower bound: 10625.0
diff --git a/previews/PR826/tutorial/pglib_opf.ipynb b/previews/PR826/tutorial/pglib_opf.ipynb new file mode 100644 index 0000000000..a3703abbe3 --- /dev/null +++ b/previews/PR826/tutorial/pglib_opf.ipynb @@ -0,0 +1,311 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Alternative forward models" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example demonstrates how to train convex and non-convex models." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "This example uses the following packages:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "import Ipopt\n", + "import PowerModels\n", + "import Test" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Formulation" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For our model, we build a simple optimal power flow model with a single\n", + "hydro-electric generator." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The formulation of our optimal power flow problem depends on `model_type`,\n", + "which must be one of the `PowerModels` formulations." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "(To run locally, download [`pglib_opf_case5_pjm.m`](pglib_opf_case5_pjm.m) and\n", + "update `filename` appropriately.)" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "function build_model(model_type)\n", + " filename = joinpath(@__DIR__, \"pglib_opf_case5_pjm.m\")\n", + " data = PowerModels.parse_file(filename)\n", + " return SDDP.PolicyGraph(\n", + " SDDP.UnicyclicGraph(0.95);\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = Ipopt.Optimizer,\n", + " ) do sp, t\n", + " power_model = PowerModels.instantiate_model(\n", + " data,\n", + " model_type,\n", + " PowerModels.build_opf;\n", + " jump_model = sp,\n", + " )\n", + " # Now add hydro power models. Assume that generator 5 is hydro, and the\n", + " # rest are thermal.\n", + " pg = power_model.var[:it][:pm][:nw][0][:pg][5]\n", + " sp[:pg] = pg\n", + " @variable(sp, x >= 0, SDDP.State, initial_value = 10.0)\n", + " @variable(sp, deficit >= 0)\n", + " @constraint(sp, balance, x.out == x.in - pg + deficit)\n", + " @stageobjective(sp, objective_function(sp) + 1e6 * deficit)\n", + " SDDP.parameterize(sp, [0, 2, 5]) do ω\n", + " return SDDP.set_normalized_rhs(balance, ω)\n", + " end\n", + " return\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Training a convex model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can build and train a convex approximation of the optimal power flow\n", + "problem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The problem with the convex model is that it does not accurately simulate the\n", + "true dynamics of the problem. Therefore, it under-estimates the true cost of\n", + "operation." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "convex = build_model(PowerModels.DCPPowerModel)\n", + "SDDP.train(convex; iteration_limit = 10)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "To more accurately simulate the dynamics of the problem, a common approach is\n", + "to write the cuts representing the policy to a file, and then read them into\n", + "a non-convex model:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.write_cuts_to_file(convex, \"convex.cuts.json\")\n", + "non_convex = build_model(PowerModels.ACPPowerModel)\n", + "SDDP.read_cuts_from_file(non_convex, \"convex.cuts.json\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Now we can simulate `non_convex` to evaluate the policy." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "result = SDDP.simulate(non_convex, 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "A problem with reading and writing the cuts to file is that the cuts have been\n", + "generated from trial points of the convex model. Therefore, the policy may be\n", + "arbitrarily bad at points visited by the non-convex model." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Training a non-convex model" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "We can also build and train a non-convex formulation of the optimal power flow\n", + "problem." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The problem with the non-convex model is that because it is non-convex,\n", + "SDDP.jl may find a sub-optimal policy. Therefore, it may over-estimate the\n", + "true cost of operation." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "non_convex = build_model(PowerModels.ACPPowerModel)\n", + "SDDP.train(non_convex; iteration_limit = 10)\n", + "result = SDDP.simulate(non_convex, 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Combining convex and non-convex models" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To summarize, training with the convex model constructs cuts at points that\n", + "may never be visited by the non-convex model, and training with the non-convex\n", + "model may construct arbitrarily poor cuts because a key assumption of SDDP is\n", + "convexity." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "As a compromise, we can train a policy using a combination of the convex and\n", + "non-convex models; we'll use the non-convex model to generate trial points on\n", + "the forward pass, and we'll use the convex model to build cuts on the backward\n", + "pass." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "convex = build_model(PowerModels.DCPPowerModel)" + ], + "metadata": {}, + "execution_count": null + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "non_convex = build_model(PowerModels.ACPPowerModel)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "To do so, we train `convex` using the `SDDP.AlternativeForwardPass`\n", + "forward pass, which simulates the model using `non_convex`, and we use\n", + "`SDDP.AlternativePostIterationCallback` as a post-iteration callback,\n", + "which copies cuts from the `convex` model back into the `non_convex` model." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.train(\n", + " convex;\n", + " forward_pass = SDDP.AlternativeForwardPass(non_convex),\n", + " post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),\n", + " iteration_limit = 10,\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "In practice, if we were to simulate `non_convex` now, we should obtain a\n", + "better policy than either of the two previous approaches." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/pglib_opf.jl b/previews/PR826/tutorial/pglib_opf.jl new file mode 100644 index 0000000000..c3bbe5ee13 --- /dev/null +++ b/previews/PR826/tutorial/pglib_opf.jl @@ -0,0 +1,130 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Alternative forward models + +# This example demonstrates how to train convex and non-convex models. + +# This example uses the following packages: + +using SDDP +import Ipopt +import PowerModels +import Test + +# ## Formulation + +# For our model, we build a simple optimal power flow model with a single +# hydro-electric generator. + +# The formulation of our optimal power flow problem depends on `model_type`, +# which must be one of the `PowerModels` formulations. + +# (To run locally, download [`pglib_opf_case5_pjm.m`](pglib_opf_case5_pjm.m) and +# update `filename` appropriately.) + +function build_model(model_type) + filename = joinpath(@__DIR__, "pglib_opf_case5_pjm.m") + data = PowerModels.parse_file(filename) + return SDDP.PolicyGraph( + SDDP.UnicyclicGraph(0.95); + sense = :Min, + lower_bound = 0.0, + optimizer = Ipopt.Optimizer, + ) do sp, t + power_model = PowerModels.instantiate_model( + data, + model_type, + PowerModels.build_opf; + jump_model = sp, + ) + ## Now add hydro power models. Assume that generator 5 is hydro, and the + ## rest are thermal. + pg = power_model.var[:it][:pm][:nw][0][:pg][5] + sp[:pg] = pg + @variable(sp, x >= 0, SDDP.State, initial_value = 10.0) + @variable(sp, deficit >= 0) + @constraint(sp, balance, x.out == x.in - pg + deficit) + @stageobjective(sp, objective_function(sp) + 1e6 * deficit) + SDDP.parameterize(sp, [0, 2, 5]) do ω + return SDDP.set_normalized_rhs(balance, ω) + end + return + end +end + +# ## Training a convex model + +# We can build and train a convex approximation of the optimal power flow +# problem. + +# The problem with the convex model is that it does not accurately simulate the +# true dynamics of the problem. Therefore, it under-estimates the true cost of +# operation. + +convex = build_model(PowerModels.DCPPowerModel) +SDDP.train(convex; iteration_limit = 10) + +# To more accurately simulate the dynamics of the problem, a common approach is +# to write the cuts representing the policy to a file, and then read them into +# a non-convex model: + +SDDP.write_cuts_to_file(convex, "convex.cuts.json") +non_convex = build_model(PowerModels.ACPPowerModel) +SDDP.read_cuts_from_file(non_convex, "convex.cuts.json") + +# Now we can simulate `non_convex` to evaluate the policy. + +result = SDDP.simulate(non_convex, 1) + +# A problem with reading and writing the cuts to file is that the cuts have been +# generated from trial points of the convex model. Therefore, the policy may be +# arbitrarily bad at points visited by the non-convex model. + +# ## Training a non-convex model + +# We can also build and train a non-convex formulation of the optimal power flow +# problem. + +# The problem with the non-convex model is that because it is non-convex, +# SDDP.jl may find a sub-optimal policy. Therefore, it may over-estimate the +# true cost of operation. + +non_convex = build_model(PowerModels.ACPPowerModel) +SDDP.train(non_convex; iteration_limit = 10) +result = SDDP.simulate(non_convex, 1) + +# ## Combining convex and non-convex models + +# To summarize, training with the convex model constructs cuts at points that +# may never be visited by the non-convex model, and training with the non-convex +# model may construct arbitrarily poor cuts because a key assumption of SDDP is +# convexity. + +# As a compromise, we can train a policy using a combination of the convex and +# non-convex models; we'll use the non-convex model to generate trial points on +# the forward pass, and we'll use the convex model to build cuts on the backward +# pass. + +convex = build_model(PowerModels.DCPPowerModel) + +#- + +non_convex = build_model(PowerModels.ACPPowerModel) + +# To do so, we train `convex` using the [`SDDP.AlternativeForwardPass`](@ref) +# forward pass, which simulates the model using `non_convex`, and we use +# [`SDDP.AlternativePostIterationCallback`](@ref) as a post-iteration callback, +# which copies cuts from the `convex` model back into the `non_convex` model. + +SDDP.train( + convex; + forward_pass = SDDP.AlternativeForwardPass(non_convex), + post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex), + iteration_limit = 10, +) + +# In practice, if we were to simulate `non_convex` now, we should obtain a +# better policy than either of the two previous approaches. diff --git a/previews/PR826/tutorial/pglib_opf/index.html b/previews/PR826/tutorial/pglib_opf/index.html new file mode 100644 index 0000000000..be182eaf31 --- /dev/null +++ b/previews/PR826/tutorial/pglib_opf/index.html @@ -0,0 +1,128 @@ + +Alternative forward models · SDDP.jl

Alternative forward models

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

This example demonstrates how to train convex and non-convex models.

This example uses the following packages:

using SDDP
+import Ipopt
+import PowerModels
+import Test

Formulation

For our model, we build a simple optimal power flow model with a single hydro-electric generator.

The formulation of our optimal power flow problem depends on model_type, which must be one of the PowerModels formulations.

(To run locally, download pglib_opf_case5_pjm.m and update filename appropriately.)

function build_model(model_type)
+    filename = joinpath(@__DIR__, "pglib_opf_case5_pjm.m")
+    data = PowerModels.parse_file(filename)
+    return SDDP.PolicyGraph(
+        SDDP.UnicyclicGraph(0.95);
+        sense = :Min,
+        lower_bound = 0.0,
+        optimizer = Ipopt.Optimizer,
+    ) do sp, t
+        power_model = PowerModels.instantiate_model(
+            data,
+            model_type,
+            PowerModels.build_opf;
+            jump_model = sp,
+        )
+        # Now add hydro power models. Assume that generator 5 is hydro, and the
+        # rest are thermal.
+        pg = power_model.var[:it][:pm][:nw][0][:pg][5]
+        sp[:pg] = pg
+        @variable(sp, x >= 0, SDDP.State, initial_value = 10.0)
+        @variable(sp, deficit >= 0)
+        @constraint(sp, balance, x.out == x.in - pg + deficit)
+        @stageobjective(sp, objective_function(sp) + 1e6 * deficit)
+        SDDP.parameterize(sp, [0, 2, 5]) do ω
+            return SDDP.set_normalized_rhs(balance, ω)
+        end
+        return
+    end
+end
build_model (generic function with 1 method)

Training a convex model

We can build and train a convex approximation of the optimal power flow problem.

The problem with the convex model is that it does not accurately simulate the true dynamics of the problem. Therefore, it under-estimates the true cost of operation.

convex = build_model(PowerModels.DCPPowerModel)
+SDDP.train(convex; iteration_limit = 10)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [20, 20]
+  AffExpr in MOI.EqualTo{Float64}         : [13, 13]
+  AffExpr in MOI.Interval{Float64}        : [6, 6]
+  VariableRef in MOI.GreaterThan{Float64} : [14, 14]
+  VariableRef in MOI.LessThan{Float64}    : [11, 11]
+numerical stability report
+  matrix range     [1e+00, 2e+02]
+  objective range  [1e+00, 1e+06]
+  bounds range     [4e-01, 6e+00]
+  rhs range        [5e-01, 5e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.458563e+07  3.264622e+04  1.050538e+00       271   1
+         3   1.883248e+06  9.059830e+04  2.166527e+00       461   1
+         8   4.978020e+05  3.616759e+05  3.320592e+00       636   1
+        10   4.551755e+06  3.694773e+05  5.082268e+00       930   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 5.082268e+00
+total solves   : 930
+best bound     :  3.694773e+05
+simulation ci  :  2.225401e+06 ± 2.832083e+06
+numeric issues : 0
+-------------------------------------------------------------------

To more accurately simulate the dynamics of the problem, a common approach is to write the cuts representing the policy to a file, and then read them into a non-convex model:

SDDP.write_cuts_to_file(convex, "convex.cuts.json")
+non_convex = build_model(PowerModels.ACPPowerModel)
+SDDP.read_cuts_from_file(non_convex, "convex.cuts.json")

Now we can simulate non_convex to evaluate the policy.

result = SDDP.simulate(non_convex, 1)
1-element Vector{Vector{Dict{Symbol, Any}}}:
+ [Dict(:bellman_term => 342080.8544885984, :noise_term => 5, :node_index => 1, :stage_objective => 21433.37543450599, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 348248.7384491772, :noise_term => 0, :node_index => 1, :stage_objective => 21433.37543450599, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 354416.622409756, :noise_term => 0, :node_index => 1, :stage_objective => 21433.37543450602, :objective_state => nothing, :belief => Dict(1 => 1.0))]

A problem with reading and writing the cuts to file is that the cuts have been generated from trial points of the convex model. Therefore, the policy may be arbitrarily bad at points visited by the non-convex model.

Training a non-convex model

We can also build and train a non-convex formulation of the optimal power flow problem.

The problem with the non-convex model is that because it is non-convex, SDDP.jl may find a sub-optimal policy. Therefore, it may over-estimate the true cost of operation.

non_convex = build_model(PowerModels.ACPPowerModel)
+SDDP.train(non_convex; iteration_limit = 10)
+result = SDDP.simulate(non_convex, 1)
1-element Vector{Vector{Dict{Symbol, Any}}}:
+ [Dict(:bellman_term => 375791.6979525628, :noise_term => 0, :node_index => 1, :stage_objective => 17587.191705451, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 375217.62261525623, :noise_term => 5, :node_index => 1, :stage_objective => 17587.19170532613, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.6946910325, :noise_term => 0, :node_index => 1, :stage_objective => 19334.84186359492, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.69504936336, :noise_term => 2, :node_index => 1, :stage_objective => 23580.696852984325, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381326.6197011669, :noise_term => 5, :node_index => 1, :stage_objective => 17587.191715384586, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 397062.19904323446, :noise_term => 0, :node_index => 1, :stage_objective => 27420.553501907507, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.6950495868, :noise_term => 2, :node_index => 1, :stage_objective => 24726.958044935694, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 428274.6140283388, :noise_term => 0, :node_index => 1, :stage_objective => 27420.553505640808, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.69504977425, :noise_term => 2, :node_index => 1, :stage_objective => 25694.765153677778, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 428274.6140398273, :noise_term => 0, :node_index => 1, :stage_objective => 27420.553505640808, :objective_state => nothing, :belief => Dict(1 => 1.0))  …  Dict(:bellman_term => 381900.69504936336, :noise_term => 2, :node_index => 1, :stage_objective => 23580.69745454595, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381326.61970116687, :noise_term => 5, :node_index => 1, :stage_objective => 17587.191715384564, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.6950491739, :noise_term => 2, :node_index => 1, :stage_objective => 22615.070526637784, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.69504936336, :noise_term => 2, :node_index => 1, :stage_objective => 23580.69745454595, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.69504936336, :noise_term => 2, :node_index => 1, :stage_objective => 23580.697454861107, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 428274.6140146454, :noise_term => 0, :node_index => 1, :stage_objective => 27420.553505640808, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381900.69504977425, :noise_term => 2, :node_index => 1, :stage_objective => 25694.765153305256, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 381326.61970157304, :noise_term => 5, :node_index => 1, :stage_objective => 17587.19171538458, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 380752.5443591121, :noise_term => 5, :node_index => 1, :stage_objective => 17587.19171048037, :objective_state => nothing, :belief => Dict(1 => 1.0)), Dict(:bellman_term => 384275.5054891627, :noise_term => 0, :node_index => 1, :stage_objective => 27420.553502152976, :objective_state => nothing, :belief => Dict(1 => 1.0))]

Combining convex and non-convex models

To summarize, training with the convex model constructs cuts at points that may never be visited by the non-convex model, and training with the non-convex model may construct arbitrarily poor cuts because a key assumption of SDDP is convexity.

As a compromise, we can train a policy using a combination of the convex and non-convex models; we'll use the non-convex model to generate trial points on the forward pass, and we'll use the convex model to build cuts on the backward pass.

convex = build_model(PowerModels.DCPPowerModel)
A policy graph with 1 nodes.
+ Node indices: 1
+
non_convex = build_model(PowerModels.ACPPowerModel)
A policy graph with 1 nodes.
+ Node indices: 1
+

To do so, we train convex using the SDDP.AlternativeForwardPass forward pass, which simulates the model using non_convex, and we use SDDP.AlternativePostIterationCallback as a post-iteration callback, which copies cuts from the convex model back into the non_convex model.

SDDP.train(
+    convex;
+    forward_pass = SDDP.AlternativeForwardPass(non_convex),
+    post_iteration_callback = SDDP.AlternativePostIterationCallback(non_convex),
+    iteration_limit = 10,
+)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 1
+  state variables : 1
+  scenarios       : Inf
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [20, 20]
+  AffExpr in MOI.EqualTo{Float64}         : [13, 13]
+  AffExpr in MOI.Interval{Float64}        : [6, 6]
+  VariableRef in MOI.GreaterThan{Float64} : [14, 14]
+  VariableRef in MOI.LessThan{Float64}    : [11, 11]
+numerical stability report
+  matrix range     [1e+00, 2e+02]
+  objective range  [1e+00, 1e+06]
+  bounds range     [4e-01, 6e+00]
+  rhs range        [5e-01, 5e+00]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   1.616405e+06  6.633473e+04  1.995130e-01        30   1
+         3   7.336368e+05  2.131314e+05  1.607509e+00       141   1
+         7   1.613523e+06  3.688984e+05  4.447348e+00       387   1
+         8   1.001434e+07  3.810947e+05  6.989353e+00       564   1
+        10   1.367906e+06  3.877966e+05  9.855739e+00       783   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 9.855739e+00
+total solves   : 783
+best bound     :  3.877966e+05
+simulation ci  :  1.638935e+06 ± 1.863579e+06
+numeric issues : 0
+-------------------------------------------------------------------

In practice, if we were to simulate non_convex now, we should obtain a better policy than either of the two previous approaches.

diff --git a/previews/PR826/tutorial/pglib_opf_case5_pjm.m b/previews/PR826/tutorial/pglib_opf_case5_pjm.m new file mode 100644 index 0000000000..7fa81a5a5e --- /dev/null +++ b/previews/PR826/tutorial/pglib_opf_case5_pjm.m @@ -0,0 +1,116 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%% %%%%% +%%%% IEEE PES Power Grid Library - Optimal Power Flow - v21.07 %%%%% +%%%% (https://github.com/power-grid-lib/pglib-opf) %%%%% +%%%% Benchmark Group - Typical Operations %%%%% +%%%% 29 - July - 2021 %%%%% +%%%% %%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% CASE5 Power flow data for modified 5 bus, 5 gen case based on PJM 5-bus system +% Please see CASEFORMAT for details on the case file format. +% +% Based on data from ... +% F.Li and R.Bo, "Small Test Systems for Power System Economic Studies", +% Proceedings of the 2010 IEEE Power & Energy Society General Meeting +% +% Created by Rui Bo in 2006, modified in 2010, 2014. +% +% Copyright (c) 2010 by The Institute of Electrical and Electronics Engineers (IEEE) +% Licensed under the Creative Commons Attribution 4.0 +% International license, http://creativecommons.org/licenses/by/4.0/ +% +% Contact M.E. Brennan (me.brennan@ieee.org) for inquries on further reuse of +% this dataset. +% +function mpc = pglib_opf_case5_pjm +mpc.version = '2'; +mpc.baseMVA = 100.0; + +%% area data +% area refbus +mpc.areas = [ + 1 4; +]; + +%% bus data +% bus_i type Pd Qd Gs Bs area Vm Va baseKV zone Vmax Vmin +mpc.bus = [ + 1 2 0.0 0.0 0.0 0.0 1 1.00000 0.00000 230.0 1 1.10000 0.90000; + 2 1 300.0 98.61 0.0 0.0 1 1.00000 0.00000 230.0 1 1.10000 0.90000; + 3 2 300.0 98.61 0.0 0.0 1 1.00000 0.00000 230.0 1 1.10000 0.90000; + 4 3 400.0 131.47 0.0 0.0 1 1.00000 0.00000 230.0 1 1.10000 0.90000; + 5 2 0.0 0.0 0.0 0.0 1 1.00000 0.00000 230.0 1 1.10000 0.90000; +]; + +%% generator data +% bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin +mpc.gen = [ + 1 20.0 0.0 30.0 -30.0 1.0 100.0 1 40.0 0.0; + 1 85.0 0.0 127.5 -127.5 1.0 100.0 1 170.0 0.0; + 3 260.0 0.0 390.0 -390.0 1.0 100.0 1 520.0 0.0; + 4 100.0 0.0 150.0 -150.0 1.0 100.0 1 200.0 0.0; + 5 300.0 0.0 450.0 -450.0 1.0 100.0 1 600.0 0.0; +]; + +%% generator cost data +% 2 startup shutdown n c(n-1) ... c0 +mpc.gencost = [ + 2 0.0 0.0 3 0.000000 14.000000 0.000000; + 2 0.0 0.0 3 0.000000 15.000000 0.000000; + 2 0.0 0.0 3 0.000000 30.000000 0.000000; + 2 0.0 0.0 3 0.000000 40.000000 0.000000; + 2 0.0 0.0 3 0.000000 10.000000 0.000000; +]; + +%% branch data +% fbus tbus r x b rateA rateB rateC ratio angle status angmin angmax +mpc.branch = [ + 1 2 0.00281 0.0281 0.00712 400.0 400.0 400.0 0.0 0.0 1 -30.0 30.0; + 1 4 0.00304 0.0304 0.00658 426 426 426 0.0 0.0 1 -30.0 30.0; + 1 5 0.00064 0.0064 0.03126 426 426 426 0.0 0.0 1 -30.0 30.0; + 2 3 0.00108 0.0108 0.01852 426 426 426 0.0 0.0 1 -30.0 30.0; + 3 4 0.00297 0.0297 0.00674 426 426 426 0.0 0.0 1 -30.0 30.0; + 4 5 0.00297 0.0297 0.00674 240.0 240.0 240.0 0.0 0.0 1 -30.0 30.0; +]; + +% INFO : === Translation Options === +% INFO : Phase Angle Bound: 30.0 (deg.) +% INFO : Line Capacity Model: stat +% INFO : Setting Flat Start +% INFO : Line Capacity PAB: 15.0 (deg.) +% INFO : +% INFO : === Generator Bounds Update Notes === +% INFO : +% INFO : === Base KV Replacement Notes === +% INFO : +% INFO : === Transformer Setting Replacement Notes === +% INFO : +% INFO : === Line Capacity Stat Model Notes === +% INFO : Updated Thermal Rating: on line 1-4 : Rate A, Rate B, Rate C , 9900.0, 0.0, 0.0 -> 426 +% INFO : Updated Thermal Rating: on line 1-5 : Rate A, Rate B, Rate C , 9900.0, 0.0, 0.0 -> 426 +% INFO : Updated Thermal Rating: on line 2-3 : Rate A, Rate B, Rate C , 9900.0, 0.0, 0.0 -> 426 +% INFO : Updated Thermal Rating: on line 3-4 : Rate A, Rate B, Rate C , 9900.0, 0.0, 0.0 -> 426 +% INFO : +% INFO : === Line Capacity Monotonicity Notes === +% INFO : +% INFO : === Voltage Setpoint Replacement Notes === +% INFO : Bus 1 : V=1.0, theta=0.0 -> V=1.0, theta=0.0 +% INFO : Bus 2 : V=1.0, theta=0.0 -> V=1.0, theta=0.0 +% INFO : Bus 3 : V=1.0, theta=0.0 -> V=1.0, theta=0.0 +% INFO : Bus 4 : V=1.0, theta=0.0 -> V=1.0, theta=0.0 +% INFO : Bus 5 : V=1.0, theta=0.0 -> V=1.0, theta=0.0 +% INFO : +% INFO : === Generator Setpoint Replacement Notes === +% INFO : Gen at bus 1 : Pg=40.0, Qg=0.0 -> Pg=20.0, Qg=0.0 +% INFO : Gen at bus 1 : Vg=1.0 -> Vg=1.0 +% INFO : Gen at bus 1 : Pg=170.0, Qg=0.0 -> Pg=85.0, Qg=0.0 +% INFO : Gen at bus 1 : Vg=1.0 -> Vg=1.0 +% INFO : Gen at bus 3 : Pg=323.49, Qg=0.0 -> Pg=260.0, Qg=0.0 +% INFO : Gen at bus 3 : Vg=1.0 -> Vg=1.0 +% INFO : Gen at bus 4 : Pg=0.0, Qg=0.0 -> Pg=100.0, Qg=0.0 +% INFO : Gen at bus 4 : Vg=1.0 -> Vg=1.0 +% INFO : Gen at bus 5 : Pg=466.51, Qg=0.0 -> Pg=300.0, Qg=0.0 +% INFO : Gen at bus 5 : Vg=1.0 -> Vg=1.0 +% INFO : +% INFO : === Writing Matpower Case File Notes === diff --git a/previews/PR826/tutorial/plotting.ipynb b/previews/PR826/tutorial/plotting.ipynb new file mode 100644 index 0000000000..d713cb5bed --- /dev/null +++ b/previews/PR826/tutorial/plotting.ipynb @@ -0,0 +1,407 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Plotting tools" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In our previous tutorials, we formulated, solved, and simulated multistage\n", + "stochastic optimization problems. However, we haven't really investigated what\n", + "the solution looks like. Luckily, `SDDP.jl` includes a number of plotting\n", + "tools to help us do that. In this tutorial, we explain the tools and make some\n", + "pretty pictures." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Preliminaries" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The next two plot types help visualize the policy. Thus, we first need to\n", + "create a policy and simulate some trajectories. So, let's take the model from\n", + "Markovian policy graphs, train it for 20 iterations, and then\n", + "simulate 100 Monte Carlo realizations of the policy." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "Ω = [\n", + " (inflow = 0.0, fuel_multiplier = 1.5),\n", + " (inflow = 50.0, fuel_multiplier = 1.0),\n", + " (inflow = 100.0, fuel_multiplier = 0.75),\n", + "]\n", + "\n", + "model = SDDP.MarkovianPolicyGraph(;\n", + " transition_matrices = Array{Float64,2}[\n", + " [1.0]',\n", + " [0.75 0.25],\n", + " [0.75 0.25; 0.25 0.75],\n", + " ],\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, node\n", + " t, markov_state = node\n", + " @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)\n", + " @variables(subproblem, begin\n", + " thermal_generation >= 0\n", + " hydro_generation >= 0\n", + " hydro_spill >= 0\n", + " inflow\n", + " end)\n", + " @constraints(\n", + " subproblem,\n", + " begin\n", + " volume.out == volume.in + inflow - hydro_generation - hydro_spill\n", + " thermal_generation + hydro_generation == 150.0\n", + " end\n", + " )\n", + " probability =\n", + " markov_state == 1 ? [1 / 6, 1 / 3, 1 / 2] : [1 / 2, 1 / 3, 1 / 6]\n", + " fuel_cost = [50.0, 100.0, 150.0]\n", + " SDDP.parameterize(subproblem, Ω, probability) do ω\n", + " JuMP.fix(inflow, ω.inflow)\n", + " @stageobjective(\n", + " subproblem,\n", + " ω.fuel_multiplier * fuel_cost[t] * thermal_generation\n", + " )\n", + " end\n", + "end\n", + "\n", + "SDDP.train(model; iteration_limit = 20, run_numerical_stability_report = false)\n", + "\n", + "simulations = SDDP.simulate(\n", + " model,\n", + " 100,\n", + " [:volume, :thermal_generation, :hydro_generation, :hydro_spill],\n", + ")\n", + "\n", + "println(\"Completed $(length(simulations)) simulations.\")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Great! Now we have some data in `simulations` to visualize." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Spaghetti plots" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The first plotting utility we discuss is a _spaghetti_ plot (you'll understand\n", + "the name when you see the graph)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To create a spaghetti plot, begin by creating a new\n", + "`SDDP.SpaghettiPlot` instance as follows:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "plt = SDDP.SpaghettiPlot(simulations)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We can add plots to `plt` using the `SDDP.add_spaghetti` function." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.add_spaghetti(plt; title = \"Reservoir volume\") do data\n", + " return data[:volume].out\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "In addition to returning values from the simulation, you can compute things:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.add_spaghetti(plt; title = \"Fuel cost\", ymin = 0, ymax = 250) do data\n", + " if data[:thermal_generation] > 0\n", + " return data[:stage_objective] / data[:thermal_generation]\n", + " else # No thermal generation, so return 0.0.\n", + " return 0.0\n", + " end\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Note that there are many keyword arguments in addition to `title`. For\n", + "example, we fixed the minimum and maximum values of the y-axis using `ymin`\n", + "and `ymax`. See the `SDDP.add_spaghetti` documentation for all the\n", + "arguments." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Having built the plot, we now need to display it using `SDDP.plot`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "```julia\n", + "SDDP.plot(plt, \"spaghetti_plot.html\")\n", + "```\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "This should open a webpage that looks like [this one](../assets/spaghetti_plot.html)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Using the mouse, you can highlight individual trajectories by hovering over\n", + "them. This makes it possible to visualize a single trajectory across multiple\n", + "dimensions." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If you click on the plot, then trajectories that are close to the mouse\n", + "pointer are shown darker and those further away are shown lighter." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Publication plots" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Instead of the interactive Javascript plots, you can also create some\n", + "publication ready plots using the `SDDP.publication_plot` function." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Info**\n", + ">\n", + "> You need to install the [Plots.jl](https://github.com/JuliaPlots/Plots)\n", + "> package for this to work. We used the `GR` backend (`gr()`), but any\n", + "> `Plots.jl` backend should work." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "`SDDP.publication_plot` implements a plot recipe to create ribbon\n", + "plots of each variable against the stages. The first argument is the vector of\n", + "simulation dictionaries and the second argument is the dictionary key that you\n", + "want to plot. Standard `Plots.jl` keyword arguments such as `title` and `xlabel`\n", + "can be used to modify the look of each plot. By default, the plot displays\n", + "ribbons of the 0-100, 10-90, and 25-75 percentiles. The dark, solid line in the\n", + "middle is the median (i.e. 50'th percentile)." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "import Plots\n", + "Plots.plot(\n", + " SDDP.publication_plot(simulations; title = \"Outgoing volume\") do data\n", + " return data[:volume].out\n", + " end,\n", + " SDDP.publication_plot(simulations; title = \"Thermal generation\") do data\n", + " return data[:thermal_generation]\n", + " end;\n", + " xlabel = \"Stage\",\n", + " ylims = (0, 200),\n", + " layout = (1, 2),\n", + ")" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "You can save this plot as a PDF using the `Plots.jl` function `savefig`:\n", + "```julia\n", + "Plots.savefig(\"my_picture.pdf\")\n", + "```" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Plotting the value function" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "You can obtain an object representing the value function of a node using\n", + "`SDDP.ValueFunction`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "V = SDDP.ValueFunction(model[(1, 1)])" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The value function can be evaluated using `SDDP.evaluate`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "SDDP.evaluate(V; volume = 1)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "`evaluate` returns the height of the value function, and a subgradient with respect to the\n", + "convex state variables." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "You can also plot the value function using `SDDP.plot`\n", + "```julia\n", + "SDDP.plot(V, volume = 0:200, filename = \"value_function.html\")\n", + "```\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "This should open a webpage that looks like [this one](../assets/value_function.html)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Convergence dashboard" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If the text-based logging isn't to your liking, you can open a visualization of\n", + "the training by passing `dashboard = true` to `SDDP.train`.\n", + "```julia\n", + "SDDP.train(model; dashboard = true)\n", + "```\n", + "By default, `dashboard = false` because there is an initial overhead\n", + "associated with opening and preparing the plot." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> The dashboard is experimental. There are known bugs associated with it,\n", + "> e.g., [SDDP.jl#226](https://github.com/odow/SDDP.jl/issues/226)." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/plotting.jl b/previews/PR826/tutorial/plotting.jl new file mode 100644 index 0000000000..f971d0f720 --- /dev/null +++ b/previews/PR826/tutorial/plotting.jl @@ -0,0 +1,201 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Plotting tools + +# In our previous tutorials, we formulated, solved, and simulated multistage +# stochastic optimization problems. However, we haven't really investigated what +# the solution looks like. Luckily, `SDDP.jl` includes a number of plotting +# tools to help us do that. In this tutorial, we explain the tools and make some +# pretty pictures. + +# ## Preliminaries + +# The next two plot types help visualize the policy. Thus, we first need to +# create a policy and simulate some trajectories. So, let's take the model from +# [Markovian policy graphs](@ref), train it for 20 iterations, and then +# simulate 100 Monte Carlo realizations of the policy. + +using SDDP, HiGHS + +Ω = [ + (inflow = 0.0, fuel_multiplier = 1.5), + (inflow = 50.0, fuel_multiplier = 1.0), + (inflow = 100.0, fuel_multiplier = 0.75), +] + +model = SDDP.MarkovianPolicyGraph(; + transition_matrices = Array{Float64,2}[ + [1.0]', + [0.75 0.25], + [0.75 0.25; 0.25 0.75], + ], + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, node + t, markov_state = node + @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200) + @variables(subproblem, begin + thermal_generation >= 0 + hydro_generation >= 0 + hydro_spill >= 0 + inflow + end) + @constraints( + subproblem, + begin + volume.out == volume.in + inflow - hydro_generation - hydro_spill + thermal_generation + hydro_generation == 150.0 + end + ) + probability = + markov_state == 1 ? [1 / 6, 1 / 3, 1 / 2] : [1 / 2, 1 / 3, 1 / 6] + fuel_cost = [50.0, 100.0, 150.0] + SDDP.parameterize(subproblem, Ω, probability) do ω + JuMP.fix(inflow, ω.inflow) + @stageobjective( + subproblem, + ω.fuel_multiplier * fuel_cost[t] * thermal_generation + ) + end +end + +SDDP.train(model; iteration_limit = 20, run_numerical_stability_report = false) + +simulations = SDDP.simulate( + model, + 100, + [:volume, :thermal_generation, :hydro_generation, :hydro_spill], +) + +println("Completed $(length(simulations)) simulations.") + +# Great! Now we have some data in `simulations` to visualize. + +# ## Spaghetti plots + +# The first plotting utility we discuss is a _spaghetti_ plot (you'll understand +# the name when you see the graph). + +# To create a spaghetti plot, begin by creating a new +# [`SDDP.SpaghettiPlot`](@ref) instance as follows: + +plt = SDDP.SpaghettiPlot(simulations) + +# We can add plots to `plt` using the [`SDDP.add_spaghetti`](@ref) function. + +SDDP.add_spaghetti(plt; title = "Reservoir volume") do data + return data[:volume].out +end + +# In addition to returning values from the simulation, you can compute things: + +SDDP.add_spaghetti(plt; title = "Fuel cost", ymin = 0, ymax = 250) do data + if data[:thermal_generation] > 0 + return data[:stage_objective] / data[:thermal_generation] + else # No thermal generation, so return 0.0. + return 0.0 + end +end + +# Note that there are many keyword arguments in addition to `title`. For +# example, we fixed the minimum and maximum values of the y-axis using `ymin` +# and `ymax`. See the [`SDDP.add_spaghetti`](@ref) documentation for all the +# arguments. + +# Having built the plot, we now need to display it using [`SDDP.plot`](@ref). + +# ```julia +# SDDP.plot(plt, "spaghetti_plot.html") +# ``` +# +# ```@raw html +# +# ``` +# +# This should open a webpage that looks like [this one](../assets/spaghetti_plot.html). + +# Using the mouse, you can highlight individual trajectories by hovering over +# them. This makes it possible to visualize a single trajectory across multiple +# dimensions. + +# If you click on the plot, then trajectories that are close to the mouse +# pointer are shown darker and those further away are shown lighter. + +# ## Publication plots + +# Instead of the interactive Javascript plots, you can also create some +# publication ready plots using the [`SDDP.publication_plot`](@ref) function. + +# !!! info +# You need to install the [Plots.jl](https://github.com/JuliaPlots/Plots) +# package for this to work. We used the `GR` backend (`gr()`), but any +# `Plots.jl` backend should work. + +# [`SDDP.publication_plot`](@ref) implements a plot recipe to create ribbon +# plots of each variable against the stages. The first argument is the vector of +# simulation dictionaries and the second argument is the dictionary key that you +# want to plot. Standard `Plots.jl` keyword arguments such as `title` and `xlabel` +# can be used to modify the look of each plot. By default, the plot displays +# ribbons of the 0-100, 10-90, and 25-75 percentiles. The dark, solid line in the +# middle is the median (i.e. 50'th percentile). + +import Plots +Plots.plot( + SDDP.publication_plot(simulations; title = "Outgoing volume") do data + return data[:volume].out + end, + SDDP.publication_plot(simulations; title = "Thermal generation") do data + return data[:thermal_generation] + end; + xlabel = "Stage", + ylims = (0, 200), + layout = (1, 2), +) + +# You can save this plot as a PDF using the `Plots.jl` function `savefig`: +# ```julia +# Plots.savefig("my_picture.pdf") +# ``` + +# ## Plotting the value function + +# You can obtain an object representing the value function of a node using +# [`SDDP.ValueFunction`](@ref). + +V = SDDP.ValueFunction(model[(1, 1)]) + +# The value function can be evaluated using [`SDDP.evaluate`](@ref). + +SDDP.evaluate(V; volume = 1) + +# `evaluate` returns the height of the value function, and a subgradient with respect to the +# convex state variables. + +# You can also plot the value function using [`SDDP.plot`](@ref) +# ```julia +# SDDP.plot(V, volume = 0:200, filename = "value_function.html") +# ``` +# +# ```@raw html +# +# ``` +# +# This should open a webpage that looks like [this one](../assets/value_function.html). + +# ## Convergence dashboard + +# If the text-based logging isn't to your liking, you can open a visualization of +# the training by passing `dashboard = true` to [`SDDP.train`](@ref). +# ```julia +# SDDP.train(model; dashboard = true) +# ``` +# By default, `dashboard = false` because there is an initial overhead +# associated with opening and preparing the plot. + +# !!! warning +# The dashboard is experimental. There are known bugs associated with it, +# e.g., [SDDP.jl#226](https://github.com/odow/SDDP.jl/issues/226). diff --git a/previews/PR826/tutorial/plotting/e5228a78.svg b/previews/PR826/tutorial/plotting/e5228a78.svg new file mode 100644 index 0000000000..d06298c5f5 --- /dev/null +++ b/previews/PR826/tutorial/plotting/e5228a78.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/previews/PR826/tutorial/plotting/index.html b/previews/PR826/tutorial/plotting/index.html new file mode 100644 index 0000000000..cd996fc1bc --- /dev/null +++ b/previews/PR826/tutorial/plotting/index.html @@ -0,0 +1,110 @@ + +Plotting tools · SDDP.jl

Plotting tools

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

In our previous tutorials, we formulated, solved, and simulated multistage stochastic optimization problems. However, we haven't really investigated what the solution looks like. Luckily, SDDP.jl includes a number of plotting tools to help us do that. In this tutorial, we explain the tools and make some pretty pictures.

Preliminaries

The next two plot types help visualize the policy. Thus, we first need to create a policy and simulate some trajectories. So, let's take the model from Markovian policy graphs, train it for 20 iterations, and then simulate 100 Monte Carlo realizations of the policy.

using SDDP, HiGHS
+
+Ω = [
+    (inflow = 0.0, fuel_multiplier = 1.5),
+    (inflow = 50.0, fuel_multiplier = 1.0),
+    (inflow = 100.0, fuel_multiplier = 0.75),
+]
+
+model = SDDP.MarkovianPolicyGraph(;
+    transition_matrices = Array{Float64,2}[
+        [1.0]',
+        [0.75 0.25],
+        [0.75 0.25; 0.25 0.75],
+    ],
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, node
+    t, markov_state = node
+    @variable(subproblem, 0 <= volume <= 200, SDDP.State, initial_value = 200)
+    @variables(subproblem, begin
+        thermal_generation >= 0
+        hydro_generation >= 0
+        hydro_spill >= 0
+        inflow
+    end)
+    @constraints(
+        subproblem,
+        begin
+            volume.out == volume.in + inflow - hydro_generation - hydro_spill
+            thermal_generation + hydro_generation == 150.0
+        end
+    )
+    probability =
+        markov_state == 1 ? [1 / 6, 1 / 3, 1 / 2] : [1 / 2, 1 / 3, 1 / 6]
+    fuel_cost = [50.0, 100.0, 150.0]
+    SDDP.parameterize(subproblem, Ω, probability) do ω
+        JuMP.fix(inflow, ω.inflow)
+        @stageobjective(
+            subproblem,
+            ω.fuel_multiplier * fuel_cost[t] * thermal_generation
+        )
+    end
+end
+
+SDDP.train(model; iteration_limit = 20, run_numerical_stability_report = false)
+
+simulations = SDDP.simulate(
+    model,
+    100,
+    [:volume, :thermal_generation, :hydro_generation, :hydro_spill],
+)
+
+println("Completed $(length(simulations)) simulations.")
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 5
+  state variables : 1
+  scenarios       : 1.08000e+02
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [7, 7]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.EqualTo{Float64}     : [1, 1]
+  VariableRef in MOI.GreaterThan{Float64} : [5, 5]
+  VariableRef in MOI.LessThan{Float64}    : [1, 2]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   2.812500e+04  1.991887e+03  4.665136e-03        18   1
+        20   1.125000e+04  8.072917e+03  3.899813e-02       360   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 3.899813e-02
+total solves   : 360
+best bound     :  8.072917e+03
+simulation ci  :  1.082898e+04 ± 2.947323e+03
+numeric issues : 0
+-------------------------------------------------------------------
+
+Completed 100 simulations.

Great! Now we have some data in simulations to visualize.

Spaghetti plots

The first plotting utility we discuss is a spaghetti plot (you'll understand the name when you see the graph).

To create a spaghetti plot, begin by creating a new SDDP.SpaghettiPlot instance as follows:

plt = SDDP.SpaghettiPlot(simulations)
A spaghetti plot with 100 scenarios and 3 stages.

We can add plots to plt using the SDDP.add_spaghetti function.

SDDP.add_spaghetti(plt; title = "Reservoir volume") do data
+    return data[:volume].out
+end

In addition to returning values from the simulation, you can compute things:

SDDP.add_spaghetti(plt; title = "Fuel cost", ymin = 0, ymax = 250) do data
+    if data[:thermal_generation] > 0
+        return data[:stage_objective] / data[:thermal_generation]
+    else  # No thermal generation, so return 0.0.
+        return 0.0
+    end
+end

Note that there are many keyword arguments in addition to title. For example, we fixed the minimum and maximum values of the y-axis using ymin and ymax. See the SDDP.add_spaghetti documentation for all the arguments.

Having built the plot, we now need to display it using SDDP.plot.

SDDP.plot(plt, "spaghetti_plot.html")

This should open a webpage that looks like this one.

Using the mouse, you can highlight individual trajectories by hovering over them. This makes it possible to visualize a single trajectory across multiple dimensions.

If you click on the plot, then trajectories that are close to the mouse pointer are shown darker and those further away are shown lighter.

Publication plots

Instead of the interactive Javascript plots, you can also create some publication ready plots using the SDDP.publication_plot function.

Info

You need to install the Plots.jl package for this to work. We used the GR backend (gr()), but any Plots.jl backend should work.

SDDP.publication_plot implements a plot recipe to create ribbon plots of each variable against the stages. The first argument is the vector of simulation dictionaries and the second argument is the dictionary key that you want to plot. Standard Plots.jl keyword arguments such as title and xlabel can be used to modify the look of each plot. By default, the plot displays ribbons of the 0-100, 10-90, and 25-75 percentiles. The dark, solid line in the middle is the median (i.e. 50'th percentile).

import Plots
+Plots.plot(
+    SDDP.publication_plot(simulations; title = "Outgoing volume") do data
+        return data[:volume].out
+    end,
+    SDDP.publication_plot(simulations; title = "Thermal generation") do data
+        return data[:thermal_generation]
+    end;
+    xlabel = "Stage",
+    ylims = (0, 200),
+    layout = (1, 2),
+)
Example block output

You can save this plot as a PDF using the Plots.jl function savefig:

Plots.savefig("my_picture.pdf")

Plotting the value function

You can obtain an object representing the value function of a node using SDDP.ValueFunction.

V = SDDP.ValueFunction(model[(1, 1)])
A value function for node (1, 1)

The value function can be evaluated using SDDP.evaluate.

SDDP.evaluate(V; volume = 1)
(23019.270833333332, Dict(:volume => -157.8125))

evaluate returns the height of the value function, and a subgradient with respect to the convex state variables.

You can also plot the value function using SDDP.plot

SDDP.plot(V, volume = 0:200, filename = "value_function.html")

This should open a webpage that looks like this one.

Convergence dashboard

If the text-based logging isn't to your liking, you can open a visualization of the training by passing dashboard = true to SDDP.train.

SDDP.train(model; dashboard = true)

By default, dashboard = false because there is an initial overhead associated with opening and preparing the plot.

Warning

The dashboard is experimental. There are known bugs associated with it, e.g., SDDP.jl#226.

diff --git a/previews/PR826/tutorial/spaghetti_plot.html b/previews/PR826/tutorial/spaghetti_plot.html new file mode 100644 index 0000000000..10b2dd3f47 --- /dev/null +++ b/previews/PR826/tutorial/spaghetti_plot.html @@ -0,0 +1,238 @@ + + + + + + + + + + + +
+ + + + + diff --git a/previews/PR826/tutorial/subproblem_2.mof.json b/previews/PR826/tutorial/subproblem_2.mof.json new file mode 100644 index 0000000000..45c4bfb105 --- /dev/null +++ b/previews/PR826/tutorial/subproblem_2.mof.json @@ -0,0 +1 @@ +{"name":"MathOptFormat Model","version":{"major":1,"minor":7},"variables":[{"name":"x_in"},{"name":"x_out"},{"name":"x3"}],"objective":{"sense":"min","function":{"type":"ScalarAffineFunction","terms":[{"coefficient":1.0,"variable":"x_out"},{"coefficient":1.0,"variable":"x3"}],"constant":0.0}},"constraints":[{"name":"c1","function":{"type":"ScalarAffineFunction","terms":[{"coefficient":1.0,"variable":"x_in"}],"constant":0.0},"set":{"type":"GreaterThan","lower":1.0}},{"function":{"type":"Variable","name":"x_in"},"set":{"type":"EqualTo","value":0.0}},{"function":{"type":"Variable","name":"x_out"},"set":{"type":"GreaterThan","lower":0.0}},{"function":{"type":"Variable","name":"x3"},"set":{"type":"GreaterThan","lower":0.0}},{"function":{"type":"Variable","name":"x3"},"set":{"type":"LessThan","upper":0.0}}]} \ No newline at end of file diff --git a/previews/PR826/tutorial/warnings.ipynb b/previews/PR826/tutorial/warnings.ipynb new file mode 100644 index 0000000000..2571f55206 --- /dev/null +++ b/previews/PR826/tutorial/warnings.ipynb @@ -0,0 +1,370 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Words of warning" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "SDDP is a powerful solution technique for multistage stochastic programming.\n", + "However, there are a number of subtle things to be aware of before creating\n", + "your own models." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Relatively complete recourse" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Models built in SDDP.jl need a property called _relatively complete recourse_." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One definition of relatively complete recourse is that _all_ feasible decisions\n", + "(not necessarily optimal) in a subproblem lead to feasible decisions in future\n", + "subproblems." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "For example, in the following problem, one feasible first stage decision is\n", + "`x.out = 0`. But this causes an infeasibility in the second stage which requires\n", + "`x.in >= 1`. This will throw an error about infeasibility if you try to solve." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 2,\n", + " lower_bound = 0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do sp, t\n", + " @variable(sp, x >= 0, SDDP.State, initial_value = 1)\n", + " if t == 2\n", + " @constraint(sp, x.in >= 1)\n", + " end\n", + " @stageobjective(sp, x.out)\n", + "end\n", + "\n", + "try #hide\n", + " SDDP.train(model; iteration_limit = 1, print_level = 0)\n", + "catch err #hide\n", + " showerror(stderr, err) #hide\n", + "end #hide" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "> **Warning**\n", + ">\n", + "> The actual constraints causing the infeasibilities can be deceptive! A good\n", + "> strategy to debug is to comment out all constraints. Then, one-by-one,\n", + "> un-comment the constraints and try resolving the model to check if it finds a\n", + "> feasible solution." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Numerical stability" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If you aren't aware, SDDP builds an outer-approximation to a convex function\n", + "using cutting planes. This results in a formulation that is particularly hard\n", + "for solvers like HiGHS, Gurobi, and CPLEX to deal with. As a result, you may\n", + "run into weird behavior. This behavior could include:\n", + "\n", + " - Iterations suddenly taking a long time (the solver stalled)\n", + " - Subproblems turning infeasible or unbounded after many iterations\n", + " - Solvers returning \"Numerical Error\" statuses" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Problem scaling" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "In almost all cases, the cause of this is poor problem scaling. For our\n", + "purpose, poor problem scaling means having variables with very large numbers\n", + "and variables with very small numbers in the same model." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "> **Tip**\n", + ">\n", + "> Gurobi has an excellent [set of articles](http://www.gurobi.com/documentation/8.1/refman/numerics_gurobi_guidelines.html)\n", + "> on numerical issues and how to avoid them." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Consider, for example, the hydro-thermal scheduling problem we have been\n", + "discussing in previous tutorials." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If we define the volume of the reservoir in terms of m³, then a lake might\n", + "have a capacity of 10^10 m³: `@variable(subproblem, 0 <= volume <= 10^10)`.\n", + "Moreover, the cost per cubic meter might be around \\$0.05/m³. To calculate\n", + "the value of water in our reservoir, we need to multiple a variable on the\n", + "order of 10^10, by one on the order of 10⁻²! That is twelve orders of\n", + "magnitude!" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To improve the performance of the SDDP algorithm (and reduce the chance of\n", + "weird behavior), try to re-scale the units of the problem in order to reduce\n", + "the largest difference in magnitude. For example, if we talk in terms of\n", + "million m³, then we have a capacity of 10⁴ million m³, and a price of\n", + "\\$50,000 per million m³. Now things are only one order of magnitude apart." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Numerical stability report" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "To aid in the diagnose of numerical issues, you can call\n", + "`SDDP.numerical_stability_report`. By default, this aggregates all of\n", + "the nodes into a single report. You can produce a stability report for each\n", + "node by passing `by_node=true`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP\n", + "\n", + "model =\n", + " SDDP.LinearPolicyGraph(; stages = 2, lower_bound = -1e10) do subproblem, t\n", + " @variable(subproblem, x >= -1e7, SDDP.State, initial_value = 1e-5)\n", + " @constraint(subproblem, 1e9 * x.out >= 1e-6 * x.in + 1e-8)\n", + " @stageobjective(subproblem, 1e9 * x.out)\n", + " end\n", + "\n", + "SDDP.numerical_stability_report(model)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The report analyses the magnitude (in absolute terms) of the coefficients in\n", + "the constraint matrix, the objective function, any variable bounds, and in the\n", + "RHS of the constraints. A warning will be thrown in `SDDP.jl` detects very\n", + "large or small values. As discussed in Problem scaling, this is an\n", + "indication that you should reformulate your model." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "By default, a numerical stability check is run when you call\n", + "`SDDP.train`, although it can be turned off by passing\n", + "`run_numerical_stability_report = false`." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "### Solver-specific options" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "If you have a particularly troublesome model, you should investigate setting\n", + "solver-specific options to improve the numerical stability of each solver. For\n", + "example, Gurobi has a [`NumericFocus`\n", + "option](http://www.gurobi.com/documentation/8.1/refman/numericfocus.html#parameter:NumericFocus)." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Choosing an initial bound" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "One of the important requirements when building a SDDP model is to choose an\n", + "appropriate bound on the objective (lower if minimizing, upper if maximizing).\n", + "However, it can be hard to choose a bound if you don't know the solution!\n", + "(Which is very likely.)" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "The bound should not be as large as possible (since this will help with\n", + "convergence and the numerical issues discussed above), but if chosen too\n", + "small, it may cut off the feasible region and lead to a sub-optimal solution." + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "Consider the following simple model, where we first set `lower_bound` to `0.0`." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 0.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, t\n", + " @variable(subproblem, x >= 0, SDDP.State, initial_value = 2)\n", + " @variable(subproblem, u >= 0)\n", + " @variable(subproblem, v >= 0)\n", + " @constraint(subproblem, x.out == x.in - u)\n", + " @constraint(subproblem, u + v == 1.5)\n", + " @stageobjective(subproblem, t * v)\n", + "end\n", + "\n", + "SDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Now consider the case when we set the `lower_bound` to `10.0`:" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using SDDP, HiGHS\n", + "\n", + "model = SDDP.LinearPolicyGraph(;\n", + " stages = 3,\n", + " sense = :Min,\n", + " lower_bound = 10.0,\n", + " optimizer = HiGHS.Optimizer,\n", + ") do subproblem, t\n", + " @variable(subproblem, x >= 0, SDDP.State, initial_value = 2)\n", + " @variable(subproblem, u >= 0)\n", + " @variable(subproblem, v >= 0)\n", + " @constraint(subproblem, x.out == x.in - u)\n", + " @constraint(subproblem, u + v == 1.5)\n", + " @stageobjective(subproblem, t * v)\n", + "end\n", + "\n", + "SDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "How do we tell which is more appropriate? There are a few clues that you\n", + "should look out for.\n", + "\n", + "- The bound converges to a value above (if minimizing) the simulated cost of\n", + " the policy. In this case, the problem is deterministic, so it is easy to\n", + " tell. But you can also check by performing a Monte Carlo simulation like we\n", + " did in An introduction to SDDP.jl.\n", + "\n", + "- The bound converges to different values when we change the bound. This is\n", + " another clear give-away. The bound provided by the user is only used in the\n", + " initial iterations. __It should not change the value of the converged\n", + " policy.__ Thus, if you don't know an appropriate value for the bound, choose\n", + " an initial value, and then increase (or decrease) the value of the bound to\n", + " confirm that the value of the policy doesn't change.\n", + "\n", + "- The bound converges to a value _close_ to the bound provided by the user.\n", + " This varies between models, but notice that `11.0` is quite close to `10.0`\n", + " compared with `3.5` and `0.0`." + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.3", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/previews/PR826/tutorial/warnings.jl b/previews/PR826/tutorial/warnings.jl new file mode 100644 index 0000000000..ea29354715 --- /dev/null +++ b/previews/PR826/tutorial/warnings.jl @@ -0,0 +1,190 @@ +# Copyright (c) 2017-25, Oscar Dowson and SDDP.jl contributors. #src +# This Source Code Form is subject to the terms of the Mozilla Public #src +# License, v. 2.0. If a copy of the MPL was not distributed with this #src +# file, You can obtain one at http://mozilla.org/MPL/2.0/. #src + +# # Words of warning + +# SDDP is a powerful solution technique for multistage stochastic programming. +# However, there are a number of subtle things to be aware of before creating +# your own models. + +# ## Relatively complete recourse + +# Models built in SDDP.jl need a property called _relatively complete recourse_. + +# One definition of relatively complete recourse is that _all_ feasible decisions +# (not necessarily optimal) in a subproblem lead to feasible decisions in future +# subproblems. + +# For example, in the following problem, one feasible first stage decision is +# `x.out = 0`. But this causes an infeasibility in the second stage which requires +# `x.in >= 1`. This will throw an error about infeasibility if you try to solve. + +using SDDP, HiGHS + +model = SDDP.LinearPolicyGraph(; + stages = 2, + lower_bound = 0, + optimizer = HiGHS.Optimizer, +) do sp, t + @variable(sp, x >= 0, SDDP.State, initial_value = 1) + if t == 2 + @constraint(sp, x.in >= 1) + end + @stageobjective(sp, x.out) +end + +try #hide + SDDP.train(model; iteration_limit = 1, print_level = 0) +catch err #hide + showerror(stderr, err) #hide +end #hide + +# !!! warning +# The actual constraints causing the infeasibilities can be deceptive! A good +# strategy to debug is to comment out all constraints. Then, one-by-one, +# un-comment the constraints and try resolving the model to check if it finds a +# feasible solution. + +# ## Numerical stability + +# If you aren't aware, SDDP builds an outer-approximation to a convex function +# using cutting planes. This results in a formulation that is particularly hard +# for solvers like HiGHS, Gurobi, and CPLEX to deal with. As a result, you may +# run into weird behavior. This behavior could include: +# +# - Iterations suddenly taking a long time (the solver stalled) +# - Subproblems turning infeasible or unbounded after many iterations +# - Solvers returning "Numerical Error" statuses + +# ### Problem scaling + +# In almost all cases, the cause of this is poor problem scaling. For our +# purpose, poor problem scaling means having variables with very large numbers +# and variables with very small numbers in the same model. + +# !!! tip +# Gurobi has an excellent [set of articles](http://www.gurobi.com/documentation/8.1/refman/numerics_gurobi_guidelines.html) +# on numerical issues and how to avoid them. + +# Consider, for example, the hydro-thermal scheduling problem we have been +# discussing in previous tutorials. + +# If we define the volume of the reservoir in terms of m³, then a lake might +# have a capacity of 10^10 m³: `@variable(subproblem, 0 <= volume <= 10^10)`. +# Moreover, the cost per cubic meter might be around \$0.05/m³. To calculate +# the value of water in our reservoir, we need to multiple a variable on the +# order of 10^10, by one on the order of 10⁻²! That is twelve orders of +# magnitude! + +# To improve the performance of the SDDP algorithm (and reduce the chance of +# weird behavior), try to re-scale the units of the problem in order to reduce +# the largest difference in magnitude. For example, if we talk in terms of +# million m³, then we have a capacity of 10⁴ million m³, and a price of +# \$50,000 per million m³. Now things are only one order of magnitude apart. + +# ### Numerical stability report + +# To aid in the diagnose of numerical issues, you can call +# [`SDDP.numerical_stability_report`](@ref). By default, this aggregates all of +# the nodes into a single report. You can produce a stability report for each +# node by passing `by_node=true`. + +using SDDP + +model = + SDDP.LinearPolicyGraph(; stages = 2, lower_bound = -1e10) do subproblem, t + @variable(subproblem, x >= -1e7, SDDP.State, initial_value = 1e-5) + @constraint(subproblem, 1e9 * x.out >= 1e-6 * x.in + 1e-8) + @stageobjective(subproblem, 1e9 * x.out) + end + +SDDP.numerical_stability_report(model) + +# The report analyses the magnitude (in absolute terms) of the coefficients in +# the constraint matrix, the objective function, any variable bounds, and in the +# RHS of the constraints. A warning will be thrown in `SDDP.jl` detects very +# large or small values. As discussed in [Problem scaling](@ref), this is an +# indication that you should reformulate your model. + +# By default, a numerical stability check is run when you call +# [`SDDP.train`](@ref), although it can be turned off by passing +# `run_numerical_stability_report = false`. + +# ### Solver-specific options + +# If you have a particularly troublesome model, you should investigate setting +# solver-specific options to improve the numerical stability of each solver. For +# example, Gurobi has a [`NumericFocus` +# option](http://www.gurobi.com/documentation/8.1/refman/numericfocus.html#parameter:NumericFocus). + +# ## Choosing an initial bound + +# One of the important requirements when building a SDDP model is to choose an +# appropriate bound on the objective (lower if minimizing, upper if maximizing). +# However, it can be hard to choose a bound if you don't know the solution! +# (Which is very likely.) + +# The bound should not be as large as possible (since this will help with +# convergence and the numerical issues discussed above), but if chosen too +# small, it may cut off the feasible region and lead to a sub-optimal solution. + +# Consider the following simple model, where we first set `lower_bound` to `0.0`. + +using SDDP, HiGHS + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 0.0, + optimizer = HiGHS.Optimizer, +) do subproblem, t + @variable(subproblem, x >= 0, SDDP.State, initial_value = 2) + @variable(subproblem, u >= 0) + @variable(subproblem, v >= 0) + @constraint(subproblem, x.out == x.in - u) + @constraint(subproblem, u + v == 1.5) + @stageobjective(subproblem, t * v) +end + +SDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false) + +# Now consider the case when we set the `lower_bound` to `10.0`: + +using SDDP, HiGHS + +model = SDDP.LinearPolicyGraph(; + stages = 3, + sense = :Min, + lower_bound = 10.0, + optimizer = HiGHS.Optimizer, +) do subproblem, t + @variable(subproblem, x >= 0, SDDP.State, initial_value = 2) + @variable(subproblem, u >= 0) + @variable(subproblem, v >= 0) + @constraint(subproblem, x.out == x.in - u) + @constraint(subproblem, u + v == 1.5) + @stageobjective(subproblem, t * v) +end + +SDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false) + +# How do we tell which is more appropriate? There are a few clues that you +# should look out for. +# +# - The bound converges to a value above (if minimizing) the simulated cost of +# the policy. In this case, the problem is deterministic, so it is easy to +# tell. But you can also check by performing a Monte Carlo simulation like we +# did in [An introduction to SDDP.jl](@ref). +# +# - The bound converges to different values when we change the bound. This is +# another clear give-away. The bound provided by the user is only used in the +# initial iterations. __It should not change the value of the converged +# policy.__ Thus, if you don't know an appropriate value for the bound, choose +# an initial value, and then increase (or decrease) the value of the bound to +# confirm that the value of the policy doesn't change. +# +# - The bound converges to a value _close_ to the bound provided by the user. +# This varies between models, but notice that `11.0` is quite close to `10.0` +# compared with `3.5` and `0.0`. diff --git a/previews/PR826/tutorial/warnings/index.html b/previews/PR826/tutorial/warnings/index.html new file mode 100644 index 0000000000..222bbc7290 --- /dev/null +++ b/previews/PR826/tutorial/warnings/index.html @@ -0,0 +1,146 @@ + +Words of warning · SDDP.jl

Words of warning

This tutorial was generated using Literate.jl. Download the source as a .jl file. Download the source as a .ipynb file.

SDDP is a powerful solution technique for multistage stochastic programming. However, there are a number of subtle things to be aware of before creating your own models.

Relatively complete recourse

Models built in SDDP.jl need a property called relatively complete recourse.

One definition of relatively complete recourse is that all feasible decisions (not necessarily optimal) in a subproblem lead to feasible decisions in future subproblems.

For example, in the following problem, one feasible first stage decision is x.out = 0. But this causes an infeasibility in the second stage which requires x.in >= 1. This will throw an error about infeasibility if you try to solve.

using SDDP, HiGHS
+
+model = SDDP.LinearPolicyGraph(;
+    stages = 2,
+    lower_bound = 0,
+    optimizer = HiGHS.Optimizer,
+) do sp, t
+    @variable(sp, x >= 0, SDDP.State, initial_value = 1)
+    if t == 2
+        @constraint(sp, x.in >= 1)
+    end
+    @stageobjective(sp, x.out)
+end
+
+    SDDP.train(model; iteration_limit = 1, print_level = 0)
[ Info: Writing cuts to the file `model_infeasible_node_2.cuts.json`
+Unable to retrieve solution from node 2.
+
+  Termination status : INFEASIBLE
+  Primal status      : NO_SOLUTION
+  Dual status        : INFEASIBILITY_CERTIFICATE.
+
+The current subproblem was written to `subproblem_2.mof.json`.
+
+There are two common causes of this error:
+  1) you have a mistake in your formulation, or you violated
+     the assumption of relatively complete recourse
+  2) the solver encountered numerical issues
+
+See https://odow.github.io/SDDP.jl/stable/tutorial/warnings/ for more information.
Warning

The actual constraints causing the infeasibilities can be deceptive! A good strategy to debug is to comment out all constraints. Then, one-by-one, un-comment the constraints and try resolving the model to check if it finds a feasible solution.

Numerical stability

If you aren't aware, SDDP builds an outer-approximation to a convex function using cutting planes. This results in a formulation that is particularly hard for solvers like HiGHS, Gurobi, and CPLEX to deal with. As a result, you may run into weird behavior. This behavior could include:

  • Iterations suddenly taking a long time (the solver stalled)
  • Subproblems turning infeasible or unbounded after many iterations
  • Solvers returning "Numerical Error" statuses

Problem scaling

In almost all cases, the cause of this is poor problem scaling. For our purpose, poor problem scaling means having variables with very large numbers and variables with very small numbers in the same model.

Tip

Gurobi has an excellent set of articles on numerical issues and how to avoid them.

Consider, for example, the hydro-thermal scheduling problem we have been discussing in previous tutorials.

If we define the volume of the reservoir in terms of m³, then a lake might have a capacity of 10^10 m³: @variable(subproblem, 0 <= volume <= 10^10). Moreover, the cost per cubic meter might be around $0.05/m³. To calculate the value of water in our reservoir, we need to multiple a variable on the order of 10^10, by one on the order of 10⁻²! That is twelve orders of magnitude!

To improve the performance of the SDDP algorithm (and reduce the chance of weird behavior), try to re-scale the units of the problem in order to reduce the largest difference in magnitude. For example, if we talk in terms of million m³, then we have a capacity of 10⁴ million m³, and a price of $50,000 per million m³. Now things are only one order of magnitude apart.

Numerical stability report

To aid in the diagnose of numerical issues, you can call SDDP.numerical_stability_report. By default, this aggregates all of the nodes into a single report. You can produce a stability report for each node by passing by_node=true.

using SDDP
+
+model =
+    SDDP.LinearPolicyGraph(; stages = 2, lower_bound = -1e10) do subproblem, t
+        @variable(subproblem, x >= -1e7, SDDP.State, initial_value = 1e-5)
+        @constraint(subproblem, 1e9 * x.out >= 1e-6 * x.in + 1e-8)
+        @stageobjective(subproblem, 1e9 * x.out)
+    end
+
+SDDP.numerical_stability_report(model)
numerical stability report
+  matrix range     [1e-06, 1e+09]
+  objective range  [1e+00, 1e+09]
+  bounds range     [1e+07, 1e+10]
+  rhs range        [1e-08, 1e-08]
+WARNING: numerical stability issues detected
+  - matrix range contains small coefficients
+  - matrix range contains large coefficients
+  - objective range contains large coefficients
+  - bounds range contains large coefficients
+  - rhs range contains small coefficients
+Very large or small absolute values of coefficients
+can cause numerical stability issues. Consider
+reformulating the model.

The report analyses the magnitude (in absolute terms) of the coefficients in the constraint matrix, the objective function, any variable bounds, and in the RHS of the constraints. A warning will be thrown in SDDP.jl detects very large or small values. As discussed in Problem scaling, this is an indication that you should reformulate your model.

By default, a numerical stability check is run when you call SDDP.train, although it can be turned off by passing run_numerical_stability_report = false.

Solver-specific options

If you have a particularly troublesome model, you should investigate setting solver-specific options to improve the numerical stability of each solver. For example, Gurobi has a NumericFocus option.

Choosing an initial bound

One of the important requirements when building a SDDP model is to choose an appropriate bound on the objective (lower if minimizing, upper if maximizing). However, it can be hard to choose a bound if you don't know the solution! (Which is very likely.)

The bound should not be as large as possible (since this will help with convergence and the numerical issues discussed above), but if chosen too small, it may cut off the feasible region and lead to a sub-optimal solution.

Consider the following simple model, where we first set lower_bound to 0.0.

using SDDP, HiGHS
+
+model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 0.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, t
+    @variable(subproblem, x >= 0, SDDP.State, initial_value = 2)
+    @variable(subproblem, u >= 0)
+    @variable(subproblem, v >= 0)
+    @constraint(subproblem, x.out == x.in - u)
+    @constraint(subproblem, u + v == 1.5)
+    @stageobjective(subproblem, t * v)
+end
+
+SDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [5, 5]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 4]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   6.500000e+00  3.000000e+00  2.904177e-03         6   1
+         5   3.500000e+00  3.500000e+00  5.540133e-03        30   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 5.540133e-03
+total solves   : 30
+best bound     :  3.500000e+00
+simulation ci  :  4.100000e+00 ± 1.176000e+00
+numeric issues : 0
+-------------------------------------------------------------------

Now consider the case when we set the lower_bound to 10.0:

using SDDP, HiGHS
+
+model = SDDP.LinearPolicyGraph(;
+    stages = 3,
+    sense = :Min,
+    lower_bound = 10.0,
+    optimizer = HiGHS.Optimizer,
+) do subproblem, t
+    @variable(subproblem, x >= 0, SDDP.State, initial_value = 2)
+    @variable(subproblem, u >= 0)
+    @variable(subproblem, v >= 0)
+    @constraint(subproblem, x.out == x.in - u)
+    @constraint(subproblem, u + v == 1.5)
+    @stageobjective(subproblem, t * v)
+end
+
+SDDP.train(model; iteration_limit = 5, run_numerical_stability_report = false)
-------------------------------------------------------------------
+         SDDP.jl (c) Oscar Dowson and contributors, 2017-25
+-------------------------------------------------------------------
+problem
+  nodes           : 3
+  state variables : 1
+  scenarios       : 1.00000e+00
+  existing cuts   : false
+options
+  solver          : serial mode
+  risk measure    : SDDP.Expectation()
+  sampling scheme : SDDP.InSampleMonteCarlo
+subproblem structure
+  VariableRef                             : [5, 5]
+  AffExpr in MOI.EqualTo{Float64}         : [2, 2]
+  VariableRef in MOI.GreaterThan{Float64} : [4, 4]
+  VariableRef in MOI.LessThan{Float64}    : [1, 1]
+-------------------------------------------------------------------
+ iteration    simulation      bound        time (s)     solves  pid
+-------------------------------------------------------------------
+         1   6.500000e+00  1.100000e+01  3.064871e-03         6   1
+         5   5.500000e+00  1.100000e+01  5.489826e-03        30   1
+-------------------------------------------------------------------
+status         : iteration_limit
+total time (s) : 5.489826e-03
+total solves   : 30
+best bound     :  1.100000e+01
+simulation ci  :  5.700000e+00 ± 3.920000e-01
+numeric issues : 0
+-------------------------------------------------------------------

How do we tell which is more appropriate? There are a few clues that you should look out for.

  • The bound converges to a value above (if minimizing) the simulated cost of the policy. In this case, the problem is deterministic, so it is easy to tell. But you can also check by performing a Monte Carlo simulation like we did in An introduction to SDDP.jl.

  • The bound converges to different values when we change the bound. This is another clear give-away. The bound provided by the user is only used in the initial iterations. It should not change the value of the converged policy. Thus, if you don't know an appropriate value for the bound, choose an initial value, and then increase (or decrease) the value of the bound to confirm that the value of the policy doesn't change.

  • The bound converges to a value close to the bound provided by the user. This varies between models, but notice that 11.0 is quite close to 10.0 compared with 3.5 and 0.0.