/*
  Copyright(C) 2007-2012 National Institute of Information and Communications Technology
*/

/*
  svmtools
  Multiclass classification module
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "exception.h"
#include "svm_common.h"
#include "svm_smo.h"
#include "svm_mc.h"


#define BUF_SIZE (32 * 1024)


static int svm_train(SVM_TPRM *tprm, SVM_EXM *exm, FILE *fp);


/*
  Multiclass-SVMの学習
*/
int svm_mc_makemdl(int method, int class, SVM_TPRM *tprm, SVM_EXM *mcexm, FILE *fp) {
  int i, j, k, l;
  SVM_EXM exm;
  int *rank;
  int *count;

  /* 初期化 */

  /* 訓練用事例 */
  exm.label = smalloc(sizeof(int) * mcexm->num);
  exm.sv = smalloc(sizeof(SVM_SV *) * mcexm->num);
  /* 確率を求めてソート */
  rank = smalloc(sizeof(int) * class);
  count = smalloc(sizeof(int) * class);
  for (i = 0; i < class; i++) {
    rank[i] = i;
    count[i] = 0;
  }
  for (i = 0; i < mcexm->num; i++) count[mcexm->label[i]]++;
  for (i = 0; i < class; i++) {
    int max, tmp;
    max = i;
    for (j = i + 1; j < class; j++) {
      if (count[rank[j]] > count[rank[max]]) max = j;
    }
    if (i != max) {
      tmp = rank[i];
      rank[i] = rank[max];
      rank[max] = tmp;
    }
  }
  free(count);

  /* モデルの共通部分書き込み */

  fprintf(fp, "svmtools multiclass\n");
  fprintf(fp, "%d\n", method);
  fprintf(fp, "%d\n", class);
  for (i = 0; i < class; i++) {
    fprintf(fp, "%d\n", rank[i]);
  }

  /* 学習 */

  switch (method) {
  case SVM_MC_METHOD_OVR:
    /* 事例のベクトルのみコピー */
    for (i = 0; i < mcexm->num; i++) {
      exm.sv[i] = mcexm->sv[i];
    }
    exm.num = mcexm->num;

    /* k個のclassifierを作成 */
    for (i = 0; i < class; i++) {
      if (svm_verbose) fprintf(stderr, "class=%d: ", i);

      /* 事例の作成(One-Versus-Rest) */
      for (j = 0; j < mcexm->num; j++) {
	if (mcexm->label[j] == i) {
	  exm.label[j] = +1;
	} else {
	  exm.label[j] = -1;
	}
      }

      /* 学習 */
      if (svm_train(tprm, &exm, fp)) return 1;
    }
    break;
  case SVM_MC_METHOD_PAIRWISE:
    /* class * (class - 1) / 2 個のclassifierを作成 */
    for (i = 0; i < class; i++) {
      /* クラスiを学習事例にセット */
      l = 0;
      for (k = 0; k < mcexm->num; k++) {
	if (mcexm->label[k] == i) {
	  exm.label[l] = +1;
	  exm.sv[l] = mcexm->sv[k];
	  l++;
	}
      }
      for (j = i + 1; j < class; j++) {
	/* クラスkを学習事例に追加 */
	exm.num = l;
	for (k = 0; k < mcexm->num; k++) {
	  if (mcexm->label[k] == j) {
	    exm.label[exm.num] = -1;
	    exm.sv[exm.num] = mcexm->sv[k];
	    (exm.num)++;
	  }
	}

	if (svm_verbose) fprintf(stderr, "class=%d/%d: ", i, j);

	if (svm_train(tprm, &exm, fp)) return 1;
      }
    }

    break;
  case SVM_MC_METHOD_SBT:
    /* (class - 1)個のclassifierを作成 */

    /* はじめに一番ランクの低いクラスを負例としてセット */
    exm.num = 0;
    for (i = 0; i < mcexm->num; i++) {
      if (mcexm->label[i] == rank[class - 1]) {
	exm.label[exm.num] = -1;
	exm.sv[exm.num] = mcexm->sv[i];
	(exm.num)++;
      }
    }
    /* ランクの低いものから作っていく */
    for (i = class - 2; i >= 0; i--) {
      int tmpnum;

      if (svm_verbose) fprintf(stderr, "class=%d: ", i);

      /* 正例を追加 */
      tmpnum = exm.num;
      for (j = 0; j < mcexm->num; j++) {
	if (mcexm->label[j] == rank[i]) {
	  exm.label[exm.num] = +1;
	  exm.sv[exm.num] = mcexm->sv[j];
	  (exm.num)++;
	}
      }

      /* 学習 */
      if (svm_train(tprm, &exm, fp)) return 1;

      /* 正例を負例に変える */
      for (j = tmpnum; j < exm.num; j++) exm.label[j] = -1;
    }

    break;
  }

  free(rank);
  free(exm.sv);
  free(exm.label);

  return 0;
}


