ProbabilityDistributionProperty.java

package org.opentrafficsim.simulationengine;

/**
 * Property that describes a probability distribution.
 * <p>
 * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
 * reserved. <br>
 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
 * <p>
 * @version 18 dec. 2014 <br>
 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
 */
public class ProbabilityDistributionProperty extends AbstractProperty<Double[]>
{
    /** The current set of probability values (should add up to 1.0). */
    private Double[] value;

    /** The names of the values. */
    private String[] names;

    /** The shortName of the property. */
    private String shortName;

    /** The description of the property. */
    private String description;

    /** The property is read-only. */
    private final Boolean readOnly;

    /**
     * Construct a new ProbabilityDistributionProperty.
     * @param shortName String; the short name of the new ProbabilityDistributionProperty
     * @param description String; the description of the new ProbabilityDistributionProperty (may use HTML markup)
     * @param elementNames String[]; names of the elements that, together, add up to probability 1.0
     * @param initialValue Double[]; array of Double values
     * @param readOnly boolean; if true this ProbabilityDistributionProperty can not be altered
     * @param displayPriority int; the display priority of the new ProbabilityDistributionProperty
     * @throws PropertyException when the array is empty, any value is outside the range 0.0 .. 1.0, or when
     *             the sum of the values is not equal to 1.0 within a small error margin
     */
    public ProbabilityDistributionProperty(final String shortName, final String description,
            final String[] elementNames, final Double[] initialValue, final boolean readOnly, final int displayPriority)
            throws PropertyException
    {
        super(displayPriority);
        this.shortName = shortName;
        this.description = description;
        this.names = elementNames;
        updateValue(initialValue);
        this.readOnly = readOnly;
    }

    /**
     * Verify that a provided array of probability values is acceptable.
     * @param values double[]; the array of values to verify
     * @throws PropertyException when the number of values is 0, any value is outside [0..1], or the sum of
     *             the values does not add up to 1.0 within a (very small) error margin
     */
    private void verifyProposedValues(final Double[] values) throws PropertyException
    {
        if (values.length < 1)
        {
            throw new PropertyException("Array of probability values may not be empty");
        }
        double sum = 0.0;
        for (double v : values)
        {
            if (v < 0.0 || v > 1.0)
            {
                throw new PropertyException("Probability value " + v
                        + " is invalid (valid range is 0.0..1.0)");
            }
            sum += v;
        }
        double maximumError = Math.ulp(1.0) * values.length;
        if (sum < 1.0 - maximumError || sum > 1.0 + maximumError)
        {
            throw new PropertyException("Probabilities do not add up to 1.0 (actual sum is " + sum + ")");
        }

    }

    /** {@inheritDoc} */
    @Override
    public final Double[] getValue()
    {
        // Double is immutable; but we should return a shallow copy of the array so the caller can't replace the
        // elements of our array
        return this.value.clone();
    }

    /**
     * Retrieve one probability value.
     * @param index int; the index of the requested probability value
     * @return Double; the requested probability value
     */
    final Double getValue(final int index)
    {
        return this.value[index];
    }

    /**
     * Retrieve the name of one of the values of this ProbabilityDistributionProperty.
     * @param index int; the index of the value
     * @return String; the name of the value at the requested index
     */
    final String getElementName(final int index)
    {
        return this.names[index];
    }

    /** {@inheritDoc} */
    @Override
    public final String getShortName()
    {
        return this.shortName;
    }

    /** {@inheritDoc} */
    @Override
    public final String getDescription()
    {
        return this.description;
    }

    /** {@inheritDoc} */
    @Override
    public final void setValue(final Double[] newValue) throws PropertyException
    {
        if (this.readOnly)
        {
            throw new PropertyException("This property is read-only");
        }
        updateValue(newValue);
    }

    /**
     * Verify proposed values and make a deep copy.
     * @param newValue Double[]; the proposed values
     */
    private void updateValue(final Double[] newValue) throws PropertyException
    {
        verifyProposedValues(newValue);
        // Make a deep copy
        this.value = new Double[newValue.length];
        for (int i = 0; i < newValue.length; i++)
        {
            this.value[i] = newValue[i];
        }
    }

    /**
     * Return the names of the elements of this ProbabilityDistributionProperty.
     * @return String[]; the names of the elements of this ProbabilityDistributionProperty
     */
    public final String[] getElementNames()
    {
        return this.names.clone();
    }

    /** {@inheritDoc} */
    @Override
    public final boolean isReadOnly()
    {
        return this.readOnly;
    }

}