Skip to content

Commit

Permalink
Added PVLib solar profiles generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Santi committed Jul 15, 2023
1 parent 0e5ef99 commit 7f2e95b
Show file tree
Hide file tree
Showing 9 changed files with 785 additions and 562 deletions.
1,138 changes: 579 additions & 559 deletions .idea/workspace.xml

Large diffs are not rendered by default.

Binary file modified Grids_and_profiles/grids/IEEE 118 Bus - ntc_areas.gridcal
Binary file not shown.
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ pyarrow
fastparquet
darkdetect
pyqtdarktheme
nptyping
nptyping
windpowerlib
pvlib
2 changes: 2 additions & 0 deletions requirements_nose.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,5 @@ wcwidth==0.2.5
xlrd==2.0.1
xlwt==1.3.0
nptyping==2.5.0
windpowerlib
pvlib
4 changes: 3 additions & 1 deletion requirements_venv.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ wcwidth==0.1.7
zipp==0.5.2
numba>=0.46
Cython
nptyping
nptyping
windpowerlib
pvlib
36 changes: 35 additions & 1 deletion src/GridCal/Gui/GridEditorWidget/generator_graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
from GridCal.Engine.Core.Devices.Injections.generator import Generator, DeviceType
from GridCal.Gui.GridEditorWidget.generic_graphics import ACTIVE, DEACTIVATED, OTHER, Circle
from GridCal.Gui.GuiFunctions import ObjectsModel
from GridCal.Gui.GridEditorWidget.messages import yes_no_question
from GridCal.Gui.GridEditorWidget.messages import yes_no_question, info_msg
from GridCal.Gui.GridEditorWidget.matplotlibwidget import MatplotlibWidget
from GridCal.Gui.GridEditorWidget.solar_power_wizzard import SolarPvWizard


class GeneratorEditor(QDialog):
Expand Down Expand Up @@ -182,6 +183,12 @@ def contextMenuEvent(self, event):
pa.setIcon(plot_icon)
pa.triggered.connect(self.plot)

pv = menu.addAction('Solar photovoltaic wizard')
pv_icon = QIcon()
pv_icon.addPixmap(QPixmap(":/Icons/icons/plot.svg"))
pv.setIcon(pv_icon)
pv.triggered.connect(self.solar_pv_wizard)

da = menu.addAction('Delete')
del_icon = QIcon()
del_icon.addPixmap(QPixmap(":/Icons/icons/delete3.svg"))
Expand Down Expand Up @@ -287,6 +294,33 @@ def edit(self):
if dlg.exec_():
pass

def solar_pv_wizard(self):
"""
Open the appropriate editor dialogue
:return:
"""

if self.diagramScene.circuit.has_time_series:

time_array = self.diagramScene.circuit.time_profile

dlg = SolarPvWizard(time_array=time_array,
peak_power=self.api_object.Pmax,
latitude=self.api_object.bus.latitude,
longitude=self.api_object.bus.longitude,
gen_name=self.api_object.name,
bus_name=self.api_object.bus.name)
if dlg.exec_():
if dlg.is_accepted:
if len(dlg.P) == len(self.api_object.P_prof):
self.api_object.P_prof = dlg.P

self.plot()
else:
raise Exception("Wrong length from the solar photovoltaic wizard")
else:
info_msg("You need to have time profiles for this function")

