Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ClockReconstructionTool.cxx 5.23 KiB
/*
  Copyright (C) 2021 CERN for the benefit of the FASER collaboration
*/

/**
 * @file ClockReconstructionTool.cxx
 * Implementation file for the ClockReconstructionTool.cxx
 * @ author E. Torrence, 2021
 **/

#include "ClockReconstructionTool.h"

#include "TVirtualFFT.h"

#include <vector>

// Constructor
ClockReconstructionTool::ClockReconstructionTool(const std::string& type, const std::string& name, const IInterface* parent) :
  base_class(type, name, parent)
{
}

// Initialization
StatusCode
ClockReconstructionTool::initialize() {
  ATH_MSG_INFO( name() << "::initalize()" );

  return StatusCode::SUCCESS;
}

// Reconstruction step
StatusCode
ClockReconstructionTool::reconstruct(const ScintWaveform& raw_wave,
				     xAOD::WaveformClock* clockdata) const {

  ATH_MSG_DEBUG("Clock reconstruct called ");

  // Check the output object
  if (!clockdata) {
    ATH_MSG_ERROR("WaveformClock passed to reconstruct() is null!");
    return StatusCode::FAILURE;
  }

  // Invalid value until we know we are OK
  clockdata->set_frequency(-1.);

  // Can we determine the actual BCID we triggered in?
  //clockdata->setTriggerTime(raw_wave.trigger_time_tag());
  //ATH_MSG_DEBUG("Trigger time: " << raw_wave.trigger_time_tag());

  // Digitized clock data, sampled at 500 MHz (2 ns)
  auto counts = raw_wave.adc_counts();

  // Check that we have some minimal amount of data to work with
  if (int(counts.size()) <= m_minimumSamples.value()) {
    ATH_MSG_WARNING("Found clock waveform with length " << counts.size() << "! Not enough data to continue!");
    return StatusCode::SUCCESS;
  }

  // Need a double array for FFT
  int N = counts.size();
  std::vector<double> wave(N);
  wave.assign(counts.begin(), counts.end());

  ATH_MSG_DEBUG("Created double array with length " << wave.size() );
  ATH_MSG_DEBUG("First 10 elements:");

  for (int i=0; i < std::min(10, N); i++)
    ATH_MSG_DEBUG(" " << i << " " << wave[i]);
  // delta_nu = 1/T where T is the total waveform length
  // Multiplying (freqmult * i) gives the frequency of point i in the FFT
  double freqmult = 1./(0.002 * N); // 2ns per point, so this is in MHz

  TVirtualFFT *fftr2c = TVirtualFFT::FFT(1, &N, "R2C");
  fftr2c->SetPoints(&wave[0]);
  fftr2c->Transform();

  // Get the coefficients
  std::vector<double> re_full(N);
  std::vector<double> im_full(N);
  std::vector<double> magnitude(N/2); 
  fftr2c->GetPointsComplex(&re_full[0],&im_full[0]);

  // Normalize the magnitude (just using the positive complex frequencies)
  unsigned int i=0;
  magnitude[i] = sqrt(pow(re_full[i], 2) + pow(im_full[i], 2))/N;
  for(i=1; i<magnitude.size(); i++) 
    magnitude[i] = 2*sqrt(pow(re_full[i], 2) + pow(im_full[i], 2))/N;

  // First, look at the DC offset
  ATH_MSG_DEBUG("DC offset: " << magnitude[0]);

  // Second, find max value (should be primary line at 40 MHz
  unsigned int imax = max_element(magnitude.begin()+1, magnitude.end()) - magnitude.begin();

  if (((int(imax)-1) < 0) || ((imax+1) >= magnitude.size())) {
    ATH_MSG_WARNING("Found maximum frequency amplitude at postion " << imax << "!");
  } else {
    ATH_MSG_DEBUG("Magnitude array at peak:");
    for(i = imax-1; i <= imax+1; i++)
      ATH_MSG_DEBUG("Index: " << i << " Freq: " << i*freqmult << " Mag: " << magnitude[i]);
  }

  // Store results
  clockdata->set_dc_offset(magnitude[0]);
  clockdata->set_frequency(imax * freqmult);
  clockdata->set_amplitude(magnitude[imax]);
  clockdata->set_phase(atan2(im_full[imax], re_full[imax]));

  ATH_MSG_DEBUG("Before correcting for finite resolution:");
  ATH_MSG_DEBUG(*clockdata);

  // Correct for the windowing to find the exact peak frequency
  // Following: https://indico.cern.ch/event/132526/contributions/128902/attachments/99707/142376/Meeting1-06-11_FFT_corrections_for_tune_measurements.pdf

  double dm;  // Fraction of integer frequency where real peak sits
  if (magnitude[imax+1] >= magnitude[imax-1]) { // Past ipeak by dm
    dm = magnitude[imax+1] / (magnitude[imax] + magnitude[imax + 1]);
  } else { // Before ipeak by dm
    dm = -magnitude[imax-1] / (magnitude[imax-1] + magnitude[imax]);
  }

  ATH_MSG_DEBUG("Found shift in frequency index: " << dm);

  // Improved values

  double phase = atan2(im_full[imax], re_full[imax]) - dm * M_PI;
  // Fix any overflows
  if (phase < M_PI) phase += (2*M_PI);
  if (phase > M_PI) phase -= (2*M_PI);

  clockdata->set_frequency( (imax+dm) * freqmult );
  clockdata->set_phase (phase);
  clockdata->set_amplitude( 2*M_PI*dm*magnitude[imax] / sin(M_PI * dm) );

  ATH_MSG_DEBUG("After correcting for finite resolution:");
  ATH_MSG_DEBUG(*clockdata);

  delete fftr2c;

  if (m_checkResult) checkResult(raw_wave, clockdata);

  return StatusCode::SUCCESS;
}

void
ClockReconstructionTool::checkResult(const ScintWaveform& raw_wave,
				     xAOD::WaveformClock* clockdata) const {

  // Go through each element in raw_wave and make sure time in clockdata matches

  float time;
  for (unsigned int i=0; i<raw_wave.adc_counts().size(); i++) {
    time = 2.*i; // Time in ns

    float dt = clockdata->time_from_clock(time);

    // Is raw_wave HI or LO?
    bool hi = raw_wave.adc_counts()[i] > clockdata->dc_offset();

    // Check for mismatch
    if (((dt < 12.5) && !hi) || ((dt > 12.5) && hi) ) 
      ATH_MSG_WARNING("Clock Time:" << time << " dt:" << dt << " found" << hi);
  }

}