private double NormalizedErrorObsNormed(PatternFeature feature, double modelValue) {
   /*
    * the w here is manual weight assigned in JSON. only for special cases!! like 3-000 to get D.ASP.
    * shouldn't be confused with dynamic W calculated in spikepatternclassifier
    * Typically, w is 0 in JSON, so that this manual weight is ignored!
    */
   double w = feature.getWeight();
   if (GeneralUtils.isCloseEnough(w, 0, 0.001)) {
     w = 1;
   }
   if (feature.isRange()) {
     return w
         * StatUtil.calculateObsNormalizedError(
             feature.getValueMin(), feature.getValueMax(), modelValue);
   } else {
     return w * StatUtil.calculateObsNormalizedError(feature.getValue(), modelValue);
   }
 }
 private float spikesBelowVrestError(float vR) {
   double avgBelowVrestOffset = modelSpikePattern.calculateAvgBelowVrest(vR);
   return (float) StatUtil.calculateObsNormalizedError(0, avgBelowVrestOffset);
 }
 /*
  * other non-feature errors; like avoid bursting...
  */
 private float nonBurstBelowVminError(float vMin) {
   double avgBelowVminOffset = modelSpikePattern.calculateAvgBelowVminAfterLastSpike(vMin, 200);
   return (float) StatUtil.calculateObsNormalizedError(0, avgBelowVminOffset);
 }
  private double singleBurstError(
      int burstIdx, HashMap<BurstFeatureID, Double> expSingleBurstFeatures) {
    if (burstIdx >= modelSpikePattern.getBurstPattern().getNBursts()) {
      return 1;
    }

    double errorBW = 0;
    double errorIBI = 0;
    double errorNspikes = 0;

    // 2. BW error
    double bw = -1;
    if (expSingleBurstFeatures.containsKey(BurstFeatureID.b_w)) {
      bw = expSingleBurstFeatures.get(BurstFeatureID.b_w);
      double modelBw = modelSpikePattern.getBurstPattern().getBW(burstIdx);
      errorBW = StatUtil.calculateObsNormalizedError(bw, modelBw);
    }

    // 3. IBI error
    double pbi = -1;
    if (expSingleBurstFeatures.containsKey(BurstFeatureID.pbi)) {
      pbi = expSingleBurstFeatures.get(BurstFeatureID.pbi);
      double modelPbi = modelSpikePattern.getBurstPattern().getIBI(burstIdx);
      errorIBI = StatUtil.calculateObsNormalizedError(pbi, modelPbi);
    }

    // 4. N spikes error
    double nspikes = expSingleBurstFeatures.get(BurstFeatureID.nspikes);
    errorNspikes =
        StatUtil.calculateObsNormalizedError(
            nspikes, modelSpikePattern.getBurstPattern().getNSpikes(burstIdx));

    /*
     * display
     */
    if (display) {
      System.out.print("\t");
      if (expSingleBurstFeatures.containsKey(BurstFeatureID.b_w)) {
        String displayString = "\tb_w\t" + GeneralUtils.formatTwoDecimal(bw);
        displayString +=
            "\t"
                + GeneralUtils.formatTwoDecimal(
                    modelSpikePattern.getBurstPattern().getBW(burstIdx));
        displayString += "\t" + GeneralUtils.formatThreeDecimal(errorBW) + "\t/";
        System.out.print(displayString);
      }

      if (expSingleBurstFeatures.containsKey(BurstFeatureID.pbi)) {
        String displayString = "\tibi\t" + GeneralUtils.formatTwoDecimal(pbi);
        displayString +=
            "\t"
                + GeneralUtils.formatTwoDecimal(
                    modelSpikePattern.getBurstPattern().getIBI(burstIdx));
        displayString += "\t" + GeneralUtils.formatThreeDecimal(errorIBI) + "\t/";
        System.out.print(displayString);
      }

      String displayString = "\tnspikes\t" + GeneralUtils.formatTwoDecimal(nspikes);
      displayString +=
          "\t"
              + GeneralUtils.formatTwoDecimal(
                  modelSpikePattern.getBurstPattern().getNSpikes(burstIdx));
      displayString += "\t" + GeneralUtils.formatThreeDecimal(errorNspikes) + "\t/";
      System.out.print(displayString + "\n");
    }

    return ((expSpikePatternData.getBurstFeatures().getFeatureWeight(BurstFeatureID.b_w) * errorBW)
        + (expSpikePatternData.getBurstFeatures().getFeatureWeight(BurstFeatureID.pbi) * errorIBI)
        + (expSpikePatternData.getBurstFeatures().getFeatureWeight(BurstFeatureID.nspikes)
            * errorNspikes));
  }