expose pvlib.irradiance.dni() in API?

80 views
Skip to first unread message

Mark Mikofski

unread,
Apr 8, 2019, 6:16:03 PM4/8/19
to pvlib-python
This is a super cool function, IMO, but there's no mention of it anywhere, despite the long docstring, and lack of preceding underscore:

def dni(ghi, dhi, zenith, clearsky_dni=None, clearsky_tolerance=1.1,
        zenith_threshold_for_zero_dni=88.0,
        zenith_threshold_for_clearsky_limit=80.0):
    """
    Determine DNI from GHI and DHI.

    When calculating the DNI from GHI and DHI the calculated DNI may be
    unreasonably high or negative for zenith angles close to 90 degrees
    (sunrise/sunset transitions). This function identifies unreasonable DNI
    values and sets them to NaN. If the clearsky DNI is given unreasonably high
    values are cut off.

    Parameters
    ----------
    ghi : Series
        Global horizontal irradiance.

    dhi : Series
        Diffuse horizontal irradiance.

    zenith : Series
        True (not refraction-corrected) zenith angles in decimal
        degrees. Angles must be >=0 and <=180.

    clearsky_dni : None or Series, default None
        Clearsky direct normal irradiance.

    clearsky_tolerance : float, default 1.1
        If 'clearsky_dni' is given this parameter can be used to allow a
        tolerance by how much the calculated DNI value can be greater than
        the clearsky value before it is identified as an unreasonable value.

    zenith_threshold_for_zero_dni : float, default 88.0
        Non-zero DNI values for zenith angles greater than or equal to
        'zenith_threshold_for_zero_dni' will be set to NaN.

    zenith_threshold_for_clearsky_limit : float, default 80.0
        DNI values for zenith angles greater than or equal to
        'zenith_threshold_for_clearsky_limit' and smaller the
        'zenith_threshold_for_zero_dni' that are greater than the clearsky DNI
        (times allowed tolerance) will be corrected. Only applies if
        'clearsky_dni' is not None.

    Returns
    -------
    dni : Series
        The modeled direct normal irradiance.
    """


    # calculate DNI
    dni = (ghi - dhi) / tools.cosd(zenith)


    # cutoff negative values
    dni[dni < 0] = float('nan')


    # set non-zero DNI values for zenith angles >=
    # zenith_threshold_for_zero_dni to NaN
    dni[(zenith >= zenith_threshold_for_zero_dni) & (dni != 0)] = float('nan')


    # correct DNI values for zenith angles greater or equal to the
    # zenith_threshold_for_clearsky_limit and smaller than the
    # upper_cutoff_zenith that are greater than the clearsky DNI (times
    # clearsky_tolerance)
    if clearsky_dni is not None:
        max_dni = clearsky_dni * clearsky_tolerance
        dni[(zenith >= zenith_threshold_for_clearsky_limit) &
            (zenith < zenith_threshold_for_zero_dni) &
            (dni > max_dni)] = max_dni
    return dni


William Holmgren

unread,
Apr 8, 2019, 6:21:25 PM4/8/19
to Mark Mikofski, pvlib-python
It should be in the API reference but I do not see it. Looks like a bug. Pull request welcome.

--
You received this message because you are subscribed to the Google Groups "pvlib-python" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pvlib-python...@googlegroups.com.
To post to this group, send email to pvlib-...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pvlib-python/12a63961-048b-40ea-ac43-d4c04ffccd87%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Mikofski

unread,
Apr 8, 2019, 6:53:17 PM4/8/19
to pvlib-python
created an issue: https://github.com/pvlib/pvlib-python/issues/686

I'll leave it for someone to take up as a first time contributor
To unsubscribe from this group and stop receiving emails from it, send an email to pvlib-...@googlegroups.com.

Debayan Paul

unread,
Jan 10, 2020, 6:03:37 AM1/10/20
to pvlib-python
Hi,

I am using this function to calculate dni values from the dataset in which I have ghi and dhi values. I also created the clearsky of ineichen model: but still getting some very high peaks (around 1600W/m2). I have also dni values in the dataset, but I am not sure of their reliability, unfortunately.



