# Path: algo/cap/const.py
# K = 5 # size of user and review latent vectors
# EM_ITER = [10]
# BURN_IN = [0]
# SAMPLES = [20]
# NR_ITER = 50
# NR_TOL = 1e-4
# NR_STEP = 0.1     # In paper: 1
# ETA = 0 
# 
# Path: algo/cap/newton_raphson.py
# def newton_raphson(fun, der, variable_group, theta_0, n_iter=NR_ITER, eps=NR_TOL, 
#     step=NR_STEP):
#   """ Applies Newton-Raphson's Method. This method finds an approximation for a 
#       root of a function by continuously updating according to the
#       derivative and the function at the current value of the variable. 
#       
#       Observation:
#       - In CAP, it is used for finding the optimal value of a function by
#       calculating an approximation of the root of its derivative, thus it uses
#       the first and the second order derivative.
# 
#       Args:
#         fun: function which evaluates over theta to calculate the root of.
#         der: function which evaluates over theta and represents the derivative.
#         theta_0: value of initial theta.
#         n_iter: number of iterations to perform.
#         eps: tolerance for difference to zero.
#         step: update rate.
# 
#       Returns:
#         The approximated value for the root of the function.
#   """
#   der_val = der(theta_0, variable_group)
#   fun_val = fun(theta_0, variable_group)
#   if any(isnan(der_val)) or any(isinf(der_val)):
#     return theta_0
#   der_inv = pinv(der_val)
#   dot = der_inv.dot(fun_val)
#   theta = theta_0 - step * dot
#   der_val = der(theta, variable_group)
#   old_fun_val = float('inf')
#   fun_val = fun(theta, variable_group)
#   i = 1
#   while i < n_iter and not allclose(fun_val, old_fun_val, atol=eps):
#     if any(isnan(der_val)) or any(isinf(der_val)):
#       return theta
#     der_inv = pinv(der_val)
#     dot = der_inv.dot(fun_val)
#     new_theta = theta - step * dot
#     theta = new_theta
#     der_val = der(theta, variable_group)
#     old_fun_val = fun_val
#     fun_val = fun(theta, variable_group)
#     i += 1
#   return theta
# 
# Path: util/aux.py
# def sigmoid(value):
#   """ Computes the sigmoid function applied to value. The function corresponds
#       to 1 / (1 + exp(-value))
# 
#       Observation:
#       - If an overflow or underflow is obtained ate each step of the
#         computation, then a asymptotic approximate value is returned.
# 
#       Args:
#         value: a float
# 
#       Returns:
#         A float with the function return value.
#   """
#   return expit(value)
# 
# def sigmoid_der1(value):
#   """ Computes the sigmoid function applied to value. The function corresponds
#       to exp(value) / (1 + exp(value)) ** 2
# 
#       Observation:
#       - If an overflow or underflow is obtained ate each step of the
#         computation, then a asymptotic approximate value is returned.
# 
#       Args:
#         value: a float
# 
#       Returns:
#         A float with the function return value.
#   """
#   e_val = expit(value)
#   return e_val * (1 - e_val)
# 
# def sigmoid_der2(value):
#   """ Computes the sigmoid function applied to value. The function corresponds
#       to - exp(value) * (exp(value) - 1) / (1 + exp(value)) ** 3
# 
#       Observation:
#       - If an overflow or underflow is obtained ate each step of the
#         computation, then a asymptotic approximate value is returned.
# 
#       Args:
#         value: a float
# 
#       Returns:
#         A float with the function return value.
#   """
#   e_val = expit(value)
#   return e_val * (2 * e_val ** 2 - 3 * e_val + 1)


from numpy import array, reshape, mean, std, identity, zeros, ones
from numpy.random import uniform
from scipy.linalg import pinv2 as pinv
from algo.cap import const
from algo.cap.newton_raphson import newton_raphson
from util.aux import sigmoid, sigmoid_der1, sigmoid_der2
 
  def get_cond_mean_and_var(self, groups, votes):
    """ Gets the conditional mean and variance of this variable.
    
        Observations:
        - Returns the variance of this variable used by Gibbs Sampling.
        - The distribution is conditioned on all other latent variables.
        - Some terms of this calculation (cond_var, var_dot) are constant in the
        same EM iteration being, thus, reused and reset in a new iteration
        (through reset_samples function).

        Args:
          groups: a dictionary of Group objects.
          votes: list of votes (training set).

        Returns:
          A 2-tuple with the mean and variance, both float values.
    """
    variance = 0.0
    mean = 0.0
    for i in self.related_votes:
      vote = votes[i] 
      rest = self.get_rest_value(groups, vote)
      mean += rest
    var_group = groups[self.name]
    mean /= var_group.var_H.value
    if self.cond_var is None: 
      self.cond_var = 1.0 / (1.0 / var_group.var_param.value + \
          float(self.num_votes) / var_group.var_H.value)
    if self.var_dot is None:

