// Copyright 2003 Regents of the University of California


// SETI_BOINC is free software; you can redistribute it and/or modify it under

// the terms of the GNU General Public License as published by the Free

// Software Foundation; either version 2, or (at your option) any later

// version.


// SETI_BOINC is distributed in the hope that it will be useful, but WITHOUT

// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or

// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for

// more details.


// You should have received a copy of the GNU General Public License along

// with SETI_BOINC; see the file COPYING.  If not, write to the Free Software

// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


// In addition, as a special exception, the Regents of the University of

// California give permission to link the code of this program with libraries

// that provide specific optimized fast Fourier transform (FFT) functions and

// distribute a linked executable.  You must obey the GNU General Public 

// License in all respects for all of the code used other than the FFT library

// itself.  Any modification required to support these libraries must be

// distributed in source code form.  If you modify this file, you may extend 

// this exception to your version of the file, but you are not obligated to 

// do so. If you do not wish to do so, delete this exception statement from 

// your version.


// gaussfit.C

// $Id: gaussfit.cpp,v 1.21.2.11 2007/05/31 22:03:10 korpela Exp $

//


// The purpose of gaussian fitting is to find signals that rise and

// fall in power over time at a rate consistent with the beam

// pattern of the telescope.


#include "sah_config.h"


#include <stdio.h>

#include <math.h>


#include <algorithm>


// debug stuff

#define DEBUG_POT

//#define DUMP_POWER_SPECTRA

//#define DUMP_GAUSSIAN

#define POT_TO_DUMP 32

#define TOFF_TO_DUMP 15

#define FFT_TO_DUMP 131072

int gul_PoT;
int gul_Fftl;
// end debug stuff


#ifdef BOINC_APP_GRAPHICS

#include "graphics_api.h"

#ifdef DYNAMIC_GRAPHICS

#include "graphics_lib.h"

#endif

#endif


//#include "util.h"

#include "s_util.h"

#include "analyze.h"

#include "seti.h"

#include "worker.h"

#include "analyzeFuncs.h"

#include "analyzeReport.h"

#include "analyzePoT.h"

#include "lcgamm.h"

#include "gaussfit.h"

#include "chirpfft.h"


#ifdef BOINC_APP_GRAPHICS

#include "sah_gfx.h"

#endif


bool dump_pot = false;
//float gauss_display_power_thresh = 0;


float f_GetPeak(
  float fp_PoT[],
  int ul_TOffset,
  int ul_HalfSumLength,
  float f_MeanPower,
  float f_PeakScaleFactor,
  float f_weight[]
) {
  // Peak power is calculated as the weighted

  // sum of all powers within ul_HalfSumLength

  // of the assumed gaussian peak.

  // The weights are given by the gaussian function itself.

  // BUG WATCH : for the f_PeakScaleFactor to work,

  // ul_HalfSumLength *must* be set to sigma.


  int i;
  float f_sum;

  f_sum = 0.0;

  // Find a weighted sum

  for (i = ul_TOffset - ul_HalfSumLength; i <= ul_TOffset + ul_HalfSumLength; i++) {
    f_sum += (fp_PoT[i] - f_MeanPower) * f_weight[abs(i-ul_TOffset)];
  }
  
  analysis_state.FLOP_counter+=6.0*ul_HalfSumLength;
  return(f_sum * f_PeakScaleFactor);
}

