*Plot the SFR History as a function of redshift*

    **Key Arguments:**
        - ``log`` -- logger
        - ``lightCurveDiscoveryTimes`` -- the lightcurve discovery times of the SN
        - ``snSurveyDiscoveryTimes`` -- the supernova discovery times relative to the survey year
        - ``redshifts`` -- the redshifts of the SN
        - ``surveyArea`` -- the area of the survey considered in square degree
        - ``surveyCadenceSettings`` -- the cadence settings for the survey
        - ``CCSNRateFraction`` -- the fraction of the IMF attributed to the progenitors of CCSN
        - ``transientToCCSNRateFraction`` -- the ratio of the transient rate to the rate of CCSNe
        - ``peakAppMagList`` -- a list of the peak magnitudes for each SN in each filter
        - ``snCampaignLengthList`` -- a list of campaign lengths in each filter
        - ``extraSurveyConstraints`` -- some extra survey constraints
        - ``pathToOutputPlotDirectory`` -- path to add plots to
        - ``lowerRedshiftLimit`` -- the lower redshist limit out to which to determine the rate
        - ``upperRedshiftLimit`` -- the redshist limit out to which to determine the rate
        - ``redshiftResolution`` -- the redshist resolution ued to determine the rate

    **Return:**
        - ``imageLink`` -- MD link to the output plot
        - ``totalRate`` -- the final transient rate calculated

