/******************************************************************************* * Copyright 2012-2013 Analog Devices, Inc. * * 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 com.analog.lyric.dimple.model.domains; import java.util.List; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.collect.ArrayUtil; import com.analog.lyric.collect.WeakInterner; import com.analog.lyric.dimple.exceptions.DomainException; import com.analog.lyric.dimple.factorfunctions.core.FactorFunctionUtilities; import com.analog.lyric.dimple.model.values.Value; import com.analog.lyric.dimple.model.variables.Bit; import com.analog.lyric.util.misc.Internal; import com.google.common.collect.Interner; import com.google.common.math.DoubleMath; import net.jcip.annotations.Immutable; /** * Domain for discrete variables that can take on a values from a predefined finite set * in a specified order. * <p> * While from a set-theoretic perspective two domains with the same elements in different order * are equivalent, these objects are only considered equal if the elements occur in the same * order because the order is intrinsic in the representation of variable values using the domain. */ @Immutable public abstract class DiscreteDomain extends Domain { private static final long serialVersionUID = 1L; private static enum InternedDomains { INSTANCE; private final Interner<DiscreteDomain> interner = WeakInterner.create(); } @Override protected DiscreteDomain intern() { return InternedDomains.INSTANCE.interner.intern(this); } @SuppressWarnings("unchecked") static <D extends DiscreteDomain> D intern(D domain) { return (D)domain.intern(); } /*-------------- * Construction */ DiscreteDomain(int hashCode) { super(hashCode); } /** * Domain consisting of the integers 0 and 1, which is the domain of {@link Bit} variables. */ public static IntRangeDomain bit() { return range(0,1); } /** * Domain consisting of the values false and true. */ public static TypedDiscreteDomain<Boolean> bool() { return create(false, true); } /** * Returns a discrete domain consisting of the specified elements in the specified order. * <p> * @param elements must either be immutable or must implement {@link Object#equals(Object)} and * {@link Object#hashCode()} methods that do not depend on any mutable state. * @since 0.08 */ public static <T> TypedDiscreteDomain<T> create(List<T> elements) { // TODO - instead implement in terms of List<T> and implement array versions using Arrays.asList(). return create(elements.get(0), 1, elements.toArray()); } /** * Returns a discrete domain consisting of the specified elements in the specified order. * <p> * @param elements must either be immutable or must implement {@link Object#equals(Object)} and * {@link Object#hashCode()} methods that do not depend on any mutable state. */ public static <T> TypedDiscreteDomain<T> create(T[] elements) { return create(elements[0], 1, elements); } public static <T> TypedDiscreteDomain<T> create(T firstElement, Object ... moreElements) { return create(firstElement, 0, moreElements); } private static <T> TypedDiscreteDomain<T> create(T firstElt, int offset, Object[] moreElements) { int size = moreElements.length; Class<?> eltClass = firstElt.getClass(); if (eltClass.isEnum() && ((Enum<?>)firstElt).ordinal() == 0) { boolean useEnumDomain = true; for (int i = offset; i < size; ++i) { Object elt = moreElements[i]; if (elt.getClass() != eltClass || ((Enum<?>)elt).ordinal() != (i + 1)) { useEnumDomain = false; break; } } if (useEnumDomain) { @SuppressWarnings({ "unchecked", "rawtypes" }) TypedDiscreteDomain<T> domain = intern(new EnumDomain(eltClass)); return domain; } } else if (Integer.class.isAssignableFrom(eltClass)) { // We could eliminate the instanceof checks if the component type of the 'elements' // array is Integer... if (size > offset && moreElements[offset] instanceof Integer) { int first = ((Integer)firstElt).intValue(); int prev = ((Integer)moreElements[offset]).intValue(); int interval = prev - first; if (interval > 0) { boolean useIntRangeDomain = true; for (int i = offset+1; i < size; ++i) { Object element = moreElements[i]; if (element instanceof Integer) { int next = prev + interval; if (next == ((Integer)element).intValue()) { prev = next; continue; } } useIntRangeDomain = false; break; } if (useIntRangeDomain) { @SuppressWarnings("unchecked") TypedDiscreteDomain<T> domain = (TypedDiscreteDomain<T>) range(first, prev, interval); return domain; } } } } else if (Double.class.isAssignableFrom(eltClass)) { if (size > offset && moreElements[offset] instanceof Double) { double first = ((Double)firstElt).doubleValue(); double prev = ((Double)moreElements[offset]).doubleValue(); double interval = prev - first; if (interval > 0.0) { double tolerance = DoubleRangeDomain.defaultToleranceForInterval(interval); boolean useDoubleRangeDomain = true; for (int i = 1+offset; i < size; ++i) { Object element = moreElements[i]; if (element instanceof Double) { double next = prev + interval; if (DoubleMath.fuzzyEquals(next, (Double)element, tolerance)) { prev = next; continue; } } useDoubleRangeDomain = false; break; } if (useDoubleRangeDomain) { @SuppressWarnings("unchecked") TypedDiscreteDomain<T> domain = (TypedDiscreteDomain<T>) range(first, prev, interval, tolerance); return domain; } } } } return intern(new ArrayDiscreteDomain<T>(firstElt, offset, moreElements)); } public static <E extends Enum<E>> EnumDomain<E> forEnum(Class<E> enumClass) { return intern(new EnumDomain<E>(enumClass)); } public static <T> JointDiscreteDomain<T> joint(JointDomainIndexer domains) { return intern(new JointDiscreteDomain<T>(domains)); } public static <T> JointDiscreteDomain<T> joint(TypedDiscreteDomain<?>... domains) { return joint(JointDomainIndexer.create(domains)); } public static JointDiscreteDomain<?> joint(DiscreteDomain ... domains) { return joint(JointDomainIndexer.create(domains)); } /** * Returns discrete domain containing all integers from {@code low} to {@code high}. * @param low is the first element of the domain * @param high is the last element of the domain, which must be greater than or equal to {@code low} * @see #range(int, int, int) */ public static IntRangeDomain range(int low, int high) { return intern(new IntRangeDomain(low, high, 1)); } /** * Returns discrete domain containing all integers from {@code low} to {@code high} in increments of * specified interval. * <p> * @param low is the first element of the domain * @param high is the upper bound of the domain, which must be greater than {@code low}. Will only * be included in the domain itself if {@code (high - low)} is a multiple of {@code interval}. * @param interval is a positive integer specifying the increment between adjacent elements in the domain. * @see #range(int, int) */ public static IntRangeDomain range(int low, int high, int interval) { return intern(new IntRangeDomain(low, high, interval)); } public static DoubleRangeDomain range(double low, double high) { return DoubleRangeDomain.create(low, high, 1.0, Double.NaN); } public static DoubleRangeDomain range(double low, double high, double interval) { return DoubleRangeDomain.create(low, high, interval, Double.NaN); } public static DoubleRangeDomain range(double low, double high, double interval, double tolerance) { return DoubleRangeDomain.create(low, high, interval, tolerance); } public static FiniteFieldDomain finiteField(int primitivePolynomial) { return intern(new FiniteFieldDomain(primitivePolynomial)); } /*---------------- * Object methods */ /** * True if elements of domain are the same and in the same order. * <p> * The default implementation simply does an elementwise comparison. */ @Override public boolean equals(@Nullable Object thatObj) { if (this == thatObj) { return true; } if (!(thatObj instanceof DiscreteDomain)) { return false; } DiscreteDomain that = (DiscreteDomain)thatObj; if (size() != that.size() || hashCode() != that.hashCode()) { return false; } for (int i = 0, end = size(); i < end; ++i) { if (!this.getElement(i).equals(that.getElement(i))) { return false; } } return true; } /*---------------- * Domain methods */ @Override public final DiscreteDomain asDiscrete() { return this; } @Override public boolean hasIntCompatibleValues() { return isIntCompatibleClass(getElementClass()); } @Override public boolean inDomain(@Nullable Object value) { return (getIndex(value) >= 0); } @Override public boolean isIntegral() { return isIntCompatibleClass(getElementClass()); } /** * @return true if {@code value} is a {@link Number} representing a valid * integer index into the array of elements returned by {@link #getElements()}. */ @Override public boolean containsValueWithRepresentation(Object value) { if (value instanceof Number) { Number number = (Number)value; int index = number.intValue(); return index >=0 && index < size(); } return false; } @Override public final boolean isDiscrete() { return true; } @Override public boolean valueInDomain(Value value) { if (this.equals(value.getDomain())) { final int index = value.getIndex(); return index >= 0 && index < size(); } else { return inDomain(value.getObject()); } } /*------------------------ * DiscreteDomain methods */ /** * Returns a view of the domain as an immutable list. * @since 0.08 */ public abstract List<? extends Object> asList(); /** * If this is a {@link TypedDiscreteDomain} with element type that extends {@code elementClass} * returns this object, otherwise null. */ public abstract @Nullable <T> TypedDiscreteDomain<T> asTypedDomain(Class<T> elementClass); /** * Returns ith element as a double * @since 0.08 */ public double getDoubleElement(int i) { return FactorFunctionUtilities.toDouble(getElement(i)); } /** * Common superclass of all elements. */ public abstract Class<?> getElementClass(); /** * Returns the value of the ith element in the domain. * @param i must be in the range [0,{@link #size()}-1]. * @throws IndexOutOfBoundsException if {@code i} is not in required range. */ public abstract Object getElement(int i); /** * Returns an array containing all the elements of the domain in their canonical order. * <p> * Because this allocates memory and costs linear time to compute, it is usually better to use * {@link #getElement(int)} to access elements one at a time and {@link #size()} to get the * number of elements. * <p> * @see #getElements(Object[]) */ public Object[] getElements() { return getElements(null); } /** * Returns an array containing all of the elements of the domain in their canonical * order using provided {@code array} if possible. * <p> * If {@code array} has a component type compatible with {@link #getElementClass()} and * length at least as large as {@link #size()} it will be filled in with the domain elements * and returned. If {@code array} has a compatible type but is not long enough, a new * array will be returned with the same component type as {@code array}. Otherwise a new * array will be returned with component type same as {@link #getElementClass()}. */ public <T> T[] getElements(@Nullable T[] array) { final int length = size(); array = ArrayUtil.allocateArrayOfType(getElementClass(), array, length); for (int i = 0; i < length; ++i) { @SuppressWarnings("unchecked") T element = (T) getElement(i); array[i] = element; } return array; } /** * Returns ith element as an integer * @since 0.08 */ public int getIntElement(int i) { return FactorFunctionUtilities.toInteger(getElement(i)); } /** * The number of elements in the domain. */ public abstract int size(); @Override public String toString() { final int maxToShow = 10; final int size = size(); final int n = Math.min(size, maxToShow) - 1; StringBuilder sb = new StringBuilder("{"); for (int i = 0; i < n; ++i) { sb.append(getElement(i)); sb.append(","); } if (n < size - 1) { sb.append("...,"); } sb.append(getElement(size-1)); sb.append("}"); return sb.toString(); } // Find the list of elements corresponding to the value; return -1 if not a valid value /** * Returns the index of {@code value} in this domains elements or else -1 if * {@code value} is not an element of this domain. * @see #getIndexOrThrow(Object) */ public abstract int getIndex(@Nullable Object value); /** * Like {@link #getIndex(Object)} but throws a {@link DomainException} instead of returning * -1 on failure. */ public int getIndexOrThrow(@Nullable Object value) { int index = getIndex(value); if (index < 0) { throw domainError(value); } return index; } /** * @deprecated Use {@link #inDomain} instead. */ @Deprecated public boolean isElementOf(Object value) { return (getIndex(value) >= 0); } /*------------------ * Internal methods */ /** * Verifies that {@code index} is in the range [0,{@link #size}-1]. * @throws IndexOutOfBoundsException if index is not in range. * @category internal */ @Internal public void assertIndexInBounds(int index) { assertIndexInBounds(index, size()); } /*------------------- * Protected methods */ /** * Verifies that {@code index} is in the range [0,size-1]. * @throws IndexOutOfBoundsException if index is not in range. */ protected static void assertIndexInBounds(int index, int size) { // We want this check to be fast. This converts index to a long and masks the low-32 bits. // As a result, all negative index values are going to end up as large positive numbers // allowing us to do this in a single compare. if ((index & 0xFFFFFFFFL) >= size) { throw new IndexOutOfBoundsException(String.format("Index '%d' is not in range [%d,%d]", index, 0, size -1)); } } }