; docformat = 'rst'

;+
;
; Class to manage a spectrum fitted using the Monte Carlo approach.
;
; Updated versions of the NASA Ames PAH IR Spectroscopic Database and
; more information can be found at: `www.astrochemistry.org/pahdb <https://www.astrochemistry.org/pahdb>`.
;
; :Examples:
;   Create and destroy an
;   AmesPAHdbIDLSuite_MCFitted_Spectrum-instance::
;
;     IDL> mcfitted = OBJ_NEW('AmesPAHdbIDLSuite_MCFitted_Spectrum')
;     IDL> mcfitted->Set,data
;     IDL> mcfitted->Plot
;     IDL> OBJ_DESTORY,mcfitted
;
; :Author:
;   Dr. Christiaan Boersma
;
; :Copyright:
;   BSD licensed
;
; :History:
;   Changes::
;
;     09-19-2024
;     Use standard deviation in DESCRIPTION. Christiaan Boersma.
;     09-10-2024
;     Added GETAVERAGENUMBEROFCARBONATOMS. Christiaan Boersma.
;     08-28-2024
;     Trim trailing whitespace. Christiaan Boersma.
;     11-22-2023
;     Add GETTOLERANCE and GETITERATIONS. Christiaan Boersma.
;     11-17-2023
;     Add MEDIUM size bin to GETBREAKDOWN, GETCLASSES, and PLOT. Christiaan
;     Boersma.
;     10-04-2023
;     Allow setting the SMALL threshold in PLOT through keyword-inheritance.
;     Christiaan Boersma
;     04-15-2023
;     Add observation property. Christiaan Boersma.
;     11-23-2022
;     Add _overloads and add+propagate missing PAHdb-keyword in INIT and
;     SET. Christiaan Boersma.
;     06-14-2022
;     Refactor lazy instantiation and keep track of function signatures
;     Christiaan Boersma.
;     06-13-2022
;     Use lazy instantiation for getters. Christiaan Boersma.
;     05-03-2022
;     Remove extraneous plot in GETSIZEDISTRIBUTION. Refactor GETCHISQUARED,
;     GETNORM, and GETRESIDUAL, making sure use of proper uncertainties.
;     Christiaan Boersma.
;     04-28-2022
;     Use piecewise errors in DESCRIPTION and add filter. Christiaan Boersma.
;     04-25-2022
;     First version of the file. Christiaan Boersma.
;-

;+
;  Output spectrum description.
;
;  :Params:
;    Str: out, optional, type="string array"
;      Ouput to Str
;
; :Categories:
;   INFORMATIVE
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum::Description,Str

  COMPILE_OPT IDL2

  ON_ERROR,2

  (*self.obj)[0]->Description,Str

  Str = STRSPLIT(Str, "!C", /REGEX, /EXTRACT)

  err = self->getError()
  ntags = N_TAGS(err)
  FOR i = 0L, ntags - 1L DO $
    IF err.(i)[1] GE 0.0D THEN $
      err.(i)[1] = SQRT(err.(i)[1])

  tags = STRLOWCASE(TAG_NAMES(err))

  filter = ['norm', 'chisquared', 'error', tags[1:*]]

  nfilter = N_ELEMENTS(filter)
  FOR i = 0L, nfilter - 1L DO BEGIN

      keep = WHERE(STRMID(Str, 0, 2 + STRLEN(filter[i])) NE "|_" + filter[i])

      Str = Str[keep]
  ENDFOR

  Str = [Str, $
         STRING(FORMAT='(A-12,":",X,A0)', "Monte Carlo"), $
         STRING(FORMAT='(A-12,":",X,A0)', "|_draw", self.distribution), $
         STRING(FORMAT='(A-12,":",X,I0)', "|_samples", N_ELEMENTS(*self.obj)), $
         STRING(FORMAT='(A-12,":",X,g-4.2,"!M'+STRING( 177B )+'",g-11.2)', "|_error", err.(0)[0], err.(0)[1])]

  FOR i = 1L, ntags - 1L DO $
    IF err.(i)[0] GE 0.0D THEN $
      Str = [Str, $
      STRING(FORMAT='(A-12,":",X,g-4.2,"!M'+STRING( 177B )+'",g-11.2)', "|_" + tags[i], err.(i)[0], err.(i)[1])]

  Str = STRJOIN(Str, "!C")

  IF N_PARAMS() GT 0 THEN RETURN

  PRINT,STRJOIN(STRSPLIT(Str, "!C", /EXTRACT, /REGEX), STRING(10B))
END