float f_GetChiSq(
  float fp_PoT[],
  int ul_PowerLen,
  int ul_TOffset,
  float f_PeakPower,
  float f_MeanPower,
  float f_weight[],
  float *xsq_null=0
) {
  // We calculate our assumed gaussian powers

  // on the fly as we try to fit them to the

  // actual powers at each point along the PoT.


  int i;
  float f_ChiSq=0,f_null_hyp=0, f_PredictedPower, f_tot_weight=0;
  double rebin=swi.nsamples/ChirpFftPairs[analysis_state.icfft].FftLen
                  /ul_PowerLen;

  for (i = 0; i < ul_PowerLen; i++) {
    f_PredictedPower = f_MeanPower +
                       f_PeakPower * f_weight[abs(i-ul_TOffset)] ;
    f_tot_weight+=f_weight[abs(i-ul_TOffset)];
#ifdef DUMP_GAUSSIAN

    if ((gul_PoT == POT_TO_DUMP && ul_TOffset == TOFF_TO_DUMP) &&
        gul_Fftl == FFT_TO_DUMP) {
      fprintf(stdout, "%f\n", f_PredictedPower);
    }
#endif

    // ChiSq in this realm is:

    //  sum[0:i]( (observed power - expected power)^2 / expected variance )

    // The power of a signal is:

    //  power = (noise + signal)^2 = noise^2 + signal^2 + 2*noise*signal

    // With mean power normalization, noise becomes 1, leaving:

    //  power = signal^2 +or- 2*signal + 1

    f_PredictedPower/=f_MeanPower;
    double signal=f_PredictedPower;
    double noise=(2.0*sqrt(std::max(f_PredictedPower,1.0f)-1)+1);

    f_ChiSq += 
      (static_cast<float>(rebin*SQUARE(fp_PoT[i]/f_MeanPower - signal)/noise));
    f_null_hyp+=
      (static_cast<float>(rebin*SQUARE(fp_PoT[i]/f_MeanPower-1)/noise));
  }

  analysis_state.FLOP_counter+=20.0*ul_PowerLen+5;
  f_ChiSq/=ul_PowerLen;
  f_null_hyp/=ul_PowerLen;

#ifdef DUMP_GAUSSIAN

  if (gul_PoT == POT_TO_DUMP
      && ul_TOffset == TOFF_TO_DUMP
      && gul_Fftl == FFT_TO_DUMP
     ) {
    for (i = 0; i < ul_PowerLen; i++) {
      fprintf(stdout, "%f\n", fp_PoT[i]);
    }
  }
#endif

  if (xsq_null) *xsq_null=f_null_hyp;
  return f_ChiSq;
}


float f_GetTrueMean(
  float fp_PoT[],
  int ul_PowerLen,
  float f_TotalPower,
  int ul_TOffset,
  int ul_ExcludeLen
) {
  // TrueMean is the mean power of the data set minus all power

  // out to ExcludeLen from our current TOffset.

  int i, i_start, i_lim;
  float f_ExcludePower = 0;

  // take care that we do not add to exclude power beyond PoT bounds!

  i_start = std::max(ul_TOffset - ul_ExcludeLen, 0);
  i_lim = std::min<int>(ul_TOffset + ul_ExcludeLen + 1, swi.analysis_cfg.gauss_pot_length);
  for (i = i_start; i < i_lim; i++) {
    f_ExcludePower += fp_PoT[i];
  }
  
  analysis_state.FLOP_counter+=(double)(i_lim-i_start+5);
  return((f_TotalPower - f_ExcludePower) / (ul_PowerLen - (i - i_start)));
}


float f_GetPeakScaleFactor(float f_sigma) {
  // The PeakScaleFactor is calculated such that when used in f_GetPeak(),

  // the actual peak power can be extracted from a weighted sum.

  // This sum (see f_GetPeak()), is calculated as :

  // sum = SUM[x from -sigma to +sigma] of (gaussian weights * our data)

  // The gaussian weights are e^(-x^2 / sigma^2).

  // Our data is A(e^(-x^2 / sigma^2)), where 'A' is the peak power.

  // Through algebraic manipulation, we have:

  // A = sum * (1 / SUM[x from -sigma to +sigma] of (e^(-x^2 / sigma^2))^2.

  // The factor by which we multiply the sum is the PeakScaleFactor.

  // It is completely determined by sigma.


  int i, i_s = static_cast<int>(floor(f_sigma+0.5));
  float f_sigma_sq = f_sigma*f_sigma;
  float f_sum = 0.0;

  for (i = -i_s; i <= i_s; i++) {
    f_sum += static_cast<float>(EXP(i, 0, f_sigma_sq));
  }
 
  analysis_state.FLOP_counter+=(13.0*f_sigma+3);
  return(1 / f_sum);
}


