From 42b63c3bd176fe4e4c7544ac226ae1ce890e517f Mon Sep 17 00:00:00 2001 From: svijaysh Date: Sat, 25 Apr 2026 20:22:34 -0600 Subject: [PATCH 1/7] Add PLM optimized dispatch controller, example 34, and tests --- .../34_plm_optimized_dispatch.yaml | 5 + .../demand_profiles/demand_profile.yaml | 3361 +++++++ .../demand_profiles/supervisory_signal.yaml | 8760 +++++++++++++++++ .../driver_config.yaml | 5 + .../plant_config.yaml | 12 + .../run_plm_optimized_dispatch.py | 94 + .../tech_config.yaml | 54 + .../plm_optimized_storage_controller.py | 536 + .../test_plm_optimized_storage_controller.py | 270 + h2integrate/core/supported_models.py | 59 +- 10 files changed, 13144 insertions(+), 12 deletions(-) create mode 100644 examples/34_plm_optimized_dispatch/34_plm_optimized_dispatch.yaml create mode 100644 examples/34_plm_optimized_dispatch/demand_profiles/demand_profile.yaml create mode 100644 examples/34_plm_optimized_dispatch/demand_profiles/supervisory_signal.yaml create mode 100644 examples/34_plm_optimized_dispatch/driver_config.yaml create mode 100644 examples/34_plm_optimized_dispatch/plant_config.yaml create mode 100644 examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py create mode 100644 examples/34_plm_optimized_dispatch/tech_config.yaml create mode 100644 h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py create mode 100644 h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py diff --git a/examples/34_plm_optimized_dispatch/34_plm_optimized_dispatch.yaml b/examples/34_plm_optimized_dispatch/34_plm_optimized_dispatch.yaml new file mode 100644 index 000000000..6f4c89466 --- /dev/null +++ b/examples/34_plm_optimized_dispatch/34_plm_optimized_dispatch.yaml @@ -0,0 +1,5 @@ +name: H2Integrate_config +system_summary: PLM MILP-optimized battery dispatch +driver_config: driver_config.yaml +technology_config: tech_config.yaml +plant_config: plant_config.yaml diff --git a/examples/34_plm_optimized_dispatch/demand_profiles/demand_profile.yaml b/examples/34_plm_optimized_dispatch/demand_profiles/demand_profile.yaml new file mode 100644 index 000000000..7f14e933f --- /dev/null +++ b/examples/34_plm_optimized_dispatch/demand_profiles/demand_profile.yaml @@ -0,0 +1,3361 @@ +- 212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +- 212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +- 212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +- 212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 +212.4377 +- 201.6801 +- 216.0036 +- 217.5245 +- 194.3917 +- 199.5826 +- 251.0227 +- 307.4701 +- 309.8656 +- 303.1756 +- 257.0352 +- 256.2223 +- 250.5282 +- 259.0179 +- 433.7401 +- 423.1257 +- 432.9500 +- 422.3289 +- 437.0276 +- 429.6006 +- 248.5211 +- 244.5526 +- 219.7803 +- 208.7638 +- 206.5734 +- 207.1829 +- 214.2585 +- 212.9236 +- 213.3019 +- 213.4466 +- 267.1332 +- 306.7487 +- 305.9021 +- 303.4898 +- 254.9278 +- 259.0318 +- 249.0884 +- 243.2787 +- 423.4042 +- 435.2047 +- 435.9460 +- 434.3452 +- 424.6759 +- 431.8573 +- 250.9335 +- 251.7495 +- 216.9714 +- 211.7888 +- 215.4313 +- 210.5406 +- 212.3130 +- 215.0503 +- 198.3428 +- 207.4426 +- 246.2370 +- 304.8890 +- 307.7989 +- 321.9595 +- 243.0734 +- 257.7462 +- 236.5370 +- 247.3209 +- 431.3020 +- 434.6898 +- 435.6898 +- 436.3468 +- 427.2102 +- 426.3012 +- 256.8638 +- 248.4696 +- 199.7945 +- 200.9337 +- 202.6444 +- 213.9773 +- 211.1394 +- 215.5239 +- 206.5820 +- 211.2683 +- 255.0047 +- 307.5252 +- 313.6542 +- 304.7046 +- 247.0956 +- 246.9461 +- 240.4333 +- 253.8958 +- 426.2448 +- 430.1000 +- 433.8460 +- 433.5722 +- 435.3231 +- 429.2121 +- 246.6136 +- 249.3623 +- 196.5013 +- 198.4231 +- 199.4184 +- 202.0220 +- 213.1982 +- 202.7562 +- 206.9747 +- 220.3938 +- 247.1499 +- 315.9001 +- 302.5311 +- 308.3565 +- 242.3998 +- 247.2877 +- 256.7225 +- 236.1814 +- 433.4754 +- 431.9019 +- 425.2468 +- 418.4315 +- 430.5770 +- 425.7641 +- 251.8614 +- 250.1748 +- 222.8142 +- 208.0852 +- 121.8120 +- 131.4342 +- 131.7600 +- 140.8735 +- 136.6809 +- 132.8550 +- 181.7064 +- 220.4899 +- 224.8820 +- 222.5874 +- 166.8815 +- 158.9865 +- 175.0812 +- 168.2222 +- 338.2335 +- 341.8754 +- 352.5081 +- 356.7050 +- 365.9738 +- 373.3109 +- 173.3153 +- 162.0837 +- 112.9436 +- 132.1417 +- 123.4965 +- 126.6771 +- 125.1032 +- 128.8737 +- 138.5278 +- 131.2564 +- 168.7309 +- 221.7148 +- 216.6025 +- 226.1095 +- 169.5697 +- 184.1434 +- 171.0422 +- 177.8619 +- 346.0056 +- 340.5204 +- 342.2791 +- 344.1982 +- 367.0278 +- 343.4289 +- 176.7079 +- 162.7766 +- 137.4526 +- 133.0796 + diff --git a/examples/34_plm_optimized_dispatch/demand_profiles/supervisory_signal.yaml b/examples/34_plm_optimized_dispatch/demand_profiles/supervisory_signal.yaml new file mode 100644 index 000000000..e92731250 --- /dev/null +++ b/examples/34_plm_optimized_dispatch/demand_profiles/supervisory_signal.yaml @@ -0,0 +1,8760 @@ +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 121.0501 +- 82.3572 +- 104.0367 +- 89.4550 +- 103.9503 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 122.5359 +- 83.9824 +- 142.5416 +- 78.9059 +- 78.5578 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 83.4137 +- 79.0599 +- 85.0666 +- 85.3182 +- 79.7008 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 82.8949 +- 89.1500 +- 78.6225 +- 83.3834 +- 101.5483 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 81.5338 +- 71.4758 +- 74.2712 +- 74.3409 +- 69.6864 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 8.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 28.0000 +- 71.5507 +- 114.5877 +- 68.5757 +- 84.7005 +- 69.4464 +- 23.0000 +- 23.0000 +- 23.0000 +- 8.0000 +- 8.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 18.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 38.0000 +- 79.3612 +- 99.0560 +- 90.6929 +- 114.6070 +- 93.6671 +- 33.0000 +- 33.0000 +- 33.0000 +- 18.0000 +- 18.0000 diff --git a/examples/34_plm_optimized_dispatch/driver_config.yaml b/examples/34_plm_optimized_dispatch/driver_config.yaml new file mode 100644 index 000000000..0b40f01a0 --- /dev/null +++ b/examples/34_plm_optimized_dispatch/driver_config.yaml @@ -0,0 +1,5 @@ +name: driver_config +description: Driver configuration for PLM MILP-optimized battery dispatch example +general: + folder_output: outputs + create_om_reports: false diff --git a/examples/34_plm_optimized_dispatch/plant_config.yaml b/examples/34_plm_optimized_dispatch/plant_config.yaml new file mode 100644 index 000000000..69e0909fc --- /dev/null +++ b/examples/34_plm_optimized_dispatch/plant_config.yaml @@ -0,0 +1,12 @@ +name: plant_config +description: Simulation for PLM MILP-optimized battery dispatch +plant: + plant_life: 1 + simulation: + n_timesteps: 8760 # full year (hourly) + dt: 3600 # 1-hour timesteps + timezone: -6 # MDT (UTC-6) + start_time: "2025/01/01 00:00:00" +tech_to_dispatch_connections: + - [grid_buy, battery] + - [battery, battery] diff --git a/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py b/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py new file mode 100644 index 000000000..a833d2cc9 --- /dev/null +++ b/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py @@ -0,0 +1,94 @@ +""" + +This example demonstrates demand-response storage dispatch using a rolling-horizon +MILP controller. The battery is scheduled to discharge during high-LMP peak hours +to reduce facility demand charges and earn performance incentives. +""" + +from pathlib import Path + +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt + +from h2integrate.core.h2integrate_model import H2IntegrateModel +from h2integrate.core.utilities import build_time_series_from_plant_config + +EXAMPLE_DIR = Path(__file__).parent + + +model = H2IntegrateModel(EXAMPLE_DIR / "34_plm_optimized_dispatch.yaml") +model.setup() + +N = model.plant_config["plant"]["simulation"]["n_timesteps"] +percentile = model.technology_config["technologies"]["battery"]["model_inputs"]["control_parameters"]["signal_threshold_percentile"] + +model.run() + +lmp = np.array( + model.technology_config["technologies"]["battery"]["model_inputs"] + ["control_parameters"]["supervisory_signal"] +)[:N] + +time_index = pd.DatetimeIndex(build_time_series_from_plant_config(model.plant_config)) +battery_power = model.prob.get_val("battery.storage_electricity_discharge", units="kW") +soc_pct = model.prob.get_val("battery.SOC", units="percent") + +peak_mask = (time_index.hour >= 14) & (time_index.hour <= 18) +threshold_pct = np.percentile(lmp, percentile) +discharge_mask = battery_power > 1.0 + + +plt.rcParams.update({"axes.spines.top": False, "axes.spines.right": False}) +fig, axes = plt.subplots(3, 1, sharex=True, figsize=(11, 7)) +days = pd.date_range(time_index[0].normalize(), periods=14, freq="D", tz=time_index.tz) +time_window = 14 * 24 + + +def shade_peaks(ax): + for day in days: + ax.axvspan( + day + pd.Timedelta(hours=14), + day + pd.Timedelta(hours=18), + color="orange", alpha=0.10, linewidth=0, zorder=0, + ) + + +w_discharge = discharge_mask[:time_window] + +ax = axes[0] +shade_peaks(ax) +ax.plot(time_index[:time_window], lmp[:time_window], color="steelblue", linewidth=1.0) +ax.axhline(threshold_pct, color="crimson", linestyle="--", linewidth=0.8) +ax.plot(time_index[:time_window][w_discharge], lmp[:time_window][w_discharge], + "r*", markersize=8, zorder=5) +ax.set_ylabel("LMP ($/MWh)", fontsize=8) +ax.set_ylim(bottom=0) + +# Panel 2: SOC +ax = axes[1] +ax.plot(time_index[:time_window], soc_pct[:time_window], color="tab:green", linewidth=1.0) +ax.axhline(90, color="gray", linestyle=":", linewidth=0.7) +ax.axhline(10, color="gray", linestyle=":", linewidth=0.7) +ax.set_ylabel("SOC (%)", fontsize=8) +ax.set_ylim([0, 105]) + +ax = axes[2] +shade_peaks(ax) +ax.axhline(0, color="black", linewidth=0.4) +width = pd.Timedelta(hours=0.8) +pos_mask = (battery_power > 1)[:time_window] +neg_mask = (battery_power < -1)[:time_window] +ax.bar(time_index[:time_window][pos_mask], battery_power[:time_window][pos_mask] * 1e-3, + width=width, color="tab:red", alpha=0.8) +ax.bar(time_index[:time_window][neg_mask], battery_power[:time_window][neg_mask] * 1e-3, + width=width, color="tab:blue", alpha=0.8) +ax.set_ylabel("Power (MW)", fontsize=8) +ax.tick_params(axis="x", labelrotation=30, labelsize=7) + +for ax in axes: + ax.tick_params(labelsize=7) + ax.grid(True, which="major", alpha=0.3, linewidth=0.5) + +plt.tight_layout() +plt.savefig("plm_optimized_dispatch.png", dpi=150, bbox_inches="tight") diff --git a/examples/34_plm_optimized_dispatch/tech_config.yaml b/examples/34_plm_optimized_dispatch/tech_config.yaml new file mode 100644 index 000000000..6bd644775 --- /dev/null +++ b/examples/34_plm_optimized_dispatch/tech_config.yaml @@ -0,0 +1,54 @@ +name: technology_config +description: 300 kW / 1.2 MWh battery with PLM MILP-optimized dispatch controller +technologies: + battery: + performance_model: + model: StoragePerformanceModel + cost_model: + model: ATBBatteryCostModel + control_strategy: + model: PLMOptimizedStorageController + model_inputs: + shared_parameters: + commodity: electricity + commodity_rate_units: kW + max_charge_rate: 300.0 # kW rated power + max_capacity: 1200.0 # kWh usable capacity (4-hour battery) + max_soc_fraction: 0.90 + min_soc_fraction: 0.10 + init_soc_fraction: 0.90 + charge_efficiency: 0.95 + discharge_efficiency: 0.95 + + performance_parameters: + demand_profile: 0.0 + control_parameters: + system_commodity_interface_limit: 1.0e9 + supervisory_signal: !include demand_profiles/supervisory_signal.yaml + peak_window: + start: "14:00:00" # 2 pm MDT + end: "18:00:00" # 6 pm MDT + performance_incentive: 14.0 # $/kW per dispatch hour + n_max_events: 10 # maximum discharge events per month + signal_threshold_percentile: 95.0 # only dispatch when LMP >= this percentile + n_control_window: 10 # number of time steps in the control window (e.g., 10 hours) + round_digits: 4 + cost_parameters: + cost_year: 2024 + energy_capex: 408 # $/kWh (2024 ATB) + power_capex: 379 # $/kW + opex_fraction: 0.025 + grid_buy: + performance_model: + model: GridPerformanceModel + cost_model: + model: GridCostModel + model_inputs: + shared_parameters: + interconnection_size: 100000 # kW + cost_parameters: + cost_year: 2024 + fixed_interconnection_cost: 0.0 + interconnection_capex_per_kw: 0.0 + interconnection_opex_per_kw: 0.0 + electricity_buy_price: 0.09 # $/kWh diff --git a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py new file mode 100644 index 000000000..a4721825f --- /dev/null +++ b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py @@ -0,0 +1,536 @@ +from datetime import datetime, timedelta + +import numpy as np +import pandas as pd +import pyomo.environ as pyomo +from attrs import field, define + +from h2integrate.core.utilities import merge_shared_inputs +from h2integrate.core.validators import range_val +from h2integrate.control.control_strategies.pyomo_storage_controller_baseclass import ( + SolverOptions, + PyomoStorageControllerBaseClass, + PyomoStorageControllerBaseConfig, +) +from h2integrate.control.control_strategies.controller_opt_problem_state import ( + DispatchProblemState, +) +from matplotlib import pyplot as plt + + +@define +class PLMOptimizedControllerConfig(PyomoStorageControllerBaseConfig): + """Configuration for the PLM DR optimized storage controller. + + Inherits base fields from ``PyomoControllerBaseConfig``: + ``max_capacity``, ``max_soc_fraction``, ``min_soc_fraction``, + ``init_soc_fraction``, ``n_control_window``, ``commodity``, + ``commodity_rate_units``, ``tech_name``, + ``system_commodity_interface_limit``, ``round_digits``. + + Attributes: + max_charge_rate (float): Maximum charge and discharge rate (kW). + supervisory_signal (list[float]): Price, demand, or price*demand + forecast time series. The rolling solver uses one window of + length ``n_control_window`` per solve. + peak_window (dict): Hours eligible for dispatch. Keys ``'start'`` + and ``'end'`` must be strings in ``HH:MM:SS`` format. + performance_incentive (float): Incentive revenue ($/kW per + dispatch hour). + charge_efficiency (float): Charge efficiency in [0, 1]. + Defaults to 1.0. + discharge_efficiency (float): Discharge efficiency in [0, 1]. + Defaults to 1.0. + n_max_events (int): Maximum discharge events per calendar month. + Defaults to 10. + n_control_window (int): Number of timesteps per rolling solve + window. Defaults to ``24 * 30`` (one month of hourly data). + signal_threshold_percentile (float): Percentile (0–100) used to + compute the signal threshold for each rolling window. Only + hours at or above this percentile of the window signal are + eligible for dispatch. Defaults to 0.0 (all hours eligible). + """ + + max_charge_rate: float = field() + supervisory_signal: list = field() + peak_window: dict = field() + performance_incentive: float = field() + charge_efficiency: float = field(validator=range_val(0, 1), default=1.0) + discharge_efficiency: float = field(validator=range_val(0, 1), default=1.0) + n_max_events: int = field(default=10) + n_control_window: int = field(default=24 * 30) # one month of hourly data + signal_threshold_percentile: float = field(default=0.0, validator=range_val(0,100)) + + +class PLMOptimizedStorageController(PyomoStorageControllerBaseClass): + """Demand-response storage controller using a rolling-horizon MILP. + + Each call to the dispatch solver iterates over the full simulation in + windows of length ``n_control_window``. For each window it receives + the monthly LMP forecast, solves the MILP to maximize incentive + revenue, then passes the resulting dispatch commands to the + performance model. The terminal SOC of each window is carried forward + as the initial SOC of the next window. + """ + + def setup(self): + """Initialize config, register OpenMDAO inputs, and pre-compute static masks. + + Raises: + ValueError: If the length of the time series built from + ``plant_config`` does not match ``n_timesteps``. + """ + self.config = PLMOptimizedControllerConfig.from_dict( + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control") + ) + + self.add_input( + "max_charge_rate", + val=self.config.max_charge_rate, + units=self.config.commodity_rate_units, + desc="Maximum charge/discharge rate P_max", + ) + self.add_input( + "storage_capacity", + val=self.config.max_capacity, + units=f"{self.config.commodity_rate_units}*h", + desc="Total storage capacity", + ) + + self.n_timesteps = self.options["plant_config"]["plant"]["simulation"][ + "n_timesteps" + ] + super().setup() + + self.updated_initial_soc = self.config.init_soc_fraction + + self.commodity_info = { + "commodity_name": self.config.commodity, + "commodity_storage_units": self.config.commodity_rate_units, + } + + self.time_index = self._build_time_index(self.options["plant_config"]) + + if len(self.time_index) != self.n_timesteps: + raise ValueError( + f"Time series length {len(self.time_index)} != n_timesteps {self.n_timesteps}" + ) + + self.in_peak_window = self._compute_peak_window_mask() # bool array, shape (T,) + self.month_ids = self._compute_month_ids() # int array, shape (T,) + + @staticmethod + def _build_time_index(plant_config: dict) -> pd.DatetimeIndex: + """Build a timezone-aware DatetimeIndex from simulation settings in plant_config. + + Args: + plant_config (dict): Plant configuration dict. Must contain + ``plant.simulation`` with keys ``n_timesteps`` (int), + ``dt`` (int, seconds), ``timezone`` (int, UTC offset), + and ``start_time`` (str). + + Returns: + pd.DatetimeIndex: DatetimeIndex of length ``n_timesteps`` spaced + ``dt`` seconds apart, starting at ``start_time`` in the given timezone. + """ + sim = plant_config["plant"]["simulation"] + n_timesteps = int(sim["n_timesteps"]) + dt_seconds = int(sim["dt"]) + tz = int(sim["timezone"]) + start = pd.Timestamp(sim["start_time"], tz=tz) + freq = pd.to_timedelta(dt_seconds, unit="s") + return pd.date_range(start=start, periods=n_timesteps, freq=freq) + + def _parse_peak_window(self) -> tuple: + """Parse the ``peak_window`` config entry into ``datetime.time`` objects. + + Returns: + tuple[datetime.time, datetime.time]: ``(start, end)`` times. + + Raises: + ValueError: If ``'start'`` or ``'end'`` keys are missing, or + if either value is not a string in ``HH:MM:SS`` format. + """ + pw = dict(self.config.peak_window) + if "start" not in pw or "end" not in pw: + raise ValueError("peak_window must contain 'start' and 'end' keys") + for key in ("start", "end"): + val = pw[key] + if not isinstance(val, str) or len(val.split(":")) != 3: + raise ValueError( + f"peak_window {key} value must be a string in HH:MM:SS format, got {val}." + ) + pw[key] = datetime.strptime(val, "%H:%M:%S").time() + return pw["start"], pw["end"] + + def _compute_peak_window_mask(self) -> np.ndarray: + """Build a boolean mask that is ``True`` for timesteps inside the peak window. + + Returns: + np.ndarray: Boolean array of shape ``(n_timesteps,)``. + + Raises: + ValueError: If ``peak_window`` end time is before start time. + """ + start, end = self._parse_peak_window() + times = pd.DatetimeIndex(self.time_index).time + if end < start: + raise ValueError("peak_window end time must be after start time.") + return np.array([start <= t <= end for t in times]) + + def _compute_month_ids(self) -> np.ndarray: + """Return the calendar month index (1–12) for each timestep. + + Returns: + np.ndarray: Integer array of shape ``(n_timesteps,)``. + """ + return pd.DatetimeIndex(self.time_index).month.to_numpy() + + def _compute_eligible_mask(self, signal_window: np.ndarray) -> np.ndarray: + """Build a boolean mask for timesteps whose signal meets the dispatch threshold. + + The threshold is derived only from ``signal_window`` — it does not + assume the full simulation signal is known in advance. When + ``signal_threshold_percentile`` is 0.0 all hours are eligible. + + Args: + signal_window (np.ndarray): Signal values for the current + rolling window. + + Returns: + np.ndarray: Boolean array of shape ``(len(signal_window),)``. + ``True`` where ``signal_t >= threshold``. + """ + eligible = np.ones(len(signal_window), dtype=bool) + + if self.config.signal_threshold_percentile > 0.0: + threshold = np.percentile( + signal_window, self.config.signal_threshold_percentile + ) + eligible = signal_window >= threshold + + return eligible + + def pyomo_setup(self, discrete_inputs): + """Return the rolling-horizon dispatch solver callable. + + Args: + discrete_inputs (dict): OpenMDAO discrete inputs (unused + directly here; passed for API compatibility). + + Returns: + callable: ``pyomo_dispatch_solver(performance_model, + performance_model_kwargs, inputs)`` that iterates over the + simulation in windows of ``n_control_window`` timesteps. + For each window it: + + 1. Builds a fresh MILP from the window's signal slice. + 2. Solves the MILP with GLPK. + 3. Calls ``performance_model`` with the resulting dispatch + commands. + 4. Carries the terminal SOC into the next window. + + Returns ``(storage_out, soc_out)`` — two ``np.ndarray`` of + length ``n_timesteps``. + """ + + def pyomo_dispatch_solver( + performance_model, + performance_model_kwargs, + inputs, + commodity_name=self.config.commodity, + ): + storage_out = np.zeros(self.n_timesteps) + soc_out = np.zeros(self.n_timesteps) + + # Track events used per calendar month so the monthly cap is + # respected across window boundaries. + events_used_per_month = {} + + n_w = self.config.n_control_window + window_start_indices = list(range(0, self.n_timesteps, n_w)) + + for window_start in window_start_indices: + window_len = min(n_w, self.n_timesteps - window_start) + + n_windows = len(window_start_indices) + report_every = max(1, n_windows // 4) + window_idx = window_start // n_w + if window_idx % report_every == 0: + pct = round(window_start / self.n_timesteps * 100) + print(f"{pct}% done with PLM rolling dispatch") + + month_ids_w = self.month_ids[window_start : window_start + window_len] + remaining_budget = { + int(m): max( + 0, + self.config.n_max_events - events_used_per_month.get(int(m), 0), + ) + for m in np.unique(month_ids_w) + } + + self.dr_model = self._build_dr_model( + window_start=window_start, + window_len=window_len, + init_soc=self.updated_initial_soc, + remaining_budget=remaining_budget, + ) + self.problem_state = DispatchProblemState() + + self.solve_dispatch_model( + start_time=window_start, + n_days=self.n_timesteps // 24, + ) + + for t in range(window_len): + if pyomo.value(self.dr_model.discharge[t]) > 0.5: + m = int(month_ids_w[t]) + events_used_per_month[m] = events_used_per_month.get(m, 0) + 1 + + storage_out_window, soc_window = performance_model( + self.storage_dispatch_commands, + **performance_model_kwargs, + sim_start_index=window_start, + ) + + # Performance model returns SOC in percent. + self.updated_initial_soc = soc_window[-1] / 100.0 + + for j in range(window_len): + storage_out[window_start + j] = storage_out_window[j] + soc_out[window_start + j] = soc_window[j] + + print( + f" Events per month: {dict(events_used_per_month)} " + f"(limit: {self.config.n_max_events}/month)" + ) + return storage_out, soc_out + + return pyomo_dispatch_solver + + def initialize_parameters(self, inputs): + """Sync OpenMDAO inputs into the config. + + Args: + inputs (dict): OpenMDAO inputs dict. Recognised keys are + ``'max_charge_rate'`` and ``'storage_capacity'``. + """ + if "max_charge_rate" in inputs: + object.__setattr__( + self.config, "max_charge_rate", float(inputs["max_charge_rate"][0]) + ) + if "storage_capacity" in inputs: + object.__setattr__( + self.config, "max_capacity", float(inputs["storage_capacity"][0]) + ) + + def _build_dr_model( + self, + window_start: int, + window_len: int, + init_soc: float, + remaining_budget: dict, + ) -> pyomo.ConcreteModel: + """Build the DR MILP for a single rolling window. + + Args: + window_start (int): Global timestep index of the first hour + in this window. + window_len (int): Number of timesteps in this window + (``n_control_window`` except possibly the last window). + init_soc (float): State-of-charge fraction at the start of + this window. + remaining_budget (dict): Mapping of ``month_id (int)`` to + remaining event slots for that month. Computed by + subtracting events already dispatched in earlier windows + from ``n_max_events``, so the monthly cap is respected + across windows. + + Returns: + pyomo.ConcreteModel: Fully formed MILP ready to solve. + """ + m = pyomo.ConcreteModel(name="plm_dr") + + P_max = self.config.max_charge_rate + E_max = self.config.max_capacity * ( + self.config.max_soc_fraction - self.config.min_soc_fraction + ) + eta_c = self.config.charge_efficiency + eta_d = self.config.discharge_efficiency + soc_max = self.config.max_soc_fraction + soc_min = self.config.min_soc_fraction + incentive = self.config.performance_incentive + N_max = self.config.n_max_events + + w = slice(window_start, window_start + window_len) + in_peak_window_w = self.in_peak_window[w] + month_ids_w = self.month_ids[w] + signal_w = np.asarray(self.config.supervisory_signal, dtype=float)[w] + eligible_t_w = self._compute_eligible_mask(signal_w) + + months_in_window = np.unique(month_ids_w).tolist() + + m.T = pyomo.Set(initialize=range(window_len), doc="Timesteps in window") + m.M = pyomo.Set(initialize=months_in_window, doc="Months in window") + + m.discharge = pyomo.Var(m.T, domain=pyomo.Binary, doc="Discharge binary: 1 = discharging at timestep t") + m.charge = pyomo.Var(m.T, domain=pyomo.Binary, doc="Charge binary: 1 = charging at timestep t") + m.soc = pyomo.Var( + m.T, + domain=pyomo.NonNegativeReals, + bounds=(soc_min, soc_max), + doc="State of charge SoC_t", + ) + + m.objective = pyomo.Objective( + expr=-incentive * P_max * sum(m.discharge[t] for t in m.T), + sense=pyomo.minimize, + ) + + m.peak_window_only = pyomo.Constraint( + m.T, + rule=lambda mdl, t: ( + mdl.discharge[t] == 0 if not in_peak_window_w[t] else pyomo.Constraint.Skip + ), + ) + + m.high_signal_only = pyomo.Constraint( + m.T, + rule=lambda mdl, t: mdl.discharge[t] <= int(eligible_t_w[t]), + ) + + def max_events_rule(mdl, month): + ts_in_month = [t for t in mdl.T if month_ids_w[t] == month] + if not ts_in_month: + return pyomo.Constraint.Skip + budget = remaining_budget.get(month, N_max) + return sum(mdl.discharge[t] for t in ts_in_month) <= budget + + m.max_events = pyomo.Constraint(m.M, rule=max_events_rule) + + m.soc_init = pyomo.Constraint(expr=m.soc[0] == init_soc) + + def soc_evolution_rule(mdl, t): + if t == 0: + return pyomo.Constraint.Skip + return mdl.soc[t] == ( + mdl.soc[t - 1] + + eta_c * mdl.charge[t] * P_max / E_max + - mdl.discharge[t] * P_max / (eta_d * E_max) + ) + + m.soc_evolution = pyomo.Constraint(m.T, rule=soc_evolution_rule) + + m.no_simultaneous = pyomo.Constraint( + m.T, + rule=lambda mdl, t: mdl.discharge[t] + mdl.charge[t] <= 1, + ) + + m.no_charge_in_window = pyomo.Constraint( + m.T, + rule=lambda mdl, t: ( + mdl.charge[t] == 0 if in_peak_window_w[t] else pyomo.Constraint.Skip + ), + ) + + return m + + def solve_dispatch_model(self, start_time: int = 0, n_days: int = 0): + """Solve the DR MILP for the current window and record solver metrics. + + Args: + start_time (int): Global timestep index of the window start. + Used only for error messages and metrics. Defaults to 0. + n_days (int): Total simulation days. Passed to + ``DispatchProblemState.store_problem_metrics``. + Defaults to 0. + + Raises: + RuntimeError: If GLPK returns a non-OK status or an + unacceptable termination condition. + """ + from pyomo.opt import SolverStatus, TerminationCondition + + solver_results = self.glpk_solve_call(self.dr_model) + + status = solver_results.solver.status + tc = solver_results.solver.termination_condition + acceptable = ( + TerminationCondition.optimal, + TerminationCondition.feasible, + TerminationCondition.maxTimeLimit, + ) + if status != SolverStatus.ok or tc not in acceptable: + raise RuntimeError( + f"PLM MILP solver failed at window start={start_time}: " + f"status={status}, termination={tc}. " + f"init_soc={self.updated_initial_soc:.4f}, " + f"window_len={len(list(self.dr_model.T))}" + ) + if tc == TerminationCondition.maxTimeLimit: + print( + f" WARNING: solver hit time limit at window start={start_time} " + f"— using best solution found so far" + ) + + self.problem_state.store_problem_metrics( + solver_results, + start_time, + n_days, + pyomo.value(self.dr_model.objective), + ) + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + """Build the DR dispatch solver and write it to discrete outputs. + + Args: + inputs (dict): OpenMDAO continuous inputs. + outputs (dict): OpenMDAO continuous outputs. + discrete_inputs (dict): OpenMDAO discrete inputs. + discrete_outputs (dict): OpenMDAO discrete outputs. The key + ``'pyomo_dispatch_solver'`` is set to the callable + returned by :meth:`pyomo_setup`. + """ + discrete_outputs["pyomo_dispatch_solver"] = self.pyomo_setup(discrete_inputs) + + @staticmethod + def glpk_solve_call( + pyomo_model: pyomo.ConcreteModel, + log_name: str = "", + user_solver_options: dict | None = None, + ): + """Solve a Pyomo MILP with GLPK. + + Args: + pyomo_model (pyomo.ConcreteModel): The model to solve. + log_name (str): Optional log file name passed to + ``SolverOptions``. Defaults to ``''``. + user_solver_options (dict | None): Optional overrides for + GLPK solver options. Defaults to ``None``. + + Returns: + pyomo.opt.SolverResults: Raw results object from GLPK. + """ + glpk_solver_options = {"cuts": None, "presol": None, "tmlim": 300} + solver_options = SolverOptions( + glpk_solver_options, log_name, user_solver_options, "log" + ) + with pyomo.SolverFactory("glpk") as solver: + results = solver.solve( + pyomo_model, options=solver_options.constructed, tee=False + ) + return results + + @property + def storage_dispatch_commands(self) -> list: + """Net dispatch commands for the solved window. + + Returns: + list[float]: ``(u_t - v_t) * P_max`` for each timestep in + the solved window. Positive = discharge, negative = charge. + """ + P_max = self.config.max_charge_rate + return [ + (pyomo.value(self.dr_model.discharge[t]) - pyomo.value(self.dr_model.charge[t])) * P_max + for t in self.dr_model.T + ] diff --git a/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py new file mode 100644 index 000000000..40b37863e --- /dev/null +++ b/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py @@ -0,0 +1,270 @@ +from types import SimpleNamespace + +from attrs import evolve +import numpy as np +import pandas as pd +import pyomo.environ as pyomo +import pytest + +from h2integrate.control.control_strategies.storage.plm_optimized_storage_controller import ( + PLMOptimizedControllerConfig, + PLMOptimizedStorageController, +) +from h2integrate.storage.storage_baseclass import StoragePerformanceBase + + +def _make_controller(): + return object.__new__(PLMOptimizedStorageController) + + +def _make_controller_with_config(config, n_timesteps=24): + """Build a controller with pre-computed masks, bypassing OpenMDAO setup.""" + controller = _make_controller() + controller.config = config + controller.updated_initial_soc = config.init_soc_fraction + controller.time_index = pd.date_range("2024-01-01", periods=n_timesteps, freq="h") + controller.in_peak_window = controller._compute_peak_window_mask() + controller.month_ids = controller._compute_month_ids() + return controller + + +@pytest.fixture +def base_config(): + """24-hour PLM config shared by optimizer regression tests. + + Storage: 10 kWh capacity, 1 kW max rate, starts fully charged. + Peak window: 08:00-18:00 + Signal: monotonically increasing. + """ + n = 24 + return PLMOptimizedControllerConfig( + max_capacity=10.0, + max_soc_fraction=1.0, + min_soc_fraction=0.0, + init_soc_fraction=1.0, + n_control_window=n, + commodity="electricity", + commodity_rate_units="kW", + tech_name="battery", + system_commodity_interface_limit=100.0, + max_charge_rate=1.0, + supervisory_signal=list(range(n)), + peak_window={"start": "08:00:00", "end": "18:00:00"}, + performance_incentive=10.0, + n_max_events=24, + signal_threshold_percentile=0.0, + ) + + +@pytest.mark.unit +def test_parse_peak_window(): + controller = _make_controller() + controller.config = SimpleNamespace( + peak_window={"start": "08:00:00", "end": "18:40:20"} + ) + start, end = controller._parse_peak_window() + assert start.hour == 8 + assert start.minute == 0 + assert start.second == 0 + assert end.hour == 18 + assert end.minute == 40 + assert end.second == 20 + + +@pytest.mark.unit +def test_parse_peak_window_invalid_format(): + controller = _make_controller() + controller.config = SimpleNamespace(peak_window={"start": "08", "end": "18:40:20"}) + with pytest.raises( + ValueError, match="peak_window start value must be a string in HH:MM:SS format" + ): + controller._parse_peak_window() + + +@pytest.mark.unit +def test_parse_peak_window_int_raises(): + controller = _make_controller() + controller.config = SimpleNamespace(peak_window={"start": 8, "end": 9}) + with pytest.raises(ValueError): + controller._parse_peak_window() + + +@pytest.mark.unit +def test_parse_peak_window_missing_key_raises(): + controller = _make_controller() + controller.config = SimpleNamespace(peak_window={"start": "08:00:00"}) + with pytest.raises( + ValueError, match="peak_window must contain 'start' and 'end' keys" + ): + controller._parse_peak_window() + + +@pytest.mark.unit +def test_compute_peak_window_mask(): + controller = _make_controller() + controller.config = SimpleNamespace( + peak_window={"start": "00:00:00", "end": "02:00:00"} + ) + controller.time_index = pd.date_range("2024-01-01", periods=24, freq="h") + mask = controller._compute_peak_window_mask() + expected = np.array([i <= 2 for i in range(24)]) + assert isinstance(mask, np.ndarray) + assert np.array_equal(mask, expected) + + +@pytest.mark.unit +def test_compute_month_ids(): + # Jan 2024: 744h, Feb 2024 (leap year): 696h, Mar 2024: 744h + controller = _make_controller() + controller.time_index = pd.date_range( + "2024-01-01", periods=744 + 696 + 744, freq="h" + ) + month_ids = controller._compute_month_ids() + expected = np.array([1] * 744 + [2] * 696 + [3] * 744) + assert np.array_equal(month_ids, expected) + + +@pytest.mark.unit +def test_compute_eligible_mask_zero_percentile_all_eligible(): + """All timesteps are eligible when signal_threshold_percentile=0.""" + controller = _make_controller() + controller.config = SimpleNamespace(signal_threshold_percentile=0.0) + signal = np.array([1.0, 5.0, 3.0, 2.0, 8.0]) + mask = controller._compute_eligible_mask(signal) + assert isinstance(mask, np.ndarray) + assert mask.dtype == bool + assert len(mask) == len(signal) + assert mask.all() + + +@pytest.mark.unit +def test_compute_eligible_mask_50th_percentile(): + """Only values at or above the 50th percentile are eligible.""" + controller = _make_controller() + controller.config = SimpleNamespace(signal_threshold_percentile=50.0) + signal = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) + mask = controller._compute_eligible_mask(signal) + expected = signal >= np.percentile(signal, 50.0) + assert np.array_equal(mask, expected) + + +@pytest.mark.unit +def test_compute_eligible_mask_100th_percentile_only_max_eligible(): + """Only the maximum value(s) are eligible at percentile=100.""" + controller = _make_controller() + controller.config = SimpleNamespace(signal_threshold_percentile=100.0) + signal = np.array([1.0, 2.0, 10.0, 3.0, 10.0]) + mask = controller._compute_eligible_mask(signal) + expected = np.array([False, False, True, False, True]) + assert np.array_equal(mask, expected) + + +@pytest.mark.unit +def test_compute_eligible_mask_uniform_signal_all_eligible(): + """All timesteps are eligible when the signal is uniform, regardless of percentile.""" + controller = _make_controller() + controller.config = SimpleNamespace(signal_threshold_percentile=75.0) + signal = np.full(10, 5.0) + mask = controller._compute_eligible_mask(signal) + assert mask.all() + + +@pytest.mark.regression +def test_optimizer_dispatch_only_in_peak_window(base_config): + """Solver must never set 1 outside the peak window.""" + # + controller = _make_controller_with_config(base_config) + model = controller._build_dr_model( + window_start=0, + window_len=24, + init_soc=base_config.init_soc_fraction, + remaining_budget={1: base_config.n_max_events}, + ) + + PLMOptimizedStorageController.glpk_solve_call(model) + + peak_start, peak_end = controller._parse_peak_window() + print("Peak window:", peak_start, "-", peak_end) + for t in range(24): + hour = pd.Timestamp("2024-01-01") + pd.Timedelta(hours=t) + in_window = peak_start <= hour.time() <= peak_end + if not in_window: + assert pyomo.value(model.discharge[t]) < 1e-3 + + +@pytest.mark.regression +def test_optimizer_dispatch_only_on_eligible_timesteps(base_config): + """Solver must never set 1 on ineligible timesteps.""" + controller = _make_controller_with_config(base_config) + model = controller._build_dr_model( + window_start=0, + window_len=24, + init_soc=base_config.init_soc_fraction, + remaining_budget={1: base_config.n_max_events}, + ) + + PLMOptimizedStorageController.glpk_solve_call(model) + + signal = np.array(controller.config.supervisory_signal) + eligible_mask = controller._compute_eligible_mask(signal) + for t in range(24): + if not eligible_mask[t]: + assert pyomo.value(model.discharge[t]) < 1e-3 + + +@pytest.mark.regression +def test_optimizer_dispatch_respects_event_budget(base_config): + """Solver must never set more than n_max_events timesteps to 1.""" + controller = _make_controller_with_config(base_config) + model = controller._build_dr_model( + window_start=0, + window_len=24, + init_soc=base_config.init_soc_fraction, + remaining_budget={1: base_config.n_max_events}, + ) + + PLMOptimizedStorageController.glpk_solve_call(model) + + total_events = sum(pyomo.value(model.discharge[t]) for t in range(24)) + assert total_events <= base_config.n_max_events + 1e-3 + + +@pytest.mark.regression +def test_optimizer_dispatch_respects_soc_constraints(base_config): + """Solver must never violate SOC constraints.""" + controller = _make_controller_with_config(base_config) + model = controller._build_dr_model( + window_start=0, + window_len=24, + init_soc=base_config.init_soc_fraction, + remaining_budget={2: base_config.n_max_events}, + ) + + PLMOptimizedStorageController.glpk_solve_call(model) + + soc = base_config.init_soc_fraction * base_config.max_capacity + for t in range(24): + charge = pyomo.value(model.charge[t]) + discharge = pyomo.value(model.discharge[t]) + soc += charge - discharge + assert soc >= base_config.min_soc_fraction * base_config.max_capacity + assert soc <= base_config.max_soc_fraction * base_config.max_capacity + + +@pytest.mark.regression +def test_optimizer_dispatch_respects_charge_discharge_exclusivity(base_config): + """Solver must never set charge and discharge to 1 in the same timestep.""" + controller = _make_controller_with_config(base_config) + model = controller._build_dr_model( + window_start=0, + window_len=24, + init_soc=base_config.init_soc_fraction, + remaining_budget={2: base_config.n_max_events}, + ) + + PLMOptimizedStorageController.glpk_solve_call(model) + + for t in range(24): + charge = pyomo.value(model.charge[t]) + discharge = pyomo.value(model.discharge[t]) + assert not (charge > 0.5 and discharge > 0.5) diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 62e197de9..7120d47a1 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -7,7 +7,10 @@ from h2integrate.finances.profast_lco import ProFastLCO from h2integrate.finances.profast_npv import ProFastNPV from h2integrate.demand.generic_demand import GenericDemandComponent -from h2integrate.converters.steel.steel import SteelPerformanceModel, SteelCostAndFinancialModel +from h2integrate.converters.steel.steel import ( + SteelPerformanceModel, + SteelCostAndFinancialModel, +) from h2integrate.converters.wind.floris import FlorisWindPlantPerformanceModel from h2integrate.demand.flexible_demand import FlexibleDemandComponent from h2integrate.converters.wind.wind_pysam import PYSAMWindPlantPerformanceModel @@ -33,7 +36,10 @@ IronTransportCostComponent, IronTransportPerformanceComponent, ) -from h2integrate.converters.nitrogen.simple_ASU import SimpleASUCostModel, SimpleASUPerformanceModel +from h2integrate.converters.nitrogen.simple_ASU import ( + SimpleASUCostModel, + SimpleASUPerformanceModel, +) from h2integrate.converters.wind.wind_plant_ard import ArdWindPlantModel from h2integrate.resource.solar.openmeteo_solar import OpenMeteoHistoricalSolarResource from h2integrate.converters.hydrogen.h2_fuel_cell import ( @@ -58,10 +64,16 @@ CompressedGasStorageCostModel, LinedRockCavernStorageCostModel, ) -from h2integrate.transporters.gas_stream_combiner import GasStreamCombinerPerformanceModel -from h2integrate.transporters.generic_transporter import GenericTransporterPerformanceModel +from h2integrate.transporters.gas_stream_combiner import ( + GasStreamCombinerPerformanceModel, +) +from h2integrate.transporters.generic_transporter import ( + GenericTransporterPerformanceModel, +) from h2integrate.converters.generic_converter_cost import GenericConverterCostModel -from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent +from h2integrate.converters.iron.humbert_ewin_perf import ( + HumbertEwinPerformanceComponent, +) from h2integrate.storage.storage_performance_model import StoragePerformanceModel from h2integrate.converters.ammonia.ammonia_synloop import ( AmmoniaSynLoopCostModel, @@ -73,13 +85,21 @@ ReverseOsmosisCostModel, ReverseOsmosisPerformanceModel, ) -from h2integrate.resource.wind.nlr_developer_wtk_api import WTKNLRDeveloperAPIWindResource +from h2integrate.resource.wind.nlr_developer_wtk_api import ( + WTKNLRDeveloperAPIWindResource, +) from h2integrate.converters.hydrogen.basic_cost_model import BasicElectrolyzerCostModel -from h2integrate.converters.hydrogen.pem_electrolyzer import ECOElectrolyzerPerformanceModel +from h2integrate.converters.hydrogen.pem_electrolyzer import ( + ECOElectrolyzerPerformanceModel, +) from h2integrate.converters.solar.atb_res_com_pv_cost import ATBResComPVCostModel from h2integrate.converters.solar.atb_utility_pv_cost import ATBUtilityPVCostModel -from h2integrate.converters.iron.martin_mine_cost_model import MartinIronMineCostComponent -from h2integrate.converters.iron.martin_mine_perf_model import MartinIronMinePerformanceComponent +from h2integrate.converters.iron.martin_mine_cost_model import ( + MartinIronMineCostComponent, +) +from h2integrate.converters.iron.martin_mine_perf_model import ( + MartinIronMinePerformanceComponent, +) from h2integrate.converters.methanol.smr_methanol_plant import ( SMRMethanolPlantCostModel, SMRMethanolPlantFinanceModel, @@ -89,7 +109,9 @@ SimpleAmmoniaCostModel, SimpleAmmoniaPerformanceModel, ) -from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent +from h2integrate.converters.iron.humbert_stinn_ewin_cost import ( + HumbertStinnEwinCostComponent, +) from h2integrate.converters.methanol.co2h_methanol_plant import ( CO2HMethanolPlantCostModel, CO2HMethanolPlantFinanceModel, @@ -101,7 +123,10 @@ ) from h2integrate.converters.water_power.pysam_marine_cost import PySAMMarineCostModel from h2integrate.converters.hydrogen.singlitico_cost_model import SingliticoCostModel -from h2integrate.converters.co2.marine.direct_ocean_capture import DOCCostModel, DOCPerformanceModel +from h2integrate.converters.co2.marine.direct_ocean_capture import ( + DOCCostModel, + DOCPerformanceModel, +) from h2integrate.converters.hydrogen.steam_methane_reformer import ( SteamMethaneReformerCostModel, SteamMethaneReformerPerformanceModel, @@ -112,7 +137,9 @@ SimpleGasConsumerPerformance, SimpleGasProducerPerformance, ) -from h2integrate.converters.hydrogen.geologic.mathur_modified import GeoH2SubsurfaceCostModel +from h2integrate.converters.hydrogen.geologic.mathur_modified import ( + GeoH2SubsurfaceCostModel, +) from h2integrate.resource.solar.nlr_developer_goes_api_models import ( GOESTMYSolarAPI, GOESConusSolarAPI, @@ -171,6 +198,12 @@ from h2integrate.control.control_strategies.storage.simple_openloop_controller import ( SimpleStorageOpenLoopController, ) +from h2integrate.control.control_strategies.storage.plm_openloop_storage_controller import ( + PeakLoadManagementOpenLoopStorageController, +) +from h2integrate.control.control_strategies.storage.plm_optimized_storage_controller import ( + PLMOptimizedStorageController, +) from h2integrate.control.control_strategies.storage.plm_openloop_storage_controller import ( PeakLoadManagementHeuristicOpenLoopStorageController, ) @@ -295,6 +328,8 @@ # Control "SimpleStorageOpenLoopController": SimpleStorageOpenLoopController, "DemandOpenLoopStorageController": DemandOpenLoopStorageController, + "PeakLoadManagementOpenLoopStorageController": PeakLoadManagementOpenLoopStorageController, + "PLMOptimizedStorageController": PLMOptimizedStorageController, "PeakLoadManagementHeuristicOpenLoopStorageController": ( PeakLoadManagementHeuristicOpenLoopStorageController ), From b0a030cf183cb484223abbd7c8bf11a9ae70baac Mon Sep 17 00:00:00 2001 From: svijaysh Date: Wed, 22 Apr 2026 16:05:42 -0600 Subject: [PATCH 2/7] Make sure the example runs --- .../run_plm_optimized_dispatch.py | 55 ++++++++++++++----- .../plm_optimized_storage_controller.py | 2 +- .../test_plm_optimized_storage_controller.py | 6 -- h2integrate/core/supported_models.py | 4 -- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py b/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py index a833d2cc9..3dff042d2 100644 --- a/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py +++ b/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py @@ -12,7 +12,6 @@ import matplotlib.pyplot as plt from h2integrate.core.h2integrate_model import H2IntegrateModel -from h2integrate.core.utilities import build_time_series_from_plant_config EXAMPLE_DIR = Path(__file__).parent @@ -21,16 +20,26 @@ model.setup() N = model.plant_config["plant"]["simulation"]["n_timesteps"] -percentile = model.technology_config["technologies"]["battery"]["model_inputs"]["control_parameters"]["signal_threshold_percentile"] +percentile = model.technology_config["technologies"]["battery"]["model_inputs"][ + "control_parameters" +]["signal_threshold_percentile"] model.run() lmp = np.array( - model.technology_config["technologies"]["battery"]["model_inputs"] - ["control_parameters"]["supervisory_signal"] + model.technology_config["technologies"]["battery"]["model_inputs"][ + "control_parameters" + ]["supervisory_signal"] )[:N] -time_index = pd.DatetimeIndex(build_time_series_from_plant_config(model.plant_config)) +sim = model.plant_config["plant"]["simulation"] +n_timesteps = int(sim["n_timesteps"]) +dt_seconds = int(sim["dt"]) +tz = int(sim["timezone"]) +start = pd.Timestamp(sim["start_time"], tz=tz) +freq = pd.to_timedelta(dt_seconds, unit="s") +time_index = pd.date_range(start=start, periods=n_timesteps, freq=freq) + battery_power = model.prob.get_val("battery.storage_electricity_discharge", units="kW") soc_pct = model.prob.get_val("battery.SOC", units="percent") @@ -50,7 +59,10 @@ def shade_peaks(ax): ax.axvspan( day + pd.Timedelta(hours=14), day + pd.Timedelta(hours=18), - color="orange", alpha=0.10, linewidth=0, zorder=0, + color="orange", + alpha=0.10, + linewidth=0, + zorder=0, ) @@ -60,14 +72,21 @@ def shade_peaks(ax): shade_peaks(ax) ax.plot(time_index[:time_window], lmp[:time_window], color="steelblue", linewidth=1.0) ax.axhline(threshold_pct, color="crimson", linestyle="--", linewidth=0.8) -ax.plot(time_index[:time_window][w_discharge], lmp[:time_window][w_discharge], - "r*", markersize=8, zorder=5) +ax.plot( + time_index[:time_window][w_discharge], + lmp[:time_window][w_discharge], + "r*", + markersize=8, + zorder=5, +) ax.set_ylabel("LMP ($/MWh)", fontsize=8) ax.set_ylim(bottom=0) # Panel 2: SOC ax = axes[1] -ax.plot(time_index[:time_window], soc_pct[:time_window], color="tab:green", linewidth=1.0) +ax.plot( + time_index[:time_window], soc_pct[:time_window], color="tab:green", linewidth=1.0 +) ax.axhline(90, color="gray", linestyle=":", linewidth=0.7) ax.axhline(10, color="gray", linestyle=":", linewidth=0.7) ax.set_ylabel("SOC (%)", fontsize=8) @@ -79,10 +98,20 @@ def shade_peaks(ax): width = pd.Timedelta(hours=0.8) pos_mask = (battery_power > 1)[:time_window] neg_mask = (battery_power < -1)[:time_window] -ax.bar(time_index[:time_window][pos_mask], battery_power[:time_window][pos_mask] * 1e-3, - width=width, color="tab:red", alpha=0.8) -ax.bar(time_index[:time_window][neg_mask], battery_power[:time_window][neg_mask] * 1e-3, - width=width, color="tab:blue", alpha=0.8) +ax.bar( + time_index[:time_window][pos_mask], + battery_power[:time_window][pos_mask] * 1e-3, + width=width, + color="tab:red", + alpha=0.8, +) +ax.bar( + time_index[:time_window][neg_mask], + battery_power[:time_window][neg_mask] * 1e-3, + width=width, + color="tab:blue", + alpha=0.8, +) ax.set_ylabel("Power (MW)", fontsize=8) ax.tick_params(axis="x", labelrotation=30, labelsize=7) diff --git a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py index a4721825f..3e0524801 100644 --- a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py @@ -59,7 +59,7 @@ class PLMOptimizedControllerConfig(PyomoStorageControllerBaseConfig): discharge_efficiency: float = field(validator=range_val(0, 1), default=1.0) n_max_events: int = field(default=10) n_control_window: int = field(default=24 * 30) # one month of hourly data - signal_threshold_percentile: float = field(default=0.0, validator=range_val(0,100)) + signal_threshold_percentile: float = field(default=0.0, validator=range_val(0,100)) # make sure this is valid class PLMOptimizedStorageController(PyomoStorageControllerBaseClass): diff --git a/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py index 40b37863e..f3aaa1ae3 100644 --- a/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/test/test_plm_optimized_storage_controller.py @@ -30,12 +30,6 @@ def _make_controller_with_config(config, n_timesteps=24): @pytest.fixture def base_config(): - """24-hour PLM config shared by optimizer regression tests. - - Storage: 10 kWh capacity, 1 kW max rate, starts fully charged. - Peak window: 08:00-18:00 - Signal: monotonically increasing. - """ n = 24 return PLMOptimizedControllerConfig( max_capacity=10.0, diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 7120d47a1..4b39a7df5 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -198,9 +198,6 @@ from h2integrate.control.control_strategies.storage.simple_openloop_controller import ( SimpleStorageOpenLoopController, ) -from h2integrate.control.control_strategies.storage.plm_openloop_storage_controller import ( - PeakLoadManagementOpenLoopStorageController, -) from h2integrate.control.control_strategies.storage.plm_optimized_storage_controller import ( PLMOptimizedStorageController, ) @@ -328,7 +325,6 @@ # Control "SimpleStorageOpenLoopController": SimpleStorageOpenLoopController, "DemandOpenLoopStorageController": DemandOpenLoopStorageController, - "PeakLoadManagementOpenLoopStorageController": PeakLoadManagementOpenLoopStorageController, "PLMOptimizedStorageController": PLMOptimizedStorageController, "PeakLoadManagementHeuristicOpenLoopStorageController": ( PeakLoadManagementHeuristicOpenLoopStorageController From 125b48f4106b976cbca067cb2f6f0fb20aa66491 Mon Sep 17 00:00:00 2001 From: svijaysh Date: Sat, 25 Apr 2026 20:26:05 -0600 Subject: [PATCH 3/7] Add docs --- docs/control/pyomo_controllers.md | 87 +++++++++++++++++++++++++++++++ docs/user_guide/model_overview.md | 1 + 2 files changed, 88 insertions(+) diff --git a/docs/control/pyomo_controllers.md b/docs/control/pyomo_controllers.md index 2da23275f..ac6a1ddc0 100644 --- a/docs/control/pyomo_controllers.md +++ b/docs/control/pyomo_controllers.md @@ -58,3 +58,90 @@ tech_to_dispatch_connections: [ ["battery", "battery"], ] ``` + +# Optimized Demand Response Controller + +This controller optimizes the dispatch of a Battery Energy Storage System (BESS) based on a pre-defined supervisory signal. This pre-defined signal could be the Locational Marginal Price (LMP) or a demand profile. It could also be a $LMP\times demand$ depending on the application. + +## Definitions + +**Given:** +- $\lambda_t$ := `supervisory_signal`: price, demand, or price $\times$ demand time series at time $t$ +- $\mathcal{W}$ := `peak_window`: set of hours eligible for dispatch (e.g., 12:00--19:00) +- $\gamma$ := performance incentive (\$/kW per dispatch hour) +- $\bar{P}$ := `max_charge_rate` (kW): maximum charge and discharge rate, used as deemed capacity since the battery is assumed to always dispatch at full rated power +- $E_{\max} :=$ `max_capacity` $\times$ (`max_soc_fraction` $-$ `min_soc_fraction`): usable energy capacity (kWh) +- $\eta_c$ := `charge_efficiency`, $\quad \eta_d$ := `discharge_efficiency` +- $\overline{\text{SoC}}$ := `max_soc_fraction`, $\quad \underline{\text{SoC}}$ := `min_soc_fraction` +- `n_control_window` := Horizon length for optimization +- $\mathcal{T} := \{0, 1, \ldots, T\}$: hourly time steps over `n_control_window` +- $\mathcal{M}_m$ := set of hours in month $m$, for $m = 1, \ldots, 12$ + +## Decision Variables + +- $u_t \in \{0, 1\}$ := discharge binary: 1 if battery dispatches at hour $t$, 0 otherwise +- $v_t \in \{0, 1\}$ := charge binary: 1 if battery charges at hour $t$, 0 otherwise + +## Optimization Problem + +This optimization is executed for each window, during which the performance model is invoked and the initial conditions are set. + +### Objective + +Maximize total annual incentive revenue: + +$$ +\max_{u_t,\, v_t} \quad \gamma \cdot \bar{P} \sum_{t \in \mathcal{T}} u_t +$$ + +### Constraints + +- Dispatch only within peak window: + +$$ +u_t = 0 \qquad \forall\, t \notin \mathcal{W} +$$ + +- Dispatch only on high supervisory signal: + +$$ +u_t = 1 \implies \lambda_t \geq \lambda^*_m \qquad \forall\, t \in \mathcal{M}_m +$$ + +where $\lambda^*_m$ is the threshold selecting the high LMP/peak load hours within month $m$. + +- Maximum 10 events per month: + +$$ +\sum_{t \in \mathcal{M}_m} u_t \leq N_{\max} \qquad \forall\, m, \quad N_{\max} = 10 +$$ + +- SoC evolution with charge and discharge: + +$$ +\text{SoC}_{t+1} = \text{SoC}_t + \frac{\eta_c \cdot v_t \cdot \bar{P}}{E_{\max}} - \frac{u_t \cdot \bar{P}}{\eta_d \cdot E_{\max}} \qquad \forall\, t \in \mathcal{T} +$$ + +- SoC bounds: + +$$ +\underline{\text{SoC}} \leq \text{SoC}_t \leq \overline{\text{SoC}} \qquad \forall\, t \in \mathcal{T} +$$ + +- No simultaneous charge and discharge: + +$$ +u_t + v_t \leq 1 \qquad \forall\, t \in \mathcal{T} +$$ + +- No charging during dispatch window (battery reserved for discharge): + +$$ +v_t = 0 \qquad \forall\, t \in \mathcal{W} +$$ + +- Binary variables: + +$$ +u_t \in \{0, 1\}, \quad v_t \in \{0, 1\}, \quad \text{SoC}_t \in [0, 1] \qquad \forall\, t, m +$$ diff --git a/docs/user_guide/model_overview.md b/docs/user_guide/model_overview.md index 6b8d6344f..2756730c2 100644 --- a/docs/user_guide/model_overview.md +++ b/docs/user_guide/model_overview.md @@ -288,6 +288,7 @@ Below summarizes the available performance, cost, and financial models for each - `'SimpleStorageOpenLoopController'`: open-loop control; manages resource flow based on demand and input commodity - `'DemandOpenLoopStorageController'`: open-loop control; manages resource flow based on demand and storage constraints - `'HeuristicLoadFollowingStorageController'`: open-loop control that works on a time window basis to set dispatch commands; uses Pyomo + - `'PLMOptimizedStorageController'`: optimized controller for demand response that works on a time window basis. - `'PeakLoadManagementHeuristicOpenLoopStorageController'`: open-loop control that reduces peaks rather than trying to meet a load - Optimized Dispatch: - `'OptimizedDispatchStorageController'`: optimization-based dispatch using Pyomo From 7903e39b496e5bd7e7ee80506dc2267e1ccd0e80 Mon Sep 17 00:00:00 2001 From: svijaysh Date: Thu, 23 Apr 2026 10:08:39 -0600 Subject: [PATCH 4/7] Add docs --- examples/test/test_all_examples.py | 2785 ++++++++++++++++++++++++++++ 1 file changed, 2785 insertions(+) diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 5d9c3cad4..11e75ee03 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -2882,3 +2882,2788 @@ def test_peak_load_management_example(subtests, temp_copy_of_example): ) grid_purchase = model.prob.get_val("grid_buy.electricity_out", units="kW") assert battery_unmet_demand.sum() == pytest.approx(grid_purchase.sum(), rel=1e-3) + +import os +import importlib +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest +import openmdao.api as om + +from h2integrate import ROOT_DIR +from h2integrate.core.file_utils import load_yaml +from h2integrate.core.h2integrate_model import H2IntegrateModel + + +ROOT = Path(__file__).parents[1] + + +# docs fencepost start: DO NOT REMOVE +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("01_onshore_steel_mn", None)]) +def test_steel_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "01_onshore_steel_mn.yaml") + # docs fencepost end: DO NOT REMOVE + # Set battery demand profile to electrolyzer capacity + demand_profile = np.ones(8760) * 720.0 + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + + # Run the model + model.run() + + model.post_process() + # Subtests for checking specific values + with subtests.test("Check total electricity produced"): + assert ( + pytest.approx( + model.prob.get_val("combiner.electricity_out", units="MW").sum(), + rel=1e-3, + ) + == 5901098.278035271 + ) + + with subtests.test("Check total adjusted CapEx (electricity)"): + assert ( + pytest.approx( + model.prob.get_val( + "finance_subgroup_electricity.total_capex_adjusted", units="USD" + )[0], + rel=1e-3, + ) + == 4314364438.840067 + ) + with subtests.test("Check total adjusted OpEx (electricity)"): + assert ( + pytest.approx( + model.prob.get_val( + "finance_subgroup_electricity.total_opex_adjusted", units="USD/year" + )[0], + rel=1e-3, + ) + == 75831805.27785796 + ) + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-3, + ) + == 90.8231905486079 + ) + + with subtests.test("Check H2 Storage capacity"): + assert ( + pytest.approx(model.prob.get_val("h2_storage.storage_capacity", units="kg"), rel=1e-3) + == 2559669.7759292 + ) + + with subtests.test("Check LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.LCOH_delivered", units="USD/kg")[0], + rel=1e-3, + ) + == 8.235313509720276 + ) + + with subtests.test("Check LCOS"): + assert ( + pytest.approx(model.prob.get_val("steel.LCOS", units="USD/t")[0], rel=1e-3) + == 1264.2821232584045 + ) + + with subtests.test("Check total adjusted CapEx"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.total_capex_adjusted", units="USD")[ + 0 + ], + rel=1e-3, + ) + == 5129491338.670795 + ) + + with subtests.test("Check total adjusted OpEx"): + assert ( + pytest.approx( + model.prob.get_val( + "finance_subgroup_hydrogen.total_opex_adjusted", units="USD/year" + )[0], + rel=1e-3, + ) + == 97887982.86294547 + ) + + with subtests.test("Check steel CapEx"): + assert ( + pytest.approx(model.prob.get_val("steel.CapEx", units="USD"), rel=1e-3) == 5.78060014e08 + ) + + with subtests.test("Check steel OpEx"): + assert ( + pytest.approx(model.prob.get_val("steel.OpEx", units="USD/year"), rel=1e-3) + == 1.0129052e08 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("02_texas_ammonia", None)]) +def test_simple_ammonia_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "02_texas_ammonia.yaml") + + # Set battery demand profile to electrolyzer capacity + demand_profile = np.ones(8760) * 640.0 + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check Wind+PV CapEx"): + wind_pv_capex = ( + model.prob.get_val("wind.CapEx", units="USD")[0] + + model.prob.get_val("solar.CapEx", units="USD")[0] + ) + assert pytest.approx(wind_pv_capex, rel=1e-3) == 1.75469962e09 + + with subtests.test("Check Wind+PV OpEx"): + wind_pv_opex = ( + model.prob.get_val("wind.OpEx", units="USD/yr")[0] + + model.prob.get_val("solar.OpEx", units="USD/yr")[0] + ) + assert pytest.approx(wind_pv_opex, rel=1e-3) == 32953490.4 + + with subtests.test("Check electrolyzer CapEx"): + assert ( + pytest.approx(model.prob.get_val("electrolyzer.CapEx", units="USD"), rel=1e-3) + == 6.00412524e08 + ) + + with subtests.test("Check electrolyzer OpEx"): + assert ( + pytest.approx(model.prob.get_val("electrolyzer.OpEx", units="USD/year"), rel=1e-3) + == 14703155.39207595 + ) + + with subtests.test("Check H2 storage CapEx"): + assert ( + pytest.approx(model.prob.get_val("h2_storage.CapEx", units="USD")[0], rel=1e-3) + == 64599012.73829915 + ) + + with subtests.test("Check H2 storage OpEx"): + assert ( + pytest.approx(model.prob.get_val("h2_storage.OpEx", units="USD/year")[0], rel=1e-3) + == 2975616.8932987223 + ) + + with subtests.test("Check ammonia CapEx"): + assert ( + pytest.approx(model.prob.get_val("ammonia.CapEx", units="USD"), rel=1e-3) + == 1.0124126e08 + ) + + with subtests.test("Check ammonia OpEx"): + assert ( + pytest.approx(model.prob.get_val("ammonia.OpEx", units="USD/year"), rel=1e-3) + == 11178036.31197754 + ) + + with subtests.test("Check total adjusted CapEx"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.total_capex_adjusted", units="USD")[ + 0 + ], + rel=1e-3, + ) + == 2577162708.3 + ) + + with subtests.test("Check total adjusted OpEx"): + assert ( + pytest.approx( + model.prob.get_val( + "finance_subgroup_hydrogen.total_opex_adjusted", units="USD/year" + )[0], + rel=1e-3, + ) + == 53842563.43404999 + ) + + # Currently underestimated compared to the Reference Design Doc + with subtests.test("Check LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0], + rel=1e-3, + ) + == 4.0155433 + ) + + with subtests.test("Check price of hydrogen"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.price_hydrogen", units="USD/kg")[0], + rel=1e-3, + ) + == 4.0155433 + ) + + # Currently underestimated compared to the Reference Design Doc + with subtests.test("Check LCOA"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_ammonia.LCOA", units="USD/kg")[0], + rel=1e-3, + ) + == 1.027395 + ) + + # Check that the expected output files exist + outputs_dir = example_folder / "outputs" + assert ( + outputs_dir / "profast_output_ammonia_config.yaml" + ).is_file(), "profast_output_ammonia.yaml not found" + assert ( + outputs_dir / "profast_output_electricity_config.yaml" + ).is_file(), "profast_output_electricity.yaml not found" + assert ( + outputs_dir / "profast_output_hydrogen_config.yaml" + ).is_file(), "profast_output_hydrogen.yaml not found" + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("12_ammonia_synloop", None)]) +def test_ammonia_synloop_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "12_ammonia_synloop.yaml") + + # Set battery demand profile to electrolyzer capacity + demand_profile = np.ones(8760) * 640.0 + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check HOPP CapEx"): + wind_pv_capex = ( + model.prob.get_val("wind.CapEx", units="USD")[0] + + model.prob.get_val("solar.CapEx", units="USD")[0] + ) + battery_capex = model.prob.get_val("battery.CapEx", units="USD") + re_capex = wind_pv_capex + battery_capex + assert pytest.approx(re_capex, rel=1e-6) == 1.75469962e09 + + with subtests.test("Check HOPP OpEx"): + wind_pv_opex = ( + model.prob.get_val("wind.OpEx", units="USD/yr")[0] + + model.prob.get_val("solar.OpEx", units="USD/yr")[0] + ) + battery_opex = model.prob.get_val("battery.OpEx", units="USD/year") + re_opex = wind_pv_opex + battery_opex + assert pytest.approx(re_opex, rel=1e-6) == 32953490.4 + + with subtests.test("Check electrolyzer CapEx"): + assert ( + pytest.approx(model.prob.get_val("electrolyzer.CapEx", units="USD"), rel=1e-6) + == 6.00412524e08 + ) + + with subtests.test("Check electrolyzer OpEx"): + assert ( + pytest.approx(model.prob.get_val("electrolyzer.OpEx", units="USD/year"), rel=1e-6) + == 14703155.39207595 + ) + + with subtests.test("Check H2 storage CapEx"): + assert ( + pytest.approx(model.prob.get_val("h2_storage.CapEx", units="USD"), rel=1e-6) + == 64553014.22218219 + ) + + with subtests.test("Check H2 storage OpEx"): + assert ( + pytest.approx(model.prob.get_val("h2_storage.OpEx", units="USD/year"), rel=1e-6) + == 2975616.89 + ) + + with subtests.test("Check ammonia CapEx"): + assert ( + pytest.approx(model.prob.get_val("ammonia.CapEx", units="USD"), rel=1e-6) + == 1.15173753e09 + ) + + with subtests.test("Check ammonia OpEx"): + assert ( + pytest.approx(model.prob.get_val("ammonia.OpEx", units="USD/year")[0], rel=1e-4) + == 25414748.989416014 + ) + + with subtests.test("Check ammonia production"): + assert ( + pytest.approx( + model.prob.get_val("ammonia.annual_ammonia_produced", units="t/yr").mean(), rel=1e-4 + ) + == 406333.161 + ) + + with subtests.test("Check total adjusted CapEx"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_nh3.total_capex_adjusted", units="USD")[0], + rel=1e-6, + ) + == 3728034379.0699997 + ) + + with subtests.test("Check total adjusted OpEx"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_nh3.total_opex_adjusted", units="USD/year")[0], + rel=1e-6, + ) + == 79257312.42365658 + ) + + with subtests.test("Check LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_h2.LCOH", units="USD/kg")[0], rel=1e-6 + ) + == 4.013427289493614 + ) + + with subtests.test("Check LCOA"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_nh3.LCOA", units="USD/kg")[0], rel=1e-6 + ) + == 1.1018637096646757 + ) + with subtests.test("Check LCON"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_n2.LCON", units="USD/t")[0], rel=1e-6 + ) + == 5.03140888 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("03_methanol/smr", None)]) +def test_smr_methanol_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "03_smr_methanol.yaml") + + # Run the model + model.run() + + model.post_process() + + # Check levelized cost of methanol (LCOM) + with subtests.test("Check SMR LCOM"): + assert ( + pytest.approx(model.prob.get_val("methanol.LCOM", units="USD/kg"), rel=1e-6) + == 0.22116813 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("03_methanol/co2_hydrogenation", None)] +) +def test_co2h_methanol_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "03_co2h_methanol.yaml") + + # Run the model + model.run() + + model.post_process() + + # Below is used as an integration test for the combiner + with subtests.test("combiner rated production"): + combined_rated_input = model.prob.get_val( + "wind.rated_electricity_production", units="MW" + ) + model.prob.get_val("solar.rated_electricity_production", units="MW") + assert ( + pytest.approx( + model.prob.get_val("combiner.rated_electricity_production", units="MW"), rel=1e-6 + ) + == combined_rated_input + ) + with subtests.test("combiner weighted CF"): + wind_weighted_cf = model.prob.get_val( + "wind.rated_electricity_production", units="MW" + ) * model.prob.get_val("wind.capacity_factor", units="unitless") + solar_weighted_cf = model.prob.get_val( + "solar.rated_electricity_production", units="MW" + ) * model.prob.get_val("solar.capacity_factor", units="unitless") + combined_cf = (wind_weighted_cf + solar_weighted_cf) / combined_rated_input + assert ( + pytest.approx( + model.prob.get_val("combiner.capacity_factor", units="unitless"), + rel=1e-6, + ) + == combined_cf + ) + + # Check levelized cost of methanol (LCOM) + with subtests.test("Check CO2 Hydrogenation LCOM"): + assert ( + pytest.approx(model.prob.get_val("methanol.LCOM", units="USD/kg")[0], rel=1e-6) + == 1.7516172 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("03_methanol/co2_hydrogenation_doc", None)] +) +def test_doc_methanol_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "03_co2h_methanol.yaml") + + # Run the model + model.run() + + model.post_process() + + # Check levelized cost of methanol (LCOM) + with subtests.test("Check CO2 Hydrogenation LCOM"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_default.LCOM", units="USD/kg")[0], + rel=1e-4, + ) + == 2.5252588 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("05_wind_h2_opt", None)]) +def test_wind_h2_opt_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Run without optimization + model_init = H2IntegrateModel(example_folder / "wind_plant_electrolyzer0.yaml") + + # Run the model + model_init.run() + + model_init.post_process() + + annual_h20 = model_init.prob.get_val("electrolyzer.annual_hydrogen_produced", units="kg/year")[ + 0 + ] + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "wind_plant_electrolyzer.yaml") + + # Run the model + model.run() + + with subtests.test("Check initial H2 production"): + assert annual_h20 < (60500000 - 10000) + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(kW*h)")[0], + rel=1e-3, + ) + == 0.059096 + ) + + with subtests.test("Check electrolyzer size"): + assert ( + pytest.approx( + model.prob.get_val("electrolyzer.electrolyzer_size_mw", units="MW")[0], + rel=1e-3, + ) + == 320.0 + ) + # Read the resulting SQL file and compare initial and final LCOH values + + sql_path = None + for root, _dirs, files in os.walk(example_folder): + for file in files: + if file == "wind_h2_opt.sql": + sql_path = Path(root) / file + break + if sql_path: + break + assert ( + sql_path is not None + ), "wind_h2_opt.sql file not found in current working directory or subdirectories." + + cr = om.CaseReader(str(sql_path)) + cases = list(cr.get_cases()) + assert len(cases) > 1, "Not enough cases recorded in SQL file." + + # Get initial and final LCOH values + + initial_lcoh = cases[0].outputs["finance_subgroup_hydrogen.LCOH"][0] + final_lcoh = cases[-1].outputs["finance_subgroup_hydrogen.LCOH"][0] + + with subtests.test("Check LCOH changed"): + assert final_lcoh != initial_lcoh + + with subtests.test("Check total adjusted CapEx"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.total_capex_adjusted", units="USD")[ + 0 + ], + rel=1e-3, + ) + == 978075832.46 + ) + with subtests.test("Check total adjusted OpEx"): + assert ( + pytest.approx( + model.prob.get_val( + "finance_subgroup_hydrogen.total_opex_adjusted", units="USD/year" + )[0], + rel=1e-3, + ) + == 27646299.56 + ) + + with subtests.test("Check minimum total hydrogen produced"): + assert ( + pytest.approx( + model.prob.get_val("electrolyzer.annual_hydrogen_produced", units="kg/year")[0], + abs=15000, + ) + == 29028700 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("06_custom_tech", None)]) +def test_paper_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "wind_plant_paper.yaml") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check LCOP"): + assert ( + pytest.approx(model.prob.get_val("paper_mill.LCOP", units="USD/t"), rel=1e-3) + == 51.733275 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("09_co2/direct_ocean_capture", None)] +) +def test_wind_wave_doc_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "offshore_plant_doc.yaml") + # Set battery demand profile + demand_profile = np.ones(8760) * 340.0 + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check LCOC"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_co2.LCOC", units="USD/kg")[0], rel=1e-3 + ) + == 1.803343170781246 + ) + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-3, + ) + == 243.723825 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("17_splitter_wind_doc_h2", None)] +) +def test_splitter_wind_doc_h2_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "offshore_plant_splitter_doc_h2.yaml") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check Electrical AEP"): + electrical_aep = ( + model.prob.get_val( + "finance_subgroup_electricity.rated_electricity_production", + units="MW", + ) + * model.prob.get_val( + "finance_subgroup_electricity.capacity_factor", + units="unitless", + ).mean() + * 8760 + ) + + assert pytest.approx(electrical_aep[0], rel=1e-3) == 511267.03627 + + with subtests.test("Check LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0], + rel=1e-3, + ) + == 9.8059083 + ) + + with subtests.test("Check LCOC"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_co2.LCOC", units="USD/kg")[0], rel=1e-3 + ) + == 13.655268 + ) + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-3, + ) + == 132.395036462 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("07_run_of_river_plant", None)] +) +def test_hydro_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "07_run_of_river.yaml") + + # Run the model + model.run() + + model.post_process() + + print(model.prob.get_val("finance_subgroup_default.LCOE", units="USD/(kW*h)")) + + # Subtests for checking specific values + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_default.LCOE", units="USD/(kW*h)")[0], + rel=1e-3, + ) + == 0.17653979 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("11_hybrid_energy_plant", None)] +) +def test_hybrid_energy_plant_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "wind_pv_battery.yaml") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check LCOE"): + assert model.prob.get_val("finance_subgroup_default.LCOE", units="USD/(MW*h)")[0] < 83.2123 + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("13_dispatch_for_electrolyzer", None)] +) +def test_electrolyzer_demand(subtests, temp_copy_of_example): + from h2integrate.core.inputs.validation import load_tech_yaml, load_plant_yaml, load_driver_yaml + + example_folder = temp_copy_of_example + + tech_config = load_tech_yaml(example_folder / "tech_config.yaml") + plant_config = load_plant_yaml(example_folder / "plant_config.yaml") + driver_config = load_driver_yaml(example_folder / "driver_config.yaml") + + # modify all the output folders to be full filepaths + driver_config["general"]["folder_output"] = str(Path(example_folder / "outputs").absolute()) + tech_config["technologies"]["distributed_wind_plant"]["model_inputs"]["performance_parameters"][ + "cache_dir" + ] = example_folder / "cache" + + input_config = { + "plant_config": plant_config, + "technology_config": tech_config, + "driver_config": driver_config, + } + + h2i = H2IntegrateModel(input_config) + + h2i.setup() + + electrolyzer_capacity_MW = 60 + + # Set the battery demand as 10% of the electrolyzer capacity + h2i.prob.set_val("battery.electricity_demand", 0.1 * electrolyzer_capacity_MW, units="MW") + h2i.prob.set_val("elec_load_demand.electricity_demand", electrolyzer_capacity_MW, units="MW") + + h2i.run() + + lcoe_gen = h2i.prob.get_val("finance_subgroup_generated_electricity.LCOE", units="USD/(MW*h)")[ + 0 + ] + lcoe_sys = h2i.prob.get_val("finance_subgroup_electrical_system.LCOE", units="USD/(MW*h)")[0] + lcoe_load = h2i.prob.get_val("finance_subgroup_electrical_load.LCOE", units="USD/(MW*h)")[0] + lcoh = h2i.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0] + + with subtests.test("LCOE of electricity generated"): + assert pytest.approx(217.53810477, rel=1e-6) == lcoe_gen + + with subtests.test("LCOE of electrical load (battery for min power)"): + assert pytest.approx(236.15820250, rel=1e-6) == lcoe_load + + with subtests.test("LCOE of electrical system (battery for min power)"): + assert pytest.approx(235.43108263, rel=1e-6) == lcoe_sys + + with subtests.test("LCOH (battery for min power)"): + assert pytest.approx(16.02862959, rel=1e-3) == lcoh + + with subtests.test("Electrolyzer capacity factor (Year 0) (battery for min power)"): + elec_cf_yr0 = h2i.prob.get_val("electrolyzer.capacity_factor", units="percent")[0] + assert pytest.approx(25.43832863, rel=1e-3) == elec_cf_yr0 + + with subtests.test("Electrical load capacity factor (battery for min power)"): + load_cf = h2i.prob.get_val("elec_load_demand.capacity_factor", units="percent")[0] + assert pytest.approx(24.29709189, rel=1e-6) == load_cf + + with subtests.test("Electricity to electrolyzer (battery for min power)"): + electricity_to_electrolyzer = h2i.prob.get_val("electrolyzer.electricity_in", "MW").sum() + assert pytest.approx(127705.51498100, rel=1e-6) == electricity_to_electrolyzer + # Re-run where we set the battery demand equal to the electrolyzer capacity + + h2i.prob.set_val("battery.electricity_demand", electrolyzer_capacity_MW, units="MW") + h2i.prob.set_val("elec_load_demand.electricity_demand", electrolyzer_capacity_MW, units="MW") + + h2i.run() + + lcoe_sys = h2i.prob.get_val("finance_subgroup_electrical_system.LCOE", units="USD/(MW*h)")[0] + lcoe_load = h2i.prob.get_val("finance_subgroup_electrical_load.LCOE", units="USD/(MW*h)")[0] + lcoh = h2i.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0] + + with subtests.test("LCOE of electrical load (battery for full power)"): + assert pytest.approx(235.46701455, rel=1e-6) == lcoe_load + + with subtests.test("LCOE of electrical system (battery for full power)"): + assert pytest.approx(235.40978870, rel=1e-6) == lcoe_sys + + with subtests.test("LCOH (battery for full power)"): + assert pytest.approx(17.21768237, rel=1e-6) == lcoh + + with subtests.test("Electrolyzer capacity factor (Year 0) (battery for full power)"): + elec_cf_yr0 = h2i.prob.get_val("electrolyzer.capacity_factor", units="percent")[0] + assert pytest.approx(24.96971302, rel=1e-6) == elec_cf_yr0 + + with subtests.test("Electrical load capacity factor (battery for full power)"): + load_cf = h2i.prob.get_val("elec_load_demand.capacity_factor", units="percent")[0] + assert pytest.approx(24.36841338, rel=1e-6) == load_cf + + with subtests.test("Electricity to electrolyzer (battery for full power)"): + electricity_to_electrolyzer = h2i.prob.get_val("electrolyzer.electricity_in", "MW").sum() + assert pytest.approx(128080.38070512, rel=1e-6) == electricity_to_electrolyzer + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("14_wind_hydrogen_dispatch", None)] +) +def test_hydrogen_dispatch_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "inputs" / "h2i_wind_to_h2_storage.yaml") + + model.run() + + model.post_process() + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-5, + ) + == 59.0962072084844 + ) + + with subtests.test("Check all h2 total_hydrogen_produced"): + all_h2_annual_prod = ( + model.prob.get_val( + "finance_subgroup_all_hydrogen.rated_hydrogen_production", units="kg/h" + )[0] + * model.prob.get_val( + "finance_subgroup_all_hydrogen.capacity_factor", units="unitless" + ).mean() + * 8760 + ) + assert ( + pytest.approx( + all_h2_annual_prod, + rel=1e-5, + ) + == model.prob.get_val("electrolyzer.annual_hydrogen_produced", units="kg/year")[0] + ) + + with subtests.test("Check total_hydrogen_produced"): + assert ( + pytest.approx( + model.prob.get_val("electrolyzer.total_hydrogen_produced", units="kg")[0], + rel=1e-5, + ) + == 61656526.36295184 + ) + + with subtests.test("Check annual hydrogen production"): + assert ( + pytest.approx( + model.prob.get_val("electrolyzer.annual_hydrogen_produced", units="kg/year")[0], + rel=1e-5, + ) + == 58458965.601815335 + ) + + with subtests.test("Check all h2 LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_all_hydrogen.LCOH", units="USD/kg")[0], + rel=1e-5, + ) + == 5.647544985152393 + ) + + with subtests.test("Check dispatched h2 LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_dispatched_hydrogen.LCOH", units="USD/kg")[0], + rel=1e-5, + ) + == 7.564000289456695 + ) + with subtests.test("Check LCOO"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_oxygen.LCOO", units="USD/kg")[0], + rel=1e-5, + ) + == 0.666523050 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("09_co2/ocean_alkalinity_enhancement", None)] +) +def test_wind_wave_oae_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "offshore_plant_oae.yaml") + + # Set battery demand profile + demand_profile = np.ones(8760) * 330.0 + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check LCOC"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_co2.LCOC", units="USD/kg")[0], rel=1e-3 + ) + == 41.156 + ) + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-3, + ) + == 263.130 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", + [("09_co2/ocean_alkalinity_enhancement_financials", None)], +) +def test_wind_wave_oae_example_with_finance(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "offshore_plant_oae.yaml") + + # Run the model + model.run() + + model.post_process() + + # Subtests for checking specific values + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-3, + ) + == 92.269663 + ) + + with subtests.test("Check Carbon Credit"): + assert ( + pytest.approx(model.prob.get_val("oae.carbon_credit_value", units="USD/t")[0], rel=1e-3) + == 1026.4684117 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("16_natural_gas", "11_hybrid_energy_plant")] +) +def test_natural_gas_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "natgas.yaml") + + # Run the model + + model.run() + + model.post_process() + solar_aep = sum(model.prob.get_val("solar.electricity_out", units="kW")) + solar_bat_out_total = sum( + model.prob.get_val("electrical_load_demand.electricity_out", units="kW") + ) + solar_curtailed_total = sum( + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="kW") + ) + + renewable_subgroup_total_electricity = ( + model.prob.get_val("finance_subgroup_renewables.rated_electricity_production", units="kW")[ + 0 + ] + * model.prob.get_val("finance_subgroup_renewables.capacity_factor", units="unitless").mean() + * 8760 + ) + electricity_subgroup_total_electricity = ( + model.prob.get_val("finance_subgroup_electricity.rated_electricity_production", units="kW")[ + 0 + ] + * model.prob.get_val( + "finance_subgroup_electricity.capacity_factor", units="unitless" + ).mean() + * 8760 + ) + natural_gas_subgroup_total_electricity = ( + model.prob.get_val("finance_subgroup_natural_gas.rated_electricity_production", units="kW")[ + 0 + ] + * model.prob.get_val( + "finance_subgroup_natural_gas.capacity_factor", units="unitless" + ).mean() + * 8760 + ) + + # NOTE: battery output power is not included in any of the financials + + pre_ng_missed_load = model.prob.get_val( + "electrical_load_demand.unmet_electricity_demand_out", units="kW" + ) + ng_electricity_demand = model.prob.get_val("natural_gas_plant.electricity_demand", units="kW") + ng_electricity_production = model.prob.get_val("natural_gas_plant.electricity_out", units="kW") + bat_init_charge = 200000.0 * 0.1 # max capacity in kW and initial charge rate percentage + + with subtests.test( + "Check solar AEP is greater than battery output (solar oversized relative to demand" + ): + assert solar_aep > solar_bat_out_total + + with subtests.test( + "Check battery outputs against battery inputs (solar oversized relative to demand" + ): + assert ( + pytest.approx(solar_bat_out_total + solar_curtailed_total, abs=bat_init_charge) + == solar_aep + ) + + with subtests.test("Check solar AEP equals total electricity for renewables subgroup"): + assert pytest.approx(solar_aep, rel=1e-6) == renewable_subgroup_total_electricity + + with subtests.test("Check natural gas AEP equals total electricity for natural_gas subgroup"): + assert ( + pytest.approx(sum(ng_electricity_production), rel=1e-6) + == natural_gas_subgroup_total_electricity + ) + + with subtests.test( + "Check natural gas + solar AEP equals total electricity for electricity subgroup" + ): + assert ( + pytest.approx(electricity_subgroup_total_electricity, rel=1e-6) + == sum(ng_electricity_production) + solar_aep + ) + + with subtests.test("Check missed load is natural gas plant electricity demand"): + assert pytest.approx(ng_electricity_demand, rel=1e-6) == pre_ng_missed_load + + with subtests.test("Check natural_gas_plant electricity out equals demand"): + assert pytest.approx(ng_electricity_demand, rel=1e-6) == ng_electricity_production + + # Subtests for checking specific values + with subtests.test("Check Natural Gas CapEx"): + capex = model.prob.get_val("natural_gas_plant.CapEx", units="USD")[0] + assert pytest.approx(capex, rel=1e-6) == 1e8 + + with subtests.test("Check Natural Gas OpEx"): + opex = model.prob.get_val("natural_gas_plant.OpEx", units="USD/year")[0] + assert pytest.approx(opex, rel=1e-6) == 2243167.24525 + + with subtests.test("Check total electricity produced"): + assert pytest.approx(natural_gas_subgroup_total_electricity, rel=1e-6) == 497266898.10354495 + + with subtests.test("Check opex adjusted ng_feedstock"): + opex_ng_feedstock = model.prob.get_val( + "finance_subgroup_natural_gas.varopex_adjusted_ng_feedstock", + units="USD/year", + )[0] + assert pytest.approx(opex_ng_feedstock, rel=1e-6) == 15281860.770986987 + + with subtests.test("Check capex adjusted natural_gas_plant"): + capex_ng_plant = model.prob.get_val( + "finance_subgroup_natural_gas.capex_adjusted_natural_gas_plant", units="USD" + )[0] + assert pytest.approx(capex_ng_plant, rel=1e-6) == 97560975.60975611 + + with subtests.test("Check opex adjusted natural_gas_plant"): + opex_ng_plant = model.prob.get_val( + "finance_subgroup_natural_gas.opex_adjusted_natural_gas_plant", units="USD/year" + )[0] + assert pytest.approx(opex_ng_plant, rel=1e-6) == 2188455.8490330363 + + with subtests.test("Check total adjusted CapEx for natural gas subgroup"): + total_capex = model.prob.get_val( + "finance_subgroup_natural_gas.total_capex_adjusted", units="USD" + )[0] + assert pytest.approx(total_capex, rel=1e-6) == 97658536.58536586 + + with subtests.test("Check LCOE (natural gas plant)"): + lcoe_ng = model.prob.get_val("finance_subgroup_natural_gas.LCOE", units="USD/(kW*h)")[0] + assert pytest.approx(lcoe_ng, rel=1e-6) == 0.05811033466 + + with subtests.test("Check LCOE (renewables plant)"): + lcoe_re = model.prob.get_val("finance_subgroup_renewables.LCOE", units="USD/(kW*h)")[0] + assert pytest.approx(lcoe_re, rel=1e-6) == 0.07102560120 + + with subtests.test("Check LCOE (renewables and natural gas plant)"): + lcoe_tot = model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(kW*h)")[0] + assert pytest.approx(lcoe_tot, rel=1e-6) == 0.063997927290 + + # Test feedstock-specific values + with subtests.test("Check feedstock output"): + ng_output = model.prob.get_val("ng_feedstock_source.natural_gas_out", units="MMBtu/h") + # Should be rated capacity (750 MMBtu/h) for all timesteps + assert all(ng_output == 750.0) + + with subtests.test("Check feedstock consumption"): + ng_consumed = model.prob.get_val("ng_feedstock.natural_gas_consumed", units="MMBtu/h") + # Total consumption should match what the natural gas plant uses + expected_consumption = ( + model.prob.get_val("natural_gas_plant.electricity_out", units="MW") * 7.5 + ) # Convert MWh to MMBtu using heat rate + assert pytest.approx(ng_consumed.sum(), rel=1e-3) == expected_consumption.sum() + + with subtests.test("Check feedstock CapEx"): + ng_capex = model.prob.get_val("ng_feedstock.CapEx", units="USD")[0] + assert pytest.approx(ng_capex, rel=1e-6) == 100000.0 # start_up_cost + + with subtests.test("Check feedstock OpEx"): + ng_opex = model.prob.get_val("ng_feedstock.VarOpEx", units="USD/year")[0] + # OpEx should be annual_cost (0) + price * consumption + ng_consumed = model.prob.get_val("ng_feedstock.natural_gas_consumed", units="MMBtu/h") + expected_opex = 4.2 * ng_consumed.sum() # price = 4.2 $/MMBtu + assert pytest.approx(ng_opex, rel=1e-6) == expected_opex + + with subtests.test("Check feedstock capacity factor"): + ng_cf = model.prob.get_val("ng_feedstock.capacity_factor", units="unitless").mean() + assert pytest.approx(ng_cf, rel=1e-6) == 0.5676562763739097 + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", + [("15_wind_solar_electrolyzer", "11_hybrid_energy_plant/")], +) +def test_wind_solar_electrolyzer_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "15_wind_solar_electrolyzer.yaml") + model.run() + + solar_fpath = model.model.get_val("solar_site.solar_resource.solar_resource_data")["filepath"] + wind_fpath = model.model.get_val("wind_site.wind_resource.wind_resource_data")["filepath"] + + with subtests.test("Wind resource file"): + assert Path(wind_fpath).name == "35.2018863_-101.945027_2012_wtk_v2_60min_utc_tz.csv" + + with subtests.test("Solar resource file"): + assert Path(solar_fpath).name == "30.6617_-101.7096_psmv3_60_2013.csv" + model.post_process() + + wind_aep = sum(model.prob.get_val("wind.electricity_out", units="kW")) + solar_aep = sum(model.prob.get_val("solar.electricity_out", units="kW")) + total_aep = model.prob.get_val("combiner.electricity_out", units="kW").sum() + + with subtests.test("Check total energy production"): + assert pytest.approx(wind_aep + solar_aep, rel=1e-6) == total_aep + + with subtests.test("Check LCOE"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0], + rel=1e-5, + ) + == 53.9306558 + ) + + with subtests.test("Check LCOH"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_hydrogen.LCOH", units="USD/kg")[0], + rel=1e-5, + ) + == 5.3063358423 + ) + + wind_generation = model.prob.get_val("wind.electricity_out", units="kW") + solar_generation = model.prob.get_val("solar.electricity_out", units="kW") + total_generation = model.prob.get_val("combiner.electricity_out", units="kW") + total_energy_to_electrolyzer = model.prob.get_val("electrolyzer.electricity_in", units="kW") + with subtests.test("Check combiner output"): + assert ( + pytest.approx(wind_generation.sum() + solar_generation.sum(), rel=1e-5) + == total_generation.sum() + ) + with subtests.test("Check electrolyzer input power"): + assert pytest.approx(total_generation.sum(), rel=1e-5) == total_energy_to_electrolyzer.sum() + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("10_electrolyzer_om", None)]) +def test_electrolyzer_om_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "electrolyzer_om.yaml") + + model.run() + + lcoe = model.prob.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0] + lcoh_with_lcoh_finance = model.prob.get_val( + "finance_subgroup_hydrogen.LCOH_lcoh_financials", units="USD/kg" + )[0] + lcoh_with_lcoe_finance = model.prob.get_val( + "finance_subgroup_hydrogen.LCOH_lcoe_financials", units="USD/kg" + )[0] + with subtests.test("Check LCOE"): + assert pytest.approx(lcoe, rel=1e-4) == 39.98869 + with subtests.test("Check LCOH with lcoh_financials"): + assert pytest.approx(lcoh_with_lcoh_finance, rel=1e-4) == 16.9204156301 + with subtests.test("Check LCOH with lcoe_financials"): + assert pytest.approx(lcoh_with_lcoe_finance, rel=1e-4) == 10.3360027653 + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("08_wind_electrolyzer", None)]) +def test_wombat_electrolyzer_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "wind_plant_electrolyzer.yaml") + + model.run() + + lcoe_with_profast_model = model.prob.get_val( + "finance_subgroup_electricity_profast.LCOE", units="USD/(MW*h)" + )[0] + lcoe_with_custom_model = model.prob.get_val( + "finance_subgroup_electricity_custom.LCOE", units="USD/(MW*h)" + )[0] + + lcoh_with_custom_model = model.prob.get_val( + "finance_subgroup_hydrogen.LCOH_produced_custom_model", units="USD/kg" + )[0] + lcoh_with_profast_model = model.prob.get_val( + "finance_subgroup_hydrogen.LCOH_produced_profast_model", units="USD/kg" + )[0] + + with subtests.test("Check LCOH from custom model"): + assert pytest.approx(lcoh_with_custom_model, rel=1e-5) == 4.1783979573 + with subtests.test("Check LCOH from ProFAST model"): + assert pytest.approx(lcoh_with_profast_model, rel=1e-5) == 5.3086307305 + with subtests.test("Check LCOE from custom model"): + assert pytest.approx(lcoe_with_custom_model, rel=1e-5) == 51.17615298 + with subtests.test("Check LCOE from ProFAST model"): + assert pytest.approx(lcoe_with_profast_model, rel=1e-5) == 59.0962084 + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("18_pyomo_heuristic_dispatch", None)] +) +def test_pyomo_heuristic_dispatch_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "pyomo_heuristic_dispatch.yaml") + + demand_profile = np.ones(8760) * 50.0 + + # TODO: Update with demand module once it is developed + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + + # Run the model + model.run() + + model.post_process() + + # Test battery storage functionality + # SOC should stay within configured bounds (10% to 90%) + # Due to pysam simulation, bounds may not be fully respected, + # but should not exceed the upper bound more than 4% SOC + # and the lower bound more than 1% SOC + soc = model.prob.get_val("battery.SOC", units="percent") + with subtests.test("Check battery SOC lower bound"): + assert all(soc >= 9.0) + with subtests.test("Check battery SOC upper bound"): + assert all(soc <= 94.0) + + with subtests.test("Check wind generation out of the wind plant"): + # Wind should generate some electricity + wind_electricity = model.prob.get_val("wind.electricity_out", units="MW") + assert wind_electricity.sum() > 0 + # Wind electricity should match battery input (direct connection) + with subtests.test("Check wind generation in to battery"): + battery_electricity_in = model.prob.get_val("battery.electricity_in", units="MW") + assert wind_electricity.sum() == pytest.approx(battery_electricity_in.sum(), rel=1e-6) + + with subtests.test("Check demand satisfaction"): + electricity_out = model.prob.get_val("electrical_load_demand.electricity_out", units="MW") + # Battery output should try to meet the 50 MW constant demand + # Average output should be close to demand when there's sufficient generation + assert electricity_out.mean() >= 45 # MW + + # Subtest for LCOE + with subtests.test("Check all LCOE value"): + lcoe = model.prob.get_val("finance_subgroup_all_electricity.LCOE", units="USD/(kW*h)")[0] + assert lcoe == pytest.approx(0.08157197567200995, rel=1e-6) + + with subtests.test("Check dispatched LCOE value"): + lcoe = model.prob.get_val( + "finance_subgroup_dispatched_electricity.LCOE", units="USD/(kW*h)" + )[0] + assert lcoe == pytest.approx(0.5975902853904799, rel=1e-6) + + # Subtest for total electricity produced + with subtests.test("Check total electricity produced"): + total_electricity = ( + model.prob.get_val( + name="finance_subgroup_all_electricity.rated_electricity_production", + units="MW", + )[0] + * model.prob.get_val( + name="finance_subgroup_all_electricity.capacity_factor", + units="unitless", + ).mean() + * 8760 + ) + assert total_electricity == pytest.approx(3125443.1089529935, rel=1e-6) + + # Subtest for electricity unused_commodity + with subtests.test("Check electricity unused commodity"): + electricity_unused_commodity = np.linalg.norm( + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="MW") + ) + assert electricity_unused_commodity == pytest.approx(36590.067573337095, rel=1e-6) + + # Subtest for unmet demand + with subtests.test("Check electricity unmet demand"): + electricity_unmet_demand = np.linalg.norm( + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="MW") + ) + assert electricity_unmet_demand == pytest.approx(711.1997294551337, rel=1e-6) + + # Check that incorrect and no tech name provided will be replaced and validate + model_config = load_yaml(example_folder / "pyomo_heuristic_dispatch.yaml") + tech = load_yaml(example_folder / "tech_config.yaml") + with subtests.test("Ensure no-tech name validates"): + tech["technologies"]["battery"]["model_inputs"]["control_parameters"] = None + model_config["technology_config"] = tech + model = H2IntegrateModel(model_config) + + with subtests.test("Ensure incorrect name is corrected"): + tech["technologies"]["battery"]["model_inputs"]["control_parameters"] = { + "tech_name": "goose" + } + model_config["technology_config"] = tech + model = H2IntegrateModel(model_config) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("19_simple_dispatch", None)]) +def test_simple_dispatch_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "wind_battery_dispatch.yaml") + + # Run the model + model.run() + + model.post_process() + + wind_aep = sum(model.prob.get_val("wind.electricity_out", units="kW")) + aep_for_finance = ( + model.prob.get_val("finance_subgroup_electricity.rated_electricity_production", units="kW")[ + 0 + ] + * model.prob.get_val( + "finance_subgroup_electricity.capacity_factor", units="unitless" + ).mean() + * 8760 + ) + battery_init_energy = 30000.0 * 0.25 # max capacity in kW and initial charge rate percentage + + with subtests.test("Check electricity is not double counted"): + assert aep_for_finance <= wind_aep + battery_init_energy + + # Test battery storage functionality + with subtests.test("Check battery SOC bounds"): + soc = model.prob.get_val("battery.SOC", units="unitless") + # SOC should stay within configured bounds (10% to 100%) + assert all(soc >= 0.1) + assert all(soc <= 1.0) + + with subtests.test("Check wind generation"): + # Wind should generate some electricity + wind_electricity = model.prob.get_val("wind.electricity_out", units="kW") + assert wind_electricity.sum() > 0 + # Wind electricity should match battery input (direct connection) + battery_electricity_in = model.prob.get_val("battery.electricity_in", units="kW") + assert pytest.approx(wind_electricity.sum(), rel=1e-6) == battery_electricity_in.sum() + + with subtests.test("Check demand satisfaction"): + electricity_out = model.prob.get_val("electrical_load_demand.electricity_out", units="MW") + # Battery output should try to meet the 5 MW constant demand + # Average output should be close to demand when there's sufficient generation + assert electricity_out.mean() > 4.20 # MW + + # Subtest for LCOE + with subtests.test("Check LCOE value"): + lcoe = model.prob.get_val( + "finance_subgroup_electricity.LCOE_all_electricity_profast", units="USD/(kW*h)" + )[0] + assert pytest.approx(lcoe, rel=1e-6) == 0.07801723344476236 + + # Subtest for NPV + with subtests.test("Check NPV value (numpy financial)"): + npv = model.prob.get_val( + "finance_subgroup_electricity.NPV_electricity_all_electricity_npv", units="USD" + )[0] + assert pytest.approx(npv, rel=1e-6) == 3791194.71 + + # Subtest for ProFAST NPV + with subtests.test("Check NPV value (profast)"): + npv = model.prob.get_val( + "finance_subgroup_electricity.NPV_electricity_all_electricity_profast_npv", + units="USD", + )[0] + assert pytest.approx(npv, rel=1e-6) == 7518969.18 + + # Subtest for total electricity produced + with subtests.test("Check total electricity produced"): + total_electricity = ( + model.prob.get_val( + "finance_subgroup_electricity.rated_electricity_production", units="kW" + )[0] + * model.prob.get_val("finance_subgroup_electricity.capacity_factor").mean() + * 8760 + ) + assert pytest.approx(total_electricity, rel=1e-6) == 62797265.9296355 + + # Subtest for electricity unused_commodity + with subtests.test("Check electricity unused commodity"): + electricity_unused_commodity = np.linalg.norm( + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="kW") + ) + assert pytest.approx(electricity_unused_commodity, rel=1e-6) == 412531.73840450746 + + # Subtest for unmet demand + with subtests.test("Check electricity unmet demand"): + electricity_unmet_demand = np.linalg.norm( + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="kW") + ) + assert pytest.approx(electricity_unmet_demand, rel=1e-6) == 165604.70758669 + + # Subtest for total electricity produced from wind, should be equal to total + # electricity produced from finance_subgroup_electricity + with subtests.test("Check total electricity produced from wind"): + wind_electricity_finance = ( + model.prob.get_val("finance_subgroup_wind.rated_electricity_production", units="kW")[0] + * model.prob.get_val("finance_subgroup_wind.capacity_factor", units="unitless").mean() + * 8760 + ) + assert pytest.approx(wind_electricity_finance, rel=1e-6) == total_electricity + + with subtests.test("Check total electricity produced from wind compared to wind aep"): + wind_electricity_performance = np.sum( + model.prob.get_val("wind.electricity_out", units="kW") + ) + assert pytest.approx(wind_electricity_performance, rel=1e-6) == wind_electricity_finance + + # Subtest for total electricity produced from battery, should be equal + # to sum of "battery.electricity_out" + with subtests.test("Check total electricity produced from battery"): + battery_electricity_finance = ( + model.prob.get_val( + "finance_subgroup_battery.rated_electricity_production", units="MW*h/year" + )[0] + * model.prob.get_val( + "finance_subgroup_battery.capacity_factor", units="unitless" + ).mean() + * 8760 + ) + battery_electricity_performance = ( + model.prob.get_val( + "electrical_load_demand.rated_electricity_production", units="MW*h/year" + )[0] + * model.prob.get_val("electrical_load_demand.capacity_factor", units="unitless").mean() + * 8760 + ) + assert ( + pytest.approx(battery_electricity_finance, rel=1e-6) == battery_electricity_performance + ) + + wind_lcoe = model.prob.get_val("finance_subgroup_wind.LCOE_wind_only", units="USD/(MW*h)")[0] + battery_lcoe = model.prob.get_val( + "finance_subgroup_battery.LCOE_battery_included", units="USD/(MW*h)" + )[0] + electricity_lcoe = model.prob.get_val( + "finance_subgroup_electricity.LCOE_all_electricity_profast", units="USD/(MW*h)" + )[0] + + with subtests.test("Check electricity LCOE is greater than wind LCOE"): + assert electricity_lcoe > wind_lcoe + + with subtests.test("Check battery LCOE is greater than electricity LCOE"): + assert battery_lcoe > electricity_lcoe + + with subtests.test("Check battery LCOE"): + assert pytest.approx(battery_lcoe, rel=1e-6) == 131.781997 + + with subtests.test("Check wind LCOE"): + assert pytest.approx(wind_lcoe, rel=1e-6) == 58.8248 + + with subtests.test("Check electricity LCOE"): + assert pytest.approx(electricity_lcoe, rel=1e-6) == 78.01723 + + +@pytest.mark.integration +@pytest.mark.skipif(importlib.util.find_spec("ard") is None, reason="ard is not installed") +@pytest.mark.parametrize("example_folder,resource_example_folder", [("29_wind_ard", None)]) +def test_windard_pv_battery_dispatch_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create the model + model = H2IntegrateModel(example_folder / "h2i_inputs/wind_pv_battery.yaml") + + # Run the model + model.run() + + # Post-process the results + model.post_process() + + with subtests.test("Check wind generation"): + # Wind should generate some electricity + wind_electricity = model.prob.get_val("wind.electricity_out", units="GW") + assert wind_electricity.sum() == pytest.approx(150.88490967164714, rel=1e-4) + + with subtests.test("Check solar generation"): + # Solar should generate some electricity + solar_electricity = model.prob.get_val("solar.electricity_out", units="GW") + assert solar_electricity.sum() == pytest.approx(44.22139046811775, rel=1e-4) + + with subtests.test("Check battery gets wind and solar output"): + # Wind plus solar electricity should match battery input (direct connection) + battery_electricity_in = model.prob.get_val("battery.electricity_in", units="GW") + assert wind_electricity.sum() + solar_electricity.sum() == pytest.approx( + battery_electricity_in.sum(), rel=1e-6 + ) + + with subtests.test("Check demand satisfaction"): + dispatched_electricity = model.prob.get_val( + "electrical_load_demand.electricity_out", units="MW" + ) + # Demand should be met for the last part of the year + assert np.allclose( + dispatched_electricity[8700:], + model.prob.get_val("battery.electricity_demand", units="MW")[8700:], + ) + + # Subtest for LCOE + with subtests.test("Check dispatched LCOE value"): + lcoe = model.prob.get_val( + "finance_subgroup_dispatched_electricity.LCOE", units="USD/(kW*h)" + )[0] + assert pytest.approx(lcoe, rel=1e-6) == 0.09289430342906849 + + with subtests.test("Check generation LCOE value (excludes battery)"): + lcoe = model.prob.get_val("finance_subgroup_produced_electricity.LCOE", units="USD/(kW*h)")[ + 0 + ] + assert pytest.approx(lcoe, rel=1e-6) == 0.07204429286793802 + + # Subtest for total electricity produced + with subtests.test("Check total electricity dispatched"): + total_electricity_year_one = ( + model.prob.get_val( + "finance_subgroup_dispatched_electricity.rated_electricity_production", + units="MW", + )[0] + * model.prob.get_val( + "finance_subgroup_dispatched_electricity.capacity_factor", + units="unitless", + )[0] + * 8760 + ) + assert total_electricity_year_one == pytest.approx(dispatched_electricity.sum()) + + # Subtest for electricity curtailed + with subtests.test("Check electricity curtailed"): + electricity_curtailed = model.prob.get_val( + "electrical_load_demand.unused_electricity_out", units="MW" + ).sum() + + assert electricity_curtailed == pytest.approx(20344.97639127703, rel=1e-6) + + # Subtest for missed load + with subtests.test("Check electricity missed load"): + electricity_missed_load = np.linalg.norm( + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="MW") + ) + assert electricity_missed_load == pytest.approx(1403.5372787817894) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("20_solar_electrolyzer_doe", None)] +) +def test_csvgen_design_of_experiments(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + with pytest.raises(UserWarning) as excinfo: + model = H2IntegrateModel(example_folder / "20_solar_electrolyzer_doe.yaml") + assert "There may be issues with the csv file csv_doe_cases.csv" in str(excinfo.value) + + from h2integrate.core.dict_utils import update_defaults + from h2integrate.core.file_utils import check_file_format_for_csv_generator + from h2integrate.core.inputs.validation import write_yaml, load_driver_yaml + + # load the driver config file + driver_config = load_driver_yaml("driver_config.yaml") + # specify the filepath to the csv file + csv_fpath = Path(driver_config["driver"]["design_of_experiments"]["filename"]).absolute() + # run the csv checker method, we want it to write the csv file to a new filepath so + # set overwrite_file=False + new_csv_filename = check_file_format_for_csv_generator( + csv_fpath, driver_config, check_only=False, overwrite_file=False + ) + + # update the csv filename in the driver config dictionary + updated_driver = update_defaults(driver_config["driver"], "filename", new_csv_filename.name) + driver_config["driver"].update(updated_driver) + + # save the updated driver to a new file + new_driver_fpath = example_folder / "driver_config_test.yaml" + new_toplevel_fpath = example_folder / "20_solar_electrolyzer_doe_test.yaml" + write_yaml(driver_config, new_driver_fpath) + + # update the driver config filename in the top-level config + main_config = load_yaml("20_solar_electrolyzer_doe.yaml") + main_config["driver_config"] = new_driver_fpath.name + + # save the updated top-level config file to a new file + write_yaml(main_config, new_toplevel_fpath) + + # Run the model + model = H2IntegrateModel(new_toplevel_fpath) + model.run() + + # summarize sql file + model.post_process(summarize_sql=True) + + with subtests.test("Check that sql file was summarized"): + assert model.recorder_path is not None + summarized_filepath = model.recorder_path.parent / f"{model.recorder_path.stem}.csv" + assert summarized_filepath.is_file() + with subtests.test("Check that sql summary file was written as expected"): + summary = pd.read_csv(summarized_filepath, index_col="Unnamed: 0") + assert len(summary) == 10 + d_var_cols = ["solar.system_capacity_DC (kW)", "electrolyzer.n_clusters (unitless)"] + assert summary.columns.to_list()[0] in d_var_cols + assert summary.columns.to_list()[1] in d_var_cols + assert "finance_subgroup_hydrogen.LCOH_optimistic (USD/kg)" in summary.columns.to_list() + # delete summary file + summarized_filepath.unlink() + + sql_fpath = example_folder / "ex_20_out" / "cases.sql" + cr = om.CaseReader(str(sql_fpath)) + cases = list(cr.get_cases()) + + with subtests.test("Check solar capacity in case 0"): + assert ( + pytest.approx(cases[0].get_val("solar.system_capacity_DC", units="MW"), rel=1e-6) + == 25.0 + ) + with subtests.test("Check solar capacity in case 9"): + assert ( + pytest.approx(cases[-1].get_val("solar.system_capacity_DC", units="MW"), rel=1e-6) + == 500.0 + ) + + with subtests.test("Check electrolyzer capacity in case 0"): + assert ( + pytest.approx( + cases[0].get_val("electrolyzer.electrolyzer_size_mw", units="MW"), rel=1e-6 + ) + == 10.0 * 5 + ) + + with subtests.test("Check electrolyzer capacity in case 9"): + assert ( + pytest.approx( + cases[-1].get_val("electrolyzer.electrolyzer_size_mw", units="MW"), rel=1e-6 + ) + == 10.0 * 10 + ) + + min_lcoh_val = 100000.0 + min_lcoh_case_num = 0 + for i, case in enumerate(cases): + lcoh = case.get_val("finance_subgroup_hydrogen.LCOH_optimistic", units="USD/kg")[0] + if lcoh < min_lcoh_val: + min_lcoh_val = np.min([lcoh, min_lcoh_val]) + min_lcoh_case_num = i + + with subtests.test("Min LCOH value"): + assert pytest.approx(min_lcoh_val, rel=1e-6) == 4.663014422338 + + with subtests.test("Min LCOH case number"): + assert min_lcoh_case_num == 6 + + with subtests.test("Min LCOH case LCOH value"): + assert ( + pytest.approx( + cases[min_lcoh_case_num].get_val( + "finance_subgroup_hydrogen.LCOH_optimistic", units="USD/kg" + ), + rel=1e-6, + ) + == min_lcoh_val + ) + + with subtests.test("Min LCOH case has lower LCOH than other cases"): + for i, case in enumerate(cases): + lcoh_case = case.get_val("finance_subgroup_hydrogen.LCOH_optimistic", units="USD/kg") + if i != min_lcoh_case_num: + assert lcoh_case > min_lcoh_val + + with subtests.test("Min LCOH solar capacity"): + assert ( + pytest.approx( + cases[min_lcoh_case_num].get_val("solar.system_capacity_DC", units="MW"), rel=1e-6 + ) + == 200.0 + ) + + with subtests.test("Min LCOH electrolyzer capacity"): + assert ( + pytest.approx( + cases[min_lcoh_case_num].get_val("electrolyzer.electrolyzer_size_mw", units="MW"), + rel=1e-6, + ) + == 100.0 + ) + + # remove files created + new_driver_fpath.unlink() + new_toplevel_fpath.unlink() + new_csv_filename.unlink() + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("22_site_doe", None)]) +def test_sweeping_solar_sites_doe(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create the model + model = H2IntegrateModel(example_folder / "22_solar_site_doe.yaml") + + # Run the model + model.run() + + # Specify the filepath to the sql file, the folder and filename are in the driver_config + sql_fpath = example_folder / "ex_22_out" / "cases.sql" + + # load the cases + cr = om.CaseReader(sql_fpath) + + cases = list(cr.get_cases()) + + res_df = pd.DataFrame() + for ci, case in enumerate(cases): + solar_resource_data = case.get_val("site.solar_resource.solar_resource_data") + lat_lon = ( + f"{case.get_val('site.latitude', units='deg')[0]} " + f"{case.get_val('site.longitude', units='deg')[0]}" + ) + solar_capacity = case.get_design_vars()["solar.system_capacity_DC"][0] + aep = case.get_val("solar.annual_electricity_produced", units="MW*h/yr")[0] + lcoe = case.get_val("finance_subgroup_electricity.LCOE_optimistic", units="USD/(MW*h)")[0] + + site_res = pd.DataFrame( + [aep, lcoe, solar_capacity], index=["AEP", "LCOE", "solar_capacity"], columns=[lat_lon] + ).T + res_df = pd.concat([site_res, res_df], axis=0) + + with subtests.test(f"Case {ci}: Solar resource latitude matches site latitude"): + assert ( + pytest.approx(case.get_val("site.latitude", units="deg"), abs=0.1) + == solar_resource_data["site_lat"] + ) + with subtests.test(f"Case {ci}: Solar resource longitude matches site longitude"): + assert ( + pytest.approx(case.get_val("site.longitude", units="deg"), abs=0.1) + == solar_resource_data["site_lon"] + ) + + locations = list(set(res_df.index.to_list())) + solar_sizes = list(set(res_df["solar_capacity"].to_list())) + + with subtests.test("Two solar sizes per site"): + assert len(solar_sizes) == 2 + with subtests.test("Two unique sites"): + assert len(locations) == 2 + + with subtests.test("Unique AEPs per case"): + assert len(list(set(res_df["AEP"].to_list()))) == len(res_df) + + with subtests.test("Unique LCOEs per case"): + assert len(list(set(res_df["LCOE"].to_list()))) == len(res_df) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", + [("23_solar_wind_ng_demand", "11_hybrid_energy_plant/")], +) +def test_ng_demand_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + generic_demand_fpath = example_folder / "solar_wind_ng_demand.yaml" + # Create a H2I model + h2i_generic = H2IntegrateModel(generic_demand_fpath) + h2i_generic.run() + + lcoe_renewables_generic = h2i_generic.prob.get_val( + "finance_subgroup_renewables.LCOE_profast_lco", units="USD/(MW*h)" + ) + npv_renewables_generic = h2i_generic.prob.get_val( + "finance_subgroup_renewables.NPV_electricity__profast_npv", units="MUSD" + ) + lcoe_ng_generic = h2i_generic.prob.get_val( + "finance_subgroup_natural_gas.LCOE", units="USD/(MW*h)" + ) + lcoe_electricity_generic = h2i_generic.prob.get_val( + "finance_subgroup_electricity.LCOE", units="USD/(MW*h)" + ) + + with subtests.test("Renewables LCOE with generic demand"): + assert pytest.approx(65.25367747, rel=1e-6) == lcoe_renewables_generic[0] + with subtests.test("Renewables NPV with generic demand"): + assert pytest.approx(-36.0408322, rel=1e-6) == npv_renewables_generic[0] + with subtests.test("Natural gas LCOE with generic demand"): + assert pytest.approx(60.30971126, rel=1e-6) == lcoe_ng_generic[0] + with subtests.test("Electricity LCOE with generic demand"): + assert pytest.approx(62.95948605, rel=1e-6) == lcoe_electricity_generic[0] + + # Run with the flexible load demand + flexible_demand_fpath = example_folder / "solar_wind_ng_flexible_demand.yaml" + h2i_flexible = H2IntegrateModel(flexible_demand_fpath) + h2i_flexible.run() + + lcoe_renewables_flexible = h2i_flexible.prob.get_val( + "finance_subgroup_renewables.LCOE_profast_lco", units="USD/(MW*h)" + ) + npv_renewables_flexible = h2i_flexible.prob.get_val( + "finance_subgroup_renewables.NPV_electricity__profast_npv", units="MUSD" + ) + lcoe_ng_flexible = h2i_flexible.prob.get_val( + "finance_subgroup_natural_gas.LCOE", units="USD/(MW*h)" + ) + lcoe_electricity_flexible = h2i_flexible.prob.get_val( + "finance_subgroup_electricity.LCOE", units="USD/(MW*h)" + ) + + with subtests.test("Renewables LCOE with flexible demand"): + assert pytest.approx(65.25367747, rel=1e-6) == lcoe_renewables_flexible[0] + with subtests.test("Renewables NPV with flexible demand"): + assert pytest.approx(-36.0408322, rel=1e-6) == npv_renewables_flexible[0] + with subtests.test("Natural gas LCOE with flexible demand"): + assert pytest.approx(115.92792486, rel=1e-6) == lcoe_ng_flexible[0] + with subtests.test("Electricity LCOE with flexible demand"): + assert pytest.approx(76.39162926, rel=1e-6) == lcoe_electricity_flexible[0] + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("26_floris", None)]) +def test_floris_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + driver_config = load_yaml(example_folder / "driver_config.yaml") + tech_config = load_yaml(example_folder / "tech_config.yaml") + plant_config = load_yaml(example_folder / "plant_config.yaml") + + h2i_config = { + "name": "H2Integrate_config", + "system_summary": "", + "driver_config": driver_config, + "technology_config": tech_config, + "plant_config": plant_config, + } + + # Create a H2I model + h2i = H2IntegrateModel(h2i_config) + + # Run the model + h2i.run() + + with subtests.test("Distributed LCOE"): + assert ( + pytest.approx( + h2i.prob.get_val("finance_subgroup_distributed.LCOE", units="USD/MW/h")[0], rel=1e-6 + ) + == 99.872209 + ) + with subtests.test("Utility LCOE"): + assert ( + pytest.approx( + h2i.prob.get_val("finance_subgroup_utility.LCOE", units="USD/MW/h")[0], rel=1e-6 + ) + == 54.2709437311 + ) + + with subtests.test("Total LCOE"): + assert ( + pytest.approx( + h2i.prob.get_val("finance_subgroup_total_electricity.LCOE", units="USD/MW/h")[0], + rel=1e-6, + ) + == 65.2444127137 + ) + + with subtests.test("Distributed wind plant capacity"): + assert ( + pytest.approx( + h2i.prob.get_val("distributed_wind_plant.rated_electricity_production", units="MW"), + rel=1e-6, + ) + == 66.0 + ) + + with subtests.test("Total distributed electricity production"): + assert ( + pytest.approx( + np.sum( + h2i.prob.get_val( + "distributed_wind_plant.total_electricity_produced", units="MW*h" + ) + ), + rel=1e-6, + ) + == 128948.21977 + ) + + with subtests.test("Total utility electricity production"): + assert ( + pytest.approx( + h2i.prob.get_val("utility_wind_plant.electricity_out", units="MW").sum(), rel=1e-6 + ) + == 406908.03381618496 + ) + + with subtests.test("Distributed wind capacity factor"): + assert ( + pytest.approx( + h2i.prob.get_val("distributed_wind_plant.capacity_factor", units="percent")[0], + rel=1e-6, + ) + == 22.30320668 + ) + + with subtests.test("Utility wind plant capacity"): + assert ( + pytest.approx( + h2i.prob.get_val("utility_wind_plant.rated_electricity_production", units="MW"), + rel=1e-6, + ) + == 120.0 + ) + + with subtests.test("Distributed wind site location"): + assert ( + pytest.approx(h2i.prob.get_val("distributed_wind_site.latitude", units="deg"), rel=1e-6) + == 44.04218 + ) + assert ( + pytest.approx( + h2i.prob.get_val("distributed_wind_site.longitude", units="deg"), rel=1e-6 + ) + == -95.19757 + ) + + with subtests.test("Distributed wind plant resource location"): + assert ( + pytest.approx( + h2i.prob.get_val("distributed_wind_plant.wind_resource_data")["site_lat"], abs=1e-2 + ) + == 44.04218 + ) + assert ( + pytest.approx( + h2i.prob.get_val("distributed_wind_plant.wind_resource_data")["site_lon"], abs=1e-2 + ) + == -95.19757 + ) + + with subtests.test("Utility wind site location"): + assert ( + pytest.approx(h2i.prob.get_val("utility_wind_site.latitude", units="deg"), rel=1e-6) + == 35.2018863 + ) + assert ( + pytest.approx(h2i.prob.get_val("utility_wind_site.longitude", units="deg"), rel=1e-6) + == -101.945027 + ) + + with subtests.test("Utility wind plant resource location"): + assert ( + pytest.approx( + h2i.prob.get_val("utility_wind_plant.wind_resource_data")["site_lat"], abs=1e-2 + ) + == 35.2018863 + ) + assert ( + pytest.approx( + h2i.prob.get_val("utility_wind_plant.wind_resource_data")["site_lon"], abs=1e-2 + ) + == -101.945027 + ) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("24_solar_battery_grid", "11_hybrid_energy_plant/")] +) +def test_24_solar_battery_grid_example(subtests, temp_copy_of_example): + # NOTE: would be good to compare LCOE against the same example without grid selling + # and see that LCOE reduces with grid selling + example_folder = temp_copy_of_example + + model = H2IntegrateModel(example_folder / "solar_battery_grid.yaml") + + model.run() + + model.post_process() + + energy_for_financials = ( + model.prob.get_val("finance_subgroup_renewables.rated_electricity_production", units="kW")[ + 0 + ] + * model.prob.get_val("finance_subgroup_renewables.capacity_factor", units="unitless").mean() + * 8760 + ) + + electricity_bought = sum(model.prob.get_val("grid_buy.electricity_out", units="kW")) + battery_missed_load = sum( + model.prob.get_val("electrical_load_demand.unmet_electricity_demand_out", units="kW") + ) + + battery_curtailed = sum( + model.prob.get_val("electrical_load_demand.unused_electricity_out", units="kW") + ) + electricity_sold = sum(model.prob.get_val("grid_sell.electricity_in", units="kW")) + + solar_aep = sum(model.prob.get_val("solar.electricity_out", units="kW")) + + with subtests.test("Behavior check battery missed load is electricity bought"): + assert pytest.approx(battery_missed_load, rel=1e-6) == electricity_bought + + with subtests.test("Behavior check battery curtailed energy is electricity sold"): + assert pytest.approx(battery_curtailed, rel=1e-6) == electricity_sold + + with subtests.test( + "Behavior check energy for financials; include solar aep and electricity bought" + ): + assert pytest.approx(energy_for_financials, rel=1e-6) == (solar_aep + electricity_bought) + + with subtests.test("Value check on LCOE"): + lcoe = model.prob.get_val("finance_subgroup_renewables.LCOE", units="USD/(MW*h)")[0] + assert pytest.approx(lcoe, rel=1e-4) == 91.7057887 + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("21_iron_examples/iron_mapping", None)] +) +@pytest.mark.skipif(importlib.util.find_spec("geopandas") is None, reason="`gis` not installed") +def test_iron_mapping_example(subtests, temp_copy_of_example): + import geopandas as gpd + import matplotlib + + from h2integrate.postprocess.mapping import ( + plot_geospatial_point_heat_map, + plot_straight_line_shipping_routes, + ) + + example_folder = temp_copy_of_example + + # Define filepaths + ex_dir = example_folder + ex_out_dir = ex_dir / "ex_out" + ore_prices_filepath = ex_dir / "example_ore_prices.csv" + shipping_coords_filepath = ROOT_DIR / "converters/iron/martin_transport/shipping_coords.csv" + shipping_prices_filepath = ex_dir / "example_shipping_prices.csv" + cases_csv_fpath = ex_out_dir / "cases.csv" + ex_png_fpath = ex_out_dir / "example_iron_map.png" + ex_png_fpath.unlink(missing_ok=True) + + # Plot LCOI results from cases.sql file, save sql data to csv + fig, ax, lcoi_layer_gdf = plot_geospatial_point_heat_map( + case_results_fpath=cases_csv_fpath, + metric_to_plot="finance_subgroup_sponge_iron.LCOS (USD/kg)", + map_preferences={ + "figsize": (10, 8), + "colorbar_label": "Levelized Cost of\nIron [$/kg]", + "colorbar_limits": (0.6, 1.0), + }, + ) + # Add a layer for example ore cost prices from select mines + fig, ax, ore_cost_layer_gdf = plot_geospatial_point_heat_map( + case_results_fpath=ore_prices_filepath, + metric_to_plot="ore_cost_per_kg", + map_preferences={ + "colormap": "Greens", + "marker": "o", + "colorbar_bbox_to_anchor": (0.025, 0.97, 1, 1), + "colorbar_label": "Levelized Cost of\nIron Ore Pellets\n[$/kg ore]", + "colorbar_limits": (0.11, 0.14), + }, + fig=fig, + ax=ax, + base_layer_gdf=lcoi_layer_gdf, + ) + # Add a layer for example waterway shipping cost from select mines to select ports + fig, ax, shipping_cost_layer_gdf = plot_geospatial_point_heat_map( + case_results_fpath=shipping_prices_filepath, + metric_to_plot="shipping_cost_per_kg", + map_preferences={ + "colormap": "Greys", + "marker": "d", + "markersize": 80, + "colorbar_bbox_to_anchor": (0.4, 0.97, 1, 1), + "colorbar_label": "Waterway Shipping Cost\n[$/kg ore]", + "colorbar_limits": (0.11, 0.14), + }, + fig=fig, + ax=ax, + base_layer_gdf=[lcoi_layer_gdf, ore_cost_layer_gdf], + ) + + # Define example water way shipping routes for plotting straight line transport + cleveland_route = [ + "Duluth", + "Keweenaw", + "Sault St Marie", + "De Tour", + "Lake Huron", + "Port Huron", + "Erie", + "Cleveland", + ] + buffalo_route = [ + "Duluth", + "Keweenaw", + "Sault St Marie", + "De Tour", + "Lake Huron", + "Port Huron", + "Erie", + "Cleveland", + "Buffalo", + ] + chicago_route = [ + "Duluth", + "Keweenaw", + "Sault St Marie", + "De Tour", + "Mackinaw", + "Manistique", + "Chicago", + ] + + # Add cleveland route as layer + fig, ax, transport_layer1_gdf = plot_straight_line_shipping_routes( + shipping_coords_fpath=shipping_coords_filepath, + shipping_route=cleveland_route, + map_preferences={}, + fig=fig, + ax=ax, + base_layer_gdf=[lcoi_layer_gdf, ore_cost_layer_gdf, shipping_cost_layer_gdf], + ) + # Add buffalo route as layer + fig, ax, transport_layer2_gdf = plot_straight_line_shipping_routes( + shipping_coords_fpath=shipping_coords_filepath, + shipping_route=buffalo_route, + map_preferences={}, + fig=fig, + ax=ax, + base_layer_gdf=[ + lcoi_layer_gdf, + ore_cost_layer_gdf, + shipping_cost_layer_gdf, + transport_layer1_gdf, + ], + ) + # Add chicago route as layer + fig, ax, transport_layer3_gdf = plot_straight_line_shipping_routes( + shipping_coords_fpath=shipping_coords_filepath, + shipping_route=chicago_route, + map_preferences={"figure_title": "Example H2 DRI Iron Costs"}, + fig=fig, + ax=ax, + base_layer_gdf=[ + lcoi_layer_gdf, + ore_cost_layer_gdf, + shipping_cost_layer_gdf, + transport_layer1_gdf, + transport_layer2_gdf, + ], + save_plot_fpath=ex_png_fpath, + ) + + with subtests.test("Type check on fig, ax, and lcoi_layer_gdf"): + assert isinstance( + fig, matplotlib.figure.Figure + ), f"Expected matplotlib.figure.Figure but got{type(fig)}" + assert isinstance( + ax, matplotlib.axes._axes.Axes + ), f"Expected matplotlib.axes._axes.Axes but got{type(ax)}" + assert isinstance( + lcoi_layer_gdf, gpd.geodataframe.GeoDataFrame + ), f"Expected gpd.geodataframe.GeoDataFrame but got{type(lcoi_layer_gdf)}" + assert isinstance( + lcoi_layer_gdf, gpd.geodataframe.GeoDataFrame + ), f"Expected gpd.geodataframe.GeoDataFrame but got{type(transport_layer1_gdf)}" + + with subtests.test("Check example_28_iron_map.png was saved"): + assert (ex_png_fpath).is_file(), "example_28_iron_map.png file not found" + + # Clean up any output files/dirs created + ex_png_fpath.unlink(missing_ok=True) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("04_geo_h2", None)]) +def test_natural_geoh2(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + h2i_nat = H2IntegrateModel(example_folder / "04_geo_h2_natural.yaml") + h2i_nat.run() + + with subtests.test("H2 Production"): + assert ( + pytest.approx( + np.mean(h2i_nat.model.get_val("geoh2_well_subsurface.hydrogen_out", units="kg/h")), + rel=1e-6, + ) + == 606.1508855232839 + ) + + with subtests.test("integrated LCOH"): + assert ( + pytest.approx( + h2i_nat.prob.get_val("finance_subgroup_h2.LCOH", units="USD/kg"), rel=1e-6 + ) + == 1.3089029 + ) + with subtests.test("subsurface Capex"): + assert ( + pytest.approx( + h2i_nat.model.get_val("geoh2_well_subsurface.CapEx", units="USD"), rel=1e-6 + ) + == 7667341.11417252 + ) + with subtests.test("subsurface fixed Opex"): + assert ( + pytest.approx( + h2i_nat.model.get_val("geoh2_well_subsurface.OpEx", units="USD/year"), rel=1e-6 + ) + == 215100.7857875 + ) + with subtests.test("subsurface variable Opex"): + assert ( + pytest.approx( + h2i_nat.model.get_val("geoh2_well_subsurface.VarOpEx", units="USD/year"), + rel=1e-6, + ) + == 0.0 + ) + with subtests.test("subsurface adjusted opex"): + adjusted_opex = h2i_nat.prob.get_val( + "finance_subgroup_h2.opex_adjusted_geoh2_well_subsurface", units="USD/year" + ) + assert pytest.approx(adjusted_opex, rel=1e-6) == 215100.7857875 + + with subtests.test("surface Capex"): + assert ( + pytest.approx(h2i_nat.model.get_val("geoh2_well_surface.CapEx", units="USD"), rel=1e-6) + == 1800711.83796 + ) + with subtests.test("surface fixed Opex"): + assert ( + pytest.approx( + h2i_nat.model.get_val("geoh2_well_surface.OpEx", units="USD/year"), rel=1e-6 + ) + == 4567464 + ) + with subtests.test("surface variable Opex"): + assert ( + pytest.approx( + h2i_nat.model.get_val("geoh2_well_surface.VarOpEx", units="USD/year"), rel=1e-6 + ) + == 989213.8787 + ) + with subtests.test("surface adjusted opex"): + surface_adjusted_opex = h2i_nat.prob.get_val( + "finance_subgroup_h2.opex_adjusted_geoh2_well_surface", units="USD/year" + ) + assert pytest.approx(surface_adjusted_opex, rel=1e-6) == 4798691.865 + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("04_geo_h2", None)]) +def test_stimulated_geoh2(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + h2i_stim = H2IntegrateModel(example_folder / "04_geo_h2_stimulated.yaml") + h2i_stim.run() + + h2_prod = h2i_stim.model.get_val("geoh2_well_subsurface.hydrogen_out", units="kg/h") + + with subtests.test("H2 Production"): + assert pytest.approx(np.mean(h2_prod), rel=1e-6) == 155.03934945719536 + + with subtests.test("integrate LCOH"): + lcoh = h2i_stim.prob.get_val("finance_subgroup_default.LCOH", units="USD/kg") + assert lcoh == pytest.approx( + 2.29337734, 1e-6 + ) # previous val from custom finance model was 1.74903827 + + # failure is expected because we are inflating using general inflation rather than CPI and CEPCI + with subtests.test("Capex"): + assert ( + pytest.approx( + h2i_stim.model.get_val("geoh2_well_subsurface.CapEx", units="USD"), rel=1e-6 + ) + == 19520122.88478073 + ) + with subtests.test("fixed Opex"): + assert ( + pytest.approx( + h2i_stim.model.get_val("geoh2_well_subsurface.OpEx", units="USD/year"), rel=1e-6 + ) + == 215100.7857875 + ) + with subtests.test("variable Opex"): + var_om_pr_h2 = h2i_stim.model.get_val( + "geoh2_well_subsurface.VarOpEx", units="USD/year" + ) / np.sum(h2_prod) + assert pytest.approx(var_om_pr_h2, rel=1e-6) == 0.32105362 + with subtests.test("adjusted Opex"): + adjusted_opex = h2i_stim.prob.get_val( + "finance_subgroup_default.opex_adjusted_geoh2_well_subsurface", units="USD/year" + ) + assert pytest.approx(adjusted_opex, rel=1e-6) == 215100.7857875 + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("21_iron_examples/iron_dri", None)] +) +def test_iron_dri_eaf_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + h2i = H2IntegrateModel(example_folder / "single_site_iron.yaml") + + h2i.run() + + with subtests.test("Value check on LCOI"): + lcoi = h2i.model.get_val("finance_subgroup_iron_ore.LCOI", units="USD/t")[0] + assert pytest.approx(lcoi, rel=1e-4) == 135.3741358811098 + + with subtests.test("Value check on LCOS"): + lcos = h2i.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/t")[0] + assert pytest.approx(lcos, rel=1e-4) == 359.670379351 + + with subtests.test("Value check on LCOS"): + lcos = h2i.model.get_val("finance_subgroup_steel.LCOS", units="USD/t")[0] + assert pytest.approx(lcos, rel=1e-4) == 531.5842266865 + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("21_iron_examples/iron_electrowinning", None)] +) +def test_iron_electrowinning_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + model = H2IntegrateModel(example_folder / "iron_electrowinning.yaml") + + with subtests.test("Value check on AHE"): + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = "ahe" + model.setup() + model.run() + lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] + assert pytest.approx(lcoi, rel=1e-4) == 2.187185703820872 + + with subtests.test("Value check on MSE"): + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = "mse" + model.technology_config["technologies"]["ewin_NaOH_feedstock"]["model_inputs"][ + "performance_parameters" + ]["rated_capacity"] = 0 + model.technology_config["technologies"]["ewin_CaCl2_feedstock"]["model_inputs"][ + "performance_parameters" + ]["rated_capacity"] = 179.0 + + model.setup() + model.run() + lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] + assert pytest.approx(lcoi, rel=1e-4) == 3.3399342887615115 + + with subtests.test("Value check on MOE"): + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = "moe" + model.technology_config["technologies"]["ewin_NaOH_feedstock"]["model_inputs"][ + "performance_parameters" + ]["rated_capacity"] = 0 + model.setup() + model.run() + lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] + assert pytest.approx(lcoi, rel=1e-4) == 2.2802793527655987 + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("27_site_doe_diff", None)]) +def test_sweeping_different_resource_sites_doe(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create the model + model = H2IntegrateModel(example_folder / "27_wind_solar_site_doe.yaml") + + # # Run the model + model.run() + + # Specify the filepath to the sql file, the folder and filename are in the driver_config + sql_fpath = example_folder / "ex_27_out" / "cases.sql" + + # load the cases + cr = om.CaseReader(sql_fpath) + + cases = list(cr.get_cases()) + + res_df = pd.DataFrame() + for ci, case in enumerate(cases): + solar_resource_data = case.get_val("solar_site.solar_resource.solar_resource_data") + wind_resource_data = case.get_val("wind_site.wind_resource.wind_resource_data") + with subtests.test(f"Case {ci}: Solar resource latitude matches site latitude"): + assert ( + pytest.approx( + case.get_val("solar_site.solar_resource.latitude", units="deg"), abs=0.1 + ) + == solar_resource_data["site_lat"] + ) + with subtests.test(f"Case {ci}: Wind resource latitude matches site latitude"): + assert ( + pytest.approx( + case.get_val("wind_site.wind_resource.latitude", units="deg"), abs=0.1 + ) + == wind_resource_data["site_lat"] + ) + + s_lat = case.get_val("solar_site.solar_resource.latitude", units="deg")[0] + s_lon = case.get_val("solar_site.solar_resource.longitude", units="deg")[0] + solar_lat_lon = f"{s_lat} {s_lon}" + w_lat = case.get_val("wind_site.wind_resource.latitude", units="deg")[0] + w_lon = case.get_val("wind_site.wind_resource.longitude", units="deg")[0] + wind_lat_lon = f"{w_lat} {w_lon}" + + solar_capacity = case.get_design_vars()["solar.system_capacity_DC"][0] + + solar_aep = np.sum(case.get_val("solar.electricity_out", units="MW")) + solar_lcoe = case.get_val("finance_subgroup_solar.LCOE", units="USD/(MW*h)")[0] + + wind_aep = np.sum(case.get_val("wind.electricity_out", units="MW")) + wind_lcoe = case.get_val("finance_subgroup_wind.LCOE", units="USD/(MW*h)")[0] + + combiner_aep = np.sum(case.get_val("combiner.electricity_out", units="MW")) + combiner_lcoe = case.get_val("finance_subgroup_electricity.LCOE", units="USD/(MW*h)")[0] + + index_cols = [ + "solar site", + "wind site", + "solar AEP", + "solar LCOE", + "solar size", + "wind AEP", + "wind LCOE", + "combiner AEP", + "combiner LCOE", + ] + vals = [ + solar_lat_lon, + wind_lat_lon, + solar_aep, + solar_lcoe, + solar_capacity, + wind_aep, + wind_lcoe, + combiner_aep, + combiner_lcoe, + ] + + site_res = pd.DataFrame(vals, index=index_cols, columns=[ci]).T + + res_df = pd.concat([site_res, res_df], axis=0) + + with subtests.test("Two unique solar capacities"): + solar_sizes = list(set(res_df["solar site"].to_list())) + assert len(solar_sizes) == 2 + + with subtests.test("Two unique solar sites"): + solar_locations = list(set(res_df["solar site"])) + assert len(solar_locations) == 2 + + with subtests.test("Two unique wind sites"): + wind_locations = list(set(res_df["wind site"])) + assert len(wind_locations) == 2 + + with subtests.test("Unique solar AEPS"): + assert len(list(set(res_df["solar AEP"].to_list()))) == 4 + + with subtests.test("Unique solar LCOEs"): + assert len(list(set(res_df["solar LCOE"].to_list()))) == 4 + + with subtests.test("Unique wind AEPS"): + assert len(list(set(res_df["wind AEP"].to_list()))) == 2 + + with subtests.test("Unique wind LCOEs"): + assert len(list(set(res_df["wind LCOE"].to_list()))) == 2 + + with subtests.test("Unique combiner AEPS"): + assert len(list(set(res_df["combiner AEP"].to_list()))) == len(res_df) + + with subtests.test("Unique LCOEs per case"): + assert len(list(set(res_df["combiner LCOE"].to_list()))) == len(res_df) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("30_pyomo_optimized_dispatch", None)] +) +def test_pyomo_optimized_dispatch_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create a H2Integrate model + model = H2IntegrateModel(example_folder / "pyomo_optimized_dispatch.yaml") + + demand_profile = np.ones(8760) * 100.0 + + # TODO: Update with demand module once it is developed + model.setup() + model.prob.set_val("battery.electricity_demand", demand_profile, units="MW") + model.prob.set_val("electrical_load_demand.electricity_demand", demand_profile, units="MW") + + # Run the model + model.run() + + model.post_process() + + with subtests.test("Check wind total electricity produced"): + wind_total = model.prob.get_val("wind.total_electricity_produced", units="kW*h")[0] + assert wind_total == pytest.approx(781_472_811.8, rel=1e-3) + + with subtests.test("Check wind capacity factor"): + wind_cf = model.prob.get_val("wind.capacity_factor", units="unitless")[0] + assert wind_cf == pytest.approx(0.4299, rel=1e-3) + + with subtests.test("Check wind CapEx"): + wind_capex = model.prob.get_val("wind.CapEx", units="USD")[0] + assert wind_capex == pytest.approx(311_250_000.0, rel=1e-3) + + # Battery checks + with subtests.test("Check battery total electricity produced"): + battery_total = model.prob.get_val( + "electrical_load_demand.total_electricity_produced", units="kW*h" + )[0] + assert battery_total == pytest.approx(645_787_407.02, rel=1e-3) + + with subtests.test("Check battery capacity factor"): + battery_cf = model.prob.get_val("electrical_load_demand.capacity_factor", units="unitless")[ + 0 + ] + assert battery_cf == pytest.approx(0.7372, rel=1e-3) + + with subtests.test("Check battery CapEx"): + battery_capex = model.prob.get_val("battery.CapEx", units="USD")[0] + assert battery_capex == pytest.approx(155_100_000.0, rel=1e-3) + + with subtests.test("Check battery OpEx"): + battery_opex = model.prob.get_val("battery.OpEx", units="USD/year")[0] + assert battery_opex == pytest.approx(38_775_000.0, rel=1e-3) + + # Finance checks + with subtests.test("Check LCOE"): + lcoe = model.prob.get_val("finance_subgroup_all_electricity.LCOE", units="USD/(kW*h)")[0] + assert lcoe == pytest.approx(0.134, rel=1e-3) + + with subtests.test("Check total adjusted CapEx"): + total_capex = model.prob.get_val( + "finance_subgroup_all_electricity.total_capex_adjusted", units="USD" + )[0] + assert total_capex == pytest.approx(490_282_207.03, rel=1e-3) + + with subtests.test("Check total adjusted OpEx"): + total_opex = model.prob.get_val( + "finance_subgroup_all_electricity.total_opex_adjusted", units="USD/year" + )[0] + assert total_opex == pytest.approx(48_830_466.21, rel=1e-3) + + with subtests.test("Check total electricity produced"): + total_electricity = ( + model.prob.get_val( + "finance_subgroup_all_electricity.rated_electricity_production", + units="kW", + )[0] + * model.prob.get_val( + "finance_subgroup_all_electricity.capacity_factor", + units="unitless", + ).mean() + * 8760 + ) + assert total_electricity == pytest.approx(781_472_811.8, rel=1e-3) + + with subtests.test("Check electricity price"): + price = model.prob.get_val( + "finance_subgroup_all_electricity.price_electricity", units="USD/(kW*h)" + )[0] + assert price == pytest.approx(0.134, rel=1e-3) + + +@pytest.mark.integration +@pytest.mark.parametrize("example_folder,resource_example_folder", [("31_tidal", None)]) +def test_tidal_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create the model + model = H2IntegrateModel(example_folder / "tidal.yaml") + + # # Run the model + model.run() + + with subtests.test("AEP"): + tidal_electricity = model.prob.get_val("tidal.electricity_out", units="GW") + assert tidal_electricity.sum() == pytest.approx(60.625515492, rel=1e-4) + + with subtests.test("Capex"): + capex = model.prob.get_val("tidal.CapEx", units="USD") + assert capex == pytest.approx(123902868.63, rel=1e-4) + + with subtests.test("OpEx"): + OpEx = model.prob.get_val("tidal.OpEx", units="USD/yr") + assert OpEx == pytest.approx(4498582.9, rel=1e-4) + + with subtests.test("LCOE"): + lcoe = model.prob.get_val("finance_subgroup_default.LCOE", units="USD/(kW*h)") + assert lcoe == pytest.approx(0.287, rel=1e-4) + + +@pytest.mark.integration +@pytest.mark.parametrize( + "example_folder,resource_example_folder", [("32_multivariable_streams", None)] +) +def test_multivariable_streams_example(subtests, temp_copy_of_example): + example_folder = temp_copy_of_example + + # Create the model + model = H2IntegrateModel(example_folder / "32_multivariable_streams.yaml") + + # Run the model + model.run() + + # Gas Producer 1 + with subtests.test("Producer 1 flow"): + flow1 = model.prob.get_val( + "gas_producer_1.wellhead_gas_mixture:mass_flow_out", units="kg/h" + ) + assert flow1.mean() == pytest.approx(149.70, rel=1e-3) + + with subtests.test("Producer 1 temperature"): + temp1 = model.prob.get_val("gas_producer_1.wellhead_gas_mixture:temperature_out", units="K") + assert temp1.mean() == pytest.approx(310.0, rel=1e-3) + + with subtests.test("Producer 1 pressure"): + pres1 = model.prob.get_val("gas_producer_1.wellhead_gas_mixture:pressure_out", units="bar") + assert pres1.mean() == pytest.approx(12.02, rel=1e-3) + + # Gas Producer 2 + with subtests.test("Producer 2 flow"): + flow2 = model.prob.get_val( + "gas_producer_2.wellhead_gas_mixture:mass_flow_out", units="kg/h" + ) + assert flow2.mean() == pytest.approx(99.68, rel=1e-3) + + with subtests.test("Producer 2 temperature"): + temp2 = model.prob.get_val("gas_producer_2.wellhead_gas_mixture:temperature_out", units="K") + assert temp2.mean() == pytest.approx(350.0, rel=1e-3) + + with subtests.test("Producer 2 pressure"): + pres2 = model.prob.get_val("gas_producer_2.wellhead_gas_mixture:pressure_out", units="bar") + assert pres2.mean() == pytest.approx(8.01, rel=1e-3) + + # Gas Combiner + with subtests.test("Combiner total flow"): + flow_out = model.prob.get_val( + "gas_combiner.wellhead_gas_mixture:mass_flow_out", units="kg/h" + ) + assert flow_out.mean() == pytest.approx(249.38, rel=1e-3) + + with subtests.test("Combiner temperature"): + temp_out = model.prob.get_val( + "gas_combiner.wellhead_gas_mixture:temperature_out", units="K" + ) + assert temp_out.mean() == pytest.approx(326.1, rel=1e-3) + + with subtests.test("Combiner pressure"): + pres_out = model.prob.get_val("gas_combiner.wellhead_gas_mixture:pressure_out", units="bar") + assert pres_out.mean() == pytest.approx(10.40, rel=1e-3) + + with subtests.test("Combiner H2 fraction"): + h2_out = model.prob.get_val("gas_combiner.wellhead_gas_mixture:hydrogen_mass_fraction_out") + assert h2_out.mean() == pytest.approx(0.800, rel=1e-3) + + # Gas Consumer + with subtests.test("Consumer H2 mass flow"): + h2_mass_flow = model.prob.get_val("gas_consumer.hydrogen_out", units="kg/h") + assert h2_mass_flow.mean() == pytest.approx(199.55, rel=1e-3) + + with subtests.test("Consumer total gas consumed"): + total_consumed = model.prob.get_val("gas_consumer.total_gas_consumed", units="kg") + assert total_consumed[0] == pytest.approx(2_184_570, rel=1e-3) + + with subtests.test("Consumer avg temperature"): + avg_temp = model.prob.get_val("gas_consumer.avg_temperature", units="K") + assert avg_temp[0] == pytest.approx(326.1, rel=1e-3) + + with subtests.test("Consumer avg pressure"): + avg_pres = model.prob.get_val("gas_consumer.avg_pressure", units="bar") + assert avg_pres[0] == pytest.approx(10.40, rel=1e-3) From eb9b449490b30416ea325355ec74d64fef6445c7 Mon Sep 17 00:00:00 2001 From: svijaysh Date: Thu, 23 Apr 2026 13:21:14 -0600 Subject: [PATCH 5/7] Update docs and add figure --- docs/control/pyomo_controllers.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/control/pyomo_controllers.md b/docs/control/pyomo_controllers.md index ac6a1ddc0..cece55d33 100644 --- a/docs/control/pyomo_controllers.md +++ b/docs/control/pyomo_controllers.md @@ -61,13 +61,13 @@ tech_to_dispatch_connections: [ # Optimized Demand Response Controller -This controller optimizes the dispatch of a Battery Energy Storage System (BESS) based on a pre-defined supervisory signal. This pre-defined signal could be the Locational Marginal Price (LMP) or a demand profile. It could also be a $LMP\times demand$ depending on the application. +This controller optimizes the dispatch of a Battery Energy Storage System (BESS) based on a pre-defined supervisory signal. This signal could be the Locational Marginal Price (LMP), a demand profile, or a $LMP\times demand$ product depending on the application. The objective is to maximize incentive payments to the battery, subject to constraints on the maximum number of dispatch events per month and on the battery state of charge. ## Definitions **Given:** - $\lambda_t$ := `supervisory_signal`: price, demand, or price $\times$ demand time series at time $t$ -- $\mathcal{W}$ := `peak_window`: set of hours eligible for dispatch (e.g., 12:00--19:00) +- $\mathcal{W}$ := `peak_window`: set of hours eligible for d`ispatch (e.g., 12:00--19:00) - $\gamma$ := performance incentive (\$/kW per dispatch hour) - $\bar{P}$ := `max_charge_rate` (kW): maximum charge and discharge rate, used as deemed capacity since the battery is assumed to always dispatch at full rated power - $E_{\max} :=$ `max_capacity` $\times$ (`max_soc_fraction` $-$ `min_soc_fraction`): usable energy capacity (kWh) @@ -145,3 +145,4 @@ $$ $$ u_t \in \{0, 1\}, \quad v_t \in \{0, 1\}, \quad \text{SoC}_t \in [0, 1] \qquad \forall\, t, m $$ +![](./figures/plm_optimized_dispatch.png) From 053e5868719aaff3ec55f430e1f5b0ab5ec21ac1 Mon Sep 17 00:00:00 2001 From: svijaysh Date: Thu, 23 Apr 2026 16:12:20 -0600 Subject: [PATCH 6/7] Edit docs page --- docs/control/pyomo_controllers.md | 8 ++-- .../run_plm_optimized_dispatch.py | 38 +++---------------- .../plm_optimized_storage_controller.py | 11 +++--- 3 files changed, 15 insertions(+), 42 deletions(-) diff --git a/docs/control/pyomo_controllers.md b/docs/control/pyomo_controllers.md index cece55d33..4fdcdf233 100644 --- a/docs/control/pyomo_controllers.md +++ b/docs/control/pyomo_controllers.md @@ -67,7 +67,7 @@ This controller optimizes the dispatch of a Battery Energy Storage System (BESS) **Given:** - $\lambda_t$ := `supervisory_signal`: price, demand, or price $\times$ demand time series at time $t$ -- $\mathcal{W}$ := `peak_window`: set of hours eligible for d`ispatch (e.g., 12:00--19:00) +- $\mathcal{W}$ := `peak_window`: set of hours eligible for dispatch (e.g., 12:00--19:00) - $\gamma$ := performance incentive (\$/kW per dispatch hour) - $\bar{P}$ := `max_charge_rate` (kW): maximum charge and discharge rate, used as deemed capacity since the battery is assumed to always dispatch at full rated power - $E_{\max} :=$ `max_capacity` $\times$ (`max_soc_fraction` $-$ `min_soc_fraction`): usable energy capacity (kWh) @@ -110,10 +110,10 @@ $$ where $\lambda^*_m$ is the threshold selecting the high LMP/peak load hours within month $m$. -- Maximum 10 events per month: +- Maximum $N_{max}$ events per month: $$ -\sum_{t \in \mathcal{M}_m} u_t \leq N_{\max} \qquad \forall\, m, \quad N_{\max} = 10 +\sum_{t \in \mathcal{M}_m} u_t \leq N_{\max} \qquad \forall\, m, \quad N_{\max}. $$ - SoC evolution with charge and discharge: @@ -145,4 +145,6 @@ $$ $$ u_t \in \{0, 1\}, \quad v_t \in \{0, 1\}, \quad \text{SoC}_t \in [0, 1] \qquad \forall\, t, m $$ +Example 34 performs the optimization with a synthetic LMP signal. The look-ahead horizon is set to 10 hours. As this value increases, the computational complexity grows and the solver may take significantly longer to run or fail to converge. Care should be taken when choosing this parameter: a short horizon limits visibility, making it difficult for the optimizer to identify the best dispatch opportunities across the full month. See figure below for the results. + ![](./figures/plm_optimized_dispatch.png) diff --git a/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py b/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py index 3dff042d2..547d030bc 100644 --- a/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py +++ b/examples/34_plm_optimized_dispatch/run_plm_optimized_dispatch.py @@ -45,11 +45,11 @@ peak_mask = (time_index.hour >= 14) & (time_index.hour <= 18) threshold_pct = np.percentile(lmp, percentile) -discharge_mask = battery_power > 1.0 +discharge_mask = battery_power > 0 plt.rcParams.update({"axes.spines.top": False, "axes.spines.right": False}) -fig, axes = plt.subplots(3, 1, sharex=True, figsize=(11, 7)) +fig, axes = plt.subplots(2, 1, sharex=True, figsize=(11, 7)) days = pd.date_range(time_index[0].normalize(), periods=14, freq="D", tz=time_index.tz) time_window = 14 * 24 @@ -71,7 +71,7 @@ def shade_peaks(ax): ax = axes[0] shade_peaks(ax) ax.plot(time_index[:time_window], lmp[:time_window], color="steelblue", linewidth=1.0) -ax.axhline(threshold_pct, color="crimson", linestyle="--", linewidth=0.8) +ax.axhline(threshold_pct, color="k", linestyle="--", linewidth=0.8) ax.plot( time_index[:time_window][w_discharge], lmp[:time_window][w_discharge], @@ -82,42 +82,14 @@ def shade_peaks(ax): ax.set_ylabel("LMP ($/MWh)", fontsize=8) ax.set_ylim(bottom=0) -# Panel 2: SOC ax = axes[1] -ax.plot( - time_index[:time_window], soc_pct[:time_window], color="tab:green", linewidth=1.0 -) +shade_peaks(ax) +ax.plot(time_index[:time_window], soc_pct[:time_window], color="g", linewidth=1.0) ax.axhline(90, color="gray", linestyle=":", linewidth=0.7) ax.axhline(10, color="gray", linestyle=":", linewidth=0.7) ax.set_ylabel("SOC (%)", fontsize=8) ax.set_ylim([0, 105]) -ax = axes[2] -shade_peaks(ax) -ax.axhline(0, color="black", linewidth=0.4) -width = pd.Timedelta(hours=0.8) -pos_mask = (battery_power > 1)[:time_window] -neg_mask = (battery_power < -1)[:time_window] -ax.bar( - time_index[:time_window][pos_mask], - battery_power[:time_window][pos_mask] * 1e-3, - width=width, - color="tab:red", - alpha=0.8, -) -ax.bar( - time_index[:time_window][neg_mask], - battery_power[:time_window][neg_mask] * 1e-3, - width=width, - color="tab:blue", - alpha=0.8, -) -ax.set_ylabel("Power (MW)", fontsize=8) -ax.tick_params(axis="x", labelrotation=30, labelsize=7) - -for ax in axes: - ax.tick_params(labelsize=7) - ax.grid(True, which="major", alpha=0.3, linewidth=0.5) plt.tight_layout() plt.savefig("plm_optimized_dispatch.png", dpi=150, bbox_inches="tight") diff --git a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py index 3e0524801..80dc9f1e6 100644 --- a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py @@ -45,7 +45,7 @@ class PLMOptimizedControllerConfig(PyomoStorageControllerBaseConfig): Defaults to 10. n_control_window (int): Number of timesteps per rolling solve window. Defaults to ``24 * 30`` (one month of hourly data). - signal_threshold_percentile (float): Percentile (0–100) used to + signal_threshold_percentile (float): Percentile (0-100) used to compute the signal threshold for each rolling window. Only hours at or above this percentile of the window signal are eligible for dispatch. Defaults to 0.0 (all hours eligible). @@ -179,7 +179,7 @@ def _compute_peak_window_mask(self) -> np.ndarray: return np.array([start <= t <= end for t in times]) def _compute_month_ids(self) -> np.ndarray: - """Return the calendar month index (1–12) for each timestep. + """Return the calendar month index (1-12) for each timestep. Returns: np.ndarray: Integer array of shape ``(n_timesteps,)``. @@ -215,8 +215,7 @@ def pyomo_setup(self, discrete_inputs): """Return the rolling-horizon dispatch solver callable. Args: - discrete_inputs (dict): OpenMDAO discrete inputs (unused - directly here; passed for API compatibility). + discrete_inputs (dict): OpenMDAO discrete inputs. Returns: callable: ``pyomo_dispatch_solver(performance_model, @@ -230,7 +229,7 @@ def pyomo_setup(self, discrete_inputs): commands. 4. Carries the terminal SOC into the next window. - Returns ``(storage_out, soc_out)`` — two ``np.ndarray`` of + Returns ``(storage_out, soc_out)`` - two ``np.ndarray`` of length ``n_timesteps``. """ @@ -439,7 +438,7 @@ def solve_dispatch_model(self, start_time: int = 0, n_days: int = 0): """Solve the DR MILP for the current window and record solver metrics. Args: - start_time (int): Global timestep index of the window start. + start_time (int): Timestep index of the window start. Used only for error messages and metrics. Defaults to 0. n_days (int): Total simulation days. Passed to ``DispatchProblemState.store_problem_metrics``. From 2f0a325ac2f1fea7dc26da80505b875af3562660 Mon Sep 17 00:00:00 2001 From: svijaysh Date: Thu, 23 Apr 2026 16:40:42 -0600 Subject: [PATCH 7/7] Edit docstrings to include raises --- .../storage/plm_optimized_storage_controller.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py index 80dc9f1e6..3e9bf6959 100644 --- a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py @@ -44,7 +44,7 @@ class PLMOptimizedControllerConfig(PyomoStorageControllerBaseConfig): n_max_events (int): Maximum discharge events per calendar month. Defaults to 10. n_control_window (int): Number of timesteps per rolling solve - window. Defaults to ``24 * 30`` (one month of hourly data). + window. Defaults to ``24`` (one day). signal_threshold_percentile (float): Percentile (0-100) used to compute the signal threshold for each rolling window. Only hours at or above this percentile of the window signal are @@ -58,7 +58,7 @@ class PLMOptimizedControllerConfig(PyomoStorageControllerBaseConfig): charge_efficiency: float = field(validator=range_val(0, 1), default=1.0) discharge_efficiency: float = field(validator=range_val(0, 1), default=1.0) n_max_events: int = field(default=10) - n_control_window: int = field(default=24 * 30) # one month of hourly data + n_control_window: int = field(default=24) # one month of hourly data signal_threshold_percentile: float = field(default=0.0, validator=range_val(0,100)) # make sure this is valid @@ -121,12 +121,12 @@ def setup(self): @staticmethod def _build_time_index(plant_config: dict) -> pd.DatetimeIndex: - """Build a timezone-aware DatetimeIndex from simulation settings in plant_config. + """Build a DatetimeIndex from simulation settings in plant_config. Args: plant_config (dict): Plant configuration dict. Must contain ``plant.simulation`` with keys ``n_timesteps`` (int), - ``dt`` (int, seconds), ``timezone`` (int, UTC offset), + ``dt`` (int, seconds), ``timezone`` (int), and ``start_time`` (str). Returns: @@ -189,7 +189,7 @@ def _compute_month_ids(self) -> np.ndarray: def _compute_eligible_mask(self, signal_window: np.ndarray) -> np.ndarray: """Build a boolean mask for timesteps whose signal meets the dispatch threshold. - The threshold is derived only from ``signal_window`` — it does not + The threshold is derived only from ``signal_window``. It does not assume the full simulation signal is known in advance. When ``signal_threshold_percentile`` is 0.0 all hours are eligible. @@ -333,10 +333,9 @@ def _build_dr_model( """Build the DR MILP for a single rolling window. Args: - window_start (int): Global timestep index of the first hour + window_start (int): Timestep index of the first hour in this window. window_len (int): Number of timesteps in this window - (``n_control_window`` except possibly the last window). init_soc (float): State-of-charge fraction at the start of this window. remaining_budget (dict): Mapping of ``month_id (int)`` to @@ -445,7 +444,7 @@ def solve_dispatch_model(self, start_time: int = 0, n_days: int = 0): Defaults to 0. Raises: - RuntimeError: If GLPK returns a non-OK status or an + RuntimeError: If GLPK returns a not OK status or an unacceptable termination condition. """ from pyomo.opt import SolverStatus, TerminationCondition