Design I: Driver/Transform

Introduction

In the driver/transform design, an active Driver generates an intermediate value u in the continuous "driver domain" from zero to unity, while a passive Transform distributes u values over whatever "target range" a particular application requires. This target range can be either discrete (values are integers) or continuous (values are real — floating point — numbers).

Thus arose the paradigm of a system coupling an active “driver” with a passive “transform”. My usage of “driver” came from acoustics, where a “driver” indicates an active component (e.g. the vocal cords) that would be coupled with a passive resonating system (e.g. the cavities of the mouth and nose). The range from zero to unity became known as the “driver range”.

If one can generate a driver sequence whose values X0, X1, X2, …, XN-1 are uniformly distributed in the continuous domain from zero to unity (and the conventional random-number generator is an example of this), then one can conform this sequence to any desired distribution by applying an appropriate statistical transform. How to do this will be explained later. Hence we gain modularity and flexibility without losing generality if the Driver contract stipulates that continuous sequences be 'normalized' to range from zero to unity. Readers should bear in mind that non-uniformity in the driver sequence will persist through the transformation into the final result. The overall target range will be valid, but local concentrations inside that range may stray from the distribution. Since it is difficult to estimate in advance how nonuniform drivers will affect a transform, it is often helpful to graph an empirical histogram of driver/transform output so one can evaluate the resulting distribution visually.

The Driver Domain

Within the Driver/Transform design, the Transform.convert() method acts as a mathematical function mapping values from a source set, the domain, to a target set, which in my day was called the range. I have adopted the term driver domain for the source set, since this set describes values produced specifically by Random.nextDouble() and more generally by Driver.next(). The driver domain is synonymous with the probability domain associated with statistical distributions, though the contexts differ.

Mathematically, driver domain values are real numbers. Real numbers are "continuous" in the sense that given any two numbers which are different (but possibly very close), the number half-way between is also a real number. This means that real numbers can be represented — up to a certain precision — in binary form with digits to the right of the binary point. Real numbers are thus distinguished from integers which are discrete and which have no digits right of the binary point. Real numbers are implemented on this site using Java's double type; other representations are available (e.g. float), but double is what most hardware floating-point processing units work with.

The driver domain runs from zero to unity. But for unity, driver values all have zero to the left of the binary point.

Adapting to a Range

Experience gained from Random.nextDouble() shows this: Having the driver domain extend continuously from zero to unity greatly simplifies the calculations needed to convert to other ranges. For example mapping the driver value u into the continuous range from A to B uses the calculation:

(B-A)*u + A.

Likewise, mapping the driver value u into the discrete range from 0 (inclusive) to N (exclusive) uses the calculation:

Math.floor(N*u).

Figure 1: Graph of probability density function for the continuous uniform distribution ranging from zero to one.

Conforming to a Distribution

Figure 1 illustrates why uniform driver values are desirable when the distribution matters. Two graphs are presented. Each x-axis plots the driver range from zero to unity. Each y-axis plots the discrete target range {0, 1, 2}. The transformation from driver to target output is accomplished by step functions, which in each graph map 1/3 of the driver range to outcome 0, 1/3 of the driver range to outcome 1, and 1/3 of the driver range to outcome 2. The vertical green lines represent specific driver values, while the rows of green dots tally how many verticals intersect with the step function at that particular output.

Leveling Driver Values

A statistical transform is reliable only to the extent that its driving input is uniform. Yet among the implementators of Driver.next():


Figure 2: An unleveled Brownian sequence (left) and the same
sequence processed using the ContinuousLevel transform (right).

Most other implementations of Driver do not strive in any way for uniformity. For example, the sequence on the left side of Figure 2 was generated using the Brownian driver. None of these sequence values fall in the range from 0.0 to 0.3. The most dense region is that between 0.6 and 0.7; that's where the sequence lingered most during this particular run.

Referring back to Figure 1, if you are employing a transform for the purpose of adapting driver-domain values to the needs of a particular application, it may not be of great concern to you that range values 0 and 2 are over-represented by comparison to range value 1. However if conformance to the distribution is essential to what you are trying to achieve, then you need some way of performing the operation shown in Figure 2, which retains the up-and-down contour of the original Brownian sequence, but which compresses sparsely populated regions (reducing empty regions down to nothing) and expands densely population regions so that equal-sized intervals afterward contain roughly the same number of samples.

The ContinuousLevel unit performs the operation illustrated in Figure 2. Although built upon the Transform base class, ContinuousLevel also implements the Driver interface. Thus ContinuousLevel maps driver-domain values right back into the driver domain, adapting non-uniform output from a 'real' Driver into uniform input for another Transform.


Figure 3: An uncontained Brownian sequence (gray), and
the same sequence processed using the WRAP ContainmentMode.

Figure 4: An uncontained Brownian sequence (gray), and
the same sequence processed using the REFLECT ContainmentMode.

Containing Within the Domain

The majority of processes implementing the Driver operate between well-defined lower and upper bounds which are thus readily scalable to the driver domain from zero to unity. In principle one should be able to exploit any desired sequence, regardless of origin. The capability to accept fully fleshed-out value sequences and to rescale these to the driver domain is particular to the One exception is DriverSequence, which presents values sequentially from a stored array which must be populated prior to the first call to DriverSequence.next(). However since the entire source set is known in advance, it is straightforward enough to ascertain lower and upper bounds, then use these bounds to rescale the set.

Two other exceptions are Brownian and Bolt, both of which are based upon Brownian motion. Brownian motion starts with some arbitrary location, and with each iteration moves randomly from wherever it was to wherever it will be.1 The identical gray contours in Figure 3 and Figure 4 exemplify Brownian motion, plotting locations along the y-axis and time (actually sequential order) along the x-axis.

Comments

  1. Historically what Brown observed in his new-fangled microscope was two-dimensional. The one-dimensional nature of Driver.next{} accomodates either the left-right (x) or the up-down (y) component, but a pair of generators will produce two-dimentional motion if desired.

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