int ChooseGaussEvent(
  int ifft,
  float PeakPower,
  float TrueMean,
  float ChiSq,
  float null_ChiSq,
  int bin,
  float sigma,
  float PoTMaxPower,
  float fp_PoT[]
) {

  GAUSS_INFO gi;
  float scale_factor;
  float gauss_bins, gauss_dof, null_dof;

  // gaussian info

  gi.bin                  = bin;
  gi.fft_ind              = ifft;
  gi.g.chirp_rate 	  = ChirpFftPairs[analysis_state.icfft].ChirpRate;
  gi.g.fft_len    	  = ChirpFftPairs[analysis_state.icfft].FftLen;
  gi.g.sigma      	  = sigma;
  gi.g.peak_power 	  = PeakPower;
  gi.g.mean_power 	  = TrueMean;
  gi.g.chisqr     	  = ChiSq;
  gi.g.null_chisqr     	  = null_ChiSq;
  gi.g.freq       	  = cnvt_bin_hz(bin,  gi.g.fft_len);
  double t_offset=(((double)gi.fft_ind+0.5)/swi.analysis_cfg.gauss_pot_length)*
                  PoTInfo.WUDuration;
  gi.g.detection_freq=calc_detection_freq(gi.g.freq,gi.g.chirp_rate,t_offset);
  gi.g.time       	  = swi.time_recorded+t_offset/86400.0;
  gi.g.max_power  = PoTMaxPower;
  time_to_ra_dec(gi.g.time, &gi.g.ra, &gi.g.decl);

  // Scale PoT down to 256 16 bit ints.

  //for (i=0; i<swi.analysis_cfg.gauss_pot_length; i++) gi.pot[i] = fp_PoT[i];   // ???

  scale_factor = static_cast<float>(gi.g.max_power) / 255.0f;
  if (gi.g.pot.size() != swi.analysis_cfg.gauss_pot_length) {
    gi.g.pot.set_size(swi.analysis_cfg.gauss_pot_length);
  }
  float_to_uchar(fp_PoT, (unsigned char *)(gi.g.pot), swi.analysis_cfg.gauss_pot_length, scale_factor);

  if (!swi.analysis_cfg.gauss_null_chi_sq_thresh)
      swi.analysis_cfg.gauss_null_chi_sq_thresh=1.890;
  // Gauss score used for "best of" and graphics.

  // This score is now set to be based upon the probability that a signal

  // would occur due to noise and the probability that it is shaped like 

  // a Gaussian (normalized to 0 at thresholds).  Thanks to Tetsuji for

  // making me think about this. The Gaussian has 62 degrees of freedom and

  // the null hypothesis has 63 degrees of freedom when gauss_pot_length=64;

  gauss_bins=static_cast<float>(swi.analysis_cfg.gauss_pot_length);
  gauss_dof=gauss_bins-2;
  null_dof=gauss_bins-1;
  gi.score = 
      lcgf(0.5*gauss_dof,std::max(gi.g.chisqr*0.5*gauss_bins,0.5*gauss_dof+1))
      -lcgf(0.5*gauss_dof,swi.analysis_cfg.gauss_chi_sq_thresh*0.5*gauss_bins)
      -lcgf(0.5*null_dof,std::max(gi.g.null_chisqr*0.5*gauss_bins,0.5*null_dof+1))
      +lcgf(0.5*null_dof,swi.analysis_cfg.gauss_null_chi_sq_thresh*0.5*gauss_bins);
  
  // Only include "real" Gaussians (those meeting the chisqr threshold)

  // in the best Gaussian display. 

  if (gi.score > best_gauss->score && (gi.g.chisqr < swi.analysis_cfg.gauss_chi_sq_thresh)) {
    *best_gauss = gi;
  }
  // Update gdata gauss info regardless of whether it is the

  // best thus far or even passes the final threshold.  If

  // a gaussian has made it this far, display it.

#ifdef BOINC_APP_GRAPHICS

  if (!nographics()) sah_graphics->gi.copy(&gi);
#endif



  analysis_state.FLOP_counter+=24.0;
  // Final thresholding and reporting.

  if (gi.g.peak_power / gi.g.mean_power >= PoTInfo.GaussPeakPowerThresh) {
    if ((gi.g.chisqr <=  PoTInfo.GaussChiSqThresh) &&
       (gi.g.null_chisqr >= swi.analysis_cfg.gauss_null_chi_sq_thresh))
    {
      return result_gaussian(gi);
    }
  }
  return 0;
}


