Residual loads

Negative residual loads might lead to an unsolvable EMPS problem, or even cause a crash when executing the model. There is no hard limit for when negative residual loads will cause these kinds of trouble, but a few rules of thumb exist: If it is negative for just one price segment in one week for one scenario, that will work just fine. And if it is always negative, it will fail for sure. A short period of time will probaly work, but the longer the period then higher is the risk of failure.

The residual load is calculated for each Market Calibration Areas.

Note

The residual load will always be calculated and it will be checked if it’s negative when executing/writing the model to disk.

The internal check uses 1 hour as sampling rate, and 52 weeks as group by.

If the validation fails, LTM-API will refuse to continue. To force LTM-API to continue, set global_settings.allow_validation_failures to true.

Definition

Residual load is the difference between loads (ENMD group 16) and unregulated energy production (wind, solar, aggregated hydro modules).

(1)\[\sum_{i=1}^{timesteps}\Biggl( \sum_{n=1}^{num loads}(\text{loads(n)}) ) - \biggl( \sum_{n=1}^{num wind}(\text{wind(n)}) + \sum_{n=1}^{num solar}(\text{solar(n)}) + \sum_{n=1}^{num unreg}(\text{unreg_agg_inflow(n)} \biggl) \Biggl)\]

If there is more unregulated production than consumtion, there is a negative residual load.

Residual load results

The results are grouped by market calibration area name. If there are no explicit groups defined, a group named default is used and all applicable objects are used to calculate the residual load.

Example results

With 2 market calibration area names, there are two groups, market_area_1 and market_area_2.

Each area contains 2 Txy variables with a total average for all scenarios, and one average per scenario.

Listing with JSON-like results

All results are intentionally 0.0.

JSON-ified results
 1{
 2    "market_area_1": {
 3        "total": {
 4            "timestamps": [
 5                "2024-01-01T00:00:00Z",
 6                "2024-12-30T00:00:00Z",
 7                "2025-12-29T00:00:00Z"
 8            ],
 9            "scenarios": [
10                [
11                    0.0,
12                    0.0,
13                    0.0
14                ]
15            ]
16        },
17        "scenario": {
18            "timestamps": [
19                "2024-01-01T00:00:00Z",
20                "2024-12-30T00:00:00Z",
21                "2025-12-29T00:00:00Z"
22            ],
23            "scenarios": [
24                [
25                    0.0,
26                    0.0,
27                    0.0
28                ],
29                [
30                    0.0,
31                    0.0,
32                    0.0
33                ]
34            ]
35        }
36    },
37    "market_area_2": {
38        "total": {
39            "timestamps": [
40                "2024-01-01T00:00:00Z",
41                "2024-12-30T00:00:00Z",
42                "2025-12-29T00:00:00Z"
43            ],
44            "scenarios": [
45                [
46                    0.0,
47                    0.0,
48                    0.0
49                ]
50            ]
51        },
52        "scenario": {
53            "timestamps": [
54                "2024-01-01T00:00:00Z",
55                "2024-12-30T00:00:00Z",
56                "2025-12-29T00:00:00Z"
57            ],
58            "scenarios": [
59                [
60                    0.0,
61                    0.0,
62                    0.0
63                ],
64                [
65                    0.0,
66                    0.0,
67                    0.0
68                ]
69            ]
70        }
71    }
72}

Example model with a load specified, with only differences to market calibration areas shown.

Download residual_loads.json.

 1--- /builds/energy/ltm/pyltmapi/tests/market_calibration_areas.json
 2+++ /builds/energy/ltm/pyltmapi/tests/residual_loads.json
 3@@ -3,7 +3,7 @@
 4     "model": {
 5         "global_settings": {
 6             "name": "global_settings",
 7-            "output_path": "testout_market_calibration_areas/",
 8+            "output_path": "testout_residual_loads/",
 9             "calc_water_value_flag": {
10                 "timestamps": [
11                     "2024-01-01T00:00:00Z",
12@@ -56,6 +56,37 @@
13                 "name": "market_area_2"
14             }
15         ],
16+        "loads": [
17+            {
18+                "name": "FLAT",
19+                "capacity": {
20+                    "timestamps": [
21+                        "2024-01-01T00:00:00Z"
22+                    ],
23+                    "scenarios": [
24+                        [
25+                            100
26+                        ]
27+                    ]
28+                }
29+            },
30+            {
31+                "name": "FLAT_SCENARIO",
32+                "capacity": {
33+                    "timestamps": [
34+                        "2024-01-01T00:00:00Z"
35+                    ],
36+                    "scenarios": [
37+                        [
38+                            100
39+                        ],
40+                        [
41+                            500
42+                        ]
43+                    ]
44+                }
45+            }
46+        ],
47         "busbars": [
48             {
49                 "name": "numedal"
50@@ -271,6 +302,14 @@
51             },
52             {
53                 "from": "LIT_OTT",
54+                "to": "otta"
55+            },
56+            {
57+                "from": "FLAT",
58+                "to": "numedal"
59+            },
60+            {
61+                "from": "FLAT_SCENARIO",
62                 "to": "otta"
63             }
64         ]

market_area_1 is assigned a constant load FLAT with 1 scenario.

market_area_2 is assigned a constant load FLAT_SCENARIO, with different values for each scenario. This example model has 2 scenarios (historical_period spans 2 years).

Listing with constant load results
 1{
 2    "market_area_1": {
 3        "total": {
 4            "timestamps": [
 5                "2024-01-01T00:00:00Z",
 6                "2024-12-30T00:00:00Z",
 7                "2025-12-29T00:00:00Z"
 8            ],
 9            "scenarios": [
10                [
11                    100.0000000000046,
12                    100.0000000000046,
13                    100.0000000000046
14                ]
15            ]
16        },
17        "scenario": {
18            "timestamps": [
19                "2024-01-01T00:00:00Z",
20                "2024-12-30T00:00:00Z",
21                "2025-12-29T00:00:00Z"
22            ],
23            "scenarios": [
24                [
25                    100.0000000000046,
26                    100.0000000000046,
27                    100.0000000000046
28                ],
29                [
30                    100.0000000000046,
31                    100.0000000000046,
32                    100.0000000000046
33                ]
34            ]
35        }
36    },
37    "market_area_2": {
38        "total": {
39            "timestamps": [
40                "2024-01-01T00:00:00Z",
41                "2024-12-30T00:00:00Z",
42                "2025-12-29T00:00:00Z"
43            ],
44            "scenarios": [
45                [
46                    300.0000000000373,
47                    300.0000000000373,
48                    300.0000000000373
49                ]
50            ]
51        },
52        "scenario": {
53            "timestamps": [
54                "2024-01-01T00:00:00Z",
55                "2024-12-30T00:00:00Z",
56                "2025-12-29T00:00:00Z"
57            ],
58            "scenarios": [
59                [
60                    100.0000000000046,
61                    100.0000000000046,
62                    100.0000000000046
63                ],
64                [
65                    500.00000000007,
66                    500.00000000007,
67                    500.00000000007
68                ]
69            ]
70        }
71    }
72}

Negative residual loads

Expanding the above model with exaggerated values for wind, solar and aggregated_hydro_modules, the residual becomes negative.

Only showing the differences between residual_loads and negative_residual_loads. Download complete negative_residual_loads.json.

Negative residual loads model file differences
  1--- /builds/energy/ltm/pyltmapi/tests/residual_loads.json
  2+++ /builds/energy/ltm/pyltmapi/tests/negative_residual_loads.json
  3@@ -3,7 +3,7 @@
  4     "model": {
  5         "global_settings": {
  6             "name": "global_settings",
  7-            "output_path": "testout_residual_loads/",
  8+            "output_path": "testout_negative_residual_loads/",
  9             "calc_water_value_flag": {
 10                 "timestamps": [
 11                     "2024-01-01T00:00:00Z",
 12@@ -87,6 +87,136 @@
 13                 }
 14             }
 15         ],
 16+        "wind": [
 17+            {
 18+                "name": "RJU_WIND",
 19+                "capacity": {
 20+                    "timestamps": [
 21+                        "2024-01-01T00:00:00Z"
 22+                    ],
 23+                    "scenarios": [
 24+                        [
 25+                            150.4
 26+                        ],
 27+                        [
 28+                            245.0
 29+                        ]
 30+                    ]
 31+                }
 32+            }
 33+        ],
 34+        "solar": [
 35+            {
 36+                "#comment": "Gruppe 18 1D",
 37+                "name": "UST_SOLAR",
 38+                "capacity": {
 39+                    "timestamps": [
 40+                        "2024-01-01T00:00:00Z"
 41+                    ],
 42+                    "scenarios": [
 43+                        [
 44+                            150.4
 45+                        ],
 46+                        [
 47+                            1000
 48+                        ]
 49+                    ]
 50+                }
 51+            }
 52+        ],
 53+        "aggregated_hydro_modules": [
 54+            {
 55+                "name": "ltldalen_enmag_aggregated",
 56+                "reservoir_energy": 63001.0,
 57+                "station_power": 20184.523,
 58+                "regulated_power_inflow": {
 59+                    "#comment": "R30 - GWh/uke i datafil, input in MW",
 60+                    "timestamps": [
 61+                        "2024-01-01T00:00:00Z",
 62+                        "2025-01-01T00:00:00Z",
 63+                        "2026-01-01T00:00:00Z"
 64+                    ],
 65+                    "scenarios": [
 66+                        [
 67+                            0.89285714286,
 68+                            1.48809523810,
 69+                            2.97619047619
 70+                        ],
 71+                        [
 72+                            1.48809523810,
 73+                            1.48809523810,
 74+                            1.48809523810
 75+                        ]
 76+                    ]
 77+                },
 78+                "unregulated_power_inflow": {
 79+                    "#comment": "U30 - GWh/uke i datafil, input in MW",
 80+                    "timestamps": [
 81+                        "2024-01-01T00:00:00Z",
 82+                        "2025-01-01T00:00:00Z",
 83+                        "2026-01-01T00:00:00Z"
 84+                    ],
 85+                    "scenarios": [
 86+                        [
 87+                            0.06547619048,
 88+                            6.54761904762,
 89+                            0.06547619048
 90+                        ],
 91+                        [
 92+                            0.07142857143,
 93+                            0.07142857143,
 94+                            0.07142857143
 95+                        ]
 96+                    ]
 97+                },
 98+                "upper_reservoir_limits": {
 99+                    "#comment": "Manually adjusted upper reserveroir limits, GWh",
100+                    "timestamps": [
101+                        "2024-01-01T00:00:00Z",
102+                        "2025-01-01T00:00:00Z"
103+                    ],
104+                    "scenarios": [
105+                        [
106+                            59000,
107+                            58000
108+                        ]
109+                    ]
110+                },
111+                "lower_reservoir_limits": {
112+                    "#comment": "Manually adjusted lower reserveroir limits, GWh",
113+                    "timestamps": [
114+                        "2024-01-01T00:00:00Z"
115+                    ],
116+                    "scenarios": [
117+                        [
118+                            1000
119+                        ]
120+                    ]
121+                },
122+                "upper_production_limits": {
123+                    "#comment": "Manually adjusted upper production limits, MW",
124+                    "timestamps": [
125+                        "2024-01-01T00:00:00Z"
126+                    ],
127+                    "scenarios": [
128+                        [
129+                            20000
130+                        ]
131+                    ]
132+                },
133+                "lower_production_limits": {
134+                    "#comment": "Manually adjusted lower production limits, MW",
135+                    "timestamps": [
136+                        "2024-01-01T00:00:00Z"
137+                    ],
138+                    "scenarios": [
139+                        [
140+                            50
141+                        ]
142+                    ]
143+                }
144+            }
145+        ],
146         "busbars": [
147             {
148                 "name": "numedal"
149@@ -311,6 +441,18 @@
150             {
151                 "from": "FLAT_SCENARIO",
152                 "to": "otta"
153+            },
154+            {
155+                "from": "ltldalen_enmag_aggregated",
156+                "to": "litldalen"
157+            },
158+            {
159+                "from": "RJU_WIND",
160+                "to": "rjukan"
161+            },
162+            {
163+                "from": "UST_SOLAR",
164+                "to": "ustaoset"
165             }
166         ]
167     }
Negative residual loads example results
{
    "market_area_1": {
        "total": {
            "timestamps": [
                "2024-01-01T00:00:00Z",
                "2024-12-30T00:00:00Z",
                "2025-12-29T00:00:00Z"
            ],
            "scenarios": [
                [
                    -672.9000000000501,
                    -672.9000000000501,
                    -672.9000000000501
                ]
            ]
        },
        "scenario": {
            "timestamps": [
                "2024-01-01T00:00:00Z",
                "2024-12-30T00:00:00Z",
                "2025-12-29T00:00:00Z"
            ],
            "scenarios": [
                [
                    -200.80000000003793,
                    -200.80000000003793,
                    -200.80000000003793
                ],
                [
                    -1145.0000000000623,
                    -1145.0000000000623,
                    -1145.0000000000623
                ]
            ]
        }
    },
    "market_area_2": {
        "total": {
            "timestamps": [
                "2024-01-01T00:00:00Z",
                "2024-12-30T00:00:00Z",
                "2025-12-29T00:00:00Z"
            ],
            "scenarios": [
                [
                    288.49999999996965,
                    -253.0082417583355,
                    284.012362637335
                ]
            ]
        },
        "scenario": {
            "timestamps": [
                "2024-01-01T00:00:00Z",
                "2024-12-30T00:00:00Z",
                "2025-12-29T00:00:00Z"
            ],
            "scenarios": [
                [
                    88.99999999998887,
                    -994.0164835166214,
                    80.0247252747196
                ],
                [
                    487.99999999995043,
                    487.99999999995043,
                    487.99999999995043
                ]
            ]
        }
    }
}

When running the model through this script, validation will fail.

 1import os
 2from datetime import timedelta
 3
 4from pyltmapi import LtmSession
 5
 6# Sampling rate
 7one_hour = timedelta(hours=1)
 8
 9# Averaging period
10one_efi_year = timedelta(weeks=52)
11
12with LtmSession("residual_session") as session:
13    session.load(filename="residual_loads.json")
14    residuals = session.model.calculate_residual_load(one_hour, one_efi_year)
15    print(residuals)
16
17with LtmSession("negative_residual_session") as session:
18    session.load(filename="negative_residual_loads.json")
19    residuals = session.model.calculate_residual_load(one_hour, one_efi_year)
20    print(residuals)
21    session.write_model()   # Will fail due to negative residual loads

Output:

(negative_residual_session) Model discrepancies found: 4
(negative_residual_session) [market_calibration_area_check] Negative residual load for area 'market_area_1' at 2024-01-01T00:00:00Z for scenario 0. Residual load -672.90
(negative_residual_session) [market_calibration_area_check] Negative residual load for area 'market_area_1' at 2024-12-30T00:00:00Z for scenario 0. Residual load -672.90
(negative_residual_session) [market_calibration_area_check] Negative residual load for area 'market_area_1' at 2025-12-29T00:00:00Z for scenario 0. Residual load -672.90
(negative_residual_session) [market_calibration_area_check] Negative residual load for area 'market_area_2' at 2024-12-30T00:00:00Z for scenario 0. Residual load -253.01
(negative_residual_session) Model validation failed