I am wondering if these two arguments(zenith_threshold_for_zero_dni,zenith_threshold_for_clearsky_limit) are good with the two default values 88 and 80 respectively, or need to be altered, depending on the location.

Also if someone knows which DNI estimator model performs better, please let me know.

Thanks,
Debayan
available dni plot.png
model estimated dni plot.png

Mark Mikofski

unread,
Jan 10, 2020, 11:24:15 AM1/10/20
to Debayan Paul, pvlib-python
Hi Paul,

I am curious, what is the time-scale of the GHI/DHI data that you have? Is it minute resolution data?

  • It is not uncommon for minute scale data to have very high instantaneous DNI values. I think this caused by clouds focusing sunlight slightly, some light get's concentrated and some is scattered making patchy distributions on the ground, you can see this sometimes from an airplane. I think it's similar to an office building that has a curved concave glass surface
  • Have you tried using just the GHI data and using the something like the Erbs decomposition model? https://pvlib-python.readthedocs.io/en/latest/generated/pvlib.irradiance.erbs.html#pvlib.irradiance.erbs and compare the DHI and DNI you get from that? I believe that GHI measurements are a little more reliable than DHI measurements, because similar to DNI, a DHI measurement either needs to track the sun, or uses a  rotating shadowband radiamoter which have a low accuracy, and therefore a DNI calculation from GHI and DHI would have a high uncertainty as well.
check out the solar calendar from the NREL MIDC Solar Radiation Research Lab near Denver
Here's an image from a day in July, and you can see instantaneous DNI is over 1200 Wm-2
image.png

--
You received this message because you are subscribed to a topic in the Google Groups "pvlib-python" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pvlib-python/aa-bZPXuohc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pvlib-python...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pvlib-python/901c6a16-3073-47da-884c-49cea3124978%40googlegroups.com.


--
Being deeply loved by someone gives you strength;
loving someone deeply gives you courage.
Lao Tzu

cwh...@sandia.gov

unread,
Jan 10, 2020, 11:51:04 AM1/10/20
to pvlib-python
Hi Paul,

If those plots are one year of results, there's something wrong either with the plots, or with the data being plotted.  Can you isolate just a few days and show that plot?

I'm guessing you are seeing very high calculated DNI at high zenith, when measured GHI and DHI values are quite low, and subject to greater proportional error due to the sun being right near the horizon. The thresholds are a convenience to help with these circumstances, and you should feel free to adjust them to suit your analysis.

Cheers,

Cliff

Debayan Paul

unread,
Jan 10, 2020, 4:10:08 PM1/10/20
to pvlib-python
Hi Mark,

My data frequency is indeed minute resolution for like 8 months.

This time I also applied the Erb's model as you suggested,

#for Erb's model
dniorderedDictList=pvlib.irradiance.erbs(sampledfDNI['global_horiz'], solpos['zenith'], sampledfDNI.index, min_cos_zenith=0.065, max_zenith=87)
dnidf=pd.DataFrame(dniorderedDictList, columns=dniorderedDictList.keys())
sampledfDNI['Erb dni']=dnidf['dni']

#from irradiance function
dniseries=pvlib.irradiance.dni(sampledfDNI['global_horiz'], sampledfDNI['diffuse'], solpos['zenith'], clearsky['dni'], clearsky_tolerance=1.1, zenith_threshold_for_zero_dni=85.0, zenith_threshold_for_clearsky_limit=80.0)
dnidf=pd.DataFrame(dniseries.values,columns=['value'])
dnilist=dnidf['value'].tolist()
sampledfDNI['Irradiance model dni']=pd.Series(dnilist,index=sampledfDNI.index)
sampledfDNI['Irradiance model dni']=pd.Series(dnilist,index=sampledfDNI.index)

where sampledfDNI, is a dataframe created from my 1-minute frequency dataset which contains ghi, dhi & dni(which maybe error-prone, but the maximum value present is 1005 W/m2). solpos is created in pvlib using location information. I replaced the negative values with zero before plotting.

For the plot attached, I filtered a few days, to visualize it more easily.

plot.png



I am looking for a suggestion or solution which can lead me a realistic dni value(within 5-10% tolerance of actual, if measured), independent of the frequency & location of the dataset. Maybe a sort of filter or adjustment in the argument's value rather than the default.



