From 9a1494cd2989e752321a6ac602d37f9479beee97 Mon Sep 17 00:00:00 2001 From: eccoope <125493409+eccoope@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:16:48 -0600 Subject: [PATCH] Fixed the horizon masking section --- docs/examples/snow-detection/snow-mode.py | 39 +- pvanalytics/data/snow_horizon.csv | 722 +++++++++++----------- pvanalytics/features/snow.py | 46 +- 3 files changed, 412 insertions(+), 395 deletions(-) diff --git a/docs/examples/snow-detection/snow-mode.py b/docs/examples/snow-detection/snow-mode.py index 016533db..758204f4 100644 --- a/docs/examples/snow-detection/snow-mode.py +++ b/docs/examples/snow-detection/snow-mode.py @@ -53,6 +53,7 @@ from pvanalytics.features import snow # %% Load in system configuration parameters (dict) +import os pvanalytics_dir = pathlib.Path(pvanalytics.__file__).parent data_file = pvanalytics_dir / 'data' / 'snow_data.csv' snowfall_file = pvanalytics_dir / 'data' / 'snow_snowfall.csv' @@ -184,7 +185,7 @@ data.loc[mask3, i] = np.nan data.loc[mask3, a] = np.nan -# %% Plot DC voltage for each combiner inputm with inverter nameplate limits +# %% Plot DC voltage for each combiner input with inverter nameplate limits fig, ax = plt.subplots(figsize=(10, 10)) date_form = DateFormatter("%m/%d") @@ -211,13 +212,24 @@ ''' -horizon = pd.read_csv(horizon_file, index_col='Az').squeeze("columns") +horizon = pd.read_csv(horizon_file, index_col='Unnamed: 0').squeeze("columns") data['Horizon Mask'] = snow.get_horizon_mask(horizon, data['azimuth'], data['elevation']) -# Exclude data collected while the sun is below the horizon -data = data[~data['Horizon Mask']] +#%% Plot horizon mask + +fig, ax = plt.subplots() + +ax.scatter(data['azimuth'],data['elevation'], s=0.5, label='data', + c=data['Horizon Mask']) +ax.scatter(horizon.index, horizon, s=0.5, label='mask') +ax.legend() +ax.set_xlabel(r'Azimuth [$\degree$]') +ax.set_ylabel(r'Elevation [$\degree$]') + +#%% Exclude data collected while the sun is below the horizon +data = data[data['Horizon Mask']] # %% @@ -442,21 +454,10 @@ def wrapper(voltage, current, temp_cell, effective_irradiance, vmp_ratio = voltage / modeled_vmp # take care of divide by zero - vmp_ratio[modeled_vmp == 0] = 0 - # Both quantities in the "where" argument of np.divide must be arrays, or - # else a RecursionError is raised + vmp_ratio[modeled_vmp == 0] = np.nan - # vmp_ratio = np.divide(voltage, modeled_vmp, - # where=((voltage > 0) & (modeled_vmp>0))) - # vmp_ratio[modeled_vmp==0] = 0 - - # TODO lets vectorize the function itself - categorize_v = np.vectorize(snow.categorize_old) - - mode = categorize_v(vmp_ratio, T, voltage, config['min_dcv'], - config['threshold_vratio'], - config['threshold_transmission']) - mode = snow.categorize(vmp_ratio, T, voltage, config['min_dcv'], + mode = snow.categorize(vmp_ratio, T, voltage, modeled_vmp, + config['min_dcv'], config['threshold_vratio'], config['threshold_transmission']) my_dict = {'transmission': T, @@ -724,3 +725,5 @@ def wrapper(voltage, current, temp_cell, effective_irradiance, ax.set_xticks(xvals, days) ax.xaxis.set_major_formatter(date_form) ax.set_title('Losses incurred in modes 0 -3', fontsize='xx-large') + +# %% diff --git a/pvanalytics/data/snow_horizon.csv b/pvanalytics/data/snow_horizon.csv index 2e2b4316..2ede61af 100644 --- a/pvanalytics/data/snow_horizon.csv +++ b/pvanalytics/data/snow_horizon.csv @@ -1,361 +1,361 @@ -Az,Elev -0,0 -1,0 -2,0 -3,0 -4,0 -5,0 -6,0 -7,0 -8,0 -9,0 -10,0 -11,0 -12,0 -13,0 -14,0 -15,0 -16,0 -17,0 -18,0 -19,0 -20,0 -21,0 -22,0 -23,0 -24,0 -25,0 -26,0 -27,0 -28,0 -29,0 -30,0 -31,0 -32,0 -33,0 -34,0 -35,0 -36,0 -37,0 -38,0 -39,0 -40,0 -41,0 -42,0 -43,0 -44,0 -45,0 -46,0 -47,0 -48,0 -49,0 -50,1 -51,1 -52,2 -53,4 -54,5 -55,6 -56,7 -57,8 -58,8 -59,8 -60,7 -61,6 -62,5 -63,4 -64,2 -65,1 -66,1 -67,0 -68,0 -69,0 -70,0 -71,0 -72,0 -73,0 -74,0 -75,0 -76,0 -77,0 -78,0 -79,0 -80,0 -81,0 -82,0 -83,0 -84,0 -85,0 -86,0 -87,0 -88,0 -89,0 -90,0 -91,0 -92,0 -93,0 -94,0 -95,0 -96,0 -97,0 -98,0 -99,0 -100,0 -101,0 -102,0 -103,0 -104,0 -105,0 -106,0 -107,0 -108,0 -109,0 -110,0 -111,0 -112,0 -113,0 -114,0 -115,0 -116,0 -117,0 -118,0 -119,0 -120,0 -121,1 -122,1 -123,1 -124,2 -125,2 -126,3 -127,3 -128,4 -129,5 -130,5 -131,6 -132,7 -133,8 -134,8 -135,9 -136,10 -137,10 -138,11 -139,12 -140,12 -141,13 -142,13 -143,14 -144,15 -145,15 -146,15 -147,16 -148,16 -149,17 -150,17 -151,18 -152,18 -153,18 -154,19 -155,19 -156,19 -157,20 -158,20 -159,20 -160,21 -161,21 -162,21 -163,21 -164,22 -165,22 -166,22 -167,22 -168,22 -169,22 -170,23 -171,23 -172,23 -173,23 -174,23 -175,23 -176,23 -177,23 -178,23 -179,23 -180,23 -181,23 -182,23 -183,23 -184,23 -185,23 -186,23 -187,23 -188,23 -189,23 -190,22 -191,22 -192,22 -193,22 -194,22 -195,22 -196,21 -197,21 -198,21 -199,21 -200,20 -201,20 -202,20 -203,19 -204,19 -205,19 -206,18 -207,18 -208,18 -209,17 -210,17 -211,16 -212,16 -213,16 -214,15 -215,15 -216,14 -217,14 -218,14 -219,13 -220,13 -221,13 -222,13 -223,12 -224,12 -225,12 -226,12 -227,12 -228,12 -229,12 -230,12 -231,12 -232,12 -233,13 -234,13 -235,13 -236,13 -237,13 -238,13 -239,12 -240,12 -241,12 -242,12 -243,11 -244,11 -245,11 -246,10 -247,10 -248,10 -249,11 -250,11 -251,11 -252,12 -253,12 -254,13 -255,14 -256,15 -257,16 -258,17 -259,18 -260,18 -261,19 -262,20 -263,20 -264,21 -265,21 -266,21 -267,20 -268,20 -269,20 -270,19 -271,18 -272,17 -273,17 -274,16 -275,15 -276,14 -277,14 -278,13 -279,13 -280,13 -281,14 -282,16 -283,17 -284,20 -285,23 -286,26 -287,29 -288,31 -289,33 -290,33 -291,33 -292,31 -293,29 -294,26 -295,23 -296,21 -297,20 -298,19 -299,19 -300,19 -301,19 -302,18 -303,17 -304,15 -305,12 -306,9 -307,7 -308,4 -309,3 -310,1 -311,1 -312,0 -313,0 -314,0 -315,0 -316,0 -317,0 -318,0 -319,0 -320,0 -321,0 -322,0 -323,0 -324,0 -325,0 -326,0 -327,0 -328,0 -329,0 -330,0 -331,0 -332,0 -333,0 -334,0 -335,0 -336,0 -337,0 -338,0 -339,0 -340,0 -341,0 -342,0 -343,0 -344,0 -345,0 -346,0 -347,0 -348,0 -349,0 -350,0 -351,0 -352,0 -353,0 -354,0 -355,0 -356,0 -357,0 -358,0 -359,0 +,Elevation +0,0.0 +1,0.0 +2,0.0 +3,0.0 +4,0.0 +5,0.0 +6,0.0 +7,0.0 +8,0.0 +9,0.0 +10,0.0 +11,0.0 +12,0.0 +13,0.0 +14,0.0 +15,0.0 +16,0.0 +17,0.0 +18,0.0 +19,0.0 +20,0.0 +21,0.0 +22,0.0 +23,0.0 +24,0.0 +25,0.0 +26,0.0 +27,0.0 +28,0.0 +29,0.0 +30,0.0 +31,0.0 +32,0.0 +33,0.0 +34,0.0 +35,0.0 +36,0.0 +37,0.0 +38,0.0 +39,0.0 +40,0.0 +41,0.0 +42,0.0 +43,0.0 +44,0.0 +45,0.0 +46,0.0 +47,0.0 +48,0.0 +49,0.0 +50,0.0 +51,0.0 +52,0.0 +53,0.0 +54,0.0 +55,0.0 +56,0.0 +57,0.0 +58,0.0 +59,0.0 +60,0.0 +61,0.0 +62,0.0 +63,0.0 +64,0.0 +65,0.0 +66,0.0 +67,0.0 +68,0.0 +69,0.0 +70,0.0 +71,0.0 +72,0.0 +73,0.0 +74,0.0 +75,0.0 +76,0.0 +77,0.0 +78,0.0 +79,0.0 +80,0.0 +81,0.0 +82,0.0 +83,0.0 +84,0.0 +85,0.0 +86,0.0 +87,0.0 +88,0.0 +89,0.0 +90,0.0 +91,0.0 +92,0.0 +93,0.0 +94,0.0 +95,0.0 +96,0.0 +97,0.0 +98,0.0 +99,0.0 +100,0.0 +101,0.0 +102,0.0 +103,0.0 +104,0.0 +105,0.0 +106,0.0 +107,0.0 +108,0.0 +109,0.0 +110,0.0 +111,0.0 +112,0.0 +113,0.0 +114,0.0 +115,0.0 +116,0.0 +117,0.0 +118,0.0 +119,0.0 +120,0.0 +121,0.0 +122,0.0 +123,0.0 +124,0.2216517857142784 +125,0.6654017857142642 +126,1.3517857142856708 +127,1.999776785714222 +128,2.695982142857057 +129,3.4080357142856066 +130,4.119196428571299 +131,4.870535714285562 +132,5.76205357142839 +133,6.491517857142654 +134,7.241071428571201 +135,8.04955357142832 +136,8.712499999999725 +137,9.409821428571131 +138,10.06138392857111 +139,10.62053571428538 +140,11.258482142856787 +141,11.912946428571052 +142,12.506026785713892 +143,12.925669642856734 +144,13.528571428571004 +145,14.179017857142412 +146,14.692633928570968 +147,15.169419642856669 +148,15.751562499999505 +149,16.28124999999948 +150,16.701785714285187 +151,17.132142857142323 +152,17.57455357142802 +153,17.956696428570865 +154,18.383035714285132 +155,18.673660714285123 +156,19.01741071428512 +157,19.360937499999395 +158,19.66874999999938 +159,19.972991071427945 +160,20.264732142856506 +161,20.40959821428507 +162,20.515401785713642 +163,20.774330357142208 +164,20.978571428570774 +165,21.038169642856484 +166,21.18102678571362 +167,21.34263392857076 +168,21.442857142856475 +169,21.552901785713615 +170,21.68616071428503 +171,21.72254464285646 +172,21.616517857142178 +173,21.774999999999316 +174,21.751339285713605 +175,21.794196428570746 +176,21.859151785713603 +177,21.91964285714217 +178,21.99999999999931 +179,22.024553571427877 +180,22.052232142856447 +181,22.100892857142163 +182,22.11383928571359 +183,22.034374999999304 +184,21.88035714285645 +185,21.828571428570744 +186,21.80558035714217 +187,21.696874999999313 +188,21.58035714285646 +189,21.525892857142185 +190,21.403348214285042 +191,21.33504464285647 +192,21.2854910714279 +193,21.085714285713625 +194,20.614732142856496 +195,20.453794642856497 +196,20.373883928570784 +197,20.02165178571365 +198,19.745982142856526 +199,19.62008928571367 +200,19.376116071427962 +201,19.022991071427974 +202,18.829687499999412 +203,18.648437499999414 +204,18.349330357142282 +205,18.070089285713713 +206,17.78950892857087 +207,17.436607142856595 +208,17.006919642856605 +209,16.326116071428057 +210,15.947544642856636 +211,15.572098214285223 +212,14.842187499999534 +213,14.513392857142398 +214,14.077901785713841 +215,13.640401785713856 +216,13.052008928571022 +217,12.791517857142452 +218,12.510044642856752 +219,12.017633928571051 +220,11.565401785713922 +221,11.286607142856784 +222,10.913392857142515 +223,10.259374999999675 +224,10.242633928571106 +225,10.133035714285393 +226,9.861607142856831 +227,10.078348214285397 +228,10.25424107142825 +229,10.220312499999679 +230,10.434151785713956 +231,10.625669642856808 +232,10.617187499999668 +233,10.555357142856813 +234,10.560267857142527 +235,10.390848214285382 +236,10.231473214285394 +237,10.308705357142536 +238,10.24799107142825 +239,10.489508928571096 +240,10.29218749999968 +241,10.236830357142532 +242,10.174776785713966 +243,9.797321428571118 +244,9.529910714285416 +245,9.150669642856853 +246,8.96428571428543 +247,8.39196428571402 +248,8.224776785714026 +249,8.33258928571402 +250,8.478794642856872 +251,8.635491071428298 +252,8.958035714285433 +253,9.78883928571398 +254,10.476785714285386 +255,11.309374999999644 +256,13.099330357142449 +257,14.25602678571384 +258,14.416741071428122 +259,15.634821428570941 +260,16.308258928570915 +261,16.673660714285194 +262,16.832366071428044 +263,16.64241071428519 +264,16.55714285714234 +265,16.31205357142806 +266,15.795089285713791 +267,14.858482142856676 +268,13.458035714285291 +269,12.427008928571036 +270,11.3462053571425 +271,9.681249999999693 +272,8.220089285714028 +273,6.747321428571215 +274,5.218526785714121 +275,3.817410714285592 +276,2.4276785714284936 +277,1.6651785714285183 +278,0.851116071428544 +279,0.07767857142856865 +280,0.0 +281,0.0 +282,0.0 +283,0.0 +284,0.0 +285,0.0 +286,0.0 +287,0.0 +288,0.0 +289,0.0 +290,0.0 +291,0.0 +292,0.0 +293,0.0 +294,0.0 +295,0.0 +296,0.0 +297,0.0 +298,0.0 +299,0.0 +300,0.0 +301,0.0 +302,0.0 +303,0.0 +304,0.0 +305,0.0 +306,0.0 +307,0.0 +308,0.0 +309,0.0 +310,0.0 +311,0.0 +312,0.0 +313,0.0 +314,0.0 +315,0.0 +316,0.0 +317,0.0 +318,0.0 +319,0.0 +320,0.0 +321,0.0 +322,0.0 +323,0.0 +324,0.0 +325,0.0 +326,0.0 +327,0.0 +328,0.0 +329,0.0 +330,0.0 +331,0.0 +332,0.0 +333,0.0 +334,0.0 +335,0.0 +336,0.0 +337,0.0 +338,0.0 +339,0.0 +340,0.0 +341,0.0 +342,0.0 +343,0.0 +344,0.0 +345,0.0 +346,0.0 +347,0.0 +348,0.0 +349,0.0 +350,0.0 +351,0.0 +352,0.0 +353,0.0 +354,0.0 +355,0.0 +356,0.0 +357,0.0 +358,0.0 +359,0.0 diff --git a/pvanalytics/features/snow.py b/pvanalytics/features/snow.py index 474abe52..f2c62a94 100644 --- a/pvanalytics/features/snow.py +++ b/pvanalytics/features/snow.py @@ -107,7 +107,7 @@ def get_transmission(measured_e_e, modeled_e_e, i_mp): Measured irradiance should be in the array's plane and represent snow-free conditions. For example, the measured irradiance could be obtained with a heated plane-of-array pyranometer. When necessary, the irradiance should be - adjusted for reflections and spectral content. + adjusted for reflections and spectral content. Parameters ---------- @@ -117,12 +117,15 @@ def get_transmission(measured_e_e, modeled_e_e, i_mp): Effective irradiance modeled from measured current at maximum power. [W/m2] i_mp : array - Maximum power current at the resolution of a single module. [A] + Maximum power DC current at the resolution of a single module. [A] Returns ------- T : array - Effective transmission. [unitless] # TODO describe when expect nan, 0 + Effective transmission. [unitless] Returns NaN where measured DC + current is NaN and where measured irradiance is zero. Returns zero + where measured current is zero. Returns 1 where the ratio between + measured and modeled irradiance exceeds 1. References ---------- @@ -131,19 +134,22 @@ def get_transmission(measured_e_e, modeled_e_e, i_mp): 50th Photovoltaic Specialists Conference (PVSC), San Juan, PR, USA, 2023, pp. 1-5. :doi:`10.1109/PVSC48320.2023.10360065` """ - # TODO only works with Series T = modeled_e_e / measured_e_e # no transmission if no current - T[i_mp.isna()] = np.nan + T[np.isnan(i_mp)] = np.nan T[i_mp == 0] = 0 + # no transmission if no irradiance + T[measured_e_e == 0] = np.nan + # bound T between 0 and 1 T[T < 0] = np.nan T[T > 1] = 1 return T -def categorize_old(vmp_ratio, transmission, voltage, min_dcv, - threshold_vratio, threshold_transmission): +def categorize_old(vmp_ratio, transmission, measured_voltage, + predicted_voltage, min_dcv, threshold_vratio, + threshold_transmission): """ Categorizes electrical behavior into a snow-related mode. @@ -151,7 +157,9 @@ def categorize_old(vmp_ratio, transmission, voltage, min_dcv, Modes are defined in [1]_: * Mode 0: system is covered with enough opaque snow that the system is - offline due to voltage below the inverter's turn-on voltage. + offline due to voltage below the inverter's turn-on voltage. Excludes + periods when system is predicted to be offline based on measured + irradiance. * Mode 1: system is online and covered with non-uniform snow, such that both operating voltage and current are decreased by the presence of snow. * Mode 2: system is online and covered with opaque snow, such that @@ -171,7 +179,7 @@ def categorize_old(vmp_ratio, transmission, voltage, min_dcv, transmission : float Fraction of plane-of-array irradiance that can reach the array's cells through the snow cover. [dimensionless] - voltage : float + measured_voltage : float [V] min_dcv : float The lower voltage bound on the inverter's maximum power point @@ -196,7 +204,7 @@ def categorize_old(vmp_ratio, transmission, voltage, min_dcv, if np.isnan(vmp_ratio) or np.isnan(transmission): return np.nan - elif voltage < min_dcv: + elif (measured_voltage < min_dcv) and (predicted_voltage > min_dcv): return 0 elif vmp_ratio < threshold_vratio: if transmission < threshold_transmission: @@ -211,8 +219,8 @@ def categorize_old(vmp_ratio, transmission, voltage, min_dcv, return np.nan -def categorize(vmp_ratio, transmission, voltage, min_dcv, - threshold_vratio, threshold_transmission): +def categorize(vmp_ratio, transmission, measured_voltage, modeled_voltage, + min_dcv, threshold_vratio, threshold_transmission): """ Categorizes electrical behavior into a snow-related mode. @@ -220,7 +228,9 @@ def categorize(vmp_ratio, transmission, voltage, min_dcv, Modes are defined in [1]_: * Mode 0: system is covered with enough opaque snow that the system is - offline due to voltage below the inverter's turn-on voltage. + offline due to voltage below the inverter's turn-on voltage. Excludes + periods when voltage modeled using measured irradiance does not + exceed the inverter's turn-on voltage. * Mode 1: system is online and covered with non-uniform snow, such that both operating voltage and current are decreased by the presence of snow. * Mode 2: system is online and covered with opaque snow, such that @@ -240,8 +250,11 @@ def categorize(vmp_ratio, transmission, voltage, min_dcv, transmission : array-like Fraction of plane-of-array irradiance that can reach the array's cells through the snow cover. [dimensionless] - voltage : array-like + measured_voltage : array-like Measured DC voltage. [V] + modeled_voltage : array-like + DC voltage modeled using measured plane-of-array irradiance and + back-of-module temperature. [V] min_dcv : float The lower voltage bound on the inverter's maximum power point tracking (MPPT) algorithm. [V] @@ -262,11 +275,12 @@ def categorize(vmp_ratio, transmission, voltage, min_dcv, 50th Photovoltaic Specialists Conference (PVSC), San Juan, PR, USA, 2023, pp. 1-5, :doi:`10.1109/PVSC48320.2023.10360065`. """ - umin = voltage >= min_dcv # necessary for all modes except 0 + umin_meas = measured_voltage >= min_dcv # necessary for all modes except 0 + umin_model = modeled_voltage >= min_dcv # necessary for all modes except 0 uvr = np.where(vmp_ratio >= threshold_vratio, 3, 1) utrans = np.where(transmission >= threshold_transmission, 1, 0) mode = np.where(np.isnan(vmp_ratio) | np.isnan(transmission), None, - umin * (uvr + utrans)) + umin_meas * umin_model * (uvr + utrans)) return mode