/**
  * This conducts both nonlinear regression-derived Fb subtraction and baseline slope correction.
  *
  * @param profile
  */
 private void conductBaselineCorrection() {
   // Start with correcting for the NR-derived Fb slope
   double[] rawFcReadings = profile.getRawFcReadings();
   double[] processedFcDataset = new double[rawFcReadings.length]; // The new optimized Fc dataset
   // This assumes that nonlinear regression has been conducted AND it was successfully completed
   double nrFb = profile.getNrFb(); // The regression derived Fb
   double nrFbSlope = profile.getNrFbSlope(); // The regression-derived Fb slope
   for (int i = 0; i < processedFcDataset.length; i++) {
     processedFcDataset[i] = rawFcReadings[i] - nrFb - (nrFbSlope * (i + 1));
   }
   profile.setFcReadings(processedFcDataset);
 }
  /**
   * Generates an optimized working Fc dataset via nonlinear regression to derived Fb and Fb-slope
   * using the current LRE window.
   *
   * <p>Nonlinear regression is conducted using Emax, Fmax and Fo derived from the current LRE
   * window, from WHICH values for Fb and Fb-slope are determined. These are then used to calculate
   * a new working Fc dataset, followed by recalculation of the LRE parameters. This process
   * repeated 3 times from which an average Fb and Fb-slope are then determined and a final
   * optimized working Fc dataset generated. This is followed by a final recalculation of the LRE
   * parameters to determine final values for Emax, Fmax and Fo. THIS DOES THIS INCLUDE ANY
   * MODIFICATION TO THE LRE WINDOW.
   *
   * <p>Note also that the LRE parameters are updated and the modified Profile is saved, which
   * includes initialization of a new Cycle linked list, so any calling function must reset its
   * runner.
   *
   * @param prfSum the ProfileSummary encapsulating the Profile
   * @return true if nonlinear regression analysis was successful or false if it failed
   */
  public boolean generateOptimizedFcDatasetUsingNonliearRegression(ProfileSummary prfSum) {
    this.prfSum = prfSum;
    profile = prfSum.getProfile();

    // The profile must have a valid LRE window
    if (!profile.hasAnLreWindowBeenFound()) {
      return false;
    }
    // Need to trim the profile in order to avoid aberrancies within early cycles and within the
    // plateau phase
    // Exclude the first three cycles
    int firstCycle = 4; // Start at cycle 4
    // Use the top of the LRE window as the last cycle included in the regression analysis******THIS
    // IS VERY IMPORTANT
    double[] fcArray = profile.getRawFcReadings();
    int lastCycle = 0;
    if (prfSum.getLreWindowEndCycle() != null) {
      lastCycle = prfSum.getLreWindowEndCycle().getCycNum();
    } else {
      return false;
    }
    int numberOfCycles = lastCycle - firstCycle + 1;
    // Construct the trimmed Fc dataset TreeMap<cycle number, Fc reading>
    TreeMap<Integer, Double> profileMap = new TreeMap<Integer, Double>();
    for (int i = 0; i < numberOfCycles; i++) {
      profileMap.put(firstCycle + i, fcArray[firstCycle - 1 + i]);
    }
    // Run NR once to grossly stablize the LRE-derived parameters
    LreParameters lreDerivedParam = getLreParameters();
    LreParameters optParam = nrService.conductNonlinearRegression(lreDerivedParam, profileMap);
    // Reinitialize the LRE-derived parameters
    // First Reset nonlinear regression-derived Fb and Fb-slope within the profile
    profile.setNrFb(optParam.getFb());
    profile.setNrFbSlope(optParam.getFbSlope());
    // Generate a new optimized Fc dataset based on NR-derived Fb and Fb-slope
    conductBaselineCorrection();
    // Updating the ProfileSummary updates the LRE-derived parameters within the Profile with no
    // change to the LRE window
    // However, this assumes that the NR was successful
    prfSum.update();
    // Reset the LRE-derived paramaters
    lreDerivedParam = getLreParameters();
    // Run the regression analysis 10 times to determine the average and SD
    // This is necessary due to the poor performance of Peter Abeles’s EJML implementation
    int numberOfIterations = 3;
    ArrayList<Double> emaxArray = new ArrayList<Double>();
    ArrayList<Double> fbArray = new ArrayList<Double>();
    ArrayList<Double> foArray = new ArrayList<Double>();
    ArrayList<Double> fmaxArray = new ArrayList<Double>();
    ArrayList<Double> fbSlopeArray = new ArrayList<Double>();
    double emaxSum = 0;
    double fbSum = 0;
    double foSum = 0;
    double fmaxSum = 0;
    double fbSlopeSum = 0;
    for (int i = 0; i < numberOfIterations; i++) {
      optParam = nrService.conductNonlinearRegression(lreDerivedParam, profileMap);
      emaxArray.add(optParam.getEmax());
      emaxSum += optParam.getEmax();
      fbArray.add(optParam.getFb());
      fbSum += optParam.getFb();
      foArray.add(optParam.getFo());
      foSum += optParam.getFo();
      fmaxArray.add(optParam.getFmax());
      fmaxSum += optParam.getFmax();
      fbSlopeArray.add(optParam.getFbSlope());
      fbSlopeSum += optParam.getFbSlope();
      // Reinitialize the LRE-derived parameters
      // First reset nonlinear regression-derived Fb and Fb-slope within the profile
      profile.setNrFb(optParam.getFb());
      profile.setNrFbSlope(optParam.getFbSlope());
      // Generate a new optimized Fc dataset
      conductBaselineCorrection();
      // Update the LRE-derived parameters within the Profile
      prfSum.update();
      // Retrieve the new LRE parameters
      lreDerivedParam = getLreParameters();
    }
    // Set the average for each parameter into the Profile
    // This allows the final recalculation of the LRE parameters based on the average Fb and
    // Fb-slope
    profile.setNrEmax(emaxSum / numberOfIterations);
    profile.setNrFb(fbSum / numberOfIterations);
    profile.setNrFo(foSum / numberOfIterations);
    profile.setNrFmax(fmaxSum / numberOfIterations);
    profile.setNrFbSlope(fbSlopeSum / numberOfIterations);
    // Determine and set the parameter SD
    profile.setNrEmaxSD(MathFunctions.calcStDev(emaxArray));
    profile.setNrFbSD(MathFunctions.calcStDev(fbArray));
    profile.setNrFoSD(MathFunctions.calcStDev(foArray));
    profile.setNrFmaxSD(MathFunctions.calcStDev(fmaxArray));
    profile.setNrFbSlopeSD(MathFunctions.calcStDev(fbSlopeArray));
    // Recaculate the optimized Fc dataset using the average NR-derived Fb and Fb-slope
    conductBaselineCorrection();
    // Update the LRE parameters
    prfSum.update();
    return testIfRegressionWasSuccessful();
  }