1
+ """
2
+ Function to compute the Joule law for power grid use case
3
+ """
4
+ from typing import Union
5
+ import numpy as np
6
+
7
+ from lightsim2grid import LightSimBackend
8
+ from grid2op .Backend import PandaPowerBackend
9
+
10
+ from ...logger import CustomLogger
11
+
12
+ def verify_joule_law (predictions : dict ,
13
+ log_path : Union [str , None ]= None ,
14
+ result_level : int = 0 ,
15
+ ** kwargs ):
16
+ """
17
+ This function compute Joule's law which is ``$PL = R \t imes I^2$``
18
+
19
+ To compute the PowerLoss(PL), we use : ``$p_ex + p_or$``
20
+ The resistance values are deduced from backends.
21
+ The currents are computed as follows : ``$\f rac{a_or + a_ex}{2}$``
22
+
23
+ TODO : consider the line disconnections
24
+
25
+ Parameters
26
+ ----------
27
+ predictions: ``dict``
28
+ Predictions made by an augmented simulator
29
+ log_path: ``str``, optional
30
+ the path where the logs should be saved, by default None
31
+ **kwargs: ``dict``
32
+ It should contain `observations` and `config` to load the tolerance
33
+
34
+ Returns
35
+ -------
36
+ ``dict``
37
+ a dictionary with useful information about the verification
38
+
39
+ These informations are:
40
+ - mae_per_obs: `array`
41
+ The Absolute error (difference) between two sides of Joule equation per observation
42
+
43
+ - mae_per_obj: `array`
44
+ The Absolute Error (difference) between two sides of Joule equation per line for each observation
45
+
46
+ - mae: `scalar`
47
+ the Mean Absolute Error (difference) between two sides of the Joule equation
48
+
49
+ - wmape: `scalar`
50
+ weighted mean absolute percentage error between two sides of the Joule equation
51
+
52
+ - violation_proportion: `scalar`
53
+ The percentage of violation of Joule law over all the observations
54
+ """
55
+ # logger
56
+ logger = CustomLogger ("PhysicsCompliances(Joule_law)" , log_path ).logger
57
+
58
+ try :
59
+ env = kwargs ["env" ]
60
+ except KeyError :
61
+ logger .error ("The requirements were not satisiftied to verify_joule_law function" )
62
+ raise
63
+
64
+ try :
65
+ config = kwargs ["config" ]
66
+ except KeyError :
67
+ try :
68
+ tolerance = kwargs ["tolerance" ]
69
+ except KeyError :
70
+ logger .error ("The tolerance could not be found for verify_joule_law function" )
71
+ raise
72
+ else :
73
+ tolerance = float (tolerance )
74
+ else :
75
+ tolerance = float (config .get_option ("eval_params" )["JOULE_tolerance" ])
76
+
77
+ if isinstance (env .backend , LightSimBackend ):
78
+ # extract the impedance values which are in p.u
79
+ z_pu = [el .r_pu for el in env .backend ._grid .get_lines ()]
80
+ z_pu_trafo = [el .r_pu for el in env .backend ._grid .get_trafos ()]
81
+ # enumerate lines and trafos
82
+ n_lines = len (z_pu )
83
+ n_trafo = len (z_pu_trafo )
84
+ # transform pu to Ohm and keep only lines
85
+ z_base = np .power (env .backend .lines_or_pu_to_kv , 2 ) / env .backend ._grid .get_sn_mva ()
86
+ r_ohm = z_pu * z_base [:n_lines ]
87
+
88
+ elif isinstance (env .backend , PandaPowerBackend ):
89
+ # n_lines
90
+ n_lines = len (env .backend ._grid .line )
91
+ # n_transformers
92
+ n_transformers = len (env .backend ._grid .trafo )
93
+ # get the resistance
94
+ r_ohm = np .array (env .backend ._grid .line ["r_ohm_per_km" ]).reshape (1 ,- 1 )
95
+
96
+ verifications = dict ()
97
+ mean_current = _get_average_currents (predictions )
98
+ mean_current_squared = np .power (mean_current , 2 )
99
+ pl_mw = _get_power_loss (predictions )
100
+ # MAE between PL and R.I^2
101
+ left_array = pl_mw [:, :n_lines ]/ 3
102
+ right_array = mean_current_squared [:, :n_lines ] * r_ohm
103
+ mae_per_obj = np .abs (left_array - right_array )
104
+ mae_per_obs = mae_per_obj .mean (axis = 1 )
105
+ mae = np .mean (mae_per_obs )
106
+ wmape = np .mean (np .abs (left_array - right_array )) / np .mean (np .abs (left_array ))
107
+ violation_prop = np .sum (mae_per_obj > tolerance ) / mae_per_obj .size
108
+
109
+ logger .info ("Joule law violation proportion: %.3f" , violation_prop )
110
+ logger .info ("MAE for Joule law: %.3f" , mae )
111
+ logger .info ("WMAPE for Joule law: %.3f" , wmape )
112
+
113
+ if result_level > 0 :
114
+ verifications ["mae_per_obs" ] = mae_per_obs
115
+ verifications ["mae_per_obj" ] = mae_per_obj
116
+ verifications ["violation_proportion" ] = violation_prop
117
+ verifications ["mae" ] = mae
118
+ verifications ["wmape" ] = wmape
119
+
120
+ return verifications
121
+
122
+ def _get_average_currents (data : dict ) -> np .ndarray :
123
+ """Compute the average current using current from both extremities of a power line
124
+
125
+ Parameters
126
+ ----------
127
+ data : ``dict``
128
+ A python dictionary including currents as keys
129
+
130
+ Returns
131
+ -------
132
+ np.array
133
+ average matrix of currents
134
+ """
135
+ current_avg = np .abs (data .get ("a_or" ) + data .get ("a_ex" ))/ 2
136
+ current_avg_kA = current_avg / 1000
137
+ return current_avg_kA
138
+
139
+ def _get_power_loss (data : dict ) -> np .ndarray :
140
+ """Compute the power loss from powers at two extremities of power lines
141
+
142
+ Parameters
143
+ ----------
144
+ data : ``dict``
145
+ A python dictionary including powers as keys
146
+
147
+ Returns
148
+ -------
149
+ ``np.ndarray``
150
+ Power losses array at the line level for all the observations
151
+ """
152
+ pl_mw = np .abs (data .get ("p_or" ) + data .get ("p_ex" ))
153
+ return pl_mw
0 commit comments