Controllable Proximity: Bolt

Introduction

The Bolt driver creates a version of Brownian Motion controlled by four parameters:

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.

Profile

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.

Coding

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.

Comments

  1. 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