/*
  SVMの学習
*/
static int svm_train(SVM_TPRM *tprm, SVM_EXM *exm, FILE *fp) {
  double *alpha;
  double b;

  /* メモリ割り当て */
  if (exm->num > 0) {
    alpha = smalloc(sizeof(double) * exm->num);
  } else {
    alpha = NULL;
  }

  /* 学習 */
  if (svm_smo(tprm, exm, alpha, &b)) return 1;

  /* モデルに変換 */
  if (svm_createmdl(&tprm->kprm, exm, alpha, b, fp)) return 1;

  if (alpha != NULL) free(alpha);

  return 0;
}


/*
  Multiclass-SVMの読み込み
*/
SVM_MC_MDL *svm_mc_readmdl(FILE *fp) {
  int i, j, k;
  char buf[BUF_SIZE];
  SVM_MC_MDL *mcmdl;

  /* メモリ確保 */
  mcmdl = smalloc(sizeof(SVM_MC_MDL));

  /* ID */
  if (fgets(buf, BUF_SIZE, fp) == NULL) return NULL;
  buf[strlen(buf) - 1] = '\0';
  if (strcmp("svmtools multiclass", buf) != 0) return NULL;

  /* 方法 */
  if (fgets(buf, BUF_SIZE, fp) == NULL) return NULL;
  buf[strlen(buf) - 1] = '\0';
  mcmdl->method = atoi(buf);

  /* クラスの数 */
  if (fgets(buf, BUF_SIZE, fp) == NULL) return NULL;
  buf[strlen(buf) - 1] = '\0';
  mcmdl->class = atoi(buf);

  /* 事前確率の順番 */
  mcmdl->rank = smalloc(sizeof(int) * mcmdl->class);
  for (i = 0; i < mcmdl->class; i++) {
    if (fgets(buf, BUF_SIZE, fp) == NULL) return NULL;
    buf[strlen(buf) - 1] = '\0';
    mcmdl->rank[i] = atoi(buf);
  }

  mcmdl->vote = smalloc(sizeof(int) * mcmdl->class);

  /* モデル */
  switch (mcmdl->method) {
  case SVM_MC_METHOD_OVR:
    /* モデルのメモリ割り当て */
    mcmdl->mdl = smalloc(sizeof(SVM_MDL *) * mcmdl->class);
    /* 各モデルの読み込み */
    for (i = 0; i < mcmdl->class; i++) {
      if ((mcmdl->mdl[i] = svm_readmdl(fp)) == NULL) return NULL;
    }
    break;
  case SVM_MC_METHOD_PAIRWISE:
    /* モデルのメモリ割り当て */
    mcmdl->mdl = smalloc(sizeof(SVM_MDL *) * mcmdl->class * (mcmdl->class - 1) / 2);
    /* 各モデルの読み込み */
    k = 0;
    for (i = 0; i < mcmdl->class; i++) {
      for (j = i + 1; j < mcmdl->class; j++) {
	if ((mcmdl->mdl[k++] = svm_readmdl(fp)) == NULL) return NULL;
      }
    }
    break;
  case SVM_MC_METHOD_SBT:
    /* モデルのメモリ割り当て */
    mcmdl->mdl = smalloc(sizeof(SVM_MDL *) * (mcmdl->class - 1));
    /* 各モデルの読み込み */
    for (i = 0; i < mcmdl->class - 1; i++) {
      if ((mcmdl->mdl[i] = svm_readmdl(fp)) == NULL) return NULL;
    }
    break;
  }

  return mcmdl;
}


/*
  分類
  tieの場合は事前確率の大きいクラスを優先
*/
int svm_mc_decision(SVM_MC_MDL *mcmdl, SVM_SV *x) {
  int i, j, k;
  int class;
  float s;

  switch (mcmdl->method) {
  case SVM_MC_METHOD_OVR:
    class = mcmdl->rank[0];
    s = svm_decision(mcmdl->mdl[class], x);
    for (i = 1; i < mcmdl->class; i++) {
      float t;
      t = svm_decision(mcmdl->mdl[mcmdl->rank[i]], x);
      if (t > s) {
	class = mcmdl->rank[i];
	s = t;
      }
    }
    break;
  case SVM_MC_METHOD_PAIRWISE:
    for (i = 0; i < mcmdl->class; i++) mcmdl->vote[i] = 0;
    k = 0;
    for (i = 0; i < mcmdl->class; i++) {
      for (j = i + 1; j < mcmdl->class; j++) {
	s = svm_decision(mcmdl->mdl[k++], x);
	if (s > 0.0) {
	  mcmdl->vote[i]++;
	} else if (s < 0.0) {
	  mcmdl->vote[j]++;
	}
      }
    }
    class = mcmdl->rank[0];
    s = mcmdl->vote[class];
    for (i = 1; i < mcmdl->class; i++) {
      if (mcmdl->vote[mcmdl->rank[i]] > s) {
	class = mcmdl->rank[i];
	s = mcmdl->vote[class];
      }
    }
    break;
  case SVM_MC_METHOD_SBT:
    for (i = mcmdl->class - 2; i >= 0; i--) {
      s = svm_decision(mcmdl->mdl[i], x);
      if (s >= 0.0) break;
    }
    class = mcmdl->rank[mcmdl->class - 2 - i];
    break;
  default:
    class = -1;
  }

  return class;
}
