View Javadoc
1   package org.opentrafficsim.simulationengine.properties;
2   
3   /**
4    * Property that describes a probability distribution.
5    * <p>
6    * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
7    * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
8    * <p>
9    * $LastChangedDate: 2015-07-26 01:01:13 +0200 (Sun, 26 Jul 2015) $, @version $Revision: 1155 $, by $Author: averbraeck $,
10   * initial version 18 dec. 2014 <br>
11   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
12   */
13  public class ProbabilityDistributionProperty extends AbstractProperty<Double[]>
14  {
15      /** The current set of probability values (should add up to 1.0). */
16      private Double[] value;
17  
18      /** The names of the values. */
19      private String[] names;
20  
21      /** The shortName of the property. */
22      private String shortName;
23  
24      /** The description of the property. */
25      private String description;
26  
27      /** The property is read-only. */
28      private final Boolean readOnly;
29  
30      /**
31       * Construct a new ProbabilityDistributionProperty.
32       * @param shortName String; the short name of the new ProbabilityDistributionProperty
33       * @param description String; the description of the new ProbabilityDistributionProperty (may use HTML markup)
34       * @param elementNames String[]; names of the elements that, together, add up to probability 1.0
35       * @param initialValue Double[]; array of Double values
36       * @param readOnly boolean; if true this ProbabilityDistributionProperty can not be altered
37       * @param displayPriority int; the display priority of the new ProbabilityDistributionProperty
38       * @throws PropertyException when the array is empty, any value is outside the range 0.0 .. 1.0, or when the sum of the
39       *             values is not equal to 1.0 within a small error margin
40       */
41      public ProbabilityDistributionProperty(final String shortName, final String description, final String[] elementNames,
42          final Double[] initialValue, final boolean readOnly, final int displayPriority) throws PropertyException
43      {
44          super(displayPriority);
45          this.shortName = shortName;
46          this.description = description;
47          this.names = new String[elementNames.length];
48          for (int i = 0; i < elementNames.length; i++)
49          {
50              this.names[i] = elementNames[i];
51          }
52          verifyProposedValues(initialValue);
53          copyValue(initialValue);
54          this.readOnly = readOnly;
55      }
56  
57      /**
58       * Verify that a provided array of probability values is acceptable.
59       * @param values double[]; the array of values to verify
60       * @throws PropertyException when the number of values is 0, any value is outside [0..1], or the sum of the values does not
61       *             add up to 1.0 within a (very small) error margin
62       */
63      private void verifyProposedValues(final Double[] values) throws PropertyException
64      {
65          if (values.length < 1)
66          {
67              throw new PropertyException("Array of probability values may not be empty");
68          }
69          double sum = 0.0;
70          for (double v : values)
71          {
72              if (v < 0.0 || v > 1.0)
73              {
74                  throw new PropertyException("Probability value " + v + " is invalid (valid range is 0.0..1.0)");
75              }
76              sum += v;
77          }
78          double maximumError = Math.ulp(1.0) * values.length;
79          if (sum < 1.0 - maximumError || sum > 1.0 + maximumError)
80          {
81              throw new PropertyException("Probabilities do not add up to 1.0 (actual sum is " + sum + ")");
82          }
83  
84      }
85  
86      /** {@inheritDoc} */
87      @Override
88      public final Double[] getValue()
89      {
90          // Double is immutable; but we should return a shallow copy of the array so the caller can't replace the
91          // elements of our array
92          return this.value.clone();
93      }
94  
95      /**
96       * Retrieve one probability value.
97       * @param index int; the index of the requested probability value
98       * @return Double; the requested probability value
99       */
100     final Double getValue(final int index)
101     {
102         return this.value[index];
103     }
104 
105     /**
106      * Retrieve the name of one of the values of this ProbabilityDistributionProperty.
107      * @param index int; the index of the value
108      * @return String; the name of the value at the requested index
109      */
110     final String getElementName(final int index)
111     {
112         return this.names[index];
113     }
114 
115     /** {@inheritDoc} */
116     @Override
117     public final String getShortName()
118     {
119         return this.shortName;
120     }
121 
122     /** {@inheritDoc} */
123     @Override
124     public final String getDescription()
125     {
126         return this.description;
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public final void setValue(final Double[] newValue) throws PropertyException
132     {
133         if (this.readOnly)
134         {
135             throw new PropertyException("This property is read-only");
136         }
137         updateValue(newValue);
138     }
139 
140     /**
141      * Make a deep copy of the provided array of values.
142      * @param newValue Double[]; the array of values to copy to this.value
143      */
144     private void copyValue(final Double[] newValue)
145     {
146         // Make a deep copy
147         this.value = new Double[newValue.length];
148         for (int i = 0; i < newValue.length; i++)
149         {
150             this.value[i] = newValue[i];
151         }
152     }
153 
154     /**
155      * Verify proposed values and make a deep copy.
156      * @param newValue Double[]; the proposed values
157      * @throws PropertyException when the number of values is 0, any value is outside [0..1], or the sum of the values does not
158      *             add up to 1.0 within a (very small) error margin
159      */
160     private void updateValue(final Double[] newValue) throws PropertyException
161     {
162         verifyProposedValues(newValue);
163         copyValue(newValue);
164     }
165 
166     /**
167      * Return the names of the elements of this ProbabilityDistributionProperty.
168      * @return String[]; the names of the elements of this ProbabilityDistributionProperty
169      */
170     public final String[] getElementNames()
171     {
172         return this.names.clone();
173     }
174 
175     /** {@inheritDoc} */
176     @Override
177     public final boolean isReadOnly()
178     {
179         return this.readOnly;
180     }
181 
182     /** {@inheritDoc} */
183     @Override
184     public final String htmlStateDescription()
185     {
186         StringBuilder result = new StringBuilder();
187         result.append("<table>");
188         result.append("<tr><th>" + getShortName() + "</th></tr>\n");
189         for (int i = 0; i < this.names.length; i++)
190         {
191             result.append("<tr><td>" + this.names[i] + ": " + String.format("%.3f", this.value[i]) + "</td></tr>\n");
192         }
193         result.append("</table>\n");
194         return result.toString();
195     }
196 
197     /** {@inheritDoc} */
198     @Override
199     public AbstractProperty<Double[]> deepCopy()
200     {
201         System.out.println("copying probabilitydistribution " + this.shortName + ", " + this.getValue(0));
202         try
203         {
204             return new ProbabilityDistributionProperty(this.shortName, this.description, this.names, this.value,
205                 this.readOnly, getDisplayPriority());
206         }
207         catch (PropertyException exception)
208         {
209             throw new Error("Cannot happen (the current values should ALWAYS be suitable for constructing a new "
210                 + "ProbabilityDistributionProperty)");
211         }
212     }
213 
214 }