On Tuesday, April 9, 2019 at 12:16:03 AM UTC+2, Mark Mikofski wrote:

cwh...@sandia.gov

unread,
Jan 10, 2020, 4:23:31 PM1/10/20
to pvlib-python
From this plot of 4 days, I suspect there's an error somewhere in the code. Perhaps double check the timestamps on the GHI data (are they in the correct time zone?) and the lat/long. Does a plot of GHI vs. zenith look correct? It looks like high GHI is being paired with high zenith.

Mark Mikofski

unread,
Jan 10, 2020, 4:41:34 PM1/10/20
to cwh...@sandia.gov, pvlib-python
I agree with Cliff, the irradiance diurnal profile is usual symmetrical and kind of sinusoidal for GHI. Direct looks like a pillbox with steep sides, and a rounded top, again here's an image of typical day from MIDC SRRL baseline measurement station in Denver for July 9th, 2019, sorry there're a few clouds in the afternoon, but I think you get the picture:
image.png

Your data looks like it's shifted in time by about 6 or 8 hours, which as Cliff suggested, implies that the timezone is off. This is a frequent dilemma when using pvlib and pandas, search through the group archives and SO questions, and you'll see folks ask it a  lot.

Can you share some of your data, or try to use something like pandas tz_convert to change to one of the Etc/GMT+<tz> timezones and try again?

And I agree, filtering will help with sunrise/sunset, that's what the zenith limits of 88-deg are for.

thanks,
Mark 

--
You received this message because you are subscribed to a topic in the Google Groups "pvlib-python" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pvlib-python/aa-bZPXuohc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pvlib-python...@googlegroups.com.

Debayan Paul

unread,
Jan 10, 2020, 5:11:52 PM1/10/20
to pvlib-python
Hi Mark,

The original data comes in Central European Time zone with Day Light Saving of 1 hour from around 31st March to 27th October. I shifted the time index during DST only by 1 hour backwards using timedelta of pandas and created solarposition with datetimeindex of that DST adjusted index.

#solpos = pvlib.solarposition.get_solarposition(dfDSTadjusted.index, location.latitude, location.longitude, altitude) 

I am sharing some data(negatives are randomly created, I replaced them with 0) in the attached. I haven't used the timezone as an input argument anywhere. If I still need to make the data timezone aware, please suggest me. Meanwhile, I will look for the topic in the group.


data.png



Thanks,
Debayan


On Tuesday, April 9, 2019 at 12:16:03 AM UTC+2, Mark Mikofski wrote:

Mark Mikofski

unread,
Jan 10, 2020, 5:16:37 PM1/10/20
to Debayan Paul, pvlib-python
yes, you must make the data timezone aware. I apologize for the inconvenience, and please let me know if this is not clear from the documentation. Otherwise, I think Python will use your local timezone, or something like that.

Try:
>>> dfDSTadjusted.tz_convert('Etc/GMT-1')

--
You received this message because you are subscribed to a topic in the Google Groups "pvlib-python" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pvlib-python/aa-bZPXuohc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pvlib-python...@googlegroups.com.

Hansen, Clifford W

unread,
Jan 10, 2020, 5:19:03 PM1/10/20
to Mark Mikofski, Debayan Paul, pvlib-python

Agree with Mark. I would avoid adjusting for DST manually; read in the timestamps and use tz_localize() to set the appropriate timezone.

--

You received this message because you are subscribed to the Google Groups "pvlib-python" group.

To unsubscribe from this group and stop receiving emails from it, send an email to pvlib-python...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pvlib-python/CALGe1A0PqMgKcdZY9F5%2Buz_ZF68hd83uBpn3RtrANi43aWQB4g%40mail.gmail.com.

Debayan Paul

unread,
Jan 16, 2020, 6:51:08 AM1/16/20
to pvlib-python
Hi,

I localized the data to 'UTC' and it solved the problem as I figured out that my timezone naive datetimeindexes were being treated as default in 'UTC' in PVLIB. The angles were actually coming in a different timezone than CET.

Thanks, everyone for your help.

Thanks
Debayan


On Tuesday, April 9, 2019 at 12:16:03 AM UTC+2, Mark Mikofski wrote:
Reply all
Reply to author
Forward
0 new messages