# Path: algo/cap/const.py
# K = 5 # size of user and review latent vectors
# EM_ITER = [10]
# BURN_IN = [0]
# SAMPLES = [20]
# NR_ITER = 50
# NR_TOL = 1e-4
# NR_STEP = 0.1     # In paper: 1
# ETA = 0 
# 
# Path: algo/cap/newton_raphson.py
# def newton_raphson(fun, der, variable_group, theta_0, n_iter=NR_ITER, eps=NR_TOL, 
#     step=NR_STEP):
#   """ Applies Newton-Raphson's Method. This method finds an approximation for a 
#       root of a function by continuously updating according to the
#       derivative and the function at the current value of the variable. 
#       
#       Observation:
#       - In CAP, it is used for finding the optimal value of a function by
#       calculating an approximation of the root of its derivative, thus it uses
#       the first and the second order derivative.
# 
#       Args:
#         fun: function which evaluates over theta to calculate the root of.
#         der: function which evaluates over theta and represents the derivative.
#         theta_0: value of initial theta.
#         n_iter: number of iterations to perform.
#         eps: tolerance for difference to zero.
#         step: update rate.
# 
#       Returns:
#         The approximated value for the root of the function.
#   """
#   der_val = der(theta_0, variable_group)
#   fun_val = fun(theta_0, variable_group)
#   if any(isnan(der_val)) or any(isinf(der_val)):
#     return theta_0
#   der_inv = pinv(der_val)
#   dot = der_inv.dot(fun_val)
#   theta = theta_0 - step * dot
#   der_val = der(theta, variable_group)
#   old_fun_val = float('inf')
#   fun_val = fun(theta, variable_group)
#   i = 1
#   while i < n_iter and not allclose(fun_val, old_fun_val, atol=eps):
#     if any(isnan(der_val)) or any(isinf(der_val)):
#       return theta
#     der_inv = pinv(der_val)
#     dot = der_inv.dot(fun_val)
#     new_theta = theta - step * dot
#     theta = new_theta
#     der_val = der(theta, variable_group)
#     old_fun_val = fun_val
#     fun_val = fun(theta, variable_group)
#     i += 1
#   return theta
# 
# Path: util/aux.py
# def sigmoid(value):
#   """ Computes the sigmoid function applied to value. The function corresponds
#       to 1 / (1 + exp(-value))
# 
#       Observation:
#       - If an overflow or underflow is obtained ate each step of the
#         computation, then a asymptotic approximate value is returned.
# 
#       Args:
#         value: a float
# 
#       Returns:
#         A float with the function return value.
#   """
#   return expit(value)
# 
# def sigmoid_der1(value):
#   """ Computes the sigmoid function applied to value. The function corresponds
#       to exp(value) / (1 + exp(value)) ** 2
# 
#       Observation:
#       - If an overflow or underflow is obtained ate each step of the
#         computation, then a asymptotic approximate value is returned.
# 
#       Args:
#         value: a float
# 
#       Returns:
#         A float with the function return value.
#   """
#   e_val = expit(value)
#   return e_val * (1 - e_val)
# 
# def sigmoid_der2(value):
#   """ Computes the sigmoid function applied to value. The function corresponds
#       to - exp(value) * (exp(value) - 1) / (1 + exp(value)) ** 3
# 
#       Observation:
#       - If an overflow or underflow is obtained ate each step of the
#         computation, then a asymptotic approximate value is returned.
# 
#       Args:
#         value: a float
# 
#       Returns:
#         A float with the function return value.
#   """
#   e_val = expit(value)
#   return e_val * (2 * e_val ** 2 - 3 * e_val + 1)


# Path: algo/cap/models.py
from numpy import array, reshape, mean, std, identity, zeros, ones
from numpy.random import uniform
from scipy.linalg import pinv2 as pinv
from algo.cap import const
from algo.cap.newton_raphson import newton_raphson
from util.aux import sigmoid, sigmoid_der1, sigmoid_der2

 
  def get_cond_mean_and_var(self, groups, votes):
    """ Gets the conditional mean and variance of this variable.
    
        Observations:
        - Returns the variance of this variable used by Gibbs Sampling.
        - The distribution is conditioned on all other latent variables.
        - Some terms of this calculation (cond_var, var_dot) are constant in the
        same EM iteration being, thus, reused and reset in a new iteration
        (through reset_samples function).

        Args:
          groups: a dictionary of Group objects.
          votes: list of votes (training set).

        Returns:
          A 2-tuple with the mean and variance, both float values.
    """
    variance = 0.0
    mean = 0.0
    for i in self.related_votes:
      vote = votes[i] 
      rest = self.get_rest_value(groups, vote)
      mean += rest
    var_group = groups[self.name]
    mean /= var_group.var_H.value
    if self.cond_var is None: 
      self.cond_var = 1.0 / (1.0 / var_group.var_param.value + \
          float(self.num_votes) / var_group.var_H.value)
    if self.var_dot is None:

      self.var_dot = sigmoid(var_group.weight_param.value.T \