1 package org.opentrafficsim.simulationengine;
2
3 /**
4 * Property that describes a probability distribution.
5 * <p>
6 * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
7 * reserved. <br>
8 * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
9 * <p>
10 * @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
39 * the values is not equal to 1.0 within a small error margin
40 */
41 public ProbabilityDistributionProperty(final String shortName, final String description,
42 final String[] elementNames, final Double[] initialValue, final boolean readOnly, final int displayPriority)
43 throws PropertyException
44 {
45 super(displayPriority);
46 this.shortName = shortName;
47 this.description = description;
48 this.names = new String[elementNames.length];
49 for (int i = 0; i < elementNames.length; i++)
50 {
51 this.names[i] = elementNames[i];
52 }
53 verifyProposedValues(initialValue);
54 copyValue(initialValue);
55 this.readOnly = readOnly;
56 }
57
58 /**
59 * Verify that a provided array of probability values is acceptable.
60 * @param values double[]; the array of values to verify
61 * @throws PropertyException when the number of values is 0, any value is outside [0..1], or the sum of the values
62 * does not add up to 1.0 within a (very small) error margin
63 */
64 private void verifyProposedValues(final Double[] values) throws PropertyException
65 {
66 if (values.length < 1)
67 {
68 throw new PropertyException("Array of probability values may not be empty");
69 }
70 double sum = 0.0;
71 for (double v : values)
72 {
73 if (v < 0.0 || v > 1.0)
74 {
75 throw new PropertyException("Probability value " + v + " is invalid (valid range is 0.0..1.0)");
76 }
77 sum += v;
78 }
79 double maximumError = Math.ulp(1.0) * values.length;
80 if (sum < 1.0 - maximumError || sum > 1.0 + maximumError)
81 {
82 throw new PropertyException("Probabilities do not add up to 1.0 (actual sum is " + sum + ")");
83 }
84
85 }
86
87 /** {@inheritDoc} */
88 @Override
89 public final Double[] getValue()
90 {
91 // Double is immutable; but we should return a shallow copy of the array so the caller can't replace the
92 // elements of our array
93 return this.value.clone();
94 }
95
96 /**
97 * Retrieve one probability value.
98 * @param index int; the index of the requested probability value
99 * @return Double; the requested probability value
100 */
101 final Double getValue(final int index)
102 {
103 return this.value[index];
104 }
105
106 /**
107 * Retrieve the name of one of the values of this ProbabilityDistributionProperty.
108 * @param index int; the index of the value
109 * @return String; the name of the value at the requested index
110 */
111 final String getElementName(final int index)
112 {
113 return this.names[index];
114 }
115
116 /** {@inheritDoc} */
117 @Override
118 public final String getShortName()
119 {
120 return this.shortName;
121 }
122
123 /** {@inheritDoc} */
124 @Override
125 public final String getDescription()
126 {
127 return this.description;
128 }
129
130 /** {@inheritDoc} */
131 @Override
132 public final void setValue(final Double[] newValue) throws PropertyException
133 {
134 if (this.readOnly)
135 {
136 throw new PropertyException("This property is read-only");
137 }
138 updateValue(newValue);
139 }
140
141 /**
142 * Make a deep copy of the provided array of values.
143 * @param newValue Double[]; the array of values to copy to this.value
144 */
145 private void copyValue(final Double[] newValue)
146 {
147 // Make a deep copy
148 this.value = new Double[newValue.length];
149 for (int i = 0; i < newValue.length; i++)
150 {
151 this.value[i] = newValue[i];
152 }
153 }
154
155 /**
156 * Verify proposed values and make a deep copy.
157 * @param newValue Double[]; the proposed values
158 */
159 private void updateValue(final Double[] newValue) throws PropertyException
160 {
161 verifyProposedValues(newValue);
162 copyValue(newValue);
163 }
164
165 /**
166 * Return the names of the elements of this ProbabilityDistributionProperty.
167 * @return String[]; the names of the elements of this ProbabilityDistributionProperty
168 */
169 public final String[] getElementNames()
170 {
171 return this.names.clone();
172 }
173
174 /** {@inheritDoc} */
175 @Override
176 public final boolean isReadOnly()
177 {
178 return this.readOnly;
179 }
180
181 }