se.toxbee.sleepfighter.challenge.math.LinearEquationProblem.java Source code

Java tutorial

Introduction

Here is the source code for se.toxbee.sleepfighter.challenge.math.LinearEquationProblem.java

Source

/*******************************************************************************
 * Copyright (c) 2013 See AUTHORS file.
 * 
 * This file is part of SleepFighter.
 * 
 * SleepFighter 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.
 * 
 * SleepFighter 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 SleepFighter. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package se.toxbee.sleepfighter.challenge.math;

import java.util.Random;

import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;

import se.toxbee.sleepfighter.R;
import se.toxbee.sleepfighter.utils.debug.Debug;
import se.toxbee.sleepfighter.utils.math.MatrixUtil;
import android.content.Context;

/*
 * Challenge: Solve a system of linear equations with three variables.
 * For the system of linear equations Ax = b, where x = (x1 x2 x3), the user is to solve
 * for either x1 or x2(just solving for x3 is too easy). 
 * The systems of linear equations generated by this class, have an unique solution, 
 * and are thus trivially solved using Gaussian elimination. 
 */
public class LinearEquationProblem implements MathProblem {

    private final Context context;

    // we use a 3x3 coefficent matrix.
    private static final int MATRIX_SIZE = 3;

    private int solution;

    private String renderedString;

    private Random rng = new Random();

    // The coefficient-matrix. 
    private RealMatrix A;

    // The constant vector b.
    private RealVector b;

    private int variableToSolveFor;

    public String render() {
        return this.renderedString;
    }

    public int solution() {
        return this.solution;
    }

    // Create A
    private RealMatrix createCoefficients() {
        return MatrixUtil.createRandomMatrix(rng, MATRIX_SIZE, -5, 16, true);
    }

    // create a fun,fun,fun solution vector x!
    private RealVector createSolutionVector() {
        RealVector s;
        do {
            s = MatrixUtil.createRandomVector(rng, MATRIX_SIZE, -10, 10, true);
        } while (isBoringSolution(s));

        return s;
    }

    public void newProblem() {

        generateProblem();
        Debug.d("coeff: " + this.A);
        Debug.d("constants: " + this.b);

        // now render the problem
        doRender();
        Debug.d("rendered problem: " + this.renderedString);

    }

    // find a system of linear equations with a real solution(because real solutions are easy to input with the android keyboard)
    private void generateProblem() {

        this.A = createCoefficients();

        int attempts = 0;
        RealVector solution;
        boolean foundb = false;
        do {

            Debug.d("try again");

            // if after 40 attempts we still can't find a good constants vector, we should give up on this 
            // coefficient matrix and try creating a new one.
            if (attempts == 40) {
                Debug.d("new coefficients matrix");

                this.A = createCoefficients();
                attempts = 0;
            }

            // Generate a solution.
            solution = this.createSolutionVector();

            // Now compute Ax = b(where x is the solution)
            this.b = multiply(this.A, solution);

            // does b satisfy our requirements?
            if (isGoodb(b)) {
                foundb = true;
            }

            attempts++;

        } while (!foundb);

        Debug.d("solution: " + solution.toString());

        // solve for either x1 or x2
        this.variableToSolveFor = rng.nextBoolean() ? 0 : 1;
        this.solution = (int) solution.getEntry(this.variableToSolveFor);
    }

    // multiply a 3x3 matrix by a 3 vector
    private static RealVector multiply(RealMatrix matrix, RealVector vector) {

        double[] m1 = matrix.getRow(0);
        double[] m2 = matrix.getRow(1);
        double[] m3 = matrix.getRow(2);

        double[] v = vector.toArray();

        return new ArrayRealVector(new double[] { m1[0] * v[0] + m1[1] * v[1] + m1[2] * v[2],
                m2[0] * v[0] + m2[1] * v[1] + m2[2] * v[2], m3[0] * v[0] + m3[1] * v[1] + m3[2] * v[2],

        });
    }

    // ensure that b isn't too big or too little
    private static boolean isGoodb(RealVector b) {
        final int MIN = -20;
        final int MAX = 59;

        return ((int) b.getEntry(0) > MIN && (int) b.getEntry(0) < MAX)
                && ((int) b.getEntry(1) > MIN && (int) b.getEntry(1) < MAX)
                && ((int) b.getEntry(2) > MIN && (int) b.getEntry(2) < MAX);
    }

    // boring solutions are solution that are small, boring numbers like 1 and 2. 
    // We will not allow boring solutions!
    private static boolean isBoringSolution(int solution) {
        return solution == 0 || Math.abs(solution) == 1 || Math.abs(solution) == 2;
    }

    private static boolean isBoringSolution(RealVector v) {
        return isBoringSolution((int) v.getEntry(0)) || isBoringSolution((int) v.getEntry(1))
                || isBoringSolution((int) v.getEntry(2));
    }

    private void doRender() {

        // begin table. 
        this.renderedString = "$\\{\\table ";

        for (int row = 0; row < MATRIX_SIZE; ++row) {

            // current row
            double[] r = this.A.getRow(row);

            // render the terms
            this.renderedString += renderTerm(r[0], getVariableString(0), true)
                    + renderTerm(r[1], getVariableString(1), false) + renderTerm(r[2], getVariableString(2), false);

            // now the constant.
            this.renderedString += " , = , " + (int) this.b.getEntry(row) + ";";
        }

        // close the table
        this.renderedString += "$";

        // now the description.
        String format = context.getResources().getString(R.string.linear_equation_challenge_desc);
        this.renderedString += "<br>"
                + String.format(format, "$" + getVariableString(this.variableToSolveFor) + "$");
    }

    private static String getVariableString(int variable) {
        if (variable == 0) {
            return "x";
        } else if (variable == 1) {
            return "y";
        } else if (variable == 2) {
            return "z";
        } else {
            throw new IllegalArgumentException("This should not happen!");
        }
    }

    private static String renderTerm(double value, String variable, boolean isFirstTerm) {
        int v = ((int) value);
        String s;
        if (isFirstTerm) {
            s = renderTermHelper(v, variable);
        } else {
            if (v < 0) {
                s = ", - , " + renderTermHelper(Math.abs(v), variable);
            } else {
                s = " , + , " + renderTermHelper(v, variable);
            }
        }

        return s + " ";
    }

    // 1x should be shown as x instead.
    public static String renderTermHelper(int value, String variable) {
        return (value == 1 ? variable : Integer.toString(value) + variable);
    }

    public LinearEquationProblem(final Context context) {
        this.context = context;
    }

}