;+
;  Plot the fitted spectrum.
;
;  :Keywords:
;    DistributionSize: in, optional, type=int
;      Toggle to plot the PAH size distribution
;    Size: in, optional, type=int
;      Toggle to plot the size breakdown of the fit
;    Charge: in, optional, type=int
;      Toggle to plot the charge breakdown of the fit
;    Composition: in, optional, type=int
;      Toggle to plot the compositional breakdown of the fit
;    Wavelength: in, optional, type=int
;      Whether to set the abscissa units to wavelength
;    Stick: in, optional, type=int
;      Whether to plot the spectrum as sticks
;    Fill: in, optional, type=int
;       Whether to solid-fill the spectrum
;    Legend: in, optional, type=int
;      Whether to show a legend
;    Color: in, optional, type=int
;      Color to plot the spectrum with
;    _EXTRA: in, optional, type=struct
;      Required for IDL's keyword-inheritance mechanism
;
; :Categories:
;   PLOTTING
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum::Plot,DistributionSize=DistributionSize,Size=Size,Charge=Charge,Composition=Composition,Wavelength=Wavelength,Stick=Stick,Fill=Fill,Legend=Legend,Color=Color,_EXTRA=EXTRA

  COMPILE_OPT IDL2

  ON_ERROR,2

  self->AmesPAHdbIDLSuite_Plot::Setup,Oplot=Oplot,XSIZE=600,YSIZE=400

  IF KEYWORD_SET(DistributionSize) THEN GOTO,SIZEDISTRIBUTION

  obj_s = (*self.obj)[0]->Get()

  x = obj_s.grid

  nx = N_ELEMENTS(x)

  xunits = obj_s.units.abscissa.str

  xrange = [MAX(x, MIN=xmin), xmin]

  obs_s = self.observation->Get()

  obs_xstdev = obs_s.data.xstdev

  IF KEYWORD_SET(Wavelength) THEN BEGIN

     obs_xstdev = 1D4 * obs_xstdev / x^2

     x = 1D4 / x

     xrange = [MIN(x, MAX=xmax), xmax]

     xunits = 'wavelength [!Mm!Xm]'
  ENDIF

  yunits = obs_s.units.ordinate.str

  IF yunits NE '' THEN yunits += '!C'

  yunits += obj_s.units.ordinate.str

  fit = self->getFit()

  fit[*, 1] = SQRT(fit[*, 1])

  IF NOT KEYWORD_SET(Oplot) THEN BEGIN

     IF KEYWORD_SET(Size) OR KEYWORD_SET(Charge) OR KEYWORD_SET(COMPOSITION) THEN self->AmesPAHdbIDLSuite_Plot::Plot,[0,1],[0,1],COLOR=Color,XRANGE=[MIN(x - obs_xstdev), MAX(x + obs_xstdev)],YRANGE=[MIN([obs_s.data.y - obs_s.data.ystdev, obs_s.data.continuum + fit[*, 0] - fit[*, 1]]), MAX([obs_s.data.y + obs_s.data.ystdev, obs_s.data.continuum + fit[*, 0] + fit[*, 1]])],XTITLE=xunits,YTITLE=yunits,POSITION=[0.2,0.2,0.95,0.9],/NoData,_EXTRA=EXTRA $
     ELSE self->AmesPAHdbIDLSuite_Plot::Plot,[0,1],[0,1],COLOR=Color,XRANGE=[MIN(x - obs_xstdev), MAX(x + obs_xstdev)],YRANGE=[MIN([obs_s.data.y - obs_s.data.ystdev, obs_s.data.continuum + fit[*, 0]] - fit[*, 1]), MAX([obs_s.data.y + obs_s.data.ystdev, obs_s.data.continuum + fit[*, 0] +fit[*, 1] ])],XTICKFORMAT='(A1)',YTITLE=yunits,POSITION=[0.2,0.45,0.95,0.9],/NoData,_EXTRA=EXTRA
  ENDIF

  self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.y,obs_s.data.ystdev,obs_xstdev,COLOR=14

  self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.y,LINESTYLE=5,COLOR=14

  self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum,LINESTYLE=2

  self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+fit[*, 0],fit[*, 1]

  self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+fit[*, 0]

  IF SIZE(Legend, /TYPE) EQ 0 THEN Legend = 1

  IF Legend THEN BEGIN

     self->Description,outs

     self->AmesPAHdbIDLSuite_Plot::Legend,outs
  ENDIF

  IF NOT KEYWORD_SET(Color) THEN Color = 2

  IF KEYWORD_SET(Size) OR KEYWORD_SET(Charge) OR KEYWORD_SET(COMPOSITION) THEN BEGIN

     classes = self->getClasses(_EXTRA=EXTRA)

     ntags = N_TAGS(classes)
     FOR i = 0L, ntags - 1 DO classes[*, 1].(i) = SQRT(classes[*, 1].(i))

     IF KEYWORD_SET(Size) THEN BEGIN

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].small,classes[*, 1].small,Color=Color

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].small,Stick=Stick,Fill=Fill,COLOR=Color

        XYOUTS,0.25,0.75,'small',COLOR=Color,/Normal

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].medium,classes[*, 1].medium,Color=Color+1

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].medium,Stick=Stick,Fill=Fill,COLOR=Color+1

        XYOUTS,0.25,0.70,'medium',COLOR=Color+1,/Normal

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].large,classes[*, 1].large,Color=Color+2

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].large,Stick=Stick,Fill=Fill,COLOR=Color+2

        XYOUTS,0.25,0.65,'large',COLOR=Color+2,/NORMAL
     ENDIF ELSE IF KEYWORD_SET(Charge) THEN BEGIN

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].anion,classes[*, 1].anion,COLOR=Color

        self->AmesPAHdbIDLSuite_Plot::oPlot,x,obs_s.data.continuum+classes[*, 0].anion,Stick=Stick,Fill=Fill,COLOR=Color

        XYOUTS,0.25,0.75,'anion',COLOR=Color,/Normal

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].neutral,classes[*, 1].neutral,COLOR=Color+1

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].neutral,Stick=Stick,Fill=Fill,COLOR=Color+1

        XYOUTS,0.25,0.70,'neutral',COLOR=Color+1,/Normal

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].cation,classes[*, 1].cation,Stick=Stick,Fill=Fill,COLOR=Color+2

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].cation,COLOR=Color+2

        XYOUTS,0.25,0.65,'cation',COLOR=Color+2,/Normal
     ENDIF ELSE BEGIN

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].pure,classes[*, 1].pure,Stick=Stick,Fill=Fill,COLOR=Color

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].pure,COLOR=Color

        XYOUTS,0.25,0.75,'pure',COLOR=Color,/Normal

        self->AmesPAHdbIDLSuite_Plot::OplotError,x,obs_s.data.continuum+classes[*, 0].nitrogen,classes[*, 1].nitrogen,Stick=Stick,Fill=Fill,COLOR=Color+1

        self->AmesPAHdbIDLSuite_Plot::Oplot,x,obs_s.data.continuum+classes[*, 0].nitrogen,COLOR=Color+1

        XYOUTS,0.25,0.70,'nitrogen',COLOR=Color+1,/Normal
     ENDELSE
  ENDIF ELSE BEGIN

     res = self->getResidual()

     y = -100D *  res[*, 0] / obs_s.data.y

     ystdev = 100 * SQRT(res[*, 1])

     self->AmesPAHdbIDLSuite_Plot::Plot,x,y,XRANGE=[MIN(x - obs_xstdev), MAX(x + obs_xstdev)],YRANGE=[MIN(y - ystdev), MAX(y + ystdev)],XTITLE=xunits,YTITLE='residual!C[%]',POSITION=[0.2,0.2,0.95,0.45],/NoData,/NOERASE,_EXTRA=EXTRA

     self->AmesPAHdbIDLSuite_Plot::OplotError,x,y,ystdev

     self->AmesPAHdbIDLSuite_Plot::Oplot,!X.CRANGE,[0,0],Color=14,LINESTYLE=5

     self->AmesPAHdbIDLSuite_Plot::Oplot,x,y,Stick=Stick,Fill=Fill,COLOR=Color
  ENDELSE

  GOTO,FINISH

