network.py 15.6 KB
Newer Older
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
1
import logging
2
import numpy as np
3
4
5
from enum import Enum

class BusType(Enum):
Jan Dinkelbach's avatar
Jan Dinkelbach committed
6
7
8
9
10
11
12
    SLACK = 1
    slack = 1
    PV = 2
    pv = 2
    PQ = 3
    pq = 3

13

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
14
class Node():
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
15
    def __init__(self, uuid='', name='', base_voltage=1.0, base_apparent_power=1.0, v_mag=0.0,
16
                 v_phase=0.0, p=0.0, q=0.0, index=0, ideal_connected_with=''):
Jan Dinkelbach's avatar
Jan Dinkelbach committed
17
        self.uuid = uuid
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
18
        self.name = name
Jan Dinkelbach's avatar
Jan Dinkelbach committed
19
20
21
22
        self.index = index
        self.baseVoltage = base_voltage
        self.base_apparent_power = base_apparent_power
        self.base_current = self.base_apparent_power / self.baseVoltage / np.sqrt(3)
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
23
        self.type = BusType["PQ"]
24
        self.voltage = complex(v_mag * np.cos(np.radians(v_phase)), v_mag * np.sin(np.radians(v_phase)))
Jan Dinkelbach's avatar
Jan Dinkelbach committed
25
26
27
        self.power = complex(p, q)
        self.power_pu = complex(p, q) / self.base_apparent_power
        self.voltage_pu = self.voltage / self.baseVoltage
28
        self.ideal_connected_with = ideal_connected_with
Jan Dinkelbach's avatar
Jan Dinkelbach committed
29

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
30
31
32
33
34
35
    def __str__(self):
        str = 'class=Node\n'
        attributes = self.__dict__
        for key in attributes.keys():
            str = str + key + '={}\n'.format(attributes[key])
        return str
36
        
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
37

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
38
class Branch():
Jan Dinkelbach's avatar
Jan Dinkelbach committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    def __init__(self, uuid='', r=0.0, x=0.0, start_node=None, end_node=None,
                 base_voltage=1.0, base_apparent_power=1.0):
        self.uuid = uuid
        self.baseVoltage = base_voltage
        self.base_apparent_power = base_apparent_power
        self.base_current = self.base_apparent_power / self.baseVoltage / np.sqrt(3)
        self.base_impedance = base_voltage ** 2 / self.base_apparent_power
        self.start_node = start_node
        self.end_node = end_node
        self.r = r
        self.x = x
        self.z = self.r + 1j * self.x
        self.y = 1 / self.z if (self.z != 0) else float("inf")
        self.r_pu = r / self.base_impedance
        self.x_pu = x / self.base_impedance
        self.z_pu = self.r_pu + 1j * self.x_pu
        self.y_pu = 1 / self.z_pu if (self.z_pu != 0) else float("inf")

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
57
58
59
60
61
62
    def __str__(self):
        str = 'class=Branch\n'
        attributes = self.__dict__
        for key in attributes.keys():
            str = str + key + '={}\n'.format(attributes[key])
        return str
Jan Dinkelbach's avatar
Jan Dinkelbach committed
63

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
64
65

class Breaker():
66
    def __init__(self, from_node, to_node, is_open=True):
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
67
        """
68
69
70
        :param from_node:
        :param to_node:
        :param is_open: True if the breaker is considered open and False if the broker is closed 
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
71
        """
72
73
74
        self.from_node = from_node
        self.to_node = to_node
        self.is_open = is_open
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
75
76
77
78
79
80
81
82

    def __str__(self):
        str = 'class=Breaker\n'
        attributes = self.__dict__
        for key in attributes.keys():
            str = str + key + '={}\n'.format(attributes[key])
        return str

83
    def open_breaker(self):
84
85
        self.is_open == True
        self.to_node.ideal_connected_with = ''
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
86

87
88
89
    def close_breaker(self):
        self.is_open == False
        self.to_node.ideal_connected_with = self.from_node.uuid
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
90

91

Jan Dinkelbach's avatar
Jan Dinkelbach committed
92
93
94
95
class System():
    def __init__(self):
        self.nodes = []
        self.branches = []
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
96
        self.breakers = []
Jan Dinkelbach's avatar
Jan Dinkelbach committed
97
98
        self.Ymatrix = np.zeros([], dtype=np.complex)
        self.Adjacencies = np.array([])
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
99

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
100
101
102
103
104
105
106
    def get_node_by_uuid(self, node_uuid):
        for node in self.nodes:
            if node.uuid == node_uuid:
                return node
        
        return False