def determine_sn_rate(
        log,
        lightCurveDiscoveryTimes,
        snSurveyDiscoveryTimes,
        redshifts,
        surveyCadenceSettings,
        surveyArea,
        CCSNRateFraction,
        transientToCCSNRateFraction,
        peakAppMagList,
        snCampaignLengthList,
        extraSurveyConstraints,
        pathToOutputPlotFolder,
        lowerRedshiftLimit=0.01,
        upperRedshiftLimit=1.0,
        redshiftResolution=0.1
):
    """
    *Plot the SFR History as a function of redshift*

    **Key Arguments:**
        - ``log`` -- logger
        - ``lightCurveDiscoveryTimes`` -- the lightcurve discovery times of the SN
        - ``snSurveyDiscoveryTimes`` -- the supernova discovery times relative to the survey year
        - ``redshifts`` -- the redshifts of the SN
        - ``surveyArea`` -- the area of the survey considered in square degree
        - ``surveyCadenceSettings`` -- the cadence settings for the survey
        - ``CCSNRateFraction`` -- the fraction of the IMF attributed to the progenitors of CCSN
        - ``transientToCCSNRateFraction`` -- the ratio of the transient rate to the rate of CCSNe
        - ``peakAppMagList`` -- a list of the peak magnitudes for each SN in each filter
        - ``snCampaignLengthList`` -- a list of campaign lengths in each filter
        - ``extraSurveyConstraints`` -- some extra survey constraints
        - ``pathToOutputPlotDirectory`` -- path to add plots to
        - ``lowerRedshiftLimit`` -- the lower redshist limit out to which to determine the rate
        - ``upperRedshiftLimit`` -- the redshist limit out to which to determine the rate
        - ``redshiftResolution`` -- the redshist resolution ued to determine the rate

    **Return:**
        - ``imageLink`` -- MD link to the output plot
        - ``totalRate`` -- the final transient rate calculated
    """
    ################ > IMPORTS ################
    ## STANDARD LIB ##
    ## THIRD PARTY ##
    import numpy as np
    ## LOCAL APPLICATION ##
    import dryxPython.astrotools as da
    import dryxPython.plotting as dp

    ################ >ACTION(S) ################
    filters = ['g', 'r', 'i', 'z']

    sdssArea = 5713.
    sdssSFRupperRedshiftLimit = 0.033
    sdssSFR = 14787.21
    # CCSNRateFraction = 0.007
    # transientToCCSNRateFraction = 1./10000.
    transientRateFraction = float(
        CCSNRateFraction) * float(transientToCCSNRateFraction)

    anchor = redshiftResolution / sdssSFRupperRedshiftLimit
    normaiseSFR = sdssSFR * anchor**3
    areaRatio = surveyArea / sdssArea
    #log.info('normaiseSFR %s' % (normaiseSFR,))

    anchorVolume = da.convert_redshift_to_distance(redshiftResolution)[
        'dl_mpc']**3
    #log.info('anchorVolume %s' % (anchorVolume,))
    xRange = [lowerRedshiftLimit, upperRedshiftLimit, redshiftResolution]

    multiplier = int(1 / redshiftResolution)
    redshiftResolution = int(redshiftResolution * multiplier)
    upperRedshiftLimit = int(upperRedshiftLimit * multiplier)

    shelltransientRateDensityList = []
    shellRedshiftList = []
    discoveryRedshiftList = []
    discoveryFractionList = []
    tooFaintRedshiftList = []
    tooFaintFractionList = []
    shortCampaignRedshiftList = []
    shortCampaignFractionList = []
    transientShellRateList = []
    tooFaintShellRateList = []
    shortCampaignShellRateList = []

    count = 0
    for shellRedshift in np.arange(lowerRedshiftLimit, upperRedshiftLimit, redshiftResolution):
        count += 1
        shellRedshift = float(shellRedshift) / float(multiplier)
        shellWidth = float(redshiftResolution) / float(multiplier)
        shellMiddle = shellRedshift - shellWidth / 2.
        shellRedshiftBottom = shellRedshift - shellWidth
        log.debug('%s. shellRedshift %s' % (count, shellRedshift,))
        log.debug('%s. shellWidth %s' % (count, shellWidth,))
        log.debug('%s. shellMiddle %s' % (count, shellMiddle,))
        log.debug('%s. shellRedshiftBottom %s' % (count, shellRedshiftBottom,))

        SFH = (1. + shellMiddle)**2.5
        if shellRedshift == 0.:
            shellVolume = 9.
        elif shellRedshift > shellWidth:
            shellVolume = da.convert_redshift_to_distance(shellRedshift)[
                'dl_mpc']**3 - da.convert_redshift_to_distance(shellRedshift - shellWidth)['dl_mpc']**3
        else:
            shellVolume = da.convert_redshift_to_distance(shellRedshift)[
                'dl_mpc']**3
        shellSFRDensity = (SFH / anchor) * normaiseSFR * \
            (shellVolume / anchorVolume)
        shelltransientRateDensity = shellSFRDensity * transientRateFraction
        shelltransientRateDensity = shelltransientRateDensity * areaRatio
        timeDilation = 1. / (1. + shellMiddle)
        shelltransientRateDensity = shelltransientRateDensity * timeDilation

        log.debug('shelltransientRateDensity %s' %
                  (shelltransientRateDensity,))
        shelltransientRateDensityList.append(shelltransientRateDensity)
        shellRedshiftList.append(shellMiddle)

        discoveryFraction, tooFaintFraction, shortCampaignFraction = calculate_fraction_of_sn_discovered(
            log, surveyCadenceSettings, snSurveyDiscoveryTimes, redshifts, peakAppMagList, snCampaignLengthList, extraSurveyConstraints, shellRedshiftBottom, shellRedshift)
        # if discoveryFraction != 0.:
        discoveryRedshiftList.append(shellMiddle)
        discoveryFractionList.append(discoveryFraction)
        log.debug('shell redshift %s, discoveryFraction %s' %
                  (shellMiddle, discoveryFraction))
        # if tooFaintFraction != 0.:
        tooFaintRedshiftList.append(shellMiddle)
        tooFaintFractionList.append(tooFaintFraction)
        # if shortCampaignFraction != 0.:
        shortCampaignRedshiftList.append(shellMiddle)
        shortCampaignFractionList.append(shortCampaignFraction)

        transientShellRate = discoveryFraction * shelltransientRateDensity
        tooFaintShellRate = tooFaintFraction * shelltransientRateDensity
        shortCampaignShellRate = shortCampaignFraction * shelltransientRateDensity
        transientShellRateList.append(transientShellRate)
        tooFaintShellRateList.append(tooFaintShellRate)
        shortCampaignShellRateList.append(shortCampaignShellRate)

    discoveryRedshiftListArray = np.array(discoveryRedshiftList)
    discoveryFractionListArray = np.array(discoveryFractionList)
    tooFaintRedshiftListArray = np.array(tooFaintRedshiftList)
    tooFaintFractionListArray = np.array(tooFaintFractionList)
    shortCampaignRedshiftListArray = np.array(shortCampaignRedshiftList)
    shortCampaignFractionListArray = np.array(shortCampaignFractionList)
    polynomialDict = {}
    orginalDataDictionary = {}
    pleasePlot = False
    if len(discoveryRedshiftListArray) > 0:
        pleasePlot = True
        thisPoly = np.polyfit(discoveryRedshiftListArray,
                              discoveryFractionListArray, 12)
        discoveryRateCurve = np.poly1d(thisPoly)
        # polynomialDict["Discovery Rate"] = discoveryRateCurve
        orginalDataDictionary["Discovery Rate"] = [
            discoveryRedshiftListArray, discoveryFractionListArray]
    else:
        discoveryRateCurve = False
    if len(tooFaintRedshiftListArray) > 0:
        pleasePlot = True
        thisPoly = np.polyfit(tooFaintRedshiftListArray,
                              tooFaintFractionListArray, 12)
        tooFaintRateCurve = np.poly1d(thisPoly)
        #polynomialDict["Detected - too faint to constrain as transient"] = tooFaintRateCurve
        orginalDataDictionary["Detected - too faint to constrain as transient"] = [
            tooFaintRedshiftListArray, tooFaintFractionListArray]
    else:
        tooFaintRateCurve = False
    if len(shortCampaignRedshiftListArray) > 0:
        pleasePlot = True
        thisPoly = np.polyfit(shortCampaignRedshiftListArray,
                              shortCampaignFractionListArray, 12)
        shortCampaignRateCurve = np.poly1d(thisPoly)
        #polynomialDict["Detected - campaign to short to constrain as transient"] = shortCampaignRateCurve
        orginalDataDictionary["Detected - campaign to short to constrain as transient"] = [
            shortCampaignRedshiftListArray, shortCampaignFractionListArray]
    else:
        shortCampaignRateCurve = False

    if pleasePlot:

        imageLink = plot_polynomial(
            log,
            title='PS1-transient Survey - Precentage of transient Detected',
            polynomialDict=polynomialDict,
            orginalDataDictionary=orginalDataDictionary,
            pathToOutputPlotsFolder=pathToOutputPlotFolder,
            xRange=xRange,
            xlabel=False,
            ylabel=False,
            xAxisLimits=False,
            yAxisLimits=[-0.05, 1.05],
            yAxisInvert=False,
            prependNum=False,
            legend=True)
    else:
        imageLink = ""

    shelltransientRateDensityArray = np.array(shelltransientRateDensityList)
    shellRedshiftArray = np.array(shellRedshiftList)
    log.debug('shellRedshiftArray %s' % (shellRedshiftArray,))
    log.debug('shelltransientRateDensityArray %s' %
              (shelltransientRateDensityArray,))
    thisPoly = np.polyfit(shellRedshiftArray,
                          shelltransientRateDensityArray, 3)
    transientRateCurve = np.poly1d(thisPoly)
    # log.info('flat %s' % (flatPoly,))
    polynomialDict = {"transient Rate": transientRateCurve}
    orginalDataDictionary = {"transient Rate": [
        shellRedshiftArray, shelltransientRateDensityArray]}
    imageLink = plot_polynomial(
        log,
        title='PS1 MDF transient Rate Density',
        polynomialDict=polynomialDict,
        orginalDataDictionary=orginalDataDictionary,
        pathToOutputPlotsFolder=pathToOutputPlotFolder,
        xRange=xRange,
        xlabel=False,
        ylabel=False,
        xAxisLimits=False,
        yAxisLimits=False,
        yAxisInvert=False,
        prependNum=False)

    polynomialDict = {}
    orginalDataDictionary = {}
    pleasePlot = False
    if discoveryRateCurve:
        pleasePlot = True
        discoveredtransientCurve = transientRateCurve * discoveryRateCurve
        discoveredtransientCurve = np.poly1d(discoveredtransientCurve)
        #polynomialDict['Discovered transient Rate'] = discoveredtransientCurve
        orginalDataDictionary['Discovered transient Rate'] = [
            shellRedshiftArray, shelltransientRateDensityArray * discoveryFractionListArray]
    if tooFaintRateCurve:
        pleasePlot = True
        tooFainttransientCurve = transientRateCurve * tooFaintRateCurve
        tooFainttransientCurve = np.poly1d(tooFainttransientCurve)
        #polynomialDict["Detected - too faint to constrain as transient"] = tooFainttransientCurve
        orginalDataDictionary["Detected - too faint to constrain as transient"] = [
            shellRedshiftArray, shelltransientRateDensityArray * tooFaintFractionListArray]
    if shortCampaignRateCurve:
        pleasePlot = True
        shortCampaigntransientCurve = transientRateCurve * shortCampaignRateCurve
        shortCampaigntransientCurve = np.poly1d(shortCampaigntransientCurve)
        #polynomialDict["Detected - campaign to short to constrain as transient"] = shortCampaigntransientCurve
        orginalDataDictionary["Detected - campaign to short to constrain as transient"] = [
            shellRedshiftArray, shelltransientRateDensityArray * shortCampaignFractionListArray]

    maxInList = max(redshifts) * 1.1

    if pleasePlot:
        imageLink = plot_polynomial(
            log,
            title='PS1-transient Survey - Relative Detected Rates',
            polynomialDict=polynomialDict,
            orginalDataDictionary=orginalDataDictionary,
            pathToOutputPlotsFolder=pathToOutputPlotFolder,
            xRange=[0.01, maxInList, 0.01],
            xlabel=False,
            ylabel=False,
            xAxisLimits=False,
            yAxisLimits=[-0.05, 3.5],
            yAxisInvert=False,
            prependNum=False,
            legend=True)
    else:
        imageLink = ""

    log.debug('transientShellRateList: %s' % (transientShellRateList,))

    totalRate = 0.
    for item in transientShellRateList:
        totalRate += item

    tooFaintRate = 0.
    for item in tooFaintShellRateList:
        tooFaintRate += item

    shortCampaignRate = 0.
    for item in shortCampaignShellRateList:
        shortCampaignRate += item

    totalRate = "%1.0f" % (totalRate,)
    tooFaintRate = "%1.0f" % (tooFaintRate,)
    shortCampaignRate = "%1.0f" % (shortCampaignRate,)
    return imageLink, totalRate, tooFaintRate, shortCampaignRate