; docformat = 'rst'
;+
; This function finds the best-fitting Choloniewski function for
; input arrays of absolute magnitudes, surface brightness, phi
; and phi_err.
;-
;+
; Estimate phierr for bins with zero phierr
;-
PRO ajs_choloniewski_fit_phierr, absmag, sb, phi, phierr, $
                                 vars=vars, zmin=zmin, zmax=zmax, area=area, $
                                 q0=q0, q1=q1
  compile_opt idl2
  debug = ajs_debug()
  IF debug GE 1 THEN BEGIN
      message, 'Estimating phierr for bins with zero phierr', /inf
      message, ajs_kw_string(zmin=zmin, zmax=zmax, area=area, q0=q0, q1=q1), $
               /inf
  ENDIF
  IF n_elements(vars) EQ 0 $
     OR n_elements(zmin) EQ 0 $
     OR n_elements(zmax) EQ 0 $
     OR n_elements(area) EQ 0 THEN $
        message, 'Zero phierr: supply vars, zmin, zmax and area' $
  ELSE BEGIN
      IF debug GE 2 THEN print, alog10(phierr), format='(' $
                                + strtrim(n_elements(absmag), 2) + 'I2)'
      ;; Multiplier: phierr = 1. / vmax * err_multip
      ;; Taking into account the number of degrees of freedom
      ;; so that 1-sigma errors (chi^2) will give phierr (why? fudge again??)
      ;; See ajs_bbd_plot
      k = n_elements(phierr)
      chi2_bin = sqrt(2. / k)
      err_multip = 1. / sqrt(chi2_bin)
      ;; Scale according to bin widths (larger error for smaller bins)
      err_multip = err_multip / abs(absmag[1] - absmag[0]) / abs(sb[1] - sb[0])
      err_multip *= 10 ; fudge
      IF debug GE 2 THEN $
         message, 'Errors multiplied by ' + strtrim(err_multip, 2), /inf
      ;; Which bins need attention?
      zeros = where(phierr LE 0)
      
      ;; How many extra variables are we dealing with?
      n_other_vars = n_elements(vars) - 2
      ;; Default coeffs (see ajs_dm_k.pro)
      coeffs = [1.89821e-05, 2.37252e-09, 1.97939e-06, 7.54209e-08, $
                1.93300e-08]
      ;; Create structure for table
      table = create_struct('coeffs', fltarr(5))
      FOR i = 0, n_elements(vars) - 1 DO BEGIN 
          table = create_struct(vars[i].abs_name, 0.0, table)
      ENDFOR 
      IF n_other_vars EQ -1 THEN BEGIN
          ;; Consider only absmag
          IF debug GE 2 THEN message, 'Using only absmag', /inf
          coeffs = coeffs # (intarr(n_elements(zeros)) + 1) 
          ;; Create table of galaxies at centre of each zero bin
          table = replicate(table, n_elements(zeros))
          ;; Assign data to table
          table.coeffs = coeffs
          table.(where(tag_names(table) EQ strupcase(vars[0].abs_name))) $
             = absmag[reform((array_indices(phi, zeros))[0, *])]
          ;; Find vmax and set error = 1. / vmax
          vmax = ajs_vmax(table, vars, zmin=zmin, zmax=zmax, area=area, $
                          q0=q0, q1=q1)
          phierr[zeros] = 1. / vmax * err_multip
      ENDIF ELSE IF n_other_vars EQ 0 THEN BEGIN
          ;; Consider only absmag and sb
          IF debug GE 2 THEN message, 'Using only absmag & sb', /inf
          coeffs = coeffs # (intarr(n_elements(zeros)) + 1) 
          ;; Create table of galaxies at centre of each zero bin
          table = replicate(table, n_elements(zeros))
          ;; Assign data to table
          table.coeffs = coeffs
          table.(where(tag_names(table) EQ strupcase(vars[0].abs_name))) $
             = absmag[reform((array_indices(phi, zeros))[0, *])]
          table.(where(tag_names(table) EQ strupcase(vars[1].abs_name))) $
             = sb[reform((array_indices(phi, zeros))[1, *])]
          ;; Find vmax and set error = 1. / vmax
          vmax = ajs_vmax(table, vars, zmin=zmin, zmax=zmax, area=area, $
                          q0=q0, q1=q1)
          phierr[zeros] = 1. / vmax * err_multip
      ENDIF ELSE BEGIN
          ;; Consider other variables apart from absmag and sb
          IF debug GE 2 THEN message, 'Using absmag, sb and ' $
                                      + strtrim(n_other_vars, 2) $
                                      + ' other variables', /inf
          ;; Create table of galaxies at centre of each bin (zero or nonzero)
          nbins = 10
          table = replicate(table, n_elements(absmag) * n_elements(sb) $
                            * nbins ^ n_other_vars)
          ;; Assign data to table
          table.coeffs = rebin(coeffs, 5, n_elements(table))
          ;; Dimensions of n-d space of bin centres
          dims = [n_elements(absmag), n_elements(sb), $
                  intarr(n_other_vars) + nbins]
          
          ;; Bin centres for other variables
          var_arr = fltarr(n_other_vars, nbins)
          FOR i = 0, n_other_vars - 1 DO BEGIN
              var_arr[i, *] = ajs_linspace(vars[i].abs_min, vars[i].abs_max, $
                                           nbins)
          ENDFOR 
          ;; Assign values to table
          FOR j = 0, n_elements(vars) - 1 DO BEGIN 
              CASE j OF
                  0: arr_tmp = absmag
                  1: arr_tmp = sb
                  ELSE: arr_tmp = var_arr[j - 2: *]
              ENDCASE
              table.(where( $
                 tag_names(table) EQ strupcase(vars[j].abs_name))) $
                 = reform(arr_tmp[(array_indices( $
                 dims, lindgen(n_elements(table)), $
                 /dimensions))[j, *]])
          ENDFOR
          IF debug GE 2 THEN message, 'Done assigning table values', /inf
          
          ;; Find vmax and set error = 1. / vmax
          vmax = ajs_vmax(table, vars, zmin=zmin, zmax=zmax, area=area, $
                          q0=q0, q1=q1)
          vmax = reform(vmax, dims)
          FOR i = 0, n_other_vars - 1 DO BEGIN
              ;; Find maximum over one dimension
              vmax = max(vmax, dimension=3)
          ENDFOR
          phierr[zeros] = 1. / vmax[zeros] * err_multip
      ENDELSE
      IF debug GE 2 THEN print, alog10(phierr), format='(' $
                                + strtrim(n_elements(absmag), 2) + 'I2)'
  ENDELSE
