com.analog.lyric.dimple.model.domains.DiscreteDomain.java Source code

Java tutorial

Introduction

Here is the source code for com.analog.lyric.dimple.model.domains.DiscreteDomain.java

Source

/*******************************************************************************
*   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));
        }
    }
}