fi.smaa.libror.UTAGMSSolver.java Source code

Java tutorial

Introduction

Here is the source code for fi.smaa.libror.UTAGMSSolver.java

Source

/*
 * This file is part of libror.
 * libror is distributed from http://smaa.fi/libror
 * Copyright (C) 2011-15 Tommi Tervonen.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fi.smaa.libror;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.math.linear.Array2DRowRealMatrix;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.RealVector;
import org.apache.commons.math.optimization.GoalType;
import org.apache.commons.math.optimization.OptimizationException;
import org.apache.commons.math.optimization.RealPointValuePair;
import org.apache.commons.math.optimization.linear.LinearConstraint;
import org.apache.commons.math.optimization.linear.LinearObjectiveFunction;
import org.apache.commons.math.optimization.linear.NoFeasibleSolutionException;
import org.apache.commons.math.optimization.linear.Relationship;
import org.apache.commons.math.optimization.linear.SimplexSolver;

import fi.smaa.libror.RORModel.PrefPair;

@SuppressWarnings("deprecation")
public class UTAGMSSolver {

    private RealMatrix necessaryRelation = null;
    private RealMatrix possibleRelation = null;
    private SimplexSolver solver = new SimplexSolver();
    private boolean strictValueFunctions = false;
    private RORModel model;
    private static final int MAX_SIMPLEX_ITERATIONS = 100000;
    private static final double MIN_EPSILON = 0.00001;

    public enum RelationsType {
        BOTH, NECESSARY, POSSIBLE
    }

    public UTAGMSSolver(RORModel model) {
        this.model = model;
        solver.setMaxIterations(MAX_SIMPLEX_ITERATIONS);
    }

    public RORModel getModel() {
        return model;
    }

    public void solve() throws InfeasibleConstraintsException {
        solve(RelationsType.BOTH);
    }

    public void solve(RelationsType rel) throws InfeasibleConstraintsException {
        necessaryRelation = new Array2DRowRealMatrix(model.getNrAlternatives(), model.getNrAlternatives());
        possibleRelation = new Array2DRowRealMatrix(model.getNrAlternatives(), model.getNrAlternatives());
        List<LinearConstraint> baseConstraints = buildRORConstraints();

        // check that the set of constraints is feasible
        try {
            RealPointValuePair res = solver.optimize(buildObjectiveFunction(), baseConstraints, GoalType.MAXIMIZE,
                    true);
            if (res.getValue() <= 0.0) {
                throw new InfeasibleConstraintsException(
                        "Preference information leading to infeasible constraints, epsilon <= 0.0");
            }
        } catch (OptimizationException e) {
            throw new InfeasibleConstraintsException(
                    "Preference information leading to infeasible constraints: " + e.getMessage());
        }

        for (int i = 0; i < model.getNrAlternatives(); i++) {
            for (int j = 0; j < model.getNrAlternatives(); j++) {
                boolean necHolds = false;
                if (rel.equals(RelationsType.NECESSARY) || rel.equals(RelationsType.BOTH)) {
                    necHolds = solveRelation(i, j, baseConstraints, true);
                    necessaryRelation.setEntry(i, j, necHolds ? 1.0 : 0.0);
                }
                if (rel.equals(RelationsType.POSSIBLE) || rel.equals(RelationsType.BOTH)) {
                    if (necHolds) {
                        possibleRelation.setEntry(i, j, 1.0);
                    } else {
                        possibleRelation.setEntry(i, j, solveRelation(i, j, baseConstraints, false) ? 1.0 : 0.0);
                    }
                }
            }
        }
    }

    public void printModel(boolean necessary, int a, int b) {
        List<LinearConstraint> constraints = buildRORConstraints();
        addNecOrPrefConstraint(a, b, necessary, constraints);
        LinearConstraintHelper.printConstraints(constraints);
    }

    List<LinearConstraint> buildRORConstraints() {
        List<LinearConstraint> c = new ArrayList<LinearConstraint>();
        for (PrefPair p : model.getPrefPairs()) {
            c.add(buildStrictlyPreferredConstraint(p.a, p.b));
        }
        for (int i = 0; i < model.getNrCriteria(); i++) {
            c.addAll(buildMonotonousConstraints(i));
        }
        for (int i = 0; i < model.getNrCriteria(); i++) {
            c.add(buildFirstLevelZeroConstraint(i));
        }
        c.add(buildBestLevelsAddToUnityConstraint());
        c.addAll(buildAllVariablesLessThan1Constraint());
        c.add(buildEpsilonStrictlyPositiveConstraint());

        return c;
    }

    private LinearConstraint buildEpsilonStrictlyPositiveConstraint() {
        double[] lhsVars = new double[getNrLPVariables()];
        lhsVars[lhsVars.length - 1] = 1.0;
        return new LinearConstraint(lhsVars, Relationship.GEQ, MIN_EPSILON);
    }

    private List<LinearConstraint> buildAllVariablesLessThan1Constraint() {
        List<LinearConstraint> con = new ArrayList<LinearConstraint>();
        for (int i = 0; i < getNrLPVariables(); i++) {
            double[] lhsVars = new double[getNrLPVariables()];
            lhsVars[i] = 1.0;
            con.add(new LinearConstraint(lhsVars, Relationship.LEQ, 1.0));
        }
        return con;
    }

    private LinearConstraint buildBestLevelsAddToUnityConstraint() {
        double[] vars = new double[getNrLPVariables()];
        for (int i = 0; i < model.getNrCriteria(); i++) {
            vars[getConstraintOffset(i) + model.getPerfMatrix().getLevels()[i].getDimension() - 1] = 1.0;
        }
        return new LinearConstraint(vars, Relationship.EQ, 1.0);
    }

    private LinearConstraint buildFirstLevelZeroConstraint(int critIndex) {
        double[] lhsVars = new double[getNrLPVariables()];
        lhsVars[getConstraintOffset(critIndex)] = 1.0;
        return new LinearConstraint(lhsVars, Relationship.EQ, 0.0);
    }

    private List<LinearConstraint> buildMonotonousConstraints(int critIndex) {
        List<LinearConstraint> constList = new ArrayList<LinearConstraint>();
        RealVector levels = model.getPerfMatrix().getLevels()[critIndex];
        for (int i = 0; i < levels.getDimension() - 1; i++) {
            double[] lhs = new double[getNrLPVariables()];
            double[] rhs = new double[getNrLPVariables()];
            lhs[getConstraintOffset(critIndex) + i] = 1.0;
            rhs[getConstraintOffset(critIndex) + i + 1] = 1.0;
            if (strictValueFunctions) {
                lhs[lhs.length - 1] = 1.0; // epsilon
            }
            constList.add(new LinearConstraint(lhs, 0.0, Relationship.LEQ, rhs, 0.0));
        }
        return constList;
    }

    /**
     * a is strictly preferred to b (v(a) >= v(b) + epsilon)
     * @param a
     * @param b
     * @return
     */
    private LinearConstraint buildStrictlyPreferredConstraint(int a, int b) {
        double[] lhsVars = new double[getNrLPVariables()];
        double[] rhsVars = new double[getNrLPVariables()];
        setVarsPositive(lhsVars, a);
        setVarsPositive(rhsVars, b);
        // set epsilon
        rhsVars[rhsVars.length - 1] = 1.0;
        return new LinearConstraint(lhsVars, 0.0, Relationship.GEQ, rhsVars, 0.0);
    }

    private void setVarsPositive(double[] vars, int altIndex) {
        for (int i = 0; i < model.getNrCriteria(); i++) {
            vars[getConstraintIndex(i, altIndex)] = 1.0;
        }
    }

    private int getNrLPVariables() {
        // utility of all alts + epsilon
        int sum = 0;
        for (RealVector vec : model.getPerfMatrix().getLevels()) {
            sum += vec.getDimension();
        }
        return sum + 1;
    }

    private int getConstraintIndex(int critIndex, int altIndex) {
        if (critIndex < 0 || altIndex < 0) {
            throw new IllegalArgumentException("PRECOND violation");
        }

        int offset = getConstraintOffset(critIndex);
        int index = Arrays.binarySearch(model.getPerfMatrix().getLevels()[critIndex].getData(),
                model.getPerfMatrix().getMatrix().getEntry(altIndex, critIndex));
        assert (index >= 0); // sanity check

        return offset + index;
    }

    private int getConstraintOffset(int critIndex) {
        int offset = 0;
        for (int i = 0; i < critIndex; i++) {
            offset += model.getPerfMatrix().getLevels()[i].getDimension();
        }
        return offset;
    }

    /**
     * Check whether relation holds.
     * 
     * @param i index of first alternative, PRECOND: >= 0
     * @param j index of the second alternative, PRECOND: >= 0
     * @param rorConstraints base constraints E_{ROR}^{A^R}
     * @param necessary true if the relation solved is the necessary one, false otherwise
     * @return
     */
    private boolean solveRelation(int i, int j, List<LinearConstraint> rorConstraints, boolean necessary) {
        if (i < 0 && j < 0) {
            throw new IllegalArgumentException("PRECOND violation");
        }

        if (i == j) {
            return true;
        }

        List<LinearConstraint> constraints = new ArrayList<LinearConstraint>(rorConstraints);
        addNecOrPrefConstraint(i, j, necessary, constraints);
        LinearObjectiveFunction goalFunction = buildObjectiveFunction();

        try {
            RealPointValuePair res = solver.optimize(goalFunction, constraints, GoalType.MAXIMIZE, true);
            if (necessary) {
                return res.getValue() <= 0.0;
            } else { // possible
                return res.getValue() > 0.0;
            }

        } catch (NoFeasibleSolutionException e) {
            if (necessary) {
                return true;
            } else { // possible
                return false;
            }
        } catch (OptimizationException e) {
            throw new IllegalStateException("Invalid OptimizationException: " + e.getMessage());
        }
    }

    private LinearObjectiveFunction buildObjectiveFunction() {
        double[] coeff = new double[getNrLPVariables()];
        coeff[coeff.length - 1] = 1.0;
        LinearObjectiveFunction goalFunction = new LinearObjectiveFunction(coeff, 0.0);
        return goalFunction;
    }

    private void addNecOrPrefConstraint(int i, int j, boolean necessary, List<LinearConstraint> constraints) {
        if (necessary) {
            constraints.add(buildStrictlyPreferredConstraint(j, i));
        } else { // possible
            constraints.add(buildWeaklyPreferredConstraint(i, j));
        }
    }

    private LinearConstraint buildWeaklyPreferredConstraint(int a, int b) {
        double[] lhsVars = new double[getNrLPVariables()];
        double[] rhsVars = new double[getNrLPVariables()];
        setVarsPositive(lhsVars, a);
        setVarsPositive(rhsVars, b);
        return new LinearConstraint(lhsVars, 0.0, Relationship.GEQ, rhsVars, 0.0);
    }

    /**
     * PRECOND: solve() executed and returned true
     * @return a matrix where >0.0 (1.0) signifies that the relation holds
     * @throws IllegalStateException if solve() hasn't been succesfully executed 
     */
    public RealMatrix getNecessaryRelation() throws IllegalStateException {
        if (necessaryRelation == null) {
            throw new IllegalStateException("violating PRECOND");
        }
        return necessaryRelation;
    }

    /**
     * PRECOND: solve() executed and returned true
     * @return a matrix where >0.0 (1.0) signifies that the relation holds
     * @throws IllegalStateException if solve() hasn't been succesfully executed 
     */
    public RealMatrix getPossibleRelation() {
        if (possibleRelation == null) {
            throw new IllegalStateException("violating PRECOND");
        }
        return possibleRelation;
    }

    public void setStrictValueFunctions(boolean b) {
        this.strictValueFunctions = b;
    }
}