int GaussFit(
  float * fp_PoT,
  int ul_FftLength,
  int ul_PoT
) {
  int i, retval;
  BOOLEAN b_IsAPeak;
  float f_NormMaxPower;
  float f_null_hyp;

  int ul_TOffset;
  int iSigma = static_cast<int>(floor(PoTInfo.GaussSigma+0.5));
  float f_GroupSum, f_GroupMax;
  int i_f, iPeakLoc;

  float f_TotalPower,
        f_MeanPower,
        f_TrueMean,
        f_ChiSq,
        f_PeakPower;

  // For setiathome the Sigma and Gaussian PoT length don't change during

  // a run of the application, so these frequently used values can be

  // precalculated and kept.

  static float f_PeakScaleFactor;
  static float *f_weight;

  if (!f_weight) {
    f_PeakScaleFactor = f_GetPeakScaleFactor(static_cast<float>(PoTInfo.GaussSigma));
    f_weight = reinterpret_cast<float *>(malloc(PoTInfo.GaussTOffsetStop*sizeof(float)));
    if (!f_weight) SETIERROR(MALLOC_FAILED, "!f_weight");
    for (i = 0; i < PoTInfo.GaussTOffsetStop; i++) {
      f_weight[i] = static_cast<float>(EXP(i, 0, PoTInfo.GaussSigmaSq));
    }
  }
  // Find mean over power-of-time array

  f_TotalPower = 0;
  for (i = 0; i < swi.analysis_cfg.gauss_pot_length; i++) {
    f_TotalPower += fp_PoT[i];
  }
  f_MeanPower = f_TotalPower / swi.analysis_cfg.gauss_pot_length;

  // Normalize power-of-time and check for the existence

  // of at least 1 peak.

  b_IsAPeak = false;
  for (i = 0; i < swi.analysis_cfg.gauss_pot_length; i++) {
    fp_PoT[i] /= f_MeanPower;
    if (fp_PoT[i] > PoTInfo.GaussPowerThresh) b_IsAPeak = true;
  }
  analysis_state.FLOP_counter+=3.0*swi.analysis_cfg.gauss_pot_length+2;

  if (!b_IsAPeak) {
    //printf("no peak\n");

    return 0;  // no peak - bail on this PoT

  }

  // Recalculate Total Power across normalized array.

  // Given that powers are positive, f_TotalPower will

  // end up being == swi.analysis_cfg.gauss_pot_length.

  f_TotalPower   = 0;
  f_NormMaxPower = 0;
  // Also locate group with the highest sum for use in second bailout check.

  f_GroupSum     = 0;
  f_GroupMax     = 0;
  for (i = 0, i_f = -iSigma; i < swi.analysis_cfg.gauss_pot_length; i++, i_f++) {
    f_TotalPower += fp_PoT[i];
    if(fp_PoT[i] > f_NormMaxPower) f_NormMaxPower = fp_PoT[i];
    f_GroupSum += fp_PoT[i] - ((i_f < 0) ? 0.0f : fp_PoT[i_f]);
    if (f_GroupSum > f_GroupMax) {
      f_GroupMax = f_GroupSum;
      iPeakLoc = i - iSigma/2;
    }
  }

  // Check at the group peak location whether data may contain Gaussians

  // (but only after the first hurry-up Gaussian has been set for graphics)


  if (best_gauss->display_power_thresh != 0) {
    iPeakLoc = std::max(PoTInfo.GaussTOffsetStart,
                       (std::min(PoTInfo.GaussTOffsetStop - 1, iPeakLoc)));
  
    f_TrueMean = f_GetTrueMean(
                   fp_PoT,
                   swi.analysis_cfg.gauss_pot_length,
                   f_TotalPower,
                   iPeakLoc,
                   2 * iSigma
                 );
  
    f_PeakPower = f_GetPeak(
                    fp_PoT,
                    iPeakLoc,
                    iSigma,
                    f_TrueMean,
                    f_PeakScaleFactor,
                    f_weight
                  );

    analysis_state.FLOP_counter+=5.0*swi.analysis_cfg.gauss_pot_length+5;

    if (f_PeakPower / f_TrueMean < best_gauss->display_power_thresh*0.5f) {
      return 0;  // not even a weak peak at max group - bail on this PoT

    }
  }


  // slide dynamic gaussian across the Power Of Time array

  for (ul_TOffset = PoTInfo.GaussTOffsetStart;
       ul_TOffset < PoTInfo.GaussTOffsetStop;
       ul_TOffset++
      ) {

    // TrueMean is the mean power of the data set minus all power

    // out to 2 sigma from our current TOffset.

    f_TrueMean = f_GetTrueMean(
                   fp_PoT,
                   swi.analysis_cfg.gauss_pot_length,
                   f_TotalPower,
                   ul_TOffset,
                   2 * iSigma
                 );

    f_PeakPower = f_GetPeak(
                    fp_PoT,
                    ul_TOffset,
                    iSigma,
                    f_TrueMean,
                    f_PeakScaleFactor,
                    f_weight
                  );

    // worth looking at ?

    if (f_PeakPower / f_TrueMean < best_gauss->display_power_thresh) {
      continue;
    }

    // bump up the display threshold to its final value.

    // We could bump it up only to the gaussian just found,

    // but that would cause a lot of time waste

    // computing chisq etc.

    if (best_gauss->display_power_thresh == 0) {
    	best_gauss->display_power_thresh = PoTInfo.GaussPeakPowerThresh/3;
    }

#ifdef TEXT_UI

    if(dump_pot) {
      printf("Found good peak at this PoT element.... truemean is %f, power is %f\n", f_TrueMean, f_PeakPower);
    }
#endif


#ifdef DUMP_GAUSSIAN

    gul_PoT = ul_PoT;
    gul_Fftl = ul_FftLength;
#endif


    // look at it - try to fit

    f_ChiSq = f_GetChiSq(
                fp_PoT,
                swi.analysis_cfg.gauss_pot_length,
                ul_TOffset,
                f_PeakPower,
                f_TrueMean,
                f_weight,
                &f_null_hyp
              );

#ifdef TEXT_UI

    if(dump_pot) {
      printf("Checking ChiSqr for PoT dump....\n");
      if(f_ChiSq <= PoTInfo.GaussChiSqThresh) {
        int dump_i;
        printf(
          "POT %d %f %f %f %f %d ",
          ul_TOffset,
          f_PeakPower,
          f_TrueMean,
          PoTInfo.GaussSigmaSq,
          f_ChiSq,
          ul_PoT
        );
        for (dump_i = 0; dump_i < swi.analysis_cfg.gauss_pot_length; dump_i++) {
          printf("%f ", fp_PoT[dump_i]);
        }
        printf("\n");
      } else {
        printf("ChiSqr is %f, not good enough.\n", f_ChiSq);
      }
    }
#endif


    retval = ChooseGaussEvent(
               ul_TOffset,
               f_PeakPower,
               f_TrueMean,
               f_ChiSq,
	       f_null_hyp,
               ul_PoT,
               static_cast<float>(PoTInfo.GaussSigma),
               f_NormMaxPower,
               fp_PoT
             );
    if (retval) SETIERROR(retval,"from ChooseGaussEvent");

  } // End of sliding gaussian


  return 0;

} // End of gaussfit()





syntax highlighted by Code2HTML, v. 0.9.1