'Predicts the value of *M(i,j)*
It is based on \'Factorization Meets the Neighborhood: a Multifaceted
Collaborative Filtering Model\' (Yehuda Koren).
Equation 3 (section 2.2):
:math:`\hat{r}_{ui} = b_{ui} + \frac{\sum_{j \in S^k(i;u)} s_{ij} (r_{uj} - b_{uj})}{\sum_{j \in S^k(i;u)} s_{ij}}`, where
:math:`b_{ui} = \mu + b_u + b_i`
http://public.research.att.com/~volinsky/netflix/kdd08koren.pdf
:param i: row in M, M(i)
:type i: user or item id
:param j: col in M, M(j)
:type j: user or item id
:param Sk: number of k elements to be used in :math:`S^k(i; u)`
:type Sk: int
:param MIN_VALUE: min. value in M (e.g. in ratings[1..5] => 1)
:type MIN_VALUE: float
:param MAX_VALUE: max. value in M (e.g. in ratings[1..5] => 5)
:type MAX_VALUE: float'

def predict(self, i, j, Sk=None, MIN_VALUE=None, MAX_VALUE=None):

    if (not Sk):
        Sk = self._Sk
    similars = self.similar_neighbours(i, j, Sk)
    bu = (self._mean_col[j] - self._mean_cols)
    bi = (self._mean_row[i] - self._mean_rows)
    bui = (bu + bi)
    sim_ratings = []
    sum_similarity = 0.0
    for (similar, sij) in similars[1:]:
        sim_rating = self.get_matrix().value(similar, j)
        if (sim_rating is None):
            continue
        ruj = sim_rating
        sum_similarity += sij
        bj = (self._mean_row[similar] - self._mean_rows)
        buj = (bu + bj)
        sim_ratings.append((sij * (ruj - buj)))
    if ((not sum_similarity) or (not sim_ratings)):
        return nan
    Sumj_Sk = (sum(sim_ratings) / sum_similarity)
    rui = (bui + Sumj_Sk)
    predicted_value = rui
    if MIN_VALUE:
        predicted_value = max(predicted_value, MIN_VALUE)
    if MAX_VALUE:
        predicted_value = min(predicted_value, MAX_VALUE)
    return float(predicted_value)
