/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.mahout.cf.taste.impl.recommender.knn;
import java.util.Arrays;
/**
* Non-negative Quadratic Optimization. Based on the paper of Robert M. Bell and Yehuda Koren in ICDM '07.
* Thanks to Dan Tillberg for the hints in the implementation.
*/
public final class NonNegativeQuadraticOptimizer implements Optimizer {
private static final double EPSILON = 1.0e-10;
private static final double CONVERGENCE_LIMIT = 0.1;
private static final int MAX_ITERATIONS = 1000;
private static final double DEFAULT_STEP = 0.001;
/**
* Non-negative Quadratic Optimization.
*
* @param matrix
* matrix nxn positions
* @param b
* vector b, n positions
* @return vector of n weights
*/
@Override
public double[] optimize(double[][] matrix, double[] b) {
int k = b.length;
double[] r = new double[k];
double[] x = new double[k];
Arrays.fill(x, 3.0 / k);
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
double rdot = 0.0;
for (int n = 0; n < k; n++) {
double sumAw = 0.0;
double[] rowAn = matrix[n];
for (int i = 0; i < k; i++) {
sumAw += rowAn[i] * x[i];
}
// r = b - Ax; // the residual, or 'steepest gradient'
double rn = b[n] - sumAw;
// find active variables - those that are pinned due to
// nonnegativity constraints; set respective ri's to zero
if (x[n] < EPSILON && rn < 0.0) {
rn = 0.0;
} else {
// max step size numerator
rdot += rn * rn;
}
r[n] = rn;
}
if (rdot <= CONVERGENCE_LIMIT) {
break;
}
// max step size denominator
double rArdotSum = 0.0;
for (int n = 0; n < k; n++) {
double sumAr = 0.0;
double[] rowAn = matrix[n];
for (int i = 0; i < k; i++) {
sumAr += rowAn[i] * r[i];
}
rArdotSum += r[n] * sumAr;
}
// max step size
double stepSize = rdot / rArdotSum;
if (Double.isNaN(stepSize)) {
stepSize = DEFAULT_STEP;
}
// adjust step size to prevent negative values
for (int n = 0; n < k; n++) {
if (r[n] < 0.0) {
double absStepSize = stepSize < 0.0 ? -stepSize : stepSize;
stepSize = Math.min(absStepSize, Math.abs(x[n] / r[n])) * stepSize / absStepSize;
}
}
// update x values
for (int n = 0; n < k; n++) {
x[n] += stepSize * r[n];
if (x[n] < EPSILON) {
x[n] = 0.0;
}
}
// TODO: do something in case of divergence
}
return x;
}
}