107
108
    def get_node_by_index(self, index):
        """
109
        return the node with node.index==index 
110
111
112
113
114
115
        """
        for node in self.nodes:
            if (node.index==index) and (node.ideal_connected_with=='') :
                return node
        
        return None
116
           
117
118
119
    def get_nodes_num(self):
        """
        return the number of nodes in the list system.nodes
120
121
        Warning: if any node is ideally connected to another node, 
        the counter is increased only one time
122
123
124
        """
        nodes_num=0
        for node in self.nodes:
125
            if node.ideal_connected_with=='':
126
127
128
129
                nodes_num+=1

        return nodes_num

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
130
    def reindex_nodes_list(self):
131
132
133
134
135
        """
        Reenumerate the nodes in system.nodes
        If any node is ideally connected to another node, 
        both receive the same index
        """
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
136
        index = 0
137
        remaining_nodes_list = []
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
138
        for node in self.nodes:
139
            if node.ideal_connected_with=='':
140
141
142
143
144
145
                node.index=index
                index+=1
            else:
                remaining_nodes_list.append(node)

        for node in remaining_nodes_list:
146
            node.index = self.get_node_by_uuid(node.ideal_connected_with).index
147
             
Jan Dinkelbach's avatar
Jan Dinkelbach committed
148
149
    def load_cim_data(self, res, base_apparent_power):
        """
150
        fill the vectors node, branch and breakers
Jan Dinkelbach's avatar
Jan Dinkelbach committed
151
152
        """
        index = 0
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
153
154
155
        list_TPNode = [elem for elem in res.values() if elem.__class__.__name__ == "TopologicalNode"]
        list_SvVoltage = [elem for elem in res.values() if elem.__class__.__name__ == "SvVoltage"]
        list_SvPowerFlow = [elem for elem in res.values() if elem.__class__.__name__ == "SvPowerFlow"]
156
157
        list_EnergySources = [elem for elem in res.values() if elem.__class__.__name__ == "EnergySource"]
        list_EnergyConsumer = [elem for elem in res.values() if elem.__class__.__name__ == "EnergyConsumer"]
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
158
159
160
        list_ACLineSegment = [elem for elem in res.values() if elem.__class__.__name__ == "ACLineSegment"]
        list_PowerTransformer = [elem for elem in res.values() if elem.__class__.__name__ == "PowerTransformer"]
        list_Terminals = [elem for elem in res.values() if elem.__class__.__name__ == "Terminal"]
161
162
        list_Terminals_ES = [elem for elem in list_Terminals if elem.ConductingEquipment.__class__.__name__ == "EnergySource"]
        list_Terminals_EC = [elem for elem in list_Terminals if elem.ConductingEquipment.__class__.__name__ == "EnergyConsumer"]
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
163
        list_PowerTransformerEnds = [elem for elem in res.values() if elem.__class__.__name__ == "PowerTransformerEnd"]
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
164
        list_Breakers = [elem for elem in res.values() if elem.__class__.__name__ == "Breaker"]
165
           
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
166
167
        #create nodes
        for TPNode in list_TPNode:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
168
            uuid_TPNode = TPNode.mRID
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
169
            name = TPNode.name
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
170
171
172
            vmag = 0.0
            vphase = 0.0
            pInj = 0.0
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
173
            qInj = 0.0
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
174
175
                
            for obj_SvVoltage in list_SvVoltage:
176
                if obj_SvVoltage.TopologicalNode.mRID == uuid_TPNode:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
177
178
179
180
                    vmag = obj_SvVoltage.v
                    vphase = obj_SvVoltage.angle
                    break
            for obj_SvPowerFlow in list_SvPowerFlow:
181
                if obj_SvPowerFlow.Terminal.TopologicalNode.mRID == uuid_TPNode:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
182
                    pInj += obj_SvPowerFlow.p
183
                    qInj += obj_SvPowerFlow.q           
184
185
186
187
188
189
190
191
192
193
194
195
            for obj_Terminal in list_Terminals_ES:
                if obj_Terminal.TopologicalNode.mRID == uuid_TPNode:
                    for obj_EnergySource in list_EnergySources:
                        if obj_EnergySource.mRID == obj_Terminal.ConductingEquipment.mRID:
                            pInj += obj_EnergySource.activePower
                            qInj += obj_EnergySource.reactivePower
            for obj_Terminal in list_Terminals_EC:
                if obj_Terminal.TopologicalNode.mRID == uuid_TPNode:
                    for obj_EnergyConsumer in list_EnergyConsumer:
                        if obj_EnergyConsumer.mRID == obj_Terminal.ConductingEquipment.mRID:
                            pInj += obj_EnergyConsumer.p
                            qInj += obj_EnergyConsumer.q
196
            