END
;+
; This function finds the best-fitting Choloniewski function for
; input arrays of absolute magnitudes, surface brightness, phi
; and phi_err.
; :Returns:
;    Choloniewski parameters [mstar,alpha,phistar,sbstar,sigmasb,beta]
; :Params:
;    absmag : in, required
;       [n1] Bin centres for absolute magnitude
;    sb : in, required
;       [n2] Bin centres for surface brightness
;    phi : in, required
;       [n1, n2] Value of space density at absmag and sb
;    phierr : in, out, required
;       [n1, n2] If any values of phierr are zero, either raises an error
;       message or estimates (and changes input) values using vars etc.
;    params_init : in, optional
;       Initial guess for 
;       [mstar,alpha,phistar,sbstar,sigmasb,beta] (or default)
; :Keywords:
;    txtfile : in, optional
;       Output text file for Choloniewski parameters
;    xrange : in, optional, type=fltarr(2)
;       Range in m1 for fitting
;    yrange : in, optional, type=fltarr(2)
;       Range in m2 for fitting
;    vars : in, optional
;       For estimating errors on bins with zero phi or phierr, for Choloniewski
;       fit. Array of structures with fields {abs_name:} giving the column
;       name in table corresponding to the absolute value, {band:} giving the
;       name of the band, {type:} giving 'M'agnitudes, 'S'urface brightness
;       or 'R'adius and {app_min:, app_max: abs_min: abs_max:} giving the
;       limits. The array must be ordered so that vars[0] corresponds
;       to absmag and vars[1] corresponds to sb.
;    zmin : in, optional
;       Minimum redshift. Use with vars when performing Choloniewski
;       fit with phi = 0 and phierr = 0 in some bins
;    zmax : in, optional
;       Maximum redshift. Use with vars when performing Choloniewski
;       fit with phi = 0 and phierr = 0 in some bins
;    area : in, optional
;       Area in square degrees. Use with vars when performing Choloniewski
;       fit with phi = 0 and phierr = 0 in some bins
;    q0 : in, optional
;       Evolution parameters: for ajs_vmax
;    q1 : in, optional
;       Evolution parameters: for ajs_vmax
;    cov_mat : out, optional
;       Covariance matrix
;    _REF_EXTRA : in, optional
;       Extra keywords to pass to mpfit2dfun
; :Uses:
;    mpfit2d (idlutils)
;    ajs_choloniewski
; :History:
;    17 Jan 2008 Created, Anthony Smith
;
;    4 Apr 2008 Added xrange and yrange
;-
FUNCTION ajs_choloniewski_fit, absmag, sb, phi, phierr, params_init, $
                               txtfile=txtfile, xrange=xrange, yrange=yrange, $
                               vars=vars, zmin=zmin, $
                               zmax=zmax, area=area, q0=q0, q1=q1, $
                               cov_mat=cov_mat, _REF_EXTRA=e
  compile_opt idl2
  debug = ajs_debug()
  IF debug GE 1 THEN message, 'Choloniewski fit', /inf
  IF n_elements(params_init) EQ 0 THEN $
     params_init=[median(absmag), -1, 0.01, $
                  median(sb), 1, 1]
  params_init = double(params_init)
  ;; Bins with zero phierr: estimate phierr from Vmax
  nans = where(finite(phierr, /nan), n_nans)
  IF n_nans GT 0 THEN $
     phierr[nans] = 0
  IF min(phierr) LE 0 THEN BEGIN
      ajs_choloniewski_fit_phierr, absmag, sb, $
                                   phi, phierr, $
                                   vars=vars, zmin=zmin, zmax=zmax, $
                                   area=area, $
                                   q0=q0, q1=q1
  ENDIF
  ;; Check xrange & yrange are [min, max] not [max, min]
  IF n_elements(xrange) GT 1 THEN $
     IF xrange[0] GT xrange[1] THEN $
        xrange = reverse(xrange)
  IF n_elements(yrange) GT 1 THEN $
     IF yrange[0] GT yrange[1] THEN $
        yrange = reverse(yrange)
  ;; Restrict fit to bins within xrange and yrange
  IF n_elements(xrange) GT 1 THEN $
     fitx = where(absmag GE xrange[0] AND absmag LE xrange[1]) $
  ELSE $
     fitx = indgen(n_elements(absmag))
  IF n_elements(yrange) GT 1 THEN $
     fity = where(sb GE yrange[0] AND sb LE yrange[1]) $
  ELSE $
     fity = indgen(n_elements(sb))
  absmag_fit = absmag[fitx]
  sb_fit = sb[fity]
  phi_fit = (phi[fitx, *])[*, fity]
  phierr_fit = (phierr[fitx, *])[*, fity]
  
  ;; Make 2d arrays from 1d inputs: Array[n_elements(absmag), n_elements(sb)]
  absmag_arr = absmag_fit # (sb_fit * 0 + 1)
  sb_arr = (absmag_fit * 0 + 1) # sb_fit
  
  params=mpfit2dfun('ajs_choloniewski', $
                    absmag_arr, $
                    sb_arr, $
                    phi_fit, $
                    phierr_fit, $
                    params_init, /quiet, covar=cov_mat, perror=perror, $
                    _STRICT_EXTRA=e)
  IF keyword_set(txtfile) THEN BEGIN
      ;; Text file
      openw, unit, txtfile, /get_lun
      printf, unit, params
      free_lun, unit
  ENDIF
  RETURN, params
