/******************************************************************************* * Copyright 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.values; import static java.util.Objects.*; import java.io.Serializable; import java.lang.reflect.Array; import java.util.Objects; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.dimple.data.DataRepresentationType; import com.analog.lyric.dimple.data.IDatum; import com.analog.lyric.dimple.factorfunctions.core.FactorFunctionUtilities; import com.analog.lyric.dimple.model.domains.DiscreteDomain; import com.analog.lyric.dimple.model.domains.Domain; import com.analog.lyric.dimple.model.domains.DomainList; import com.analog.lyric.dimple.model.domains.DoubleRangeDomain; import com.analog.lyric.dimple.model.domains.FiniteFieldDomain; import com.analog.lyric.dimple.model.domains.FiniteFieldNumber; import com.analog.lyric.dimple.model.domains.IntDomain; import com.analog.lyric.dimple.model.domains.IntRangeDomain; import com.analog.lyric.dimple.model.domains.ObjectDomain; import com.analog.lyric.dimple.model.domains.RealDomain; import com.analog.lyric.dimple.model.domains.RealJointDomain; import com.analog.lyric.dimple.model.domains.TypedDiscreteDomain; /** * Holder for a values for a given {@link Domain}. * <p> * Subclasses are implemented to take advantage of knowledge of domain values. * * @since 0.05 */ public abstract class Value implements IDatum, Cloneable, Serializable { private static final long serialVersionUID = 1L; /*-------------- * Construction */ /** * Creates an immutable {@link Value} instance appropriate for given domain and value. * @since 0.08 * @see #create(Domain) */ public static Value constant(Domain domain, @Nullable Object value) { if (value instanceof Value) { Value v = (Value)value; if (domain.equals(v.getDomain())) { return v.immutableClone(); } value = v.getObject(); } if (domain instanceof DiscreteDomain) { return constantDiscrete((DiscreteDomain)domain, requireNonNull(value)); } if (domain.isReal()) { return constantReal(FactorFunctionUtilities.toDouble(value)); } if (domain.isRealJoint()) { return constantRealJoint((double[])requireNonNull(value)); } if (domain instanceof IntDomain) { return new ConstantIntValue(FactorFunctionUtilities.toInteger(value)); } return new ConstantObjectValue(value); } /** * Creates an immutable {@link Value} instance appropriate for given value. * @since 0.08 * @see #create(Object) */ public static Value constant(@Nullable Object value) { if (value instanceof Number) { Number number = (Number)value; if (number instanceof Integer || number instanceof Short || number instanceof Byte) { return new ConstantIntValue(number.intValue()); } else if (number instanceof FiniteFieldNumber) { return new ConstantFiniteFieldValue((FiniteFieldNumber)number); } else { // TODO: There is no LongValue so long gets returned as a Real return constantReal(number.doubleValue()); } } else if (value instanceof Boolean) { return new ConstantDiscreteValue(DiscreteDomain.bool(), (Boolean)value ? 1 : 0); } else if (value instanceof double[]) { return new ConstantRealJointValue((double[])value); } else if (value instanceof Value) { return ((Value)value).immutableClone(); } return new ConstantObjectValue(value); } /** * Create an immutable {@link DiscreteValue} with given domain and value. * @since 0.08 * @see #constantWithIndex(DiscreteDomain, int) */ public static DiscreteValue constantDiscrete(DiscreteDomain domain, Object value) { return constantWithIndex(domain, domain.getIndexOrThrow(value)); } /** * Creates an immutable {@link RealValue} with unbounded domain and given value. * @since 0.08 */ public static RealValue constantReal(double value) { return new ConstantRealValue(value); } /** * Creates an immutable {@link RealJointValue} with unbounded domain and given value. * @since 0.08 */ public static RealJointValue constantRealJoint(double ... value) { return new ConstantRealJointValue(value.clone()); } /** * Create an immutable {@link DiscreteValue} with given domain and value. * @since 0.08 * @see #constantDiscrete(DiscreteDomain, Object) * @see #createWithIndex(DiscreteDomain, int) */ public static DiscreteValue constantWithIndex(DiscreteDomain domain, int index) { return new ConstantDiscreteValue(domain, index); } /** * Creates a {@code Value} instance appropriate to the given {@code domain} * with a default value depending on the domain: zero for numeric domains, * the first element for discrete domains, and null for {@link ObjectDomain}. * <p> * If {@code domain} is null, an {@link ObjectValue} will be returned. */ public static Value create(@Nullable Domain domain) { if (domain != null) { DiscreteDomain discrete = domain.asDiscrete(); if (discrete != null) { return create(discrete); } RealDomain real = domain.asReal(); if (real != null) { return create(real); } RealJointDomain realJoint = domain.asRealJoint(); if (realJoint != null) { return create(realJoint); } if (domain.isIntegral()) { return new IntValue(); } assert(domain == ObjectDomain.instance()); } return new ObjectValue(); } /** * Creates a {@link RealValue} instance for given {@code domain}. */ public static RealValue create(RealDomain domain) { return new RealValue(0.0); } /** * Creates a {@link RealJointValue} instance with dimensions matching {@code domain}. * @since 0.08 */ public static RealJointValue create(RealJointDomain domain) { return new RealJointValue(domain); } /** * Creates a {@link DiscreteValue} instance for given {@code domain}. */ public static DiscreteValue create(DiscreteDomain domain) { if (domain.isIntegral()) { if (domain instanceof IntRangeDomain) { IntRangeDomain rangeDomain = (IntRangeDomain)domain; if (rangeDomain.getLowerBound() == 0 && rangeDomain.getInterval() == 1) { return new SimpleIntRangeValue(rangeDomain); } else { return new IntRangeValue(rangeDomain); } } else if (domain instanceof FiniteFieldDomain) { return new FiniteFieldValue((FiniteFieldDomain)domain); } @SuppressWarnings("unchecked") TypedDiscreteDomain<Integer> intDomain = (TypedDiscreteDomain<Integer>) domain; return new GenericIntDiscreteValue(intDomain); } else if (Double.class.isAssignableFrom(domain.getElementClass())) { if (domain instanceof DoubleRangeDomain) { DoubleRangeDomain rangeDomain = (DoubleRangeDomain)domain; if (rangeDomain.getLowerBound() == 0.0 && rangeDomain.getInterval() == 1.0) { return new SimpleDoubleRangeValue(rangeDomain); } else { return new DoubleRangeValue(rangeDomain); } } @SuppressWarnings("unchecked") TypedDiscreteDomain<Double> doubleDomain = (TypedDiscreteDomain<Double>) domain; return new GenericDoubleDiscreteValue(doubleDomain); } else { return new GenericDiscreteValue(domain.asDiscrete()); } } /** * Creates a {@link FiniteFieldValue} instance for given {@code domain}. * @since 0.07 */ public static FiniteFieldValue create(FiniteFieldDomain domain) { return new FiniteFieldValue(domain); } /** * Creates a {@code Value} instance for given {@code domain} with specified initial {@code value}. * <p> * Simply calls {@link Value#setObject(Object)} on instance returned by {@link #create(Domain)}. */ public static Value create(Domain domain, @Nullable Object value) { Value instance = create(domain); instance.setObject(value); return instance; } /** * Creates a {@link DiscreteValue} instance for given {@code domain} with specified initial {@code value}. * <p> * Simply calls {@link Value#setObject(Object)} on instance returned by {@link #create(DiscreteDomain)}. * @see #createWithIndex(DiscreteDomain, int) */ public static DiscreteValue create(DiscreteDomain domain, Object value) { DiscreteValue discrete = create(domain); discrete.setObject(value); return discrete; } /** * Creates a {@link RealValue} instance for given {@code domain} with specified initial {@code value}. */ public static RealValue create(RealDomain domain, double value) { // TODO: use domain return new RealValue(value); } /** * Creates a {@link RealJointValue} instance for given {@code domain} with specified initial {@code value}. */ public static RealJointValue create(RealJointDomain domain, double[] value) { // TODO: use domain return new RealJointValue(value); } /** * Creates a {@link RealValue} with unbounded domain and initial value matching {@code value}. * @since 0.08 */ public static RealValue createReal(double value) { return new RealValue(value); } /** * Creates a {@link RealJointValue} with unbounded domain and dimensions and initial value matching {@code value}. * @since 0.08 */ public static RealJointValue createRealJoint(double ... value) { return new RealJointValue(value); } /** * Creates a new {@link DiscreteValue} instance for given {@code domain} with value specified by {@code index}. * @param domain is the domain of the new value. * @param index is an index into the domain specifying the value's initial value. * @throws IndexOutOfBoundsException if {@code index} is not in range. * @since 0.08 */ public static DiscreteValue createWithIndex(DiscreteDomain domain, int index) { DiscreteValue discrete = create(domain); discrete.setIndex(index); return discrete; } /** * Creates a {@code Value} instance with specified {@code initialValue}. */ public static Value create(@Nullable Object initialValue) { if (initialValue instanceof Number) { Number number = (Number)initialValue; if (number instanceof Integer || number instanceof Short || number instanceof Byte) { return new IntValue(number.intValue()); } else if (number instanceof FiniteFieldNumber) { return new FiniteFieldValue((FiniteFieldNumber)number); } else { return new RealValue(number.doubleValue()); } } else if (initialValue instanceof double[]) { return new RealJointValue((double[])initialValue); } else if (initialValue instanceof Boolean) { return createWithIndex(DiscreteDomain.bool(), (Boolean)initialValue ? 1 : 0); } else { return new ObjectValue(initialValue); } } /** * Returns a copy of the object. * <p> * Note that if {@link #isMutable()} is false, this will return an immutable * object (and may in fact return the same object). If you need a mutable copy * then instead use {@link #mutableClone()}. */ @Override public abstract Value clone(); /** * Returns an immutable clone of the value. * <p> * If the value is already immutable it will simply be returned, otherwise this * will return a newly created immutable value. * <p> * @since 0.08 */ public abstract Value immutableClone(); /** * Returns a mutable clone of the value. * <p> * This returns a {@linkplain #isMutable() mutable} copy of the value. * <p> * @since 0.08 */ public Value mutableClone() { return clone(); } /*---------------- * Object methods */ @Override public String toString() { return getObject() + ""; } /*----------------- * IEquals methods */ @Override public boolean objectEquals(@Nullable Object other) { if (this == other) { return true; } if (other instanceof Value) { return valueEquals((Value)other); } return false; } /*---------------- * IDatum methods */ /** * {@inheritDoc} * <p> * Simply returns 0.0 if passed in {@code value} {@link #valueEquals} this value, and infinity otherwise. */ @Override public double evalEnergy(Value value) { return valueEquals(value) ? 0.0 : Double.POSITIVE_INFINITY; } /** * {@inheritDoc} * <p> * Returns {@link DataRepresentationType#VALUE}. */ @Override public final DataRepresentationType representationType() { return DataRepresentationType.VALUE; } /*--------------- * Value methods */ /** * True if the value may be changed after construction. * <p> * If false, then {@link #setObject} and all other {@code set*} methods * will throw a {@link UnsupportedOperationException}. * <p> * The default implementation returns true. * @since 0.08 */ public boolean isMutable() { return true; } /** * Domain for valid contents of value object. */ public abstract Domain getDomain(); /** * Returns current value as a {@link Object}. * <p> * This may create a new object if underlying representation is a primitive type such as {@code int} or * {@code double}; in such cases it is preferable to use {@link #getInt()}, {@link #getDouble()}, etc. */ public abstract @Nullable Object getObject(); /** * Sets current value from an {@link Object}. */ public abstract void setObject(@Nullable Object value); /** * Returns current value as a {@code boolean}. */ public boolean getBoolean() { return FactorFunctionUtilities.toBoolean(getObject()); } /** * Gets current value as a {@code double}. */ public double getDouble() { return FactorFunctionUtilities.toDouble(getObject()); } public double[] getDoubleArray() { Object val = getObject(); if (val instanceof double[]) { return (double[])val; } return new double[] { getDouble() }; } /** * Sets current value from a {@code double}. */ public void setDouble(double value) { setObject(value); } /** * Gets the current value as a {@code FiniteFieldNumber} * @since 0.07 * @throws ClassCastException if value does not contain a finite field value * @throws NullPointerException if {@link #getObject()} is null. */ public FiniteFieldNumber getFiniteField() { return (FiniteFieldNumber)requireNonNull(getObject()); } /** * Sets the current value from a {@code FiniteFieldNumber} * @since 0.07 */ public void setFiniteField(FiniteFieldNumber value) { setObject(value); } /** * If value is known to be a member of a {@link DiscreteDomain}, returns its index within its domain otherwise -1. */ public int getIndex() { return -1; } /** * Returns either discrete index or integer value. * <p> * Same as {@link #getIndex()} if this is a {@link DiscreteValue} and otherwise is the same as {@link #getInt()}. * @since 0.08 */ public int getIndexOrInt() { return getInt(); } /** * Returns current value as a {@code int}. */ public int getInt() { return FactorFunctionUtilities.toInteger(getObject()); } /** * Sets contents from another value. * <p> * Default implementation uses {@link #getObject()}/{@link #setObject(Object)}. Subclasses implement * this more efficiently to avoid {@link Object} conversion. */ public void setFrom(Value value) { setObject(value.getObject()); } /** * If value is known to be a member of a {@link DiscreteDomain}, sets value to element with * given index within the domain. * @throws UnsupportedOperationException if not a {@link DiscreteValue} * @throws IndexOutOfBoundsException if index is negative or greater than or equal to the domain size. */ public void setIndex(int index) { throw new UnsupportedOperationException("setIndex only supported on DiscreteValue"); } /** * Sets contents from a {@code int}. */ public void setInt(int value) { setObject(value); } /** * Sets contents from a {@code boolean} * Subclasses should override this to set a boolean in the appropriate form * @since 0.07 */ public void setBoolean(boolean value) { setObject(value); } /** * True if {@link #setFrom(Value)} on {@code other} would not change the state of this value. * <p> * Default implementation compares {@link #getObject()} values. */ public boolean valueEquals(Value other) { return Objects.equals(getObject(), other.getObject()); } /*---------- * Internal */ /** * Returns an exception indicating that value is not mutable. * @since 0.08 */ protected RuntimeException notMutable() { throw new UnsupportedOperationException("Value is not mutable"); } /*----------------------- * Static helper methods */ /** * Fills in and returns {@code Value} array from array of objects. * * @param objs must have values that are compatible with members of output array. * @param output must be the same length as object array and contain concrete {@link Value} * instances compatible with corresponding objects. * @return {@code output} argument * * @see #createFromObjects(Object[], Class, Domain...) */ public static <T extends Value> T[] copyFromObjects(Object[] objs, T[] output) { for (int i = objs.length; --i>=0;) { output[i].setObject(objs[i]); } return output; } /** * Creates {@code Value} array from array of objects using declared subclass type and provided * domains. * * @param objs * @param valueClass specifies the component type of the the returned array. * @param domains specifies the domains of the objects. This must have between 1 and {@code objs.length} * elements. If less than the maximum, the last domain will be applied to all remaining objects. * @return array of {@code Value} instances containing the corresponding values from the object array. * * @see #createFromObjects(Object[], Domain...) * @see #copyFromObjects(Object[], Value[]) */ public static <T extends Value> T[] createFromObjects(Object[] objs, Class<T> valueClass, Domain ... domains) { final int size = objs.length; final int nDomains = domains.length; assert(nDomains > 0 && nDomains <= size); @SuppressWarnings("unchecked") final T[] output = (T[]) Array.newInstance(valueClass, size); for (int i = 0; i < size; ++i) { final Domain domain = i < nDomains ? domains[i] : domains[nDomains - 1]; @SuppressWarnings("unchecked") T value = (T) create(domain, objs[i]); output[i] = value; } return output; } /** * Same as {@link #createFromObjects(Object[], Class, Domain...)} */ public static Value[] createFromObjects(Object[] objs, Domain ... domains) { return createFromObjects(objs, Value.class, domains); } /** * Creates {@code Value} array with empty or default content from array of Domains. * * @param domains specifies the domains of the objects. This must have between 1 and {@code objs.length} * elements. If less than the maximum, the last domain will be applied to all remaining objects. * @return array of {@code Value} instances each with the corresponding domain. * @since 0.07 */ public static Value[] createFromDomains(Domain... domains) { final int nDomains = domains.length; final Value[] output = new Value[nDomains]; for (int i = 0; i < nDomains; ++i) { Value value = create(domains[i]); output[i] = value; } return output; } /** * Creates {@code Value} array with empty or default content from a DomainList. * * @param domains specifies the domains of the objects. This must have between 1 and {@code objs.length} * elements. If less than the maximum, the last domain will be applied to all remaining objects. * @return array of {@code Value} instances each with the corresponding domain. * @since 0.07 */ public static Value[] createFromDomains(DomainList<?> domains) { final int nDomains = domains.size(); final Value[] output = new Value[nDomains]; for (int i = 0; i < nDomains; ++i) { Value value = create(domains.get(i)); output[i] = value; } return output; } /** * Copies current contents of {@code values} into {@code output} array. */ public static <T extends Value> Object[] toObjects(T[] values, Object[] output) { for (int i = values.length; --i>=0;) { output[i] = values[i].getObject(); } return output; } /** * Converts an array of {@code Value} to an array of {@link Object} containing the current * contents of the values. */ public static <T extends Value> Object[] toObjects(T[] values) { return toObjects(values, new Object[values.length]); } }