SIZEDISTRIBUTION:

  sd = self->getSizeDistribution(_EXTRA=EXTRA)

  tot = TOTAL(sd.distribution[*, 0])

  dist = 100.0 * sd.distribution[*, 0] / tot

  unc = 100.0 * SQRT(sd.distribution[*, 1]) / tot

  nbins = N_ELEMENTS(sd.size) - 1

  PLOT,[0,1],[0,1],XRANGE=[sd.size[0], sd.size[nbins]],YRANGE=[0, MAX(dist + unc)],XTITLE='n!LCarbon!N',YTITLE='frequency [%]',POSITION=[0.2,0.2,0.95,0.9],/NODATA

  PLOTS,[sd.size[0],sd.size[0],sd.size[1]],[0,dist[0],dist[0]],COLOR=2,THICK=2

  FOR i = 1, nbins - 1 DO PLOTS,[sd.size[i], sd.size[i], sd.size[i+1]],[dist[i-1],dist[i],dist[i]],COLOR=2,THICK=2

  PLOTS,[1,1]*sd.size[nbins],[0,dist[nbins-1]],COLOR=2,THICK=2

  FOR i = 0L, nbins - 1L DO PLOTS,sd.size[i]*[1,1]+(sd.size[i+1]-sd.size[i])/2.0,dist[i]+[-1.0,1.0]*unc[i],COLOR=2,THICK=2

FINISH:

  self->AmesPAHdbIDLSuite_Plot::Restore
END

