#!/usr/bin/env python
import itertools
import math
import optparse
import random
import sys
import bleu
from collections import namedtuple
import mlpy

# Simplified implementation of Pairwise Ranking Optimization (PRO) due to
# Hopkins & May (2011): http://aclweb.org/anthology-new/D/D11/D11-1125.pdf
#
# Differences from paper:
# -Does not inclucd ..de candidate generation (candidates are assumed to be given)
# -Uses perceptron instead of maxent for classification

translation_candidate = namedtuple("candidate", "features, smoothed_bleu")
optparser = optparse.OptionParser()
optparser.add_option("-r", "--reference", dest="reference", default="data/train.en", help="English reference sentences")
optparser.add_option("-n", "--nbest", dest="nbest", default="data/train.nbest.shuffled", help="N-best lists")
optparser.add_option("-m", "--num-training-sentences", dest="m", default=sys.maxint, type="int", help="Number of training sentences (default=all)")
optparser.add_option("-t", "--tau", dest="tau", default=5000, type="int", help="PRO samples per input sentence (tau, default=5000)")
optparser.add_option("-x", "--xi", dest="xi", default=50, type="int", help="PRO training instances per input sentence (xi, default=50)")
optparser.add_option("-e", "--eta", dest="eta", default=0.1, type="float", help="Perceptron learning rate (eta, default=0.1)")
optparser.add_option("-a", "--alpha", dest="alpha", default=0.05, type="float", help="Sampler acceptance cutoff (alpha, default=0.05)")
optparser.add_option("-i", "--epochs", dest="epochs", default=5, type="int", help="Perceptron epochs (default=10)")
optparser.add_option("-s", "--random-seed", dest="seed", default="0", type="string", help="Random number seed (default='0')")
(opts,_) = optparser.parse_args()

ref = [line.strip().split() for line in open(opts.reference)][:opts.m]

sys.stderr.write("Reading N-best lists...")
nbests = [[] for _ in ref]
for n, line in enumerate(open(opts.nbest)):
  (i, sentence, features) = line.strip().split("|||")
  (i, features) = (int(i), [float(h) for h in features.strip().split()])
  if i >= len(ref):
    break
  stats = tuple(bleu.bleu_stats(sentence.strip().split(), ref[i]))
  nbests[i].append(translation_candidate(features, bleu.smoothed_bleu(stats)))
  if n % 2000 == 0:
    sys.stderr.write(".")
sys.stderr.write("\n")


# PRO using ElasticNet
x = []
y = []
for nbest in nbests:
  def V():
    for _ in xrange(opts.tau):
      c1 = random.choice(nbest)
      c2 = random.choice(nbest)
      if c1 != c2 and math.fabs(c1.smoothed_bleu - c2.smoothed_bleu) > opts.alpha:
        x.append([c1j-c2j for c1j,c2j in zip(c1.features, c2.features)])
        if c1.smoothed_bleu > c2.smoothed_bleu:
          y.append(1)
        else:
          y.append(-1)
  V()
en = mlpy.ElasticNetC(lmb=0.01, eps=0.0001)
en.learn(x,y)
w = en.w()

  
print "\n".join([str(weight) for weight in w])
