Continuous Transforms

Introduction

Classes implementing the Transform.Continuous interface adapt driver values to real-number ranges. Real numbers are continuous: if A and B are real numbers then 0.5*(A+B) is also a real number. Digits right of the decimal point are what distinguish real numbers from integers.

All continuous-transform names are prefixed with the word "Continuous"; the prefix was needed to distinguish ContinuousUniform and ContinuousWeighted from DiscreteUniform and DiscreteWeighted. I thought, well the others are continuous too.

Listed alphabetically, the continuous transforms are:

Transform.Continuous ranges often have lower and upper bounds, so the primary purpose of these transforms is to rescale driver-domain values from zero to unity so that they conform to these bounds.

Some Transform.Continuous ranges do not have an explicit upper bound; in this circumstance the lower bound is always zero and the upper range tails away to zero. The continuous transform for the Normal distribution is unbounded in both directions; the distribution tails away to zero both ways.

Coding

/**
 * The {@link ContinuousDistributionTransform} class implements a
 * continuous statistical transform leveraging a
 * {@link ContinuousDistribution} instance which is referenced internally.
 * @author Charles Ames
 */
public abstract class ContinuousDistributionTransform
extends TransformBase<Double> implements Transform.Continuous {
   private ContinuousDistribution distribution;
   /**
    * Constructor for {@link ContinuousDistributionTransform} instances.
    * @param container An entity which contains this transform.
    */
   public ContinuousDistributionTransform(WriteableEntity container) {
      super(container);
   }
   protected ContinuousDistribution getDistribution() {
      if (null == distribution) {
         distribution = new ContinuousDistribution(null);
      }
      return distribution;
   }
   /**
    * Add a new range interval with associated leftmost and rightmost
    * weights.
    * @param left Leftmost ordinate.
    * @param right Rightmost ordinate
    * @param origin Weight for leftmost ordinate.
    * @param goal Weight for rightmost ordinate.
    * @throws IllegalArgumentException when right is &le; left.
    * @throws IllegalArgumentException when the value is negative or
    * when the new interval of values
    * is not contiguous with the existing range of values.
    * @throws IllegalArgumentException when either the origin is
    * negative or the goal is negative.
    */
   public final void addItem(double left, double right, double origin, double goal) {
      distribution.addItem(left, right, origin, goal);
   }
   /**
    * Get the heaviest weight in the distribution.
    * @return The heaviest weight.
    * @throws IllegalArgumentException when the distribution has
    * not been normalized.
    * @throws RuntimeException when the distribution has no peak-weight item.
    */
   public final double getHeaviestWeight() {
      ContinuousDistributionItem heaviestItem = distribution.getHeaviestItem();
      return Double.max(heaviestItem.getOrigin(), heaviestItem.getGoal());
   }
   @Override
   public Double convert(double driver) {
      if (null == distribution)
         throw new UninitializedException("Distribution not initialized");
      return distribution.quantile(driver);
   }
   @Override
   public Double minGraphValue(double tail) {
      return distribution.minGraphValue(tail);
   }
   @Override
   public Double maxGraphValue(double tail) {
      return distribution.maxGraphValue(tail);
   }
   @Override
   public Double minRange() {
      return distribution.minRange();
   }
   @Override
   public Double maxRange() {
      return distribution.maxRange();
   }
}
Listing 1: The ContinuousDistributionTransform base class.

The abstract ContinuousDistributionTransform class presented as Listing 1 manages an embedded ContinuousDistribution instance. Most, but not all, of the classes implementing the Transform.Continuous interface subclass from ContinuousDistributionTransform.

The ContinuousDistributionTransform class stores its embedded ContinuousDistribution instance in the distribution field. Since subclasses can potentially make no use of this class's functionality, the distribution field is populated upon the first getDistribution() call. Hence methods that reference the distribution field must perform their own initialization checks.

