/* Copyright 2009-2016 David Hadka * * This file is part of the MOEA Framework. * * The MOEA Framework is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * The MOEA Framework 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>. */ package org.moeaframework.core.variable; import java.util.BitSet; import org.moeaframework.core.Solution; import org.moeaframework.core.Variable; /** * Helper methods for working with various decision variable types and * encodings. First, these methods perform any necessary type checking and * type conversion. Instead of writing: * <pre> * double value = ((RealVariable)solution.getVariable(i)).getValue() * </pre> * the following simplified version is allowed: * <pre> * double value = getReal(solution.getVariable(i)); * </pre> * <p> * Support for integer encodings is now supported using the * {@link #newInt(int, int)} or {@link #newBinaryInt(int, int)}. The former * represents integers as floating-point values while the latter uses binary * strings. Both representations are accessed using {@link #getInt(Variable)} * and {@link #setInt(Variable, int)} methods. * <p> * This class also provides methods for converting between {@link RealVariable} * and {@link BinaryVariable} in both binary and gray code formats. */ public class EncodingUtils { /** * The error message shown when the array length is not valid. */ private static final String INVALID_LENGTH = "invalid number of values"; /** * The error message shown when the number of bits provided is not valid. */ private static final String INVALID_BITS = "invalid number of bits"; /** * The error message shown when the decision variable is not real-valued. */ private static final String NOT_REAL = "not a real variable"; /** * The error message shown when the decision variable is not integer-valued. */ private static final String NOT_INT = "not an integer variable"; /** * The error message shown when the decision variable is not a permutation. */ private static final String NOT_PERMUTATION = "not a permutation"; /** * The error message shown when the decision variable is not a subset. */ private static final String NOT_SUBSET = "not a subset"; /** * The error message shown when the decision variable is not a binary value. */ private static final String NOT_BINARY = "not a binary variable"; /** * The error message shown when the decision variable is not a boolean. */ private static final String NOT_BOOLEAN = "not a boolean variable"; /** * Private constructor to prevent instantiation. */ private EncodingUtils() { super(); } /** * Encodes the specified real variable into a binary variable. The number of * bits used in the encoding is {@code binary.getNumberOfBits()}. * * @param real the real variable * @param binary the binary variable to which the real value is encoded */ public static void encode(RealVariable real, BinaryVariable binary) { int numberOfBits = binary.getNumberOfBits(); double lowerBound = real.getLowerBound(); double upperBound = real.getUpperBound(); double value = real.getValue(); double scale = (value - lowerBound) / (upperBound - lowerBound); long index = Math.round(scale * ((1L << numberOfBits) - 1)); encode(index, binary); } /** * Decodes the specified binary variable into its real value. * * @param binary the binary variable * @param real the real variable to which the value is decoded */ public static void decode(BinaryVariable binary, RealVariable real) { int numberOfBits = binary.getNumberOfBits(); double lowerBound = real.getLowerBound(); double upperBound = real.getUpperBound(); long index = decode(binary); double scale = index / (double)((1L << numberOfBits) - 1); double value = lowerBound + (upperBound - lowerBound) * scale; real.setValue(value); } /** * Encodes the integer into the specified binary variable. The number of * bits used in the encoding is {@code binary.getNumberOfBits()}. * * @param value an integer * @param binary the binary variable to which the value is encoded */ public static void encode(long value, BinaryVariable binary) { int numberOfBits = binary.getNumberOfBits(); if (value < 0) { throw new IllegalArgumentException("negative value"); } if ((numberOfBits < 1) || (numberOfBits > 63)) { throw new IllegalArgumentException(INVALID_BITS); } if ((1L << numberOfBits) <= value) { throw new IllegalArgumentException( "number of bits not sufficient to represent value"); } for (int i = 0; i < numberOfBits; i++) { binary.set(i, (value & (1L << i)) != 0); } } /** * Decodes the specified binary variable into its integer value. * * @param binary the binary variable * @return the integer value of the specified binary variable */ public static long decode(BinaryVariable binary) { int numberOfBits = binary.getNumberOfBits(); if ((numberOfBits < 1) || (numberOfBits > 63)) { throw new IllegalArgumentException(INVALID_BITS); } long value = 0; for (int i = 0; i < numberOfBits; i++) { if (binary.get(i)) { value |= (1L << i); } } return value; } /** * Converts a binary variable from a binary encoding to gray encoding. The * gray encoding ensures two adjacent values have binary representations * differing in only {@code 1} bit (a Hamming distance of {@code 1}). * * @param variable the variable to be converted */ public static void binaryToGray(BinaryVariable variable) { int n = variable.getNumberOfBits(); BitSet binary = variable.getBitSet(); variable.set(n - 1, binary.get(n - 1)); for (int i = n - 2; i >= 0; i--) { variable.set(i, binary.get(i + 1) ^ binary.get(i)); } } /** * Converts a binary variable from a gray encoding to binary encoding. * * @param variable the variable to be converted */ public static void grayToBinary(BinaryVariable variable) { int n = variable.getNumberOfBits(); BitSet gray = variable.getBitSet(); variable.set(n - 1, gray.get(n - 1)); for (int i = n - 2; i >= 0; i--) { variable.set(i, variable.get(i + 1) ^ gray.get(i)); } } /** * Returns a new floating-point decision variable bounded within the * specified range. * * @param lowerBound the lower bound of the floating-point value * @param upperBound the upper bound of the floating-point value * @return a new floating-point decision variable bounded within the * specified range */ public static RealVariable newReal(double lowerBound, double upperBound) { return new RealVariable(lowerBound, upperBound); } /** * Returns a new integer-valued decision variable bounded within the * specified range. The integer value is encoded using a * {@link RealVariable}. * * @param lowerBound the lower bound of the integer value * @param upperBound the upper bound of the integer value * @return a new integer-valued decision variable bounded within the * specified range */ public static RealVariable newInt(int lowerBound, int upperBound) { return new RealVariable(lowerBound, Math.nextAfter( (double)(upperBound+1), Double.NEGATIVE_INFINITY)); } /** * Returns a new integer-valued decision variable bounded within the * specified range. The integer value is encoded using a * {@link BinaryVariable}. * * @param lowerBound the lower bound of the integer value * @param upperBound the upper bound of the integer value * @return a new integer-valued decision variable bounded within the * specified range */ public static BinaryIntegerVariable newBinaryInt(int lowerBound, int upperBound) { return new BinaryIntegerVariable(lowerBound, upperBound); } /** * Returns a new boolean decision variable. * * @return a new boolean decision variable */ public static BinaryVariable newBoolean() { return new BinaryVariable(1); } /** * Returns a new binary decision variable with the specified number of bits. * * @param length the number of bits in the binary decision variable * @return a new binary decision variable with the specified number of bits */ public static BinaryVariable newBinary(int length) { return new BinaryVariable(length); } /** * Returns a new permutation with the specified number of items. * * @param length the number of items in the permutation * @return a new permutation with the specified number of items */ public static Permutation newPermutation(int length) { return new Permutation(length); } /** * Returns a new subset with the specified number of items. * * @param k the fixed size of the subset * @param n the total number of items in the set * @return a new subset with the specified number of items */ public static Subset newSubset(int k, int n) { return new Subset(k, n); } /** * Returns the value stored in a floating-point decision variable. * * @param variable the decision variable * @return the value stored in a floating-point decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link RealVariable} */ public static double getReal(Variable variable) { if (variable instanceof RealVariable) { return ((RealVariable)variable).getValue(); } else { throw new IllegalArgumentException(NOT_REAL); } } /** * Returns the value stored in an integer-valued decision variable. * * @param variable the decision variable * @return the value stored in an integer-valued decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link RealVariable} */ public static int getInt(Variable variable) { if (variable instanceof RealVariable) { return (int)Math.floor(((RealVariable)variable).getValue()); } else if (variable instanceof BinaryIntegerVariable) { return (int)Math.floor(((BinaryIntegerVariable)variable).getValue()); } else { throw new IllegalArgumentException(NOT_INT); } } /** * Returns the value stored in a binary decision variable as a * {@link BitSet}. * * @param variable the decision variable * @return the value stored in a binary decision variable as a * {@code BitSet} * @throws IllegalArgumentException if the decision variable is not of type * {@link BinaryVariable} */ public static BitSet getBitSet(Variable variable) { if (variable instanceof BinaryVariable) { return ((BinaryVariable)variable).getBitSet(); } else { throw new IllegalArgumentException(NOT_BINARY); } } /** * Returns the value stored in a binary decision variable as a boolean * array. * * @param variable the decision variable * @return the value stored in a binary decision variable as a boolean * array * @throws IllegalArgumentException if the decision variable is not of type * {@link BinaryVariable} */ public static boolean[] getBinary(Variable variable) { if (variable instanceof BinaryVariable) { BinaryVariable binaryVariable = (BinaryVariable)variable; boolean[] result = new boolean[binaryVariable.getNumberOfBits()]; for (int i=0; i<binaryVariable.getNumberOfBits(); i++) { result[i] = binaryVariable.get(i); } return result; } else { throw new IllegalArgumentException(NOT_BINARY); } } /** * Returns the value stored in a boolean decision variable. * * @param variable the decision variable * @return the value stored in a boolean decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link BinaryVariable} */ public static boolean getBoolean(Variable variable) { boolean[] values = getBinary(variable); if (values.length == 1) { return values[0]; } else { throw new IllegalArgumentException(NOT_BOOLEAN); } } /** * Returns the value stored in a permutation decision variable. * * @param variable the decision variable * @return the value stored in a permutation decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link Permutation} */ public static int[] getPermutation(Variable variable) { if (variable instanceof Permutation) { return ((Permutation)variable).toArray(); } else { throw new IllegalArgumentException(NOT_PERMUTATION); } } /** * Returns the value stored in a subset decision variable. * * @param variable the decision variable * @return the value stored in a subset decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link Subset} */ public static int[] getSubset(Variable variable) { if (variable instanceof Subset) { return ((Subset)variable).toArray(); } else { throw new IllegalArgumentException(NOT_SUBSET); } } /** * Returns the array of floating-point decision variables stored in a * solution. The solution must contain only floating-point decision * variables. * * @param solution the solution * @return the array of floating-point decision variables stored in a * solution * @throws IllegalArgumentException if any decision variable contained in * the solution is not of type {@link RealVariable} */ public static double[] getReal(Solution solution) { return getReal(solution, 0, solution.getNumberOfVariables()); } /** * Returns the array of floating-point decision variables stored in a * solution between the specified indices. The decision variables located * between the start and end index must all be floating-point decision * variables. * * @param solution the solution * @param startIndex the start index (inclusive) * @param endIndex the end index (exclusive) * @return the array of floating-point decision variables stored in a * solution between the specified indices * @throws IllegalArgumentException if any decision variable contained in * the solution between the start and end index is not of type * {@link RealVariable} */ public static double[] getReal(Solution solution, int startIndex, int endIndex) { double[] result = new double[endIndex - startIndex]; for (int i=startIndex; i<endIndex; i++) { result[i-startIndex] = getReal(solution.getVariable(i)); } return result; } /** * Returns the array of integer-valued decision variables stored in a * solution. The solution must contain only integer-valued decision * variables. * * @param solution the solution * @return the array of integer-valued decision variables stored in a * solution * @throws IllegalArgumentException if any decision variable contained in * the solution is not of type {@link RealVariable} */ public static int[] getInt(Solution solution) { return getInt(solution, 0, solution.getNumberOfVariables()); } /** * Returns the array of integer-valued decision variables stored in a * solution between the specified indices. The decision variables located * between the start and end index must all be integer-valued decision * variables. * * @param solution the solution * @param startIndex the start index (inclusive) * @param endIndex the end index (exclusive) * @return the array of integer-valued decision variables stored in a * solution between the specified indices * @throws IllegalArgumentException if any decision variable contained in * the solution between the start and end index is not of type * {@link RealVariable} */ public static int[] getInt(Solution solution, int startIndex, int endIndex) { int[] result = new int[endIndex - startIndex]; for (int i=startIndex; i<endIndex; i++) { result[i-startIndex] = getInt(solution.getVariable(i)); } return result; } /** * Sets the value of a floating-point decision variable. * * @param variable the decision variable * @param value the value to assign the floating-point decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link RealVariable} * @throws IllegalArgumentException if the value is out of bounds * ({@code value < getLowerBound()) || (value > getUpperBound()}) */ public static void setReal(Variable variable, double value) { if (variable instanceof RealVariable) { ((RealVariable)variable).setValue(value); } else { throw new IllegalArgumentException(NOT_REAL); } } /** * Sets the values of all floating-point decision variables stored in the * solution. The solution must contain only floating-point decision * variables. * * @param solution the solution * @param values the array of floating-point values to assign the solution * @throws IllegalArgumentException if any decision variable contained in * the solution is not of type {@link RealVariable} * @throws IllegalArgumentException if any of the values are out of bounds * ({@code value < getLowerBound()) || (value > getUpperBound()}) */ public static void setReal(Solution solution, double[] values) { setReal(solution, 0, solution.getNumberOfVariables(), values); } /** * Sets the values of the floating-point decision variables stored in a * solution between the specified indices. The decision variables located * between the start and end index must all be floating-point decision * variables. * * @param solution the solution * @param startIndex the start index (inclusive) * @param endIndex the end index (exclusive) * @param values the array of floating-point values to assign the * decision variables * @throws IllegalArgumentException if any decision variable contained in * the solution between the start and end index is not of type * {@link RealVariable} * @throws IllegalArgumentException if an invalid number of values are * provided * @throws IllegalArgumentException if any of the values are out of bounds * ({@code value < getLowerBound()) || (value > getUpperBound()}) */ public static void setReal(Solution solution, int startIndex, int endIndex, double[] values) { if (values.length != (endIndex - startIndex)) { throw new IllegalArgumentException(INVALID_LENGTH); } for (int i=startIndex; i<endIndex; i++) { setReal(solution.getVariable(i), values[i-startIndex]); } } /** * Sets the value of an integer-valued decision variable. * * @param variable the decision variable * @param value the value to assign the integer-valued decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link RealVariable} * @throws IllegalArgumentException if the value is out of bounds * ({@code value < getLowerBound()) || (value > getUpperBound()}) */ public static void setInt(Variable variable, int value) { if (variable instanceof RealVariable) { ((RealVariable)variable).setValue(value); } else if (variable instanceof BinaryIntegerVariable) { ((BinaryIntegerVariable)variable).setValue(value); } else { throw new IllegalArgumentException(NOT_INT); } } /** * Sets the values of all integer-valued decision variables stored in the * solution. The solution must contain only integer-valued decision * variables. * * @param solution the solution * @param values the array of integer values to assign the solution * @throws IllegalArgumentException if any decision variable contained in * the solution is not of type {@link RealVariable} * @throws IllegalArgumentException if any of the values are out of bounds * ({@code value < getLowerBound()) || (value > getUpperBound()}) */ public static void setInt(Solution solution, int[] values) { setInt(solution, 0, solution.getNumberOfVariables(), values); } /** * Sets the values of the integer-valued decision variables stored in a * solution between the specified indices. The decision variables located * between the start and end index must all be integer-valued decision * variables. * * @param solution the solution * @param startIndex the start index (inclusive) * @param endIndex the end index (exclusive) * @param values the array of floating-point values to assign the * decision variables * @throws IllegalArgumentException if any decision variable contained in * the solution between the start and end index is not of type * {@link RealVariable} * @throws IllegalArgumentException if an invalid number of values are * provided * @throws IllegalArgumentException if any of the values are out of bounds * ({@code value < getLowerBound()) || (value > getUpperBound()}) */ public static void setInt(Solution solution, int startIndex, int endIndex, int[] values) { if (values.length != (endIndex - startIndex)) { throw new IllegalArgumentException(INVALID_LENGTH); } for (int i=startIndex; i<endIndex; i++) { setInt(solution.getVariable(i), values[i-startIndex]); } } /** * Sets the bits in a binary decision variable using the given * {@link BitSet}. * * @param variable the decision variable * @param bitSet the bits to set in the binary decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link BinaryVariable} */ public static void setBitSet(Variable variable, BitSet bitSet) { if (variable instanceof BinaryVariable) { BinaryVariable binaryVariable = (BinaryVariable)variable; for (int i=0; i<binaryVariable.getNumberOfBits(); i++) { binaryVariable.set(i, bitSet.get(i)); } } else { throw new IllegalArgumentException(NOT_BINARY); } } /** * Sets the bits in a binary decision variable using the given boolean * array. * * @param variable the decision variable * @param values the bits to set in the binary decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link BinaryVariable} * @throws IllegalArgumentException if an invalid number of values are * provided */ public static void setBinary(Variable variable, boolean[] values) { if (variable instanceof BinaryVariable) { BinaryVariable binaryVariable = (BinaryVariable)variable; if (values.length != binaryVariable.getNumberOfBits()) { throw new IllegalArgumentException(INVALID_LENGTH); } for (int i=0; i<binaryVariable.getNumberOfBits(); i++) { binaryVariable.set(i, values[i]); } } else { throw new IllegalArgumentException(NOT_BINARY); } } /** * Sets the value of a boolean decision variable. * * @param variable the decision variable * @param value the value to assign the boolean decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link BinaryVariable} * @throws IllegalArgumentException if the number of bits in the binary * variable is not {@code 1} */ public static void setBoolean(Variable variable, boolean value) { setBinary(variable, new boolean[] { value }); } /** * Sets the value of a permutation decision variable. * * @param variable the decision variable * @param values the permutation to assign the permutation decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link Permutation} * @throws IllegalArgumentException if {@code values} is not a valid * permutation */ public static void setPermutation(Variable variable, int[] values) { if (variable instanceof Permutation) { ((Permutation)variable).fromArray(values); } else { throw new IllegalArgumentException(NOT_PERMUTATION); } } /** * Sets the value of a subset decision variable. * * @param variable the decision variable * @param values the subset to assign the subset decision variable * @throws IllegalArgumentException if the decision variable is not of type * {@link Subset} * @throws IllegalArgumentException if {@code values} is not a valid * subset */ public static void setSubset(Variable variable, int[] values) { if (variable instanceof Subset) { ((Subset)variable).fromArray(values); } else { throw new IllegalArgumentException(NOT_SUBSET); } } }