;+
; Write the fitted spectrum to file as an IPAC-table.
;
; :Params:
;   Filename: in, optional, type=string
;     Output filename
;
; :Categories:
;   OUTPUT
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum::Write,Filename

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF N_PARAMS() LT 1 THEN Filename = OBJ_CLASS(self) + '.tbl'

  timestamp = SYSTIME()
  hdr = []
  FXADDPAR,hdr,"DATE",timestamp," Date this file was generated"
  FXADDPAR,hdr,"ORIGIN","NASA Ames Research Center"," Organization generating this file"
  FXADDPAR,hdr,"CREATOR",STRING(FORMAT='("IDL",X,A0,X,"on",X,A0)', !VERSION.RELEASE, !VERSION.OS_NAME)," Software used to create this file"
  FXADDPAR,hdr,"SOFTWARE","AmesPAHdbIDLSuite"," Program used to create this file"
  FXADDPAR,hdr,"AUTHOR","Dr. C. Boersma"," Author of the program"
  FXADDPAR,hdr,"TYPE",OBJ_CLASS(self)," AmesPAHdbIDLSuite data type"

  self->Description,description

  comments = STRSPLIT(description, "!C", /EXTRACT, /REGEX, COUNT=ncomments)

  FOR i = 0L, ncomments - 1 DO FXADDPAR,hdr,"COMMENT",comments[i]

  obj_s = (*self.obj)[0]->Get()

  abscissa = STREGEX(obj_s.units.abscissa.str, '(.*) \[(.*)\]', /SUBEXPR, /EXTRACT)

  ordinate = STREGEX(obj_s.units.ordinate.str, '(.*) \[(.*)\]', /SUBEXPR, /EXTRACT)

  ordinate_unc = ordinate[1] + '_unc'

  half_abscissa_len = STRLEN(abscissa[1]) / 2
  half_ordinate_len = STRLEN(ordinate[1]) / 2
  half_ordinate_unc_len = STRLEN(ordinate_unc) / 2


  fmt1 = '("|",A' + STRING(FORMAT='(I0)', 12 + half_abscissa_len) + ',' + STRING(FORMAT='(I0)', 13 - half_abscissa_len) + 'X,' + $
          '"|",A' + STRING(FORMAT='(I0)', 12 + half_ordinate_len) + ',' + STRING(FORMAT='(I0)', 13 - half_ordinate_len) + 'X,' + $
          '"|",A' + STRING(FORMAT='(I0)', 12 + half_ordinate_unc_len) + ',' + STRING(FORMAT='(I0)', 13 - half_ordinate_unc_len) + 'X,' + $
          '"|")'

  fmt2 = '("|",A' + STRING(FORMAT='(I0)', 12 + 3) + ',' + STRING(FORMAT='(I0)', 13 - 3) + 'X,' + $
          '"|",A' + STRING(FORMAT='(I0)', 12 + 3) + ',' + STRING(FORMAT='(I0)', 13 - 3) + 'X,' + $
          '"|",A' + STRING(FORMAT='(I0)', 12 + 3) + ',' + STRING(FORMAT='(I0)', 13 - 3) + 'X,' + $
          '"|")'

  half_abscissa_len = STRLEN(abscissa[2]) / 2
  half_ordinate_len = STRLEN(ordinate[2]) / 2

  fmt3 = '("|",A' + STRING(FORMAT='(I0)', 12 + half_abscissa_len) + ',' + STRING(FORMAT='(I0)', 13 - half_abscissa_len) + 'X,' + $
          '"|",A' + STRING(FORMAT='(I0)', 12 + half_ordinate_len) + ',' + STRING(FORMAT='(I0)', 13 - half_ordinate_len) + 'X,' + $
          '"|",A' + STRING(FORMAT='(I0)', 12 + half_ordinate_len) + ',' + STRING(FORMAT='(I0)', 13 - half_ordinate_len) + 'X,' + $
          '"|")'

  cols = [STRING(FORMAT=fmt1,STRUPCASE(abscissa[1]),STRUPCASE(ordinate[1]),STRUPCASE(ordinate_unc)), $
          STRING(FORMAT=fmt2,"double","double","double"), $
          STRING(FORMAT=fmt3,abscissa[2],ordinate[2],ordinate[2])]


  grid = self->GetGrid()

  fit = self->GetFit()

  OPENW,funit,Filename,/GET_LUN
  PRINTF,funit,FORMAT='("\",A0)',hdr[0:WHERE(STRPOS(hdr, 'END') EQ 0)]
  PRINTF,funit,STRJOIN(cols, STRING( 10B ))

  n = N_ELEMENTS(grid)
  FOR i = 0L, n - 1L DO PRINTF,funit,FORMAT='(X,F25.6,X,F25.6,X,F25.6)',grid[i],fit[i,0],SQRT(fit[i,1])

  CLOSE,funit
  FREE_LUN,funit

  PRINT
  PRINT,"========================================================="
  PRINT,"    WRITTEN IPAC TABLE: ", Filename
  PRINT,"========================================================="
  PRINT
END

;+
; Retrieves the AmesPAHdbIDLSuite_MCFitted_Spectrum representation in a
; structure.
;
; :Returns:
;   Structure
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::Get

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self.obj) THEN RETURN, 0

  RETURN,CREATE_STRUCT('type', OBJ_CLASS(self)+'_S', $
                       'obj', *self.obj, $
                       'distribution', self.distribution, $
                       'observation', self.observation)
END

;+
; Populates the AmesPAHdbIDLSuite_MCFitted_Spectrum-instance.
;
; :Params:
;   Struct: in, optional, type=struct
;     Data structure
;
; :Keywords:
;   Type: in, optional, type=string
;     Type of Data
;   obj: in, optional, type=obj-array
;     Array holding AmesPAHdbIDLSuiteFittedSpectrum instances
;   distribution: in, optional, type=string
;     Distribution used for permutating errors
;
; :Categories:
;   SET/GET
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum::Set,Struct,Type=Type,Obj=Obj,Distribution=Distribution,Observation=Observation,PAHdb=PAHdb

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF N_PARAMS() GT 0 THEN BEGIN

     tag = WHERE(TAG_NAMES(Struct) EQ 'TYPE', ntype)

     IF ntype EQ 1 THEN BEGIN

        IF Struct.(tag) EQ OBJ_CLASS(self)+'_S' THEN BEGIN

           IF NOT KEYWORD_SET(Obj) THEN BEGIN

              IF PTR_VALID(self.obj) THEN BEGIN

                PTR_FREE,self.obj

                self.InvalidateLazy
              ENDIF

              self.obj = PTR_NEW(Struct.obj)
           ENDIF

          IF NOT KEYWORD_SET(Distribution) THEN self.distribution = Struct.distribution

          IF NOT KEYWORD_SET(Observation) THEN BEGIN

            IF OBJ_VALID(Struct.observation) THEN self.observation = Struct.observation
          ENDIF

        ENDIF
     ENDIF
  ENDIF

  IF KEYWORD_SET(Obj) THEN BEGIN

    IF PTR_VALID(self.obj) THEN BEGIN

        PTR_FREE,self.obj

        self.InvalidateLazy
    ENDIF

      self.obj = PTR_NEW(Obj)
   ENDIF

   IF KEYWORD_SET(Distribution) THEN self.distribution = Distribution

   IF KEYWORD_SET(Observation) THEN BEGIN

     IF OBJ_VALID(Observation) THEN self.observation = Observation
   ENDIF

   IF KEYWORD_SET(PAHdb) THEN BEGIN

     IF PTR_VALID(self.obj) THEN BEGIN

       nobj = N_ELEMENTS(*self.obj)

       FOR i = 0L, nobj - 1L DO (*self.obj)[i]->Set,PAHdb=PAHdb

     ENDIF

   ENDIF

END

;+
; Retrieves the residual of the fit.
;
; :Returns:
;    A n by four-element vector containing the mean, variance, skewness, and kurtosis
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetResidual

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.residual) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    res = DBLARR(nobj, N_ELEMENTS((*self.obj)[0]->GetGrid()))
    FOR i = 0L, nobj - 1L DO res[i, *] = (*self.obj)[i]->GetResidual()

    self._lazy.residual = PTR_NEW(MOMENT(res, DIMENSION=1))
  ENDIF

  RETURN, *self._lazy.residual
END

;+
; Retrieves the total fit.
;
; :Returns:
;   float array
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetFit

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.fit) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    spc = DBLARR(nobj, N_ELEMENTS((*self.obj)[0]->GetGrid()))
    FOR i = 0L, nobj - 1L DO spc[i, *] = (*self.obj)[i]->GetFit()

    self._lazy.fit = PTR_NEW(MOMENT(spc, DIMENSION=1))
  ENDIF

  RETURN,*self._lazy.fit
END

;+
; Retrieves the PAH size distribution of the fitted PAHs.
;
; :Keywords:
;   NBins: in, optional, type=int
;     Set the number of bins to use, otherwise Rice's rule is used.
;   Min: in, optional, type=float
;    Set the minimum size to consider.
;   Max: in, optional, type=float
;    Set the maximum size to consider.
;
; :Returns:
;   A structure with the distribution as a four-element vectors
;   containing the mean, variance, skewness, and kurtosis
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetSizeDistribution,NBins=nbins,Min=min,Max=max

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.sizedistribution) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    his = PTRARR(nobj, /ALLOCATE_HEAP)

    left = 1D100 ; a Google!

    right = 0

    n = 0L

    FOR i = 0L, nobj - 1L DO BEGIN

      (*his[i]) = (*self.obj)[i]->GetSizeDistribution(NBins=nbins, Min=min, Max=max)

      hleft = MIN((*his[i]).size, MAX=hright)

      IF hleft LT left THEN min = hleft
      IF hright GT right THEN max = hright

      n += N_ELEMENTS((*his[i]).size)
    ENDFOR

    nbins = FLOOR((n - 1L) / FLOAT(nobj))

    size = min + (max - min) * DINDGEN(nbins + 1) / DOUBLE(nbins)

    dist = DBLARR(nbins, nobj)

    FOR i = 0L, nobj - 1L DO BEGIN

      idx = VALUE_LOCATE(size, (*his[i]).size)

      left = WHERE(idx LT 0, c)
      IF c GT 0 THEN idx[left] = 0
      right = WHERE(idx GE nbins, c)
      IF c GT 0 THEN idx[right] = nbins - 1

      n = N_ELEMENTS((*his[i]).distribution)
      FOR j = 0L, n - 1L DO dist[idx[j], i] = (*his[i]).distribution[j]
    ENDFOR

    FOR i = 0L, nobj - 1L DO PTR_FREE,his[i]

    self._lazy.sizedistribution = PTR_NEW({AmesPAHdbIDLSuite_MCFitted_SizeDistribution, $
                                           size:size, $
                                           distribution:MOMENT(dist, DIMENSION=2)})
  ENDIF

  RETURN,*self._lazy.sizedistribution
END

;+
; Retrieves the average number and standard deviation of carbon atoms of the
; fitted PAHs.
;
; :Returns:
;   double array
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetAverageNumberOfCarbonAtoms

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.averagenumberofcarbonatoms) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    nc = DBLARR(nobj, 2)

    FOR i = 0L, nobj - 1L DO $
      nc[i,*] = (*self.obj)[i]->GetAverageNumberOfCarbonAtoms()

      self._lazy.averagenumberofcarbonatoms = PTR_NEW([MOMENT(nc[*,0]), MOMENT(nc[*,1])])
  END

  RETURN,*self._lazy.averagenumberofcarbonatoms
END

;+
; Retrieves the breakdown of the fitted PAHs.
;
; :Keywords:
;   Small: in, optional, type=int
;     Cut-off size for small PAHs, defaults to 50
;   Medium: in, optional, type=int
;     Cut-off size for medium PAHs, defaults to 90
;   Flux: in, optional, type=int
;     Whether to use the flux to determine the breakdown
;   Absolute: in, optional, type=int
;     Whether to return the absolute breakdown, otherwise normalize
;
; :Returns:
;   A structure with four-element vectors containing the mean,
;   variance, skewness, and kurtosis
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetBreakdown,Small=Small,Medium=Medium,Flux=Flux,Absolute=Absolute

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF PTR_VALID(self._lazy.breakdown) THEN BEGIN

    signature = ([KEYWORD_SET(Small) ? Small : 0L, $
                  KEYWORD_SET(Medium) ? Medium : 0L, $
                  KEYWORD_SET(Flux), $
                  KEYWORD_SET(Absolute)]).HASHCODE()

    IF signature NE self._lazy.breakdown_sig THEN BEGIN

      PTR_FREE,self._lazy.breakdown

      self._lazy.breakdown_sig = signature
    ENDIF
  ENDIF

  IF NOT PTR_VALID(self._lazy.breakdown) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    bd = REPLICATE({AmesPAHdb_Breakdown, $
                     anion:0D, $
                     neutral:0D, $
                     cation:0D, $
                     small:0D, $
                     medium:0D, $
                     large:0D, $
                     pure:0D, $
                     nitrogen:0D, $
                     solo:0L, $
                     duo:0L, $
                     trio:0L, $
                     quartet:0L, $
                     quintet:0L}, nobj)

    FOR i = 0L, nobj - 1L DO $
      bd[i] = (*self.obj)[i]->GetBreakdown(Small=Small,Medium=Medium,Flux=Flux,Absolute=Absolute)

    mcbd = {AmesPAHdb_MCBreakdown, $
            anion:DBLARR(4), $
            neutral:DBLARR(4), $
            cation:DBLARR(4), $
            small:DBLARR(4), $
            medium:DBLARR(4), $
            large:DBLARR(4), $
            pure:DBLARR(4), $
            nitrogen:DBLARR(4), $
            solo:LONARR(4), $
            duo:LONARR(4), $
            trio:LONARR(4), $
            quartet:LONARR(4), $
            quintet:LONARR(4)}

    ntags = N_TAGS(bd)

    FOR i = 0L, ntags - 1L DO $
      mcbd.(i) = MOMENT(bd.(i))

    self._lazy.breakdown = PTR_NEW(mcbd)
  ENDIF

  RETURN,*self._lazy.breakdown
END

;+
; Retrieves the spectra of the different classes of the fitted PAHs.
;
; :Keywords:
;   Small: in, optional, type=int
;     Cut-off size for small PAHs, defaults to 50
;   Medium: in, optional, type=int
;     Cut-off size for medium PAHs, defaults to 90
;
; :Returns:
;   Structure
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetClasses,Small=Small,Medium=Medium

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF PTR_VALID(self._lazy.classes) THEN BEGIN

    signature = ([KEYWORD_SET(Small) ? Small : 0L, $
                  KEYWORD_SET(Medium) ? Medium : 0L]).HASHCODE()

    IF signature NE self._lazy.classes_sig THEN BEGIN

      PTR_FREE,self._lazy.classes

      self._lazy.classes_sig = signature
    ENDIF
  ENDIF

  IF NOT PTR_VALID(self._lazy.classes) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    ngrid = N_ELEMENTS((*self.obj)[0]->GetGrid())

    cls = REPLICATE({AmesPAHdb_Classes, $
                     anion:0D, $
                     neutral:0D, $
                     cation:0D, $
                     small:0D, $
                     medium:0D, $
                     large:0D, $
                     pure:0D, $
                     nitrogen:0D}, ngrid, nobj)

    FOR i = 0L, nobj - 1L DO $
      cls[*, i] = (*self.obj)[i]->GetClasses(Small=Small, Medium=Medium)

    mccls = REPLICATE({AmesPAHdb_Classes, $
                       anion:0D, $
                       neutral:0D, $
                       cation:0D, $
                       small:0D, $
                       medium:0D, $
                       large:0D, $
                       pure:0D, $
                       nitrogen:0D}, ngrid, 4)

    ntags = N_TAGS(cls)

    FOR i = 0L, ntags - 1L DO $
      mccls.(i) = MOMENT(cls.(i), DIMENSION=2)

    self._lazy.classes = PTR_NEW(mccls)
  ENDIF

  RETURN,*self._lazy.classes
END

;+
; Retrieves the abscissa values.
;
; :Returns:
;   double array (1D)
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetGrid

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF PTR_VALID(self.obj) THEN RETURN, (*self.obj)[0]->GetGrid()

  RETURN,0
END

;+
; Retrieves the Norm of the fit
;
; :Returns:
;   A four-element vector containing the mean, variance, skewness, and kurtosis
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetNorm

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.norm) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    nrm = DBLARR(nobj)
    FOR i = 0L,  nobj - 1L DO nrm[i] = (*self.obj)[i]->getNorm()

    self._lazy.norm = PTR_NEW(MOMENT(nrm))
  ENDIF

  RETURN,*self._lazy.norm
END

;+
; Retrieves the Chi-squared of the fit.
;
; :Returns:
;   A four-element vector containing the mean, variance, skewness, and kurtosis
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetChiSquared

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.chisquared) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    chi = DBLARR(nobj)
    FOR i = 0L,  nobj - 1L DO chi[i] = (*self.obj)[i]->GetChiSquared()

    self._lazy.chisquared = PTR_NEW(MOMENT(chi))
  ENDIF

  RETURN,*self._lazy.chisquared
END

;+
; Retrieves the error for the fit.
;
; :Returns:
;   A four-element vector containing the mean, variance, skewness, and kurtosis
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetError

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self._lazy.error) THEN BEGIN

    nobj = N_ELEMENTS(*self.obj)

    tags = ['err',  'e127',  'e112',  'e77', 'e62', 'e33']

    mcerr = REPLICATE(CREATE_STRUCT(NAME='AmesPAHdb_Piecewise_Error', $
                                    tags, -1.0D, -1.0D, -1.0D, -1.0D, -1.0D, -1.0D), $
                                    nobj)

    FOR i = 0L, nobj - 1L DO mcerr[i] = (*self.obj)[i]->GetError()

    arr = MAKE_ARRAY(4, VALUE=-1.0D)
    err = CREATE_STRUCT(NAME='AmesPAHdb_Piecewise_MCError', $
                        tags, arr, arr, arr, arr, arr, arr)

    ntags = N_ELEMENTS(tags)
    FOR i = 0, ntags - 1L DO err.(i) = MOMENT(mcerr.(i), /DOUBLE)

    self._lazy.error = PTR_NEW(err)
  ENDIF

  RETURN,*self._lazy.error
END

;+
; Retrieves the method used for the fit.
;
; :Returns:
;   string
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetMethod

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,(*self.obj)[0]->GetMethod()
END

;+
; Retrieves the tolerance used for the fit.
;
; :Returns:
;   double
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetTolerance

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,(*self.obj)[0]->getTolerance()
END

;+
; Retrieves the number of iterations needed for the fit.
;
; :Returns:
;   long
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetIterations

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,(*self.obj)[0]->getIterations()
END

;+
; Retrieves the number of samples.
;
; :Returns:
;   int
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetSamples

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,PTR_VALID(self.obj) ? N_ELEMENTS(*self.obj) : 0L
END

;+
; Retrieves the observation.
;
; :Returns:
;   float array
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetObservation

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF OBJ_VALID(self.observation) THEN RETURN,self.observation

  RETURN,0
END

;+
; Retrieves the distribution used for permutating errors.
;
; :Returns:
;   string
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::GetDistribution

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,self.distribution
END

;+
; Invalidate the AmesPAHdbIDLSuite_Fitted_Lazy structure.
;
; :Categories:
;   CLASS
;
; :Private:
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum::InvalidateLazy

  COMPILE_OPT IDL2

  ON_ERROR,2

  FOR i = 0L, N_TAGS(self._lazy) - 1L DO BEGIN

    IF PTR_VALID(self._lazy.(i)) THEN PTR_FREE,self._lazy.(i)
   ENDFOR
END

;+
; Overload the array subscript access operator.
;
; :Returns:
;   Object element
;
; :Params:
;   isRange: in, required, type=int
;     Whether index is an array
;   index: in, required, type="long or long array (1D)"
;     Index
;
; :Categories:
;   OVERLOAD
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadBracketsRightSide,isRange,index

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF PTR_VALID(self.obj) THEN BEGIN

     RETURN,(*self.obj)[index]
  ENDIF

  RETURN,-1
END