def mousePressEvent(self, QGraphicsSceneMouseEvent):
"""
mouse press: display the editor
Expand Down
161 changes: 161 additions & 0 deletions src/GridCal/Gui/GridEditorWidget/solar_power_wizzard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# GridCal
# Copyright (C) 2015 - 2023 Santiago Peñate Vera
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import numpy as np
from typing import List, Union, Tuple
from datetime import datetime

import pandas as pd
import requests
from PySide6 import QtCore, QtWidgets
import pvlib
from GridCal.Gui.GridEditorWidget.messages import error_msg
from GridCal.Engine.basic_structures import Logger, DateVec


def get_pv_lib_weather_df(time_array: DateVec, latitude, longitude, peak_power) -> Tuple[bool, pd.DataFrame]:
"""
:param time_array:
:param latitude:
:param longitude:
:param peak_power:
:return:
"""
max_year_span = 2015 - 2010 # 10 years, this is due to PVLIB's database

ts1 = time_array[0]
ts2 = time_array[-1]
year_span = ts2.year - ts1.year

if year_span <= max_year_span:

s = datetime(year=2010, month=ts1.month, day=ts1.day, hour=ts1.hour, minute=ts1.minute)
e = datetime(year=2010 + year_span, month=ts2.month, day=ts2.day, hour=ts2.hour, minute=ts2.minute)

new_ts = pd.to_datetime([datetime(year=2010 + ts.year - ts1.year,
month=ts.month,
day=ts.day,
hour=ts.hour,
minute=ts.minute) for ts in time_array]).values.astype(float).astype(
np.int64)

try:

data, meta, inputs = pvlib.iotools.get_pvgis_hourly(latitude=latitude,
longitude=longitude,
start=s,
end=e,
pvcalculation=True,
peakpower=peak_power * 1e3, # kW
)

data.index = data.index.values.astype(float).astype(np.int64)
data2 = data.reindex(new_ts).interpolate(method='linear')

P = data2['P'].values / 1e6 # Power in MW

return True, data2

except requests.HTTPError as err:
error_msg("pvlib's http request failed :(\n" + str(err))
return False, pd.DataFrame(data={'P': np.zeros(len(time_array))})

else:
error_msg("The time span of your profile is {} year(s), Pvlib's span is 10 years maximum")
return False, pd.DataFrame(data={'P': np.zeros(len(time_array))})


class SolarPvWizard(QtWidgets.QDialog):
"""
New solar photovoltaic wizard window
"""

def __init__(self, time_array: List[str], peak_power: float, latitude: float, longitude: float,
gen_name='', bus_name='',
title='solar photovoltaic wizard'):
"""
:param time_array: array of time values
:param peak_power: generator peak power in MW
:param latitude: latitude (float)
:param longitude: longitude (float)
:param title: Window title
"""
QtWidgets.QDialog.__init__(self)
self.setObjectName("self")
self.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
self.main_layout = QtWidgets.QVBoxLayout(self)

self.is_accepted: bool = False
self.selected_indices: List[int] = list()

self.label_gen = QtWidgets.QLabel()
self.label_gen.setText("Generator {}".format(gen_name))

self.label_bus = QtWidgets.QLabel()
self.label_bus.setText("Bus: {}".format(bus_name))

self.label_peak = QtWidgets.QLabel()
self.label_peak.setText("peak power {} MW".format(peak_power))

self.lat_label = QtWidgets.QLabel()
self.lat_label.setText("Latitude {} deg".format(latitude))

self.lon_label = QtWidgets.QLabel()
self.lon_label.setText("Longitude {} deg".format(longitude))

self.peak_power = peak_power
self.latitude = latitude
self.longitude = longitude
self.time_array = time_array
self.P = np.zeros(len(time_array))

# accept button
self.accept_btn = QtWidgets.QPushButton()
self.accept_btn.setText('Accept')
self.accept_btn.clicked.connect(self.accept_click)

# add all to the GUI
self.main_layout.addWidget(self.label_gen)
self.main_layout.addWidget(self.label_bus)
self.main_layout.addWidget(self.label_peak)
self.main_layout.addWidget(self.lat_label)
self.main_layout.addWidget(self.lon_label)
self.main_layout.addWidget(self.accept_btn)

self.setLayout(self.main_layout)

self.setWindowTitle(title)

h = 260
self.resize(h, int(0.8 * h))

def accept_click(self):
"""
Accept and close
"""
ok, df = get_pv_lib_weather_df(time_array=self.time_array,
latitude=self.latitude,
longitude=self.longitude,
peak_power=self.peak_power)

if ok:
self.P = df['P'].values / 1e6 # Power in MW

self.is_accepted = ok
self.accept()
Empty file.
2 changes: 2 additions & 0 deletions src/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
"darkdetect",
"pyqtdarktheme",
"nptyping",
"windpowerlib",
"pvlib"
]

extras_require = {
Expand Down

0 comments on commit 7f2e95b

Please sign in to comment.