package com.airbnb.aerosolve.core.models;

import com.airbnb.aerosolve.core.DebugScoreRecord;
import com.airbnb.aerosolve.core.FeatureVector;
import com.airbnb.aerosolve.core.ModelHeader;
import com.airbnb.aerosolve.core.MulticlassScoringResult;
import com.airbnb.aerosolve.core.util.FloatVector;
import lombok.Getter;
import lombok.Setter;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * Created by hector_yee on 8/25/14.
 * Base class for models
 */

public abstract class AbstractModel implements Model, Serializable {

  private static final long serialVersionUID = -5011350794437028492L;

  @Getter @Setter
  protected double offset = 0.0;

  @Getter @Setter
  protected double slope = 1.0;

  // Scores a single item. The transforms should already have been applied to
  // the context and item and combined item.
  abstract public float scoreItem(FeatureVector combinedItem);

  // Debug scores a single item. These are explanations for why a model
  // came up with the score.
  abstract public float debugScoreItem(FeatureVector combinedItem,
                                       StringBuilder builder);

  abstract public List<DebugScoreRecord> debugScoreComponents(FeatureVector combinedItem);

  // Loads model from a buffered stream.
  abstract protected void loadInternal(ModelHeader header, BufferedReader reader) throws IOException;

  abstract public void save(BufferedWriter writer) throws IOException;

  // returns probability: 1 / (1 + exp(-(offset + scale * score))
  public double scoreProbability(float score) {
    return 1.0 / (1.0 + Math.exp(-(offset + slope * score)));
  }
  
  // Optional method for multi-class classifiers.
  public ArrayList<MulticlassScoringResult> scoreItemMulticlass(FeatureVector combinedItem) {
    assert(false);
    return new ArrayList<MulticlassScoringResult>();
  }
  
  // Populates a multiclass result probability using softmax over the scores by default.
  // Some models might override this if their scores are already probabilities e.g. random forests.
  public void scoreToProbability(ArrayList<MulticlassScoringResult> results) {
    FloatVector vec = new FloatVector(results.size());
    for (int i = 0; i < results.size(); i++) {
      vec.values[i] = (float) results.get(i).score;
    }
    vec.softmax();
    for (int i = 0; i < results.size(); i++) {
      results.get(i).probability = vec.values[i];
    }
  }

  // Optional method implemented by online updatable models e.g. Spline, RBF
  public void onlineUpdate(float grad, float learningRate, Map<String, Map<String, Double>> flatFeatures) {
    assert(false);
  }

  // Helper function for FOBOS updates.
  // http://papers.nips.cc/paper/3793-efficient-learning-using-forward-backward-splitting.pdf
  public static float fobosUpdate(
      float currWeight,
      float gradient,
      float learningRate,
      float l1Reg,
      float l2Reg,
      float ssg) {
    float etaT = learningRate / (float) Math.sqrt(ssg);
    float etaTHalf = learningRate / (float) Math.sqrt(ssg + 0.5);
    // FOBOS l2 regularization
    float wt = (currWeight - gradient * etaT) / (1.0f + l2Reg * etaTHalf);
    // FOBOS l1 regularization
    float sign = 0.0f;
    if (wt > 0.0) {
      sign = 1.0f;
    } else {
      sign = -1.0f;
    }
    float step = (float) Math.max(0.0, Math.abs(wt) - l1Reg * etaTHalf);
    return sign * step;
  }
}

@@ -1679,24 +1679,164 @@
 at score) %7B%0A
+    return scoreProbability(score, offset, slope);%0A  %7D%0A%0A  public static double scoreProbability(float score, double offset, double slope) %7B%0A
     return 1
@@ -1885,26 +1885,24 @@
 ore)));%0A  %7D%0A
-  
 %0A  // Option
@@ -2110,18 +2110,16 @@
 ();%0A  %7D%0A
-  
 %0A  // Po

add static function scoreProbability so that scoring use different offset and slope.