197
            base_voltage = TPNode.BaseVoltage.nominalVoltage
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
198
            self.nodes.append(Node(name=name, uuid=uuid_TPNode, base_voltage=base_voltage, v_mag=vmag,
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
199
                                   base_apparent_power=base_apparent_power, v_phase=vphase,
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
200
                                   p=pInj, q=qInj, index=index))
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
201
202
            index = index + 1
        
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
203
204
        self._setNodeType(list_Terminals)   

205
        #create branches type ACLineSegment
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
206
        for ACLineSegment in list_ACLineSegment:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
207
            uuid_ACLineSegment = ACLineSegment.mRID
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
208
209
210
            nodes = self._get_nodes(list_Terminals, uuid_ACLineSegment)
            start_node = nodes[0]
            end_node = nodes[1]
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
211

212
            base_voltage = ACLineSegment.BaseVoltage.nominalVoltage
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
213
            self.branches.append(Branch(uuid=uuid_ACLineSegment, r=ACLineSegment.r, x=ACLineSegment.x, 
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
214
215
216
                                        start_node=start_node, end_node=end_node, 
                                        base_voltage=base_voltage, base_apparent_power=base_apparent_power))

217
        #create branches type powerTransformer
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
218
        for power_transformer in list_PowerTransformer:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
219
            uuid_power_transformer = power_transformer.mRID
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
220
221
222
223
            nodes = self._get_nodes(list_Terminals, uuid_power_transformer)
            start_node = nodes[0]
            end_node = nodes[1]
            
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
224
            # base voltage = high voltage side (=primaryConnection)
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
225
            primary_connection = self._get_primary_connection(list_PowerTransformerEnds, uuid_power_transformer)
226
            base_voltage = primary_connection.BaseVoltage.nominalVoltage
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
227
            self.branches.append(Branch(uuid=uuid_power_transformer, r=primary_connection.r, x=primary_connection.x,
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
228
229
230
                                        start_node=start_node, end_node=end_node, base_voltage=base_voltage,
                                        base_apparent_power=base_apparent_power))

231
        #create breakers
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
232
        for obj_Breaker in list_Breakers:
233
            is_open = obj_Breaker.normalOpen
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
234
            nodes = self._get_nodes(list_Terminals, obj_Breaker.mRID)
235
            self.breakers.append(Breaker(from_node=nodes[0], to_node=nodes[1], is_open=is_open))
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
236

237
238
            #if the breaker is open == closed --> close broker
            if is_open == False:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
239
                self.breakers[-1].close_breaker(self)
240
241
            else:
                self.breakers[-1].ideal_connected_with = ''
242
            
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
243
        #calculate admitance matrix
Jan Dinkelbach's avatar
Jan Dinkelbach committed
244
        self.Ymatrix_calc()
245

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
246
    def _get_nodes(self, list_Terminals, elem_uuid):
247
        """
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
248
249
250
        get the the start and end node of the element with uuid=elem_uuid
        This function can used only with element which are connected 
        to 2 topologicalNodes, for example: ACLineSegment, PowerTransformer and Breaker 
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
251
        :param list_Terminals: list of all elements of type Terminal
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
252
        :param elem_uuid: uuid of the element for which the start and end node ID are searched
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
253
        :return list: [startNodeID, endNodeID]
254
        """
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
255
256
        start_node_uuid = None
        end_node_uuid = None
257
        
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
258
        for terminal in list_Terminals:
259
            if (terminal.ConductingEquipment.mRID != elem_uuid):
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
260
                continue
261
            sequence_number = terminal.sequenceNumber            
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
262
            if sequence_number == 1:
263
                start_node_uuid = terminal.TopologicalNode.mRID
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
264
            elif sequence_number == 2:
265
                end_node_uuid = terminal.TopologicalNode.mRID
266
        
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
267
268
269
270
271
272
273
274
275
276
277
278
        start_node = None
        end_node = None
        if (start_node_uuid==None):
            print('WARNING: It could not find a start node for the element with uuid={}'.format(elem_uuid))
        else:
            start_node = self.get_node_by_uuid(start_node_uuid)
        if (end_node_uuid==None):
            print('WARNING: It could not find a end node for the element with uuid={}'.format(elem_uuid))
        else:
            end_node = self.get_node_by_uuid(end_node_uuid)

        return [start_node, end_node]
279

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
280
    def _get_primary_connection(self, list_PowerTransformerEnds, elem_uuid):
281
        """
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
282
        get primaryConnection of the powertransformer with uuid = elem_uuid
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
283
        :param list_PowerTransformerEnd: list of all elements of type PowerTransformerEnd
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
284
        :param elem_uuid: uuid of the power transformer for which the primary connection is searched
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
285
        :return: primary_connection
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
286
        """
