├── LICENSE ├── README.md ├── NEMDE_constraints.py └── NEMDE_constraints_demo.ipynb /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Julius Susanto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NEM_constraints 2 | Python functions for wrangling public NEMDE constraint equation formulations: 3 | 4 | - **get_constraint_list**: get a list of constraints from the archive for a specific month/year 5 | - **find_constraint**: search for a specific constraint equation 6 | - **get_LHS_terms**: get the left-hand side (LHS) terms of a specific constraint equation 7 | - **get_RHS_terms**: get the right-hand side (RHS) terms of a specific constraint equation 8 | - **get_constraint_details**: get the description, LHS and RHS terms of a specific constraint equation 9 | - **find_generic_RHS_func**: find a generic RHS function definition from the archive 10 | - **get_generic_RHS_func**: get the terms for a generic RHS function 11 | 12 | The [Jupyter notebook here](https://github.com/susantoj/NEM_constraints/blob/main/NEMDE_constraints_demo.ipynb) provides a illustrative demonstration of how the functions can be used. 13 | 14 | ## More Information 15 | AEMO provides a lot of reference information on constraints, including the following: 16 | 17 | - [MMS Data Model Reports](https://visualisations.aemo.com.au/aemo/di-help/Content/Data_Model/MMS_Data_Model.htm?TocPath=_____8) 18 | - [Congestion Information Resource (CIR))](https://www.aemo.com.au/energy-systems/electricity/national-electricity-market-nem/system-operations/congestion-information-resource) provides quite a bit of reference information, including: 19 | - [Constraint Implementation Guidelines](https://www.aemo.com.au/-/media/files/stakeholder_consultation/consultations/nem-consultations/2023/constraints-implementation-guidelines/final-constraint-implementation-guidelines-v3.pdf?la=en)) which has details on the RHS data types and reverse polish notation calculation engine 20 | - [Constraint Formulation Guidelines](https://www.aemo.com.au/-/media/files/electricity/nem/security_and_reliability/congestion-information/2021/constraint-formulation-guidelines.pdf?la=en) which has details on the relevant MMS tables and principles for formulating constraints 21 | - [Constraint Naming Guidelines](https://www.aemo.com.au/-/media/files/electricity/nem/security_and_reliability/congestion-information/2016/constraint-naming-guidelines.pdf) -------------------------------------------------------------------------------- /NEMDE_constraints.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python functions for wrangling public NEMDE constraint equation formulations: 3 | - get_constraint_list (month, year, prefix) 4 | - find_constraint (constraint, start_date, end_date) 5 | - get_LHS_terms(constraint, month, year) 6 | - get_RHS_terms(constraint, month, year) 7 | - get_constraint_details (constraint, month, year) 8 | - find_generic_RHS_func (equation_id, start_date, end_date) 9 | - get_generic_RHS_func(equation_id, month, year) 10 | 11 | Last updated: @JSusanto / Apr 2023 12 | """ 13 | 14 | import pandas as pd 15 | from datetime import date 16 | from dateutil.relativedelta import relativedelta 17 | 18 | 19 | def get_mms_table(month, year, table): 20 | """"Get a table from MMS at a specific month and year and return a DataFrame""" 21 | 22 | if month < 10: 23 | str_month = '0' + str(month) 24 | else: 25 | str_month = str(month) 26 | 27 | url_prefix = 'https://nemweb.com.au/Data_Archive/Wholesale_Electricity/MMSDM/' + str(year) + '/MMSDM_' + str(year) + '_' + str_month + '/MMSDM_Historical_Data_SQLLoader/DATA/PUBLIC_DVD_' 28 | url_suffix = '_' + str(year) + str_month + '010000.zip' 29 | 30 | return pd.read_csv(url_prefix + table + url_suffix, compression='zip', header=1) 31 | 32 | 33 | def get_constraint_list(month, year, prefix = None): 34 | """Get list of constraints from a specific month/year and with an optional prefix 35 | Inputs: 36 | month specified integer month 37 | year specified integer year 38 | prefix optional string prefix, e.g. 'Q_', 'S_', etc 39 | 40 | Returns: 41 | df a dataframe with list of constraints from GENCONDATA table 42 | """ 43 | 44 | pd.set_option('display.max_rows', None) 45 | df = get_mms_table(month, year, 'GENCONDATA') 46 | df.drop(index=df.index[-1],axis=0,inplace=True) 47 | 48 | if prefix: 49 | df = df[df['GENCONID'].str.startswith(prefix)] 50 | 51 | print(df[['GENCONID','DESCRIPTION']]) 52 | 53 | return df 54 | 55 | 56 | def find_constraint(constraint, start_date = date(2009,7,1), end_date = None): 57 | """Find the last update of a specific constraint over an optional period of dates 58 | Inputs: 59 | constraint string of constraint to search for 60 | start_date date object for date to start search (optional, default is July 2009) 61 | end_date date object for date end search (optional, default is now) 62 | 63 | Returns: 64 | df a dataframe with the constraint description from GENCONDATA table 65 | found True if constraint found, False otherwise 66 | """ 67 | 68 | df = pd.DataFrame() 69 | 70 | if not end_date: 71 | end_date = date.today() - relativedelta(months=2) 72 | 73 | cur_date = end_date 74 | 75 | while df.empty and cur_date > start_date: 76 | print('Searching archive from ' + cur_date.strftime("%B %Y")) 77 | df = get_mms_table(cur_date.month, cur_date.year, 'GENCONDATA') 78 | df.drop(index=df.index[-1],axis=0,inplace=True) 79 | 80 | df = df[df['GENCONID'].str.startswith(constraint)] 81 | 82 | cur_date = cur_date - relativedelta(months=1) 83 | 84 | if df.empty: 85 | print('Constraint equation not found over period ' + start_date.strftime("%B %Y") + ' and ' + end_date.strftime("%B %Y")) 86 | found = False 87 | else: 88 | df = df.drop(df.columns[[0, 2, 3]],axis = 1) 89 | print(df.iloc[0]) 90 | found = True 91 | 92 | return df, found 93 | 94 | 95 | def get_LHS_terms(constraint, month, year): 96 | """Get LHS terms for a constraint equation 97 | Inputs: 98 | constraint string of the specific constraint equation 99 | month specified integer month 100 | year specified integer year 101 | 102 | Returns: 103 | df a dataframe with the LHS terms of the constraint equation 104 | (returns empty dataframe if constraint equation not found) 105 | """ 106 | 107 | dict_LHS = {'type' : [], 'ID' : [], 'DUID' : [], 'factor' : [], 'bidtype' : []} 108 | 109 | # Connection points 110 | df = get_mms_table(month, year, 'SPDCONNECTIONPOINTCONSTRAINT') 111 | df.drop(index=df.index[-1],axis=0,inplace=True) 112 | df = df[df['GENCONID'] == constraint] 113 | 114 | if not df.empty: 115 | df_lookup = get_mms_table(month, year, 'DUDETAIL') 116 | df_lookup.drop(index=df_lookup.index[-1],axis=0,inplace=True) 117 | 118 | for i in range(len(df)): 119 | df_DUID = df_lookup[df_lookup['CONNECTIONPOINTID'] == df['CONNECTIONPOINTID'].iloc[i]]['DUID'] 120 | if df_DUID.empty: 121 | DUID = 'DUID not found' 122 | else: 123 | DUID = df_DUID.iloc[0] 124 | 125 | dict_LHS['type'].append('CONNECTIONPOINT') 126 | dict_LHS['ID'].append(df['CONNECTIONPOINTID'].iloc[i]) 127 | dict_LHS['DUID'].append(DUID) 128 | dict_LHS['factor'].append(df['FACTOR'].iloc[i]) 129 | dict_LHS['bidtype'].append(df['BIDTYPE'].iloc[i]) 130 | 131 | # Interconnectors 132 | df = get_mms_table(month, year, 'SPDINTERCONNECTORCONSTRAINT') 133 | df.drop(index=df.index[-1],axis=0,inplace=True) 134 | df = df[df['GENCONID'] == constraint] 135 | 136 | if not df.empty: 137 | for i in range(len(df)): 138 | dict_LHS['type'].append('INTERCONNECTOR') 139 | dict_LHS['ID'].append(df['INTERCONNECTORID'].iloc[i]) 140 | dict_LHS['DUID'].append(df['INTERCONNECTORID'].iloc[i]) 141 | dict_LHS['factor'].append(df['FACTOR'].iloc[i]) 142 | dict_LHS['bidtype'].append('N/A') 143 | 144 | # Regions 145 | df = get_mms_table(month, year, 'SPDREGIONCONSTRAINT') 146 | df.drop(index=df.index[-1],axis=0,inplace=True) 147 | df = df[df['GENCONID'] == constraint] 148 | 149 | if not df.empty: 150 | for i in range(len(df)): 151 | dict_LHS['type'].append('REGION') 152 | dict_LHS['ID'].append(df['REGIONID'].iloc[i]) 153 | dict_LHS['DUID'].append(df['REGIONID'].iloc[i]) 154 | dict_LHS['factor'].append(df['FACTOR'].iloc[i]) 155 | dict_LHS['bidtype'].append('N/A') 156 | 157 | return pd.DataFrame(dict_LHS) 158 | 159 | 160 | def get_RHS_terms(constraint, month, year): 161 | """Get RHS terms for a constraint equation 162 | Inputs: 163 | constraint string of the specific constraint equation 164 | month specified integer month 165 | year specified integer year 166 | 167 | Returns: 168 | df a dataframe with the RHS terms of the constraint equation 169 | (returns empty dataframe if constraint equation not found) 170 | """ 171 | 172 | dict_RHS = {'term_ID' : [], 'ID' : [], 'type' : [], 'description' : [], 'factor' : [], 'operation' : []} 173 | 174 | df = get_mms_table(month, year, 'GENERICCONSTRAINTRHS') 175 | df.drop(index=df.index[-1],axis=0,inplace=True) 176 | 177 | df = df[df['GENCONID'] == constraint] 178 | 179 | if df.empty: 180 | print('Constraint equation not found...') 181 | else: 182 | # Lookup table for SCADA terms 183 | df_ems = get_mms_table(month, year, 'EMSMASTER') 184 | df_ems.drop(index=df_ems.index[-1],axis=0,inplace=True) 185 | 186 | for i in range(len(df)): 187 | spd_type = df['SPD_TYPE'].iloc[i] 188 | 189 | if spd_type in ['A', 'S', 'I', 'T', 'R']: 190 | # SCADA terms in EMSMASTER table 191 | df1 = df_ems[df_ems['SPD_ID'] == df['SPD_ID'].iloc[i]]['DESCRIPTION'] 192 | if df1.empty: 193 | desc = '-' 194 | else: 195 | desc = df1.iloc[0] 196 | 197 | elif spd_type == 'X': 198 | # Generic function 199 | # Note that generic functions may be defined in previous month/years and need to be found separately 200 | desc = 'Generic RHS function' 201 | 202 | else: 203 | desc = '-' 204 | 205 | str_op = str(df['OPERATION'].iloc[i]) 206 | if str_op == 'nan': 207 | str_op = '-' 208 | 209 | dict_RHS['term_ID'].append(df['TERMID'].iloc[i]) 210 | dict_RHS['ID'].append(df['SPD_ID'].iloc[i]) 211 | dict_RHS['description'].append(desc) 212 | dict_RHS['factor'].append(df['FACTOR'].iloc[i]) 213 | dict_RHS['operation'].append(str_op) 214 | dict_RHS['type'].append(spd_type) 215 | 216 | return pd.DataFrame(dict_RHS).sort_values(by='term_ID') 217 | 218 | 219 | def get_constraint_details(constraint, month, year): 220 | """Get details of a specific constraint equation formulation 221 | Inputs: 222 | constraint string of the specific constraint equation 223 | month specified integer month 224 | year specified integer year 225 | 226 | Returns: 227 | df a dataframe with the constraint description from GENCONDATA table 228 | LHS a dataframe with the LHS terms of the constraint equation 229 | RHS a dataframe with the LHS terms of the constraint equation 230 | 231 | (return empty dataframes if constraint equation not found) 232 | """ 233 | 234 | df = get_mms_table(month, year, 'GENCONDATA') 235 | df.drop(index=df.index[-1],axis=0,inplace=True) 236 | 237 | df = df[df['GENCONID'] == constraint] 238 | 239 | if df.empty: 240 | print('Constraint equation not found...') 241 | else: 242 | df = df.drop(df.columns[[0, 2, 3]],axis = 1) 243 | 244 | return df, get_LHS_terms(constraint, month, year), get_RHS_terms(constraint, month, year) 245 | 246 | 247 | def find_generic_RHS_func(equation_id, start_date = date(2009,7,1), end_date = None): 248 | """Find the last update of a specific generic RHS function 249 | Inputs: 250 | equation_id string of generic function ID to search for 251 | start_date date object for date to start search (optional, default is July 2009) 252 | end_date date object for date end search (optional, default is now) 253 | 254 | Returns: 255 | df a dataframe with the generic function description 256 | found True if constraint found, False otherwise 257 | """ 258 | 259 | df = pd.DataFrame() 260 | 261 | if not end_date: 262 | end_date = date.today() - relativedelta(months=2) 263 | 264 | cur_date = end_date 265 | 266 | while df.empty and cur_date > start_date: 267 | print('Searching archive from ' + cur_date.strftime("%B %Y")) 268 | df = get_mms_table(cur_date.month, cur_date.year, 'GENERICEQUATIONDESC') 269 | df.drop(index=df.index[-1],axis=0,inplace=True) 270 | 271 | df = df[df['EQUATIONID'].str.startswith(equation_id)] 272 | 273 | cur_date = cur_date - relativedelta(months=1) 274 | 275 | if df.empty: 276 | print('Generic RHS function not found over period ' + start_date.strftime("%B %Y") + ' and ' + end_date.strftime("%B %Y")) 277 | found = False 278 | else: 279 | df = df.drop(df.columns[[0, 2, 3]],axis = 1) 280 | print(df.iloc[0]) 281 | found = True 282 | 283 | return df, found 284 | 285 | 286 | def get_generic_RHS_func(equation_id, month, year): 287 | """Get terms for a generic RHS function 288 | Inputs: 289 | equation_id string of the specific generic function ID 290 | month specified integer month 291 | year specified integer year 292 | 293 | Returns: 294 | df a dataframe with the terms of the generic RHS function 295 | (returns empty dataframe if constraint equation not found) 296 | """ 297 | 298 | dict_RHS_func = {'term_ID' : [], 'ID' : [], 'type' : [], 'description' : [], 'factor' : [], 'operation' : []} 299 | 300 | df = get_mms_table(month, year, 'GENERICEQUATIONRHS') 301 | df.drop(index=df.index[-1],axis=0,inplace=True) 302 | 303 | df = df[df['EQUATIONID'] == equation_id] 304 | 305 | if df.empty: 306 | print('Generic RHS function not found...') 307 | else: 308 | # Lookup table for SCADA terms 309 | df_ems = get_mms_table(month, year, 'EMSMASTER') 310 | df_ems.drop(index=df_ems.index[-1],axis=0,inplace=True) 311 | 312 | for i in range(len(df)): 313 | spd_type = df['SPD_TYPE'].iloc[i] 314 | 315 | if spd_type in ['A', 'S', 'I', 'T', 'R']: 316 | # SCADA terms in EMSMASTER table 317 | df1 = df_ems[df_ems['SPD_ID'] == df['SPD_ID'].iloc[i]]['DESCRIPTION'] 318 | if df1.empty: 319 | desc = '-' 320 | else: 321 | desc = df1.iloc[0] 322 | else: 323 | desc = '-' 324 | 325 | str_op = str(df['OPERATION'].iloc[i]) 326 | if str_op == 'nan': 327 | str_op = '-' 328 | 329 | dict_RHS_func['term_ID'].append(df['TERMID'].iloc[i]) 330 | dict_RHS_func['ID'].append(df['SPD_ID'].iloc[i]) 331 | dict_RHS_func['description'].append(desc) 332 | dict_RHS_func['factor'].append(df['FACTOR'].iloc[i]) 333 | dict_RHS_func['operation'].append(str_op) 334 | dict_RHS_func['type'].append(spd_type) 335 | 336 | return pd.DataFrame(dict_RHS_func).sort_values(by='term_ID') -------------------------------------------------------------------------------- /NEMDE_constraints_demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 5, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from NEMDE_constraints import get_constraint_list, find_constraint, get_LHS_terms, get_RHS_terms, get_constraint_details, find_generic_RHS_func, get_generic_RHS_func\n", 10 | "from datetime import date" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "## Getting a list of constraint equations\n", 18 | "The constraint library in the MMS data archive is organised by month and year. The **get_constraint_list** function allows you to get a list of the constraint formulations contained in a specifc month/year, with optional filtering by a prefix. The example below gets a list of constraints in January 2023 for Queensland (prefix = 'Q_'):" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "name": "stdout", 28 | "output_type": "stream", 29 | "text": [ 30 | " GENCONID DESCRIPTION\n", 31 | "6814 Q_STR_7C2K_KBWF_9 Limit Kaban Solar Farm to 25% if Stan>=2+Stan+...\n", 32 | "6815 Q_STR_7C2K_KBWF_9_I Allowable turbine online indication for Kaban ...\n", 33 | "7496 Q_STR_7C8C_CLRSF Limit Clare Solar Farm output depends on the n...\n", 34 | "7497 Q_STR_7C8C_CLRSF_I Allowable Inverter online indication for Clare...\n", 35 | "7498 Q_STR_7C8C_COLSF Limit Collinsville Solar Farm output depends o...\n", 36 | "7499 Q_STR_7C8C_COLSF_I Allowable Inverter online indication for Colli...\n", 37 | "7500 Q_STR_7C8C_DAYSF Limit Daydream Solar Farm output depends on th...\n", 38 | "7501 Q_STR_7C8C_DAYSF_I Allowable Inverter online indication for Daydr...\n", 39 | "7502 Q_STR_7C8C_HAMSF Limit Hamilton Solar Farm output depends on th...\n", 40 | "7503 Q_STR_7C8C_HAMSF_I Allowable Inverter online indication for Hamil...\n", 41 | "7504 Q_STR_7C8C_HAYSF Limit Hayman Solar Farm output depends on the ...\n", 42 | "7505 Q_STR_7C8C_HAYSF_I Allowable Inverter online indication for Hayma...\n", 43 | "7506 Q_STR_7C8C_KBWF Limit Kaban Wind Farm output depends on the nu...\n", 44 | "7507 Q_STR_7C8C_KEP Limit Kennedy Energy Park output depends on th...\n", 45 | "7820 Q_STR_7C8C_KBWF_I Allowable turbine online indication for Kaban ...\n", 46 | "7821 Q_STR_7C8C_KEP_I Allowable Inverter online indication for Kenne...\n", 47 | "7822 Q_STR_7C8C_KIDSF Limit Kidston Solar Farm output depends on the...\n", 48 | "7823 Q_STR_7C8C_KIDSF_I Allowable Inverter online indication for Kidst...\n", 49 | "7824 Q_STR_7C8C_MEWF Limit Mt Emerald Wind Farm output depends on t...\n", 50 | "7825 Q_STR_7C8C_MEWF_I Allowable turbine online indication for Mt Eme...\n", 51 | "7826 Q_STR_7C8C_RRSF Limit Ross River Solar Farm output depends on ...\n", 52 | "7827 Q_STR_7C8C_RRSF_I Allowable Inverter online indication for Ross ...\n", 53 | "7828 Q_STR_7C8C_RUGSF Limit Rugby Run Solar Farm output depends on t...\n", 54 | "7829 Q_STR_7C8C_RUGSF_I Allowable Inverter online indication for Rugby...\n", 55 | "7830 Q_STR_7C8C_SMSF Limit Sun Metals Solar Farm output depends on ...\n", 56 | "8153 Q_STR_7C8C_SMSF_I Allowable Inverter online indication for Sun m...\n", 57 | "8154 Q_STR_7C8C_WHTSF Limit Whitsunday Solar Farm output depends on ...\n", 58 | "8155 Q_STR_7C8C_WHTSF_I Allowable Inverter online indication for Whits...\n", 59 | "9257 Q_STR_KBWF_N-2 Out = Nil, Loss of 858 and 857 declared credib...\n", 60 | "9258 Q_STR_KBWF_N-2_I Allowable turbine online indication for Kaban ...\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "df = get_constraint_list(1, 2023, 'Q_')" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": {}, 71 | "source": [ 72 | "## Searching for a specific constraint equation\n", 73 | "Because the MMS data archive is organised such that only the constraint equations that have been updated are included in the monthly archive, you sometimes have to search back through the archive to find a constraint formulation. The **find_constraint** function trawls back through the archive to find the last update of a constraint equation, for example 'V::S_NIL_MG_PP_2-DS':" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 4, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "name": "stdout", 83 | "output_type": "stream", 84 | "text": [ 85 | "Searching archive from February 2023\n", 86 | "Searching archive from January 2023\n", 87 | "Searching archive from December 2022\n", 88 | "GENCONDATA GENCONDATA\n", 89 | "EFFECTIVEDATE 2022/12/12 00:00:00\n", 90 | "VERSIONNO 1.0\n", 91 | "GENCONID V::S_NIL_MG_PP_2-DS\n", 92 | "CONSTRAINTTYPE <=\n", 93 | "CONSTRAINTVALUE 300.0\n", 94 | "DESCRIPTION Out =Nil(Note: with both Black Range series ca...\n", 95 | "STATUS NaN\n", 96 | "GENERICCONSTRAINTWEIGHT 35.0\n", 97 | "AUTHORISEDDATE 2022/12/12 13:41:07\n", 98 | "AUTHORISEDBY HAHASAN\n", 99 | "DYNAMICRHS 1.0\n", 100 | "LASTCHANGED 2022/12/12 13:41:14\n", 101 | "DISPATCH 1.0\n", 102 | "PREDISPATCH 0.0\n", 103 | "STPASA 0.0\n", 104 | "MTPASA 0.0\n", 105 | "IMPACT SA Generation + Interconnectors\n", 106 | "SOURCE Electranet\n", 107 | "LIMITTYPE Transient Stability\n", 108 | "REASON loss of Pelican Point PS declared as credible\n", 109 | "MODIFICATIONS Added Tailembend SF 2 and Tailembend Battery\n", 110 | "ADDITIONALNOTES Electranet limits advice dated 26/5/21.Added T...\n", 111 | "P5MIN_SCOPE_OVERRIDE NaN\n", 112 | "LRC 1.0\n", 113 | "LOR 1.0\n", 114 | "FORCE_SCADA 0.0\n", 115 | "Name: 3838, dtype: object\n" 116 | ] 117 | } 118 | ], 119 | "source": [ 120 | "df, found = find_constraint('V::S_NIL_MG_PP_2-DS')" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "Sometimes you don't want an earlier version of the constraint equations, so you can also search over a specific period of time, e.g. find the last update of the constraint equation 'V::S_NIL_MG_PP_2-DS' between January 2020 and January 2021:" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 6, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "name": "stdout", 137 | "output_type": "stream", 138 | "text": [ 139 | "Searching archive from January 2021\n", 140 | "Searching archive from December 2020\n", 141 | "Searching archive from November 2020\n", 142 | "Searching archive from October 2020\n", 143 | "GENCONDATA GENCONDATA\n", 144 | "EFFECTIVEDATE 2020/10/14 00:00:00\n", 145 | "VERSIONNO 1.0\n", 146 | "GENCONID V::S_NIL_MG_PP_2-DS\n", 147 | "CONSTRAINTTYPE <=\n", 148 | "CONSTRAINTVALUE 300.0\n", 149 | "DESCRIPTION Out =Nil(Note: with both Black Range series ca...\n", 150 | "STATUS NaN\n", 151 | "GENERICCONSTRAINTWEIGHT 35.0\n", 152 | "AUTHORISEDDATE 2020/10/14 08:05:47\n", 153 | "AUTHORISEDBY DHOOLE\n", 154 | "DYNAMICRHS 1.0\n", 155 | "LASTCHANGED 2020/10/14 08:05:47\n", 156 | "DISPATCH 1.0\n", 157 | "PREDISPATCH 0.0\n", 158 | "STPASA 0.0\n", 159 | "MTPASA 0.0\n", 160 | "IMPACT SA Generation + Interconnectors\n", 161 | "SOURCE Electranet\n", 162 | "LIMITTYPE Transient Stability\n", 163 | "REASON loss of Pelican Point PS declared as credible\n", 164 | "MODIFICATIONS Added Lake Bonney Battery\n", 165 | "ADDITIONALNOTES limits advice for S-NIL dated 30/05/2016 and e...\n", 166 | "P5MIN_SCOPE_OVERRIDE NaN\n", 167 | "LRC 1.0\n", 168 | "LOR 1.0\n", 169 | "FORCE_SCADA 0.0\n", 170 | "Name: 3672, dtype: object\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "df, found = find_constraint('V::S_NIL_MG_PP_2-DS', start_date = date(2020,1,1), end_date = date(2021,1,1))" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "## Getting LHS and RHS terms of a specific constraint equation\n", 183 | "The **get_LHS_terms** and **get_RHS_terms** functions get the left-hand and righ-hand side terms respectively for a specific constraint equation (at a given month/year). The function **get_constraint_details** is a wrapper that gets the constraint summary, LHS and RHS terms. \n", 184 | "\n", 185 | "For example, getting the LHS terms for the constraint equation 'V::S_NIL_MG_PP_2-DS' from December 2022:" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 8, 191 | "metadata": {}, 192 | "outputs": [ 193 | { 194 | "data": { 195 | "text/html": [ 196 | "
\n", 197 | "\n", 210 | "\n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | "
typeIDDUIDfactorbidtype
0CONNECTIONPOINTSLBB1LLBBG10.60ENERGY
1CONNECTIONPOINTSLBB2LLBBL1-0.86ENERGY
2CONNECTIONPOINTSMAY1LKBONNY10.80ENERGY
3CONNECTIONPOINTSMAY2LKBONNY20.82ENERGY
4CONNECTIONPOINTSMAY3WLKBONNY30.82ENERGY
5CONNECTIONPOINTSPEW1LADBROK10.13ENERGY
6CONNECTIONPOINTSPEW2LADBROK20.13ENERGY
7CONNECTIONPOINTSPPTDRYCNL31.20ENERGY
8CONNECTIONPOINTSSGA1SNUG10.80ENERGY
9CONNECTIONPOINTSSNN1CNUNDAWF0.52ENERGY
10CONNECTIONPOINTSTBB1TDUID not found0.14ENERGY
11CONNECTIONPOINTSTBB2TDUID not found0.14ENERGY
12CONNECTIONPOINTSTBB3TDUID not found-0.14ENERGY
13CONNECTIONPOINTSTBS1TTBSF10.14ENERGY
14INTERCONNECTORV-SAV-SA1.00N/A
\n", 344 | "
" 345 | ], 346 | "text/plain": [ 347 | " type ID DUID factor bidtype\n", 348 | "0 CONNECTIONPOINT SLBB1L LBBG1 0.60 ENERGY\n", 349 | "1 CONNECTIONPOINT SLBB2L LBBL1 -0.86 ENERGY\n", 350 | "2 CONNECTIONPOINT SMAY1 LKBONNY1 0.80 ENERGY\n", 351 | "3 CONNECTIONPOINT SMAY2 LKBONNY2 0.82 ENERGY\n", 352 | "4 CONNECTIONPOINT SMAY3W LKBONNY3 0.82 ENERGY\n", 353 | "5 CONNECTIONPOINT SPEW1 LADBROK1 0.13 ENERGY\n", 354 | "6 CONNECTIONPOINT SPEW2 LADBROK2 0.13 ENERGY\n", 355 | "7 CONNECTIONPOINT SPPT DRYCNL3 1.20 ENERGY\n", 356 | "8 CONNECTIONPOINT SSGA1 SNUG1 0.80 ENERGY\n", 357 | "9 CONNECTIONPOINT SSNN1 CNUNDAWF 0.52 ENERGY\n", 358 | "10 CONNECTIONPOINT STBB1T DUID not found 0.14 ENERGY\n", 359 | "11 CONNECTIONPOINT STBB2T DUID not found 0.14 ENERGY\n", 360 | "12 CONNECTIONPOINT STBB3T DUID not found -0.14 ENERGY\n", 361 | "13 CONNECTIONPOINT STBS1T TBSF1 0.14 ENERGY\n", 362 | "14 INTERCONNECTOR V-SA V-SA 1.00 N/A" 363 | ] 364 | }, 365 | "execution_count": 8, 366 | "metadata": {}, 367 | "output_type": "execute_result" 368 | } 369 | ], 370 | "source": [ 371 | "df = get_LHS_terms('V::S_NIL_MG_PP_2-DS', 12, 2022)\n", 372 | "df" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "metadata": {}, 378 | "source": [ 379 | "Similarly, the RHS terms for the constraint equation 'V::S_NIL_MG_PP_2-DS' from December 2022:" 380 | ] 381 | }, 382 | { 383 | "cell_type": "code", 384 | "execution_count": 9, 385 | "metadata": {}, 386 | "outputs": [ 387 | { 388 | "data": { 389 | "text/html": [ 390 | "
\n", 391 | "\n", 404 | "\n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | "
term_IDIDtypedescriptionfactoroperation
01.0CONSTANTC-736.00-
12.0SE_SUMLOAD_MWAMeasured South East MW Load1.69-
23.0SA_RFTOP_PV_SHAKEOFFG--1.00-
134.0X_SA_TOTAL_DPVXGeneric RHS function0.45-
145.0X_SA_DAYXGeneric RHS function1.00MUL
36.0OperatingMarginC--25.00-
47.0SWAMP_PELPT<MaxGenC-10000.00PUSH
58.0TOTAL_PelPt_GenG-1.00PUSH
159.0S_PPGT1.MWAPelican Point GT 1 Generation1.00-
1610.0S_PPST.MWAPelican Point Steam Turbine Generation1.00-
1711.0S_PPGT2.MWAPelican Point GT 2 Generation1.00-
1812.0X_SA_MAXG_ST_DSXGeneric RHS function-1.00-
1913.0NaNC-0.50-
2014.0NaNU-1.00STEP
615.0NaNU-1.00POP
716.0NaNU-1.00EXLEZ
817.0NaNU-1.00POP
918.0Swp_1OR2_BlkRg_C_ISC-10000.00PUSH
1019.0NO_OF_BLKRG_CAP_ISAIndicates the number of Black Range series cap...1.00PUSH
1120.0NaNU-1.00POP
1221.0NaNU-1.00EXLEZ
\n", 608 | "
" 609 | ], 610 | "text/plain": [ 611 | " term_ID ID type \\\n", 612 | "0 1.0 CONSTANT C \n", 613 | "1 2.0 SE_SUMLOAD_MW A \n", 614 | "2 3.0 SA_RFTOP_PV_SHAKEOFF G \n", 615 | "13 4.0 X_SA_TOTAL_DPV X \n", 616 | "14 5.0 X_SA_DAY X \n", 617 | "3 6.0 OperatingMargin C \n", 618 | "4 7.0 SWAMP_PELPT\n", 725 | "\n", 738 | "\n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | " \n", 816 | " \n", 817 | " \n", 818 | " \n", 819 | " \n", 820 | " \n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | "
term_IDIDtypedescriptionfactoroperation
01.0SA_TOTAL_DPV_CALCG-1.0-
52.0Calc1-SA_ASEFS2G-1.0-
103.0SA_ASEFS2E-1.0-
64.0Calc2-NonScada_PVNSGG-1.0PUSH
115.0SA_ASEFS2E-1.0-
126.0PVNSG_Capacity_65MWC-65.0MUL
137.0SA_ASEFS2_CapacityC-1804.0PUSH
148.0DIVU-1.0DIV
79.0Capc3-SAPN_Contrl_PVG-1.0PUSH
1510.0SA_SAPN_CONTRL_PVASA SAPN?s SCADA controlled non-market nonsched...1.0-
811.0Total_SA_NonSchd_PVU-1.0ADD
912.0Total_SA_NonSchd_PVU-1.0ADD
113.0HDRESS_DPV_OVERRIDEG-1.0PUSH
1616.0SA_ASEFS2_DEF_HDRESSADEFAULT ASEFS2 SCADA point hand dressed to 100...-1.0PUSH
1717.010000_CHECKC-10000.0-
1818.0NaNU-1.0STEP
1919.0PERIOD_ID_01E-1.0MUL
2020.0SA_ASEFS2_DEF_HDRESSADEFAULT ASEFS2 SCADA point hand dressed to 100...1.0MUL
221.0NaNU-1.0DUP
322.0Check_OverrideU-1.0POP
423.0Swap_Calc_OverrideU-1.0EXLEZ
\n", 942 | "" 943 | ], 944 | "text/plain": [ 945 | " term_ID ID type \\\n", 946 | "0 1.0 SA_TOTAL_DPV_CALC G \n", 947 | "5 2.0 Calc1-SA_ASEFS2 G \n", 948 | "10 3.0 SA_ASEFS2 E \n", 949 | "6 4.0 Calc2-NonScada_PVNSG G \n", 950 | "11 5.0 SA_ASEFS2 E \n", 951 | "12 6.0 PVNSG_Capacity_65MW C \n", 952 | "13 7.0 SA_ASEFS2_Capacity C \n", 953 | "14 8.0 DIV U \n", 954 | "7 9.0 Capc3-SAPN_Contrl_PV G \n", 955 | "15 10.0 SA_SAPN_CONTRL_PV A \n", 956 | "8 11.0 Total_SA_NonSchd_PV U \n", 957 | "9 12.0 Total_SA_NonSchd_PV U \n", 958 | "1 13.0 HDRESS_DPV_OVERRIDE G \n", 959 | "16 16.0 SA_ASEFS2_DEF_HDRESS A \n", 960 | "17 17.0 10000_CHECK C \n", 961 | "18 18.0 NaN U \n", 962 | "19 19.0 PERIOD_ID_01 E \n", 963 | "20 20.0 SA_ASEFS2_DEF_HDRESS A \n", 964 | "2 21.0 NaN U \n", 965 | "3 22.0 Check_Override U \n", 966 | "4 23.0 Swap_Calc_Override U \n", 967 | "\n", 968 | " description factor operation \n", 969 | "0 - 1.0 - \n", 970 | "5 - 1.0 - \n", 971 | "10 - 1.0 - \n", 972 | "6 - 1.0 PUSH \n", 973 | "11 - 1.0 - \n", 974 | "12 - 65.0 MUL \n", 975 | "13 - 1804.0 PUSH \n", 976 | "14 - 1.0 DIV \n", 977 | "7 - 1.0 PUSH \n", 978 | "15 SA SAPN?s SCADA controlled non-market nonsched... 1.0 - \n", 979 | "8 - 1.0 ADD \n", 980 | "9 - 1.0 ADD \n", 981 | "1 - 1.0 PUSH \n", 982 | "16 DEFAULT ASEFS2 SCADA point hand dressed to 100... -1.0 PUSH \n", 983 | "17 - 10000.0 - \n", 984 | "18 - 1.0 STEP \n", 985 | "19 - 1.0 MUL \n", 986 | "20 DEFAULT ASEFS2 SCADA point hand dressed to 100... 1.0 MUL \n", 987 | "2 - 1.0 DUP \n", 988 | "3 - 1.0 POP \n", 989 | "4 - 1.0 EXLEZ " 990 | ] 991 | }, 992 | "execution_count": 12, 993 | "metadata": {}, 994 | "output_type": "execute_result" 995 | } 996 | ], 997 | "source": [ 998 | "df = get_generic_RHS_func('X_SA_TOTAL_DPV', 11, 2022)\n", 999 | "df" 1000 | ] 1001 | } 1002 | ], 1003 | "metadata": { 1004 | "interpreter": { 1005 | "hash": "6f46228ee2210f97bb8eefaae85c045237848198f3335e35a28e6477d499b624" 1006 | }, 1007 | "kernelspec": { 1008 | "display_name": "Python 3.10.1 64-bit", 1009 | "language": "python", 1010 | "name": "python3" 1011 | }, 1012 | "language_info": { 1013 | "codemirror_mode": { 1014 | "name": "ipython", 1015 | "version": 3 1016 | }, 1017 | "file_extension": ".py", 1018 | "mimetype": "text/x-python", 1019 | "name": "python", 1020 | "nbconvert_exporter": "python", 1021 | "pygments_lexer": "ipython3", 1022 | "version": "3.10.1" 1023 | }, 1024 | "orig_nbformat": 4 1025 | }, 1026 | "nbformat": 4, 1027 | "nbformat_minor": 2 1028 | } 1029 | --------------------------------------------------------------------------------