All Downloads are FREE. Search and download functionalities are using the official Maven repository.

androidAndroidTest.androidx.constraintlayout.core.LinearEquation.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed 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 androidx.constraintlayout.core

/**
 * LinearEquation is used to represent the linear equations fed into the solver.

* A linear equation can be an equality or an inequation (left term or to the right term).

* The general form will be similar to `a0x0 + a1x1 + ... = C + a2x2 + a3x3 + ... ,` where `a0x0` is a term representing * a variable x0 of an amount `a0`, and `C` represent a constant term. The amount of terms on the left side or the right * side of the equation is arbitrary. */ class LinearEquation { private val mLeftSide = ArrayList() /** * Accessor for the right side of the equation. * @return the equation's right side. */ val rightSide = ArrayList() private var mCurrentSide: ArrayList? = null val isNull: Boolean get() { if (mLeftSide.size == 0 && rightSide.size == 0) { return true } if (mLeftSide.size == 1 && rightSide.size == 1) { val v1 = mLeftSide[0] val v2 = rightSide[0] if (v1!!.isConstant && v2!!.isConstant && v1.amount!!.isNull && v2.amount!!.isNull) { return true } } return false } private enum class Type { EQUALS, LOWER_THAN, GREATER_THAN } private var mType = Type.EQUALS private var mSystem: LinearSystem? = null /** * Copy constructor * @param equation to copy */ constructor(equation: LinearEquation) { val mLeftSide1 = equation.mLeftSide run { var i = 0 val mLeftSide1Size = mLeftSide1.size while (i < mLeftSide1Size) { val v = mLeftSide1[i] val v2 = EquationVariable(v) mLeftSide.add(v2) i++ } } val mRightSide1 = equation.rightSide var i = 0 val mRightSide1Size = mRightSide1.size while (i < mRightSide1Size) { val v = mRightSide1[i] val v2 = EquationVariable(v) rightSide.add(v2) i++ } mCurrentSide = rightSide } /** * Insert the equation in the system */ fun i() { if (mSystem == null) { return } val row = createRowFromEquation(mSystem, this) mSystem!!.addConstraint(row) } /** * Set the current side to be the left side */ fun setLeftSide() { mCurrentSide = mLeftSide } /** * Remove any terms on the left side of the equation */ fun clearLeftSide() { mLeftSide.clear() } /** * Remove [EquationVariable] pointing to [SolverVariable] * @param v the [SolverVariable] we want to remove from the equation */ fun remove(v: SolverVariable) { var ev = find(v, mLeftSide) if (ev != null) { mLeftSide.remove(ev) } ev = find(v, rightSide) if (ev != null) { rightSide.remove(ev) } } /** * Base constructor, set the current side to the left side. */ constructor() { mCurrentSide = mLeftSide } /** * Base constructor, set the current side to the left side. */ constructor(system: LinearSystem?) { mCurrentSide = mLeftSide mSystem = system } /** * Set the current equation system for this equation * @param system the equation system this equation belongs to */ fun setSystem(system: LinearSystem?) { mSystem = system } /** * Set the equality operator for the equation, and switch the current side to the right side * @return this */ fun equalsTo(): LinearEquation { mCurrentSide = rightSide return this } /** * Set the greater than operator for the equation, and switch the current side to the right side * @return this */ fun greaterThan(): LinearEquation { mCurrentSide = rightSide mType = Type.GREATER_THAN return this } /** * Set the lower than operator for the equation, and switch the current side to the right side * @return this */ fun lowerThan(): LinearEquation { mCurrentSide = rightSide mType = Type.LOWER_THAN return this } /** * Normalize the linear equation. If the equation is an equality, transforms it into * an equality, adding automatically slack or error variables. */ fun normalize() { if (mType == Type.EQUALS) { return } mCurrentSide = mLeftSide if (mType == Type.LOWER_THAN) { withSlack(1) } else if (mType == Type.GREATER_THAN) { withSlack(-1) } mType = Type.EQUALS mCurrentSide = rightSide } /** * Will simplify the equation per side -- regroup similar variables into one. * E.g. 2a + b + 3a = b - c will be turned into 5a + b = b - c. */ fun simplify() { simplifySide(mLeftSide) simplifySide(rightSide) } /** * Simplify an array of [EquationVariable] * @param side Array of EquationVariable */ private fun simplifySide(side: ArrayList) { var constant: EquationVariable? = null val variables = HashMap() val variablesNames = ArrayList() run { var i = 0 val sideSize = side.size while (i < sideSize) { val v = side[i] if (v!!.isConstant) { if (constant == null) { constant = v } else { constant!!.add(v) } } else { if (variables.containsKey(v.name)) { val original = variables[v.name] original!!.add(v) } else { variables[v.name] = v variablesNames.add(v.name ?: "") } } i++ } } side.clear() if (constant != null) { side.add(constant) } variablesNames.sort() var i = 0 val variablesNamesSize = variablesNames.size while (i < variablesNamesSize) { val name = variablesNames[i] val v = variables[name] side.add(v) i++ } removeNullTerms(side) } fun moveAllToTheRight() { var i = 0 val mLeftSideSize = mLeftSide.size while (i < mLeftSideSize) { val v = mLeftSide[i] rightSide.add(v!!.inverse()) i++ } mLeftSide.clear() } /** * Balance an equation to have only one term on the left side. * The preference is to first pick an unconstrained variable, then a slack variable, then an error variable. */ fun balance() { if (mLeftSide.size == 0 && rightSide.size == 0) { return } mCurrentSide = mLeftSide run { var i = 0 val mLeftSideSize = mLeftSide.size while (i < mLeftSideSize) { val v = mLeftSide[i] rightSide.add(v!!.inverse()) i++ } } mLeftSide.clear() simplifySide(rightSide) var found: EquationVariable? = null var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i]!! if (v.type === SolverVariable.Type.UNRESTRICTED) { found = v break } i++ } if (found == null) { var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i]!! if (v.type === SolverVariable.Type.SLACK) { found = v break } i++ } } if (found == null) { var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i]!! if (v.type === SolverVariable.Type.ERROR) { found = v break } i++ } } if (found == null) { return } rightSide.remove(found) found.inverse() if (!found.amount!!.isOne) { val foundAmount = found.amount var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i] v!!.amount!!.divide(foundAmount) i++ } found.amount = Amount(1) } simplifySide(rightSide) mLeftSide.add(found) } /** * Check the equation to possibly remove null terms */ private fun removeNullTerms(list: ArrayList) { var hasNullTerm = false var i = 0 val listSize = list.size while (i < listSize) { val v = list[i]!! if (v.amount!!.isNull) { hasNullTerm = true break } i++ } if (hasNullTerm) { // if some elements are now zero, we need to remove them from the right side val newSide: ArrayList newSide = ArrayList() var i = 0 val listSize = list.size while (i < listSize) { val v = list[i]!! if (!v.amount!!.isNull) { newSide.add(v) } i++ } list.clear() list.addAll(newSide) } } /** * Pivot this equation on the variable -- e.g. the variable will be the only term on the left side of the equation. * @param variable variable pivoted on */ fun pivot(variable: SolverVariable) { if (mLeftSide.size == 1 && mLeftSide[0]!!.solverVariable == variable ) { // no-op, we're already pivoted. return } run { var i = 0 val mLeftSideSize = mLeftSide.size while (i < mLeftSideSize) { val v = mLeftSide[i] rightSide.add(v!!.inverse()) i++ } } mLeftSide.clear() simplifySide(rightSide) var found: EquationVariable? = null var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i]!! if (v.solverVariable == variable) { found = v break } i++ } if (found != null) { rightSide.remove(found) found.inverse() if (!found.amount!!.isOne) { val foundAmount = found.amount var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i]!! v.amount!!.divide(foundAmount) i++ } found.amount = Amount(1) } mLeftSide.add(found) } } /** * Returns true if the constant is negative * @return true if the constant is negative. */ fun hasNegativeConstant(): Boolean { var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i] if (v!!.isConstant) { if (v.amount!!.isNegative) { return true } } i++ } return false } /** * If present, returns the constant on the right side of the equation. * The equation is expected to be balanced before using this function. * @return The equation constant */ val constant: Amount? get() { var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i] if (v!!.isConstant) { return v.amount } i++ } return null } /** * Inverse the equation (multiply both left and right terms by -1) */ fun inverse() { val amount = Amount(-1) run { var i = 0 val mLeftSideSize = mLeftSide.size while (i < mLeftSideSize) { val v = mLeftSide[i] v!!.multiply(amount) i++ } } var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i] v!!.multiply(amount) i++ } } /** * Returns the first unconstrained variable encountered in this equation * @return an unconstrained variable or null if none are found */ val firstUnconstrainedVariable: EquationVariable? get() { run { var i = 0 val mLeftSideSize = mLeftSide.size while (i < mLeftSideSize) { val v = mLeftSide[i]!! if (v.type === SolverVariable.Type.UNRESTRICTED) { return v } i++ } } var i = 0 val mRightSideSize = rightSide.size while (i < mRightSideSize) { val v = rightSide[i]!! if (v.type === SolverVariable.Type.UNRESTRICTED) { return v } i++ } return null } /** * Returns the basic variable of the equation * @return basic variable */ val leftVariable: EquationVariable? get() = if (mLeftSide.size == 1) { mLeftSide[0] } else null /** * Replace the variable v in this equation (left or right side) by the right side of the equation l * @param v the variable to replace * @param l the equation we use to replace it with */ fun replace(v: SolverVariable, l: LinearEquation) { replace(v, l, mLeftSide) replace(v, l, rightSide) } /** * Convenience function to replace the variable v possibly contained inside list * by the right side of the equation l * @param v the variable to replace * @param l the equation we use to replace it with * @param list the list of [EquationVariable] to work on */ private fun replace(v: SolverVariable, l: LinearEquation, list: ArrayList) { val toReplace = find(v, list) if (toReplace != null) { list.remove(toReplace) val amount = toReplace.amount val mRightSide1 = l.rightSide var i = 0 val mRightSide1Size = mRightSide1.size while (i < mRightSide1Size) { val lv = mRightSide1[i] list.add(EquationVariable(amount, lv)) i++ } } } /** * Returns the [EquationVariable] associated to * the [SolverVariable] found in the * list of [EquationVariable] * @param v the variable to find * @param list list the list of [EquationVariable] to search in * @return the associated [EquationVariable] */ private fun find(v: SolverVariable, list: ArrayList): EquationVariable? { var i = 0 val listSize = list.size while (i < listSize) { val ev = list[i]!! if (ev.solverVariable == v) { return ev } i++ } return null } /** * Returns true if this equation contains a give variable * @param solverVariable the variable we are looking for * @return true if found, false if not. */ operator fun contains(solverVariable: SolverVariable): Boolean { if (find(solverVariable, mLeftSide) != null) { return true } return if (find(solverVariable, rightSide) != null) { true } else false } /** * Returns the [EquationVariable] associated with a given * [SolverVariable] in this equation * @param solverVariable the variable we are looking for * @return the [EquationVariable] associated if found, otherwise null */ fun getVariable(solverVariable: SolverVariable): EquationVariable? { val variable = find(solverVariable, rightSide) return variable ?: find(solverVariable, mLeftSide) } /** * Add a constant to the current side of the equation * * @param amount the value of the constant * @return this */ fun `var`(amount: Int): LinearEquation { val e = EquationVariable(mSystem, amount) mCurrentSide!!.add(e) return this } /** * Add a fractional constant to the current side of the equation * * @param numerator the value of the constant's numerator * @param denominator the value of the constant's denominator * @return this */ fun `var`(numerator: Int, denominator: Int): LinearEquation { val e = EquationVariable(Amount(numerator, denominator)) mCurrentSide!!.add(e) return this } /** * Add an unrestricted variable to the current side of the equation * * @param name the name of the variable * @return this */ fun `var`(name: String?): LinearEquation { val e = EquationVariable(mSystem, name, SolverVariable.Type.UNRESTRICTED) mCurrentSide!!.add(e) return this } /** * Add an unrestricted variable to the current side of the equation * * @param amount the amount of the variable * @param name the name of the variable * @return this */ fun `var`(amount: Int, name: String?): LinearEquation { val e = EquationVariable(mSystem, amount, name, SolverVariable.Type.UNRESTRICTED) mCurrentSide!!.add(e) return this } /** * Add an unrestricted fractional variable to the current side of the equation * * @param numerator the value of the variable's numerator * @param denominator the value of the variable's denominator * @param name the name of the variable * @return this */ fun `var`(numerator: Int, denominator: Int, name: String?): LinearEquation { val amount = Amount(numerator, denominator) val e = EquationVariable(mSystem, amount, name, SolverVariable.Type.UNRESTRICTED) mCurrentSide!!.add(e) return this } /** * Convenience function to add a variable, based on [var)][LinearEquation.var] * * @param name the variable's name * @return this */ operator fun plus(name: String?): LinearEquation { `var`(name) return this } /** * Convenience function to add a variable, based on [var)][LinearEquation.var] * * @param amount the variable's amount * @param name the variable's name * @return this */ fun plus(amount: Int, name: String?): LinearEquation { `var`(amount, name) return this } /** * Convenience function to add a negative variable, based on [var)][LinearEquation.var] * * @param name the variable's name * @return this */ operator fun minus(name: String?): LinearEquation { `var`(-1, name) return this } /** * Convenience function to add a negative variable, based on [var)][LinearEquation.var] * * @param amount the variable's amount * @param name the variable's name * @return this */ fun minus(amount: Int, name: String?): LinearEquation { `var`(-1 * amount, name) return this } /** * Convenience function to add a constant, based on [var)][LinearEquation.var] * * @param amount the constant's amount * @return this */ operator fun plus(amount: Int): LinearEquation { `var`(amount) return this } /** * Convenience function to add a negative constant, based on [var)][LinearEquation.var] * * @param amount the constant's amount * @return this */ operator fun minus(amount: Int): LinearEquation { `var`(amount * -1) return this } /** * Convenience function to add a fractional constant, based on [var)][LinearEquation.var] * * @param numerator the value of the variable's numerator * @param denominator the value of the variable's denominator * @return this */ fun plus(numerator: Int, denominator: Int): LinearEquation { `var`(numerator, denominator) return this } /** * Convenience function to add a negative fractional constant, based on [var)][LinearEquation.var] * * @param numerator the value of the constant's numerator * @param denominator the value of the constant's denominator * @return this */ fun minus(numerator: Int, denominator: Int): LinearEquation { `var`(numerator * -1, denominator) return this } /** * Add an error variable to the current side * * @param name the name of the error variable * @param strength the strength of the error variable * @return this */ fun withError(name: String?, strength: Int): LinearEquation { val e = EquationVariable(mSystem, strength, name, SolverVariable.Type.ERROR) mCurrentSide!!.add(e) return this } fun withError(amount: Amount?, name: String?): LinearEquation { val e = EquationVariable(mSystem, amount, name, SolverVariable.Type.ERROR) mCurrentSide!!.add(e) return this } /** * Add an error variable to the current side * @return this */ fun withError(): LinearEquation { val name = nextErrorVariableName withError("$name+", 1) withError("$name-", -1) return this } fun withPositiveError(): LinearEquation { val name = nextErrorVariableName withError("$name+", 1) return this } fun addArtificialVar(): EquationVariable { val e = EquationVariable( mSystem, 1, nextArtificialVariableName, SolverVariable.Type.ERROR ) mCurrentSide!!.add(e) return e } /** * Add an error variable to the current side * * @param strength the strength of the error variable * @return this */ fun withError(strength: Int): LinearEquation { withError(nextErrorVariableName, strength) return this } /** * Add a slack variable to the current side * * @param name the name of the slack variable * @param strength the strength of the slack variable * @return this */ fun withSlack(name: String?, strength: Int): LinearEquation { val e = EquationVariable(mSystem, strength, name, SolverVariable.Type.SLACK) mCurrentSide!!.add(e) return this } fun withSlack(amount: Amount?, name: String?): LinearEquation { val e = EquationVariable(mSystem, amount, name, SolverVariable.Type.SLACK) mCurrentSide!!.add(e) return this } /** * Add a slack variable to the current side * @return this */ fun withSlack(): LinearEquation { withSlack(nextSlackVariableName, 1) return this } /** * Add a slack variable to the current side * * @param strength the strength of the slack variable * @return this */ fun withSlack(strength: Int): LinearEquation { withSlack(nextSlackVariableName, strength) return this } /** * Override the toString() method to display the linear equation */ override fun toString(): String { var result = "" result = sideToString(mLeftSide) result += when (mType) { Type.EQUALS -> { "= " } Type.LOWER_THAN -> { "<= " } Type.GREATER_THAN -> { ">= " } } result += sideToString(rightSide) return result.trim { it <= ' ' } } /** * Returns a string representation of an array of [EquationVariable] * @param side array of [EquationVariable] * @return a String representation of the array of variables */ private fun sideToString(side: ArrayList): String { var result = "" var first = true var i = 0 val sideSize = side.size while (i < sideSize) { val v = side[i] if (first) { result += if (v!!.amount!!.isPositive) { v.toString() + " " } else { v!!.signString() + " " + v + " " } first = false } else { result += v!!.signString() + " " + v + " " } i++ } if (side.size == 0) { result = "0" } return result } companion object { private var artificialIndex = 0 private var slackIndex = 0 private var errorIndex = 0 val nextArtificialVariableName: String get() = "a" + ++artificialIndex val nextSlackVariableName: String get() = "s" + ++slackIndex val nextErrorVariableName: String get() = "e" + ++errorIndex /** * Reset the counters for the automatic slack and error variable naming */ fun resetNaming() { artificialIndex = 0 slackIndex = 0 errorIndex = 0 } /** * Transform a LinearEquation into a Row * @param linearSystem * @param e linear equation * @return a Row object */ fun createRowFromEquation(linearSystem: LinearSystem?, e: LinearEquation?): ArrayRow { e!!.normalize() e.moveAllToTheRight() // Let's build a row from the LinearEquation val row = linearSystem!!.createRow() val eq = e.rightSide val count = eq.size for (i in 0 until count) { val v = eq[i]!! val sv = v.solverVariable if (sv != null) { val previous = row.variables!![sv] row.variables!!.put(sv, previous + v.amount!!.toFloat()) } else { row.constantValue = v.amount!!.toFloat() } } return row } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy