docs: 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