END
;+
; Test ajs_choloniewski_fit
;-
PRO ajs_choloniewski_fit_test
  compile_opt idl2
  params_true = [-23.5, -1, 0.01, 17, 1, 0.5]
  n_mag = 32
  n_sb = 24
  m = ajs_linspace(-20, -26, n_mag, /bincentres)
  sb = ajs_linspace(20, 14, n_sb, /bincentres)
  chol = ajs_choloniewski(m, sb, params_true)
  cholerr = chol / 10
  IF 1 THEN BEGIN 
      ;; Simulate magnitude-limited sample
      ;; Table, with one galaxy at each abs_mag
      coeffs = [1.89821e-05, 2.37252e-09, 1.97939e-06, 7.54209e-08, $
                1.93300e-08]
      table = replicate(create_struct('coeffs', coeffs, 'absmag', 0.0), n_mag)
      table.absmag = m
      
      ;; Limits
      vars = [{abs_name:'absmag', band:'K', type:'M', app_min:12, app_max:16}]
      
      ;; Calculate Vmax
      vmax = ajs_vmax(table, vars, zmin=0.01, zmax=0.3, area=500)
      vmax_bbd = rebin(vmax, n_mag, n_sb)
      
      ;; Number of galaxies at each value of absmag & sb
      n_gals = chol * vmax_bbd
      cholerr = sqrt(chol / vmax_bbd) ; = chol / sqrt(n_gals)
      min_n = 100
      IF min(n_gals) LT min_n THEN BEGIN
          chol[where(n_gals LT min_n)] = 0
          cholerr[where(n_gals LT min_n)] = 1000. / vmax_bbd[where(n_gals $
                                                                   LT min_n)]
      ENDIF   
  ENDIF
 
  ;; Fit
                                ;  params_init = params_true * 1.1
  params = ajs_choloniewski_fit(m, sb, chol, cholerr, cov_mat=cov_mat)
                                ;, params_init)
  perror = sqrt(diag_matrix(cov_mat))
  print,'True/Fitted parameters, +/- error:'
  print, transpose([[params_true], [params], [perror]])
  window, 0
  ajs_bbd_plot, m, sb, chol, phibbderr=cholerr, choloniewski=params, band='K'
  ;; Adjust errors to give 1-sigma in chi^2
  k = n_elements(cholerr)
  chi2_bin = sqrt(2. / k)
  cholerr_chi2 = sqrt(chi2_bin) * cholerr
  window, 1
  ajs_bbd_plot, m, sb, chol + cholerr_chi2, choloniewski=params, band='K'
  window, 2
  ajs_bbd_plot, m, sb, chol - cholerr_chi2, choloniewski=params, band='K'
END