The application range is divided into equal-sized intervals. For each of these intervals, the ContinuousDistribution instance maintains and item detailing a leftmost range value, a rightmost range value, an origin weight (unnormalized probability), a goal weight. These two values and their corresponding weights form a trapezoid with area 0.5×(left−right)×(origin+goal); that area is recorded along with the cumulative sum of areas up to and including the present item. The trapezoids define the distribution, while the cumulative sums support ContinuousDistribution.quantile().

Of the methods implemented in the class, convert() does the actual work of transforming driver-domain values into application-range values.

/**
 * The {@link BoundedTransform} class partially implements a continuous
 * statistical transform mapping the driver domain from zero to unity
 * to a finite range which is bounded by {@link #minRange} and
 * {@link maxRange} fields.
 * @author Charles Ames
 */
public abstract class BoundedTransform
extends ContinuousDistributionTransform {
   /**
    * Determines the leftmost bound of the range.
    */
   private double minRange;
   /**
    * Determines the rightmost bound of the range.
    */
   private double maxRange;
   /**
    * Constructor for {@link BoundedTransform} instances.
    * @param container An entity which contains this transform.
    */
   public BoundedTransform(WriteableEntity container) {
      super(container);
      this.minRange = Double.NaN;
      this.maxRange = Double.NaN;
   }
   /**
    * Getter for {@link #minRange}.
    * @return The assigned {@link #minRange} value.
    * @throws UninitializedException when {@link #minRange}
    * is not initialized.
    */
   @Override
   public final Double minRange() {
      if (Double.isNaN(this.minRange))
         throw new UninitializedException("Min range not initialized");
      return this.minRange;
   }
   /**
    * Setter for {@link #minRange}.
    * @param minRange The intended {@link #minRange} value.
    */
   public final void setMinRange(double minRange) {
      checkMinRange(minRange);
      this.minRange = minRange;
   }
   /**
    * Check if the indicated value is suitable for {@link #minRange}.
    * @param minRange The indicated value.
    */
   public final void checkMinRange(double minRange) {
      if (!Double.isNaN(this.maxRange)) {
         if (this.maxRange - minRange < MathMethods.TINY) {
            throw new IllegalArgumentException(
                  "Max range not significantly greater than min range.");
         }
      }
   }
   /**
    * Getter for {@link #maxRange}.
    * @return The assigned {@link #maxRange} value.
    * @throws UninitializedException when {@link #maxRange}
    * is not initialized.
    */
   @Override
   public final Double maxRange() {
      if (Double.isNaN(this.maxRange))
         throw new UninitializedException("Max range not initialized");
      return this.maxRange;
   }
   /**
    * Setter for {@link #maxRange}.
    * @param maxRange The intended {@link #maxRange} value.
    */
   public final void setMaxRange(double maxRange) {
      checkMaxRange(maxRange);
      this.maxRange = maxRange;
   }
   /**
    * Check if the indicated value is suitable for {@link #maxRange}.
    * @param maxRange The indicated value.
    */
   public final void checkMaxRange(double maxRange) {
      if (!Double.isNaN(this.minRange)) {
         if (maxRange - this.minRange < MathMethods.TINY) {
            throw new IllegalArgumentException(
                  "Max range not significantly greater than min range.");
         }
      }
   }
   @Override
   public final Double minGraphValue(double tail) {
      return minRange();
   }
   @Override
   public final Double maxGraphValue(double tail) {
      return maxRange();
   }
   @Override
   public Double convert(double driver) {
      return interpolate(super.convert(driver));
   }
   /**
    * Take an interpolation factor, scale it to the range (max-min),
    * then offset it by the lower bound.
    * @param factor Argument ranging from zero to unity.
    * @return A value interpolated between {@link minRange}
    * and {@link maxRange}.
    */
   public Double interpolate(double factor) {
      double l = minRange();
      return (maxRange()-l) * factor + l;
   }
}
Listing 2: The BoundedTransform base class.

The abstract BoundedTransform class presented as Listing 2 implements the lower and upper fields required to identify range boundaries. BoundedTransform extends ContinuousDistributionTransform. The two bounded transforms which do not actually leverage ContinuousDistributionTransform functionality simply never make calls to getDistribution().

© Charles Ames Page created: 2022-08-29 Last updated: 2022-08-29