;+
; Overload FOREACH operator.
;
; :Returns:
;   1 on success or 0 on failure
;
; :Params:
;   value: out, required, type=object
;     Object element
;   index: out, required, type=long
;     Index associated with Object element
;
; :Categories:
;   OVERLOAD
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadForeach,value,index

  COMPILE_OPT IDL2

  ON_ERROR,2

  status = index LT N_ELEMENTS(*self.obj)

  IF status THEN value = self->_overloadBracketsRightSide(isRange, index)

  RETURN,status
END

;+
; Overload the Size information.
;
; :Returns:
;   long or long array
;
; :Categories:
;   OVERLOAD
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadSize

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,SIZE(*self.obj, /DIMENSIONS)
END

;+
; Overload the PRINT information.
;
; :Returns:
;   string or string array
;
; :Categories:
;   OVERLOAD
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadPrint

  COMPILE_OPT IDL2

  ON_ERROR,2

  self->Description,Str

  RETURN, STRJOIN(STRSPLIT(Str, "!C", /EXTRACT, /REGEX), STRING(10B))
END

;+
; Overload the Implied PRINT information.
;
; :Returns:
;   string or string array
;
; :Params:
;   arg: in, required, type=string
;     Variable name
;
; :Categories:
;   OVERLOAD
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadImpliedPrint,arg

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN, self->AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadPrint()
END

;+
; Overload the HELP information.
;
; :Returns:
;   string or string array
;
; :Params:
;   arg: in, required, type=string
;     Variable name
;
; :Categories:
;   OVERLOAD
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadHelp,arg

  COMPILE_OPT IDL2

  ON_ERROR,2

  Str = '<' + arg + '>' + STRING(10B) + $
        self->AmesPAHdbIDLSuite_MCFitted_Spectrum::_overloadPrint()

  RETURN,Str
END

;+
; Clean-up an AmesPAHdbIDLSuite_MCFitted_Spectrum-instance
;
; :Categories:
;   CLASS
;
; :Private:
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum::Cleanup

  COMPILE_OPT IDL2

  ON_ERROR,2

  self->AmesPAHdbIDLSuite_Plot::Cleanup

  IF PTR_VALID(self.obj) THEN OBJ_DESTROY,*self.obj
END

;+
; Create an AmesPAHdbIDLSuite_MCFitted_Spectrum-instance
;
; :Returns:
;   AmesPAHdbIDLSuite_MCFitted_Spectrum-instance
;
; :Params:
;   Struct: in, optional, type=struct
;     Data structure
;
; :Keywords:
;   Type: in, optional, type=string
;     Type of Data
;   obj: in, optional, type=obj-array
;     Array holding AmesPAHdbIDLSuiteFittedSpectrum instances
;   distribution: in, optional, type=string
;     Distribution used for permutating errors
;   observation: in, optional, type=string
;     Observation
;   PAHdb: in, optional, type=pointer
;     Pointer to parsed database file
;
; :Categories:
;   CLASS
;-
FUNCTION AmesPAHdbIDLSuite_MCFitted_Spectrum::Init,Struct,Type=Type,Obj=Obj,Distribution=Distribution,Observation=Observation,PAHdb=PAHdb

  COMPILE_OPT IDL2

  ON_ERROR,2

  self.state = self->AmesPAHdbIDLSuite_Plot::Init()

  IF N_PARAMS() GT 0 THEN self->Set,Struct,Type=Type,Obj=Obj,Distribution=Distribution,Observation=Observation,PAHdb=PAHdb $
  ELSE self->Set,Type=Type,Obj=Obj,Distribution=Distribution,Observation=Observation,PAHdb=PAHdb

  RETURN,self.state
END

;+
; Defines the AmesPAHdbIDLSuite_MCFitted_Spectrum Class
;
; :Fields:
;   state: type=long
;     Internal state
;   obj: type=obj-array
;     Array holding AmesPAHdbIDLSuiteFittedSpectrum instances
;   distribution: type=string
;     Distribution used for permutating errors
;
; :Categories:
;   CLASS
;
; :Private:
;-
PRO AmesPAHdbIDLSuite_MCFitted_Spectrum__DEFINE

  COMPILE_OPT IDL2

  ON_ERROR,2

  void = {AmesPAHdbIDLSuite_MCFitted_Spectrum, $
          INHERITS IDL_Object, $
          INHERITS AmesPAHdbIDLSuite_Plot, $
          state:0L, $
          obj:PTR_NEW(), $
          distribution:'', $
          observation:OBJ_NEW(), $
          _lazy:{AmesPAHdbIDLSuite_Fitted_Lazy, $
                 residual:PTR_NEW(), $
                 fit:PTR_NEW(), $
                 sizedistribution:PTR_NEW(), $
                 averagenumberofcarbonatoms:PTR_NEW(), $
                 breakdown:PTR_NEW(), $
                 breakdown_sig:0L, $
                 classes:PTR_NEW(), $
                 classes_sig:0L, $
                 norm:PTR_NEW(), $
                 chisquared:PTR_NEW(), $
                 error:PTR_NEW() $
                 }}
END

; END OF AmesPAHdbIDLSuite_MCFitted_Spectrum__define.pro
