Controllable Proximity: Bolt
The Bolt
driver creates a version of Brownian Motion
controlled by four parameters:
length
, symbolized N, indicates the number of
samples in the sequence.
origin
, symbolized X(0), indicates the starting sequence value.
goal
, symbolized X(N), indicates the ending sequence value.
deviation
, symbolized σ, controls the jitteryness of
sample-to-sample transitions.
mode
indicates how to deal with samples falling outside the driver range from zero to unity.
The options are ContainmentMode.WRAP
and
ContainmentMode.REFLECT
The chapter on "Brownian Motion" in my college textbook on Stochastic Processes1 asserts
that:
Under the condition that X(0) = 0, the variance of
X(t) is σ2t.
The present implementation diverges from usual driver practice by calculating all values up front and storing them in an
array. It makes use of the above insight by reasoning that if a starting value X(i)
and an ending value X(i+k) are known, then the mid-point value X(i+k/2)
will be normally distributed with mean (X(i)+X(i+k))/2 and deviation
σ√k/2. Since the overall
starting-and-ending values X(0) and X(N) are known,
this tells us how to generate the middle value X(N/2). From there we can keep on filling in middle values
until the entire array is populated.
This driver is closely related to the
Brownian
driver.
Figure 1 (a): Sample output from Bolt.next()
with
representative deviation settings. The left
graph in each row displays samples in time-series while the right graph in the same row presents a histogram analyzed from the same samples.
Figure 1 (a) illustrates five examples of Bolt
output with a sequence of 50 samples generated. All five examples were generated using a random seed of 1, an origin
of
0.25, a goal
of
0.75, and ContainmentMode.WRAP
.
The only difference is the deviation
parameter, which varies as indicated.
The vertical x axes for the two graphs in each row represent the
driver domain from zero to unity; the horizontal k
axis of the time-series graph (left) plots ordinal sequence numbers; the horizontal f(x)
axis of the histogram (right) plots the relative concentration of samples at each point in the driver domain.
The main thing to notice about these sequences is that as the deviation
drops toward
zero, the graph comes more and more to resemble a straight line from origin
to
goal
.
Figure 1 (b): Sample output from Bolt.next()
with the . The left
graph displays samples in time-series while the right graph presents a histogram analyzed from the same samples.
Figure 1 (b) illustrates the difference in effect between the two containment modes.
For both sequences the random seed is 1, the origin
is 0.25, the
goal
is 0.75, and the deviation
is 1/4 (0.25).
Of the two sequences pictured, the upper uses
ContainmentMode.WRAP
and produces
exactly the same results as in the uppermost sequence of Figure 1 (a). The lower sequence uses
ContainmentMode.REFLECT
.
The entire sequence is generated up front and stored in an array, but the containment operation is not applied until the individual
requests for samples. Thus ContainmentMode.REFLECT
has the effect of flipping those portions of the sequence which stray above unity back down under the
x = 1 horizontal. Likewise those portions of the sequence which stray below zero flip back up over the
x axis.
/**
* Instances of the {@link Bolt} class generate a Brownian driver
* sequence with a specified number of values.
* @author Charles Ames
*/
public class Bolt extends DriverBase {
/**
* Value of element 0 in the {@link #values} array.
* Can be set explicitly by {@link #setOrigin(double)} or randomized
* by {@link #randomizeOrigin()}.
*/
private double origin;
/**
* Value of element {@link #length}-1 in the {@link #values} array.
* Defaults to 0.5. Can be set explicitly by {@link #setGoal(double)}
* or randomized by {@link #randomizeGoal()}.
*/
private double goal;
/**
* Root-mean-square distance between consecutive sequence values.
*/
private double deviation;
/**
* Holds the sequence of samples. Sized by calling {@link #setLength(int)}.
*/
private List<Double> values;
private Iterator<Double> iterator;
/**
* Option for handling out-of-range values.
*/
private ContainmentMode containmentMode;
/**
* Singleton {@link ContinuousDistribution} instance of a standard bell curve
* centered around zero with unit deviation.
*/
private ContinuousDistribution normalDistribution;
/**
* Constructor for {@link Bolt} instances with container.
* @param container An entity which contains this driver.
*/
public Bolt(WriteableEntity container) {
super(container);
this.normalDistribution = ContinuousDistribution.getNormalDistribution();
this.values = new ArrayList<Double>();
this.iterator = null;
this.origin = .5;
this.goal = .5;
this.deviation = .1;
this.containmentMode = ContainmentMode.REFLECT;
}
/**
* Constructor for {@link Bolt} instances without container.
*/
public Bolt() {
this(null);
}
/**
* Getter for {@link #containmentMode}.
* @return The assigned {@link #containmentMode} value.
*/
public ContainmentMode getMode() {
return containmentMode;
}
/**
* Setter for {@link #containmentMode}.
* @param containmentMode The intended {@link #containmentMode} value.
*/
public void setMode(ContainmentMode containmentMode) {
this.containmentMode = containmentMode;
}
/**
* Getter for {@link #length}.
* @return The assigned {@link #length} value.
* @throws UninitializedException when the output array has not been allocated
* via a call to {@link #setLength(int)}.
*/
public int getLength() {
if (null == values) throw new UninitializedException("Output array not initialized");
return values.size() - 1;
}
/**
* Setter for {@link #length}.
* @param length The intended {@link #length} value.
* @throws IllegalArgumentException when the length is not positive.
*/
public void setLength(int length) {
if (0 >= length) {
throw new IllegalArgumentException("Length not positive");
}
if (values.size()-1 != length) {
values.clear();
for (int index = 0; index <= length; index++)
values.add(0.);
}
}
/**
* Getter for {@link #origin}.
* @return the assigned {@link #origin} value.
*/
public double getOrigin() {
return origin;
}
/**
* Setter for {@link #origin}.
* @param origin the intended {@link #origin} value.
* @throws IllegalArgumentException when the argument falls outside
* the range from zero to unity.
*/
public void setOrigin(double origin) {
Driver.checkDriverValue(origin);
this.origin = origin;
}
/**
* Randomize {@link #origin}.
*/
public void randomizeOrigin() {
this.origin = getRandom().nextDouble();
}
/**
* Getter for {@link #goal}.
* @return the assigned {@link #goal} value.
*/
public double getGoal() {
return goal;
}
/**
* Setter for {@link #goal}.
* @param goal the intended {@link #goal} value.
* @throws IllegalArgumentException when the argument falls outside
* the range from zero to unity.
*/
public void setGoal(double goal) {
Driver.checkDriverValue(goal);
this.goal = goal;
}
/**
* Randomize {@link #goal}.
*/
public void randomizeGoal() {
this.goal = getRandom().nextDouble();
}
/**
* Getter for {@link #goal}.
* @return The assigned {@link #goal} value.
*/
public double getDeviation() {
return deviation;
}
/**
* Setter for {@link #deviation}.
* @param deviation The intended {@link #deviation} value.
* @throws IllegalArgumentException when the argument is not positive.
*/
public void setDeviation(double deviation) {
if (deviation < MathMethods.TINY)
throw new IllegalArgumentException("Deviation not positive");
this.deviation = deviation;
}
protected double generate() {
if (null == iterator) throw new UnsupportedOperationException("Iteration not in progress");
return containmentMode.contain(iterator.next());
}
/**
* Test if the if the {@link values} collection still contains unpresented values
* @return true if the sequence from {@link values} contains unpresented values; false
* if the sequence is complete.
* @throws {@link UnsupportedOperationException} when iteration from {@link values} is not in progress.
*/
@Override
public boolean hasNext() {
if (null == iterator) throw new UnsupportedOperationException("Iteration not in progress");
return iterator.hasNext();
}
@Override
public boolean hasReset() {return true;}
@Override
public void reset() {
int length = values.size()-1;
values.set(0, origin);
values.set(length, goal);
calculate(values, deviation, 0, length);
iterator = values.iterator();
setValue(iterator.next());
}
private void calculate(List<Double> values, double deviation, int low, int high) {
if (low >= high - 1) return;
int mid = (low + high) / 2;
Double lowValue = values.get(low);
Double highValue = values.get(high);
double avg = (lowValue + highValue) / 2.;
double midValue;
double u = getRandom().nextDouble();
midValue = avg + (Math.sqrt(mid - low) * deviation * normalDistribution.quantile(u));
values.set(mid, midValue);
if (low < mid - 1) calculate(values, deviation, low, mid);
if (high > mid + 1) calculate(values, deviation, mid, high);
}
}
Listing 1: The
Bolt
implementation class.
The type hierarchy for Bolt
is:
Listing 1 provides the source code for the Bolt
class. The sequential process described at the top of this page is implemented by
generate()
, which is not public facing. Instead,
generate()
is
called by DriverBase.next()
.
DriverBase
.next() also
takes care to store the new sample in the field
DriverBase
.value, where
generate()
can employ
DriverBase.getValue()
to pick this
(now previous) sample up for the next sample iteration.
DriverBase
also offers
setValue()
and randomizeValue()
methods to establish the initial sequence value.
-
Samual Karlin & Howard M. Taylor, A First Course in Stochastic Processes (New York: Academic Press, 1975), p. 372.
© Charles Ames |
Page created: 2022-08-29 |
Last updated: 2022-08-30 |