287
        primary_connection = None
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
288
289
        power_transformer_ends = []

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
290
        #search for two elements of class powertransformerend that point to the powertransformer with ID = elem_uuid
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
291
        for power_transformer_end in list_PowerTransformerEnds:
292
293
294
295
296
297
298
299
300
301
            power_transformer = None
            if isinstance(power_transformer_end.PowerTransformer, list):
                if (len(power_transformer_end.PowerTransformer)!=1):
                    print('WARNING: len(power_transformer_end.PowerTransformer)!=1 for the element with uuid={}. \
                        The first element will be used'.format(power_transformer_end.mRID))
                power_transformer = power_transformer_end.PowerTransformer[0]
            else:
                power_transformer = power_transformer_end.PowerTransformer
        
            if power_transformer.mRID == elem_uuid:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
302
                power_transformer_ends.append(power_transformer_end)
303

304
        if power_transformer_ends[0].BaseVoltage.nominalVoltage>=power_transformer_ends[1].BaseVoltage.nominalVoltage:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
305
            primary_connection=power_transformer_ends[0]
306
        elif power_transformer_ends[1].BaseVoltage.nominalVoltage>=power_transformer_ends[0].BaseVoltage.nominalVoltage:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
307
            primary_connection=power_transformer_ends[1]
308
309
310

        return primary_connection

Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
311
    def _setNodeType(self, list_Terminals):
Jan Dinkelbach's avatar
Jan Dinkelbach committed
312
        """
313
        set the parameter "type" of all elements of the list self.nodes
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
314
315
        :param list_PowerTransformerEnd: list of all elements of type Terminal
        :return None
Jan Dinkelbach's avatar
Jan Dinkelbach committed
316
        """
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
317
        #get a list of Terminals for which the ConductingEquipment is a element of class ExternalNetworkInjection
318
        list_Terminals_ENI = [elem for elem in list_Terminals if elem.ConductingEquipment.__class__.__name__ == "ExternalNetworkInjection"]
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
319
        for terminal in list_Terminals_ENI:
320
            node_uuid = terminal.TopologicalNode.mRID
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
321
            for node in self.nodes:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
322
                if node.uuid == node_uuid:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
323
324
                    node.type = BusType["SLACK"]
            
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
325
326
        #TODO the search for PV nodes has not been tested yet
        #get a list of Terminals for which the ConductingEquipment is a element of class SynchronousMachine
327
        list_Terminals_SM = [elem for elem in list_Terminals if elem.ConductingEquipment.__class__.__name__ == "SynchronousMachine"]
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
328
        for terminal in list_Terminals_SM:
329
            node_uuid = terminal.TopologicalNode.mRID
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
330
            for node in self.nodes:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
331
                if node.uuid == node_uuid:
Martin Alonso Moraga's avatar
Martin Alonso Moraga committed
332
                    node.type = BusType["PV"]
333

Jan Dinkelbach's avatar
Jan Dinkelbach committed
334
    def Ymatrix_calc(self):
335
336
        self.reindex_nodes_list()
        nodes_num = self.get_nodes_num()
Jan Dinkelbach's avatar
Jan Dinkelbach committed
337
        self.Ymatrix = np.zeros((nodes_num, nodes_num), dtype=np.complex)
338
        self.Bmatrix = np.zeros((nodes_num, nodes_num), dtype=np.complex)
Jan Dinkelbach's avatar
Jan Dinkelbach committed
339
340
341
342
343
344
        self.Adjacencies = [[] for _ in range(nodes_num)]
        for branch in self.branches:
            fr = branch.start_node.index
            to = branch.end_node.index
            self.Ymatrix[fr][to] -= branch.y_pu
            self.Ymatrix[to][fr] -= branch.y_pu
345
346
347
348
349
350
            self.Ymatrix[fr][fr] += branch.y_pu  # + branch.b_pu
            self.Ymatrix[to][to] += branch.y_pu  # + branch.b_pu
            # self.Bmatrix[fr][to] += branch.b_pu
            # self.Bmatrix[to][fr] += branch.b_pu
            self.Adjacencies[fr].append(to + 1)  # to + 1??? To be checked 
            self.Adjacencies[to].append(fr + 1)  # fr + 1??? To be checked
351
352
353
354
355
356
357
358
359
360
361
362
363
364
    
    #Testing functions
    def print_nodes_names(self):
        for node in self.nodes:
            print('{} {}'.format(node.name, node.index))

    def print_node_types(self):
        for node in self.nodes:
            print('{} {}'.format(node.name, node.type))
    
    def print_power(self):
        for node in self.nodes:
            print('{} {}'.format(node.name, node.power))