Skip to content

Commit 7f0504c

Browse files
authored
Upload main extension files, first version
1 parent ba70dc6 commit 7f0504c

File tree

2 files changed

+1755
-0
lines changed

2 files changed

+1755
-0
lines changed
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
import sys
5+
from copy import deepcopy
6+
from io import StringIO
7+
8+
from Qt import QtCompat, QtWidgets
9+
from electricalsim.extensions.extension_classes import ExtensionBase
10+
import pandas as pd
11+
import pandapower as pp
12+
from pandapower.pypower.printpf import printpf
13+
from pandapower.pypower.ppoption import ppoption
14+
15+
16+
directory = os.path.dirname(__file__)
17+
input_ui_path = os.path.join(directory, 'input.ui')
18+
19+
20+
class Extension(ExtensionBase):
21+
def __init__(self, **kwargs):
22+
super().__init__(**kwargs)
23+
24+
self.set_separate_thread(True)
25+
self.set_extension_window(True)
26+
27+
self.input_dialog = QtCompat.loadUi(uifile=input_ui_path)
28+
self.input_dialog.setWindowIcon(self.egs_icon())
29+
30+
self.compute_opf = False
31+
self.data = None
32+
33+
self.copy_net = None # Deepcopy of the network just before OPF calculation
34+
35+
def read_data(self):
36+
"""
37+
Returns the DataFrame with data costs. Colums are:
38+
39+
'name', 'element_type', 'element_id',
40+
'bus', 'cp1_eur_per_mw', 'cp0_eur',
41+
'cq1_eur_per_mvar', 'cq0_eur',
42+
'cp2_eur_per_mw2', 'cq2_eur_per_mvar2'
43+
"""
44+
data = pd.DataFrame(columns=['name', 'element_type', 'element_id',
45+
'bus', 'cp1_eur_per_mw', 'cp0_eur',
46+
'cq1_eur_per_mvar', 'cq0_eur',
47+
'cp2_eur_per_mw2', 'cq2_eur_per_mvar2']) # Empty DataFrame
48+
49+
for _, row in self.net.poly_cost.iterrows():
50+
new_data_row = pd.Series(index=['name', 'element_type', 'element_id',
51+
'bus', 'cp1_eur_per_mw', 'cp0_eur',
52+
'cq1_eur_per_mvar', 'cq0_eur',
53+
'cp2_eur_per_mw2', 'cq2_eur_per_mvar2'])
54+
55+
type_ = row['et']
56+
element_id = row['element']
57+
new_data_row['element_id'] = element_id
58+
new_data_row['element_type'] = type_
59+
new_data_row['cp0_eur'] = row['cp0_eur']
60+
new_data_row['cp1_eur_per_mw'] = row['cp1_eur_per_mw']
61+
new_data_row['cp2_eur_per_mw2'] = row['cp2_eur_per_mw2']
62+
new_data_row['cq0_eur'] = row['cq0_eur']
63+
new_data_row['cq1_eur_per_mvar'] = row['cq1_eur_per_mvar']
64+
new_data_row['cq2_eur_per_mvar2'] = row['cq2_eur_per_mvar2']
65+
new_data_row['name'] = self.net[type_].at[element_id, 'name']
66+
if type_=='dcline':
67+
bus_id_from = self.net.dcline.at[element_id, 'bus_from']
68+
bus_id_to = self.net.dcline.at[element_id, 'bus_to']
69+
70+
bus_name_from = self.net.bus.at[bus_id_from, 'name']
71+
bus_name_to = self.net.bus.at[bus_id_to, 'name']
72+
73+
new_data_row['bus'] = f"{bus_id_from} - {bus_id_to} ({bus_name_from} - {bus_name_to})"
74+
else:
75+
bus_id = self.net[type_].at[element_id, 'bus']
76+
bus_name = self.net.bus.at[bus_id, 'name']
77+
new_data_row['bus'] = f"{bus_id} ({bus_name})"
78+
79+
data = pd.concat((data, new_data_row.to_frame().T), axis=0, ignore_index=True)
80+
81+
return data
82+
83+
def __call__(self):
84+
if self.compute_opf is False:
85+
return
86+
87+
self.input_dialog.selector.currentTextChanged.disconnect()
88+
self.net.poly_cost = self.net.poly_cost.iloc[0:0] # Removing old data...
89+
90+
for index, row in self.data.iterrows():
91+
pp.create_poly_cost(self.net, element=row['element_id'], et=row['element_type'],
92+
cp1_eur_per_mw=row['cp1_eur_per_mw'],
93+
cp0_eur=row['cp0_eur'],
94+
cq1_eur_per_mvar=row['cq1_eur_per_mvar'],
95+
cq0_eur=row['cq0_eur'],
96+
cp2_eur_per_mw2=row['cp2_eur_per_mw2'],
97+
cq2_eur_per_mvar2=row['cq2_eur_per_mvar2'],
98+
index=index, check=True)
99+
100+
# Removing piecewise linear costs...
101+
self.net.pwl_cost = self.net.pwl_cost.iloc[0:0]
102+
103+
# Solving the OPF...
104+
calculate_voltage_angles = self.input_dialog.calculate_voltage_angles.isChecked()
105+
switch_rx_ratio = self.input_dialog.switch_rx_ratio.value()
106+
delta = self.input_dialog.delta.value() * 1e-6
107+
init = self.input_dialog.init.currentText()
108+
trafo3w_losses = self.input_dialog.trafo3w_losses.currentText()
109+
consider_line_temperature = self.input_dialog.consider_line_temperature.isChecked()
110+
self.copy_net = deepcopy(self.net)
111+
112+
# tmp = sys.stdout # Capture old stdout with a temporary variable
113+
my_result = StringIO()
114+
# sys.stdout = my_result # New stdout liked to the new StrigIO object
115+
if self.input_dialog.radio_acopf.isChecked():
116+
pp.runopp(self.copy_net, verbose=False,
117+
calculate_voltage_angles=calculate_voltage_angles,
118+
check_connectivity=True, suppress_warnings=True,
119+
switch_rx_ratio=switch_rx_ratio, delta=delta, init=init,
120+
numba=True, trafo3w_losses=trafo3w_losses,
121+
consider_line_temperature=consider_line_temperature,
122+
OPF_VIOLATION=self.input_dialog.OPF_VIOLATION.value()*1e-6,
123+
PDIPM_COSTTOL=self.input_dialog.PDIPM_COSTTOL.value()*1e-6,
124+
PDIPM_GRADTOL=self.input_dialog.PDIPM_GRADTOL.value()*1e-6,
125+
PDIPM_COMPTOL=self.input_dialog.PDIPM_COMPTOL.value()*1e-6,
126+
PDIPM_FEASTOL=self.input_dialog.PDIPM_FEASTOL.value()*1e-6,
127+
PDIPM_MAX_IT=self.input_dialog.PDIPM_MAX_IT.value(),
128+
SCPDIPM_RED_IT=self.input_dialog.SCPDIPM_RED_IT.value())
129+
else:
130+
pp.rundcopp(self.copy_net, verbose=False, check_connectivity=True,
131+
suppress_warnings=True, switch_rx_ratio=switch_rx_ratio,
132+
delta=delta, trafo3w_losses=trafo3w_losses)
133+
# sys.stdout = tmp # Back to normal
134+
135+
if not self.copy_net['OPF_converged']:
136+
title = 'Not converged!'
137+
text_content = 'Solver did not converge.'
138+
QtWidgets.QMessageBox.critical(self.input_dialog, title, text_content)
139+
return
140+
141+
ac = self.copy_net["_options"]["ac"]
142+
if ac is True:
143+
result = self.copy_net['_ppc_opf']
144+
init = self.copy_net["_options"]["init"]
145+
ppopt = ppoption(VERBOSE=True, PF_DC=not ac, INIT=init)
146+
ppopt['OUT_ALL'] = 1
147+
148+
printpf(baseMVA=result["baseMVA"], bus=result["bus"], gen=result["gen"],
149+
branch=result["branch"], f=result["f"], success=result["success"],
150+
et=result["et"], fd=my_result, ppopt=ppopt)
151+
152+
self.print(my_result.getvalue())
153+
else:
154+
self.print(f'OPTIMIZED COST: {self.compute_cost()}')
155+
156+
self.print('\n\nGENERATORS:')
157+
self.print(self.copy_net.res_gen.to_string())
158+
159+
self.print('\n\nSTATIC GENERATORS:')
160+
self.print(self.copy_net.res_sgen.to_string())
161+
162+
self.print('\n\nBUSES:')
163+
self.print(self.copy_net.res_bus.to_string())
164+
165+
self.print('\n\nEXTERNAL GRIDS:')
166+
self.print(self.copy_net.res_ext_grid.to_string())
167+
168+
self.print('\n\nLINES:')
169+
self.print(self.copy_net.res_line.to_string())
170+
171+
self.print('\n\nDC LINES:')
172+
self.print(self.copy_net.res_dcline.to_string())
173+
174+
self.print('\n\nSTORAGES:')
175+
self.print(self.copy_net.res_storage.to_string())
176+
177+
self.print('\n\nTWO-WINDING TRANSFORMERS:')
178+
self.print(self.copy_net.res_trafo.to_string())
179+
180+
self.print('\n\nTHREE-WINDING TRANSFORMERS:')
181+
self.print(self.copy_net.res_trafo3w.to_string())
182+
183+
self.print('\n\nLOADS:')
184+
self.print(self.copy_net.res_load.to_string())
185+
186+
def before_running(self):
187+
"""
188+
Exceuted just before showing the extension dialog.
189+
"""
190+
self.data = self.read_data()
191+
192+
# Reading data...
193+
for row in self.net.gen.iterrows():
194+
id_ = row[0]
195+
cols = row[1][['name', 'bus']]
196+
if id_ in self.data['element_id'].values and cols['name'] in self.data['name'].values:
197+
continue
198+
cols['bus'] = f"{cols['bus']} ({self.net.bus.at[cols['bus'], 'name']})"
199+
cols['element_type'] = 'gen'
200+
cols['element_id'] = id_
201+
self.data = pd.concat((self.data, cols.to_frame().T), axis=0, ignore_index=True)
202+
203+
for row in self.net.sgen.iterrows():
204+
id_ = row[0]
205+
cols = row[1][['name', 'bus']]
206+
if id_ in self.data['element_id'].values and cols['name'] in self.data['name'].values:
207+
continue
208+
cols['bus'] = f"{cols['bus']} ({self.net.bus.at[cols['bus'], 'name']})"
209+
cols['element_type'] = 'sgen'
210+
cols['element_id'] = id_
211+
self.data = pd.concat((self.data, cols.to_frame().T), axis=0, ignore_index=True)
212+
213+
for row in self.net.ext_grid.iterrows():
214+
id_ = row[0]
215+
cols = row[1][['name', 'bus']]
216+
if id_ in self.data['element_id'].values and cols['name'] in self.data['name'].values:
217+
continue
218+
cols['bus'] = f"{cols['bus']} ({self.net.bus.at[cols['bus'], 'name']})"
219+
cols['element_type'] = 'ext_grid'
220+
cols['element_id'] = id_
221+
self.data = pd.concat((self.data, cols.to_frame().T), axis=0, ignore_index=True)
222+
223+
for row in self.net.storage.iterrows():
224+
id_ = row[0]
225+
cols = row[1][['name', 'bus']]
226+
if id_ in self.data['element_id'].values and cols['name'] in self.data['name'].values:
227+
continue
228+
cols['bus'] = f"{cols['bus']} ({self.net.bus.at[cols['bus'], 'name']})"
229+
cols['element_type'] = 'storage'
230+
cols['element_id'] = id_
231+
self.data = pd.concat((self.data, cols.to_frame().T), axis=0, ignore_index=True)
232+
233+
for row in self.net.load.iterrows():
234+
id_ = row[0]
235+
cols = row[1][['name', 'bus']]
236+
if id_ in self.data['element_id'].values and cols['name'] in self.data['name'].values:
237+
continue
238+
cols['bus'] = f"{cols['bus']} ({self.net.bus.at[cols['bus'], 'name']})"
239+
cols['element_type'] = 'load'
240+
cols['element_id'] = id_
241+
self.data = pd.concat((self.data, cols.to_frame().T), axis=0, ignore_index=True)
242+
243+
for row in self.net.dcline.iterrows():
244+
id_ = row[0]
245+
cols = pd.Series(index=['name', 'bus', 'element_type', 'element_id'])
246+
cols['name'] = row[1]['name']
247+
if id_ in self.data['element_id'].values and cols['name'] in self.data['name'].values:
248+
continue
249+
cols['bus'] = str(row[1]['from_bus']) + ' - ' + str(row[1]['to_bus']) +\
250+
f" ({self.net.bus.at[row[1]['from_bus'], 'name']} - {self.net.bus.at[row[1]['to_bus'], 'name']})"
251+
cols['element_type'] = 'dcline'
252+
cols['element_id'] = id_
253+
self.data = pd.concat((self.data, cols.to_frame().T), axis=0, ignore_index=True)
254+
255+
self.data = self.data.fillna(0.0) # Replace NaN with zeros
256+
names = list(self.data['name'])
257+
if not names:
258+
title = 'No enough data'
259+
text_content = 'There are no generators, external grids, storage devices,' \
260+
' DC lines or loads in this network.'
261+
QtWidgets.QMessageBox.critical(self.standard_extension_win.w, title, text_content)
262+
return
263+
264+
# Selector (combobox)...
265+
self.input_dialog.selector.clear()
266+
self.input_dialog.selector.currentTextChanged.connect(self.change_selection)
267+
self.input_dialog.selector.addItems(names)
268+
269+
# Real power coeffs...
270+
self.input_dialog.cp2_eur_per_mw2.valueChanged.connect(self.change_coeff)
271+
self.input_dialog.cp1_eur_per_mw.valueChanged.connect(self.change_coeff)
272+
self.input_dialog.cp0_eur.valueChanged.connect(self.change_coeff)
273+
274+
# Reactive power coeffs...
275+
self.input_dialog.cq2_eur_per_mvar2.valueChanged.connect(self.change_coeff)
276+
self.input_dialog.cq1_eur_per_mvar.valueChanged.connect(self.change_coeff)
277+
self.input_dialog.cq0_eur.valueChanged.connect(self.change_coeff)
278+
279+
# Showing the dialog...
280+
if self.input_dialog.exec():
281+
self.clear_output()
282+
self.compute_opf = True
283+
284+
def change_coeff(self, *args):
285+
"""
286+
Updates the parameters data when they are changed in the GUI.
287+
"""
288+
name = self.input_dialog.selector.currentText()
289+
row = self.data[self.data['name']==name]
290+
291+
# Modifying row...
292+
row['cp2_eur_per_mw2'] = self.input_dialog.cp2_eur_per_mw2.value()
293+
row['cp1_eur_per_mw'] = self.input_dialog.cp1_eur_per_mw.value()
294+
row['cp0_eur'] = self.input_dialog.cp0_eur.value()
295+
row['cq2_eur_per_mvar2'] = self.input_dialog.cq2_eur_per_mvar2.value()
296+
row['cq1_eur_per_mvar'] = self.input_dialog.cq1_eur_per_mvar.value()
297+
row['cq0_eur'] = self.input_dialog.cq0_eur.value()
298+
299+
self.data[self.data['name']==name] = row
300+
301+
def compute_cost(self):
302+
"""
303+
Returns the total cost.
304+
"""
305+
cost = 0.
306+
for _, row in self.data.iterrows():
307+
id_ = row['element_id']
308+
et = row['element_type']
309+
if et=='dcline':
310+
p_mw = self.copy_net.res_dcline.at[id_, 'p_from_mw']
311+
q_mvar = self.copy_net.res_dcline.at[id_, 'q_from_mvar']
312+
else:
313+
p_mw = self.copy_net[f'res_{et}'].at[id_, 'p_mw']
314+
q_mvar = self.copy_net[f'res_{et}'].at[id_, 'q_mvar']
315+
316+
cost_p = (row['cp2_eur_per_mw2'] * p_mw**2 + row['cp1_eur_per_mw'] * p_mw +
317+
row['cp0_eur'])
318+
319+
cost_q = (row['cq2_eur_per_mvar2'] * q_mvar**2 + row['cq1_eur_per_mvar'] * q_mvar +
320+
row['cq0_eur'])
321+
322+
cost += cost_p + cost_q
323+
324+
return cost
325+
326+
def change_selection(self, name):
327+
"""
328+
Shows a specific element data when its name is selected in the combobox.
329+
330+
:param name: Element name
331+
:return: None
332+
"""
333+
row = self.data.loc[self.data['name'] == name]
334+
335+
idx = row.index[0]
336+
self.input_dialog.element_type.setText(row.at[idx, 'element_type'])
337+
self.input_dialog.element_id.setText(str(int(row.at[idx, 'element_id'])))
338+
self.input_dialog.bus.setText(str(row.at[idx, 'bus']))
339+
340+
self.input_dialog.cp2_eur_per_mw2.setValue(row.at[idx, 'cp2_eur_per_mw2'])
341+
self.input_dialog.cp1_eur_per_mw.setValue(row.at[idx, 'cp1_eur_per_mw'])
342+
self.input_dialog.cp0_eur.setValue(row.at[idx, 'cp0_eur'])
343+
344+
self.input_dialog.cq2_eur_per_mvar2.setValue(row.at[idx, 'cq2_eur_per_mvar2'])
345+
self.input_dialog.cq1_eur_per_mvar.setValue(row.at[idx, 'cq1_eur_per_mvar'])
346+
self.input_dialog.cq0_eur.setValue(row.at[idx, 'cq0_eur'])
347+
348+
def finish(self):
349+
"""
350+
Executed just after the OPF calculation.
351+
"""
352+
self.graph.session_change_warning()

0 commit comments

Comments
 (0)