Conditional Selection: MarkovMatrix

Introduction

Instances of the MarkovMatrix class generate sequences by conditioning each element upon its immediate predecessor. In this scenario the sequences are called Markov chains after Andrey Markov, the Russian mathematician who first studied them. Following the Index/Supply design, chain elements from MarkovMatrix can be integer indices ranging from 0 to N-1, where N is the length of the external array or list from which supply elements are to be drawn. Alternatively, chain elements can be MarkovState instances which encapsulate an index together with its corresponding supply element in the same unitary object.

Each MarkovState under a MarkovMatrix lists a set of weights detailing how frequently the state should transition to itself, or to any other state. Within this implementation, transition weights are expressed as relative frequencies. They may never be negative but are not required to sum to unity. For example if the set of weights associated with index value 0 is {1., 0., 2.}, that means that index 0 should transition to index 2 twice as often as it transitions to itself, but index 0 should never transition to index 1. So each set of weights is effectively a discrete probability distribution, except that the sum of weights is tracked separately.

01234
00.1.0.0.0.
10.0.1.0.0.
20.0.0.1.0.
30.0.0.0.1.
41.0.0.0.0.
Matrix 1

Transition weights are commonly arranged in an N×N square Markov matrix. This matrix contains a row for each potential source and a column for each potential target. For example Matrix 1 here to the right causes each source index to transition to its numerical successor, with state #4 wrapping around to state #0.

From the perspective of Noam Chomsky's phrase-structure grammars, Markov chains fill the niche of "leftward" context sensitivity. That is, the choice of the next chain element (the next state) depends only upon where the chain has been — not where it is going to. Understand that phrase-structure grammars are concerned with whether a statement is competent (correct) rather than whether it is usual. A particular state-to-state transition is competent if the transition weight is nonzero. Whether that weight is large or small does not matter from the phrase-structure grammar perspective. Conversely if the transition weight is zero, then the transition is incorrect.

Unless you have a good reason for doing otherwise, I recommend that transition weights be limited to one for valid transitions and zero for invalid transitions.

One good reason for doing otherwise is to affect the long-term or steady-state distribution of states. If the weight transitioning from one source to one particular target is small compared to weights for the source's other transition, and if that small weight is not countered by an alternative, less inhibited pathway to the same target, then that target will recur less often in the chain. More about that below under Steady-State Weights.

A Markov matrix is said to be sparse when a preponderance of transition weights are zero. The MarkovMatrix structure accommodates sparseness by assuming that a transition weight is zero when that transition is not explicitly declared under a state. (Explicit zero-weighted transitions are allowed but not recommended.) Within a sparse matrix, the number of pathways communicating between one state and another becomes severely reduced. When state A has no pathway to state B, then state B will ultimately drop out of the chain. (This probably indicates a matrix configuration error.) When state A has multiple pathways to state B, then transition weights make it possible to influence which pathways happen most frequently, or alternatively to establsh a balance between pathways. This is a second good reason to stray from ones and zeros.

Two questions must be answered before a chain can be generated: (1) With what state should the chain start? (2) When does the chain stop?

Starting: One answer to question #1 is to simply prescribe the starting state. Another option is to prescribe a set of starting weights, one for each state, and let the MarkovMatrix instance employ weighted random selection. For example, the starting weights {2., 0., 0., 0., 1.} indicate "start with state #0 for 2 out of 3 chains, start with state #4 for 1 out of 3 chains, and don't start with any other state".

Unless the same matrix will be used to generate many chains, it doesn't make much sense to employ weights other than one for valid starting states and zero for invalid starting states.

01234
00.1.0.0.0.
10.0.1.0.0.
20.0.0.1.0.
30.0.0.0.1.
40.0.0.0.0.
Matrix 2

Stopping: For the chain generated using the transition weights in Matrix 1 above, the answer to question #2 is … never. In this situation the chain-generating process must be supplied with a maximum chain length. However, Matrix 2 here modifies Matrix 1 by zeroing out the transition weights for state #4. States with no positive transition weights are called a terminal states, because when a chain reaches this state, there's nowhere else to go.

Steady-State Weights

A steady-state probability for a state is the probability that an arbitrary element of a Markov chain will match that particular state. Each Markov process has one steady-state probability per state. If the process has one or more terminal states, then its steady-state probabilities are all zero. Otherwise you can estimate the steady-state probabilities by generating a chain which is long enough for the law of large numbers to exert influence, by counting the number of times each state occurs in the chain (obtaining the state's relative weight), and by dividing each state count by the number of chain elements (obtaining the state's probability).

0123
01.1.1.1.
13.1.1.1.
23.1.1.1.
33.1.1.1.
Matrix 3

The more usual method of getting to the steady-state models many chains running simultaneously. I will explain it using the transition weights listed in Matrix 3 to the right. Notice that for state #0 in Matrix 3, the weight for transitioning to any state (including itself) is the same. For the remaining states, the weight for transitioning back to state #0 is equal to the combined weights for transitioning to any state other than #0. Because these other states have a propensity to transition back to state #0, it would be reasonable to assume that state #0 would have a higher steady-state probability. Let's see.

Suppose we have 4000 chains running simultaneously using the transition weights listed in Table 3, that M0,0=1000 of these chains begin with state #0, that M1,0=1000 more begin with state #1, that M2,0=1000 begin with state #2, and that M3,0=1000 begin with state #3.

Remembering that probability distributions are all about population, we can use the following formula to predict how the relative proportions of chain states will change during one iteration:

Mn,k = ω0,n×M0,k-1 + ω1,n×M1,k-1 + … + ωN-1,n×MN-1,k-1

where N is the number of states and

ωi,j  = 
Wi,j
Wi,0 + Wi,1 + … + Wi,N-1

is the (normalized) probability of transitioning from state i to state j. For iteration #1 with the initial Mn,0 numbers of 1000 each, the redistribution of chain states may be calulated as:

M0,1 = 0.250×1000.0 + 0.500×1000.0 + 0.500×1000.0 + 0.500×1000.0 = 1750.0
M1,1 = 0.250×1000.0 + 0.167×1000.0 + 0.167×1000.0 + 0.167×1000.0 = 750.0
M2,1 = 0.250×1000.0 + 0.167×1000.0 + 0.167×1000.0 + 0.167×1000.0 = 750.0
M3,1 = 0.250×1000.0 + 0.167×1000.0 + 0.167×1000.0 + 0.167×1000.0 = 750.0

Adding the results gives 1750.0+750.0+750.0+750.0=4000.0, which is what we should expect given that the total number of simultaneous chains is fixed. The total going down would indicate the presence of a terminal state.

Repeating the calculations for iteration #2:

M0,2 = 0.250×1750.0 + 0.500×750.0 + 0.500×750.0 + 0.500×750.0 = 1562.5
M1,2 = 0.250×1750.0 + 0.167×750.0 + 0.167×750.0 + 0.167×750.0 = 812.5
M2,2 = 0.250×1750.0 + 0.167×750.0 + 0.167×750.0 + 0.167×750.0 = 812.5
M3,2 = 0.250×1750.0 + 0.167×750.0 + 0.167×750.0 + 0.167×750.0 = 812.5

Repeating the calculations for iteration #3:

M0,3 = 0.250×1562.5 + 0.500×812.5 + 0.500×812.5 + 0.500×812.5 = 1609.4
M1,3 = 0.250×1562.5 + 0.167×812.5 + 0.167×812.5 + 0.167×812.5 = 796.9
M2,3 = 0.250×1562.5 + 0.167×812.5 + 0.167×812.5 + 0.167×812.5 = 796.9
M3,3 = 0.250×1562.5 + 0.167×812.5 + 0.167×812.5 + 0.167×812.5 = 796.9

Repeating the calculations for iteration #4:

M0,4 = 0.250×1609.4 + 0.500×796.9 + 0.500×796.9 + 0.500×796.9 = 1597.7
M1,4 = 0.250×1609.4 + 0.167×796.9 + 0.167×796.9 + 0.167×796.9 = 800.8
M2,4 = 0.250×1609.4 + 0.167×796.9 + 0.167×796.9 + 0.167×796.9 = 800.8
M3,4 = 0.250×1609.4 + 0.167×796.9 + 0.167×796.9 + 0.167×796.9 = 800.8

After just four iterations we witness the distribution converging to its steady-state values of 1600 for state #0 and 800 for state #1, state #2, and state #3. These weights simplify to {2., 1., 1., 1.} which converts to probabilities as {0.4, 0.2, 0.2, 0.2}.

Unfortunately, the method just demonstrated doesn't always work. It depends upon the matrix. Sometimes the distribution never settles down but rather goes into oscillation, favoring one set of states after one iteration and a different set of states after another iteration. To get around this it is necessary to combine the weights into averages:

Mn  = 
Mn,1 + Mn,2 + … + Mn,K
K

Detachment Issues

The Index/Supply design isolates supply from selection principle by detaching the supply element from the index used to select it. However such detachment becomes problematic with MarkovMatrix, where the context preceding each sequence element strongly influences the choice of a successor. Using a pitch supply for example, it seems absurdly detached to indicate that index 14 favors transitions to index 7 while index 17 favors transitions to index 24 — and oh by the way, index 14 references C♯, index 7 references D♮, index 17 references D♭, and index 24 references C♮.

The Index/Supply design made sense in programs like Project2 and Compose. The latter, and probably the former, no longer exists because the platforms upon which they were developed are obsolete. However there is another program called Markov which I originally developed for composer Petr Kotik on the Apple II and which Kotik is still using today in an OSX version. The latest version employs the MarkovMatrix class described on this page. Although the MarkovMatrix class supports Index/Supply, it also supports an alternative approach by making index and supply element both properties of a unitary MarkovState instance.

Matrix Order

The order of a Markov process designates how many previous states are taken into account when formulating the likelihood of transitioning to a particular upcoming state. The MarkovMatrix design described here accomodates Nth-order chains, where N is a parameter provided at matrix-creation time. This capability is the reason why the design distinguishes MarkovSource instances from MarkovState instances. For a 1st-order matrix, every MarkovState has exactly one MarkovSource. For a 2nd-order matrix with K states, the MarkovState for state j can have up to K MarkovSource instances, one for each predecessor to state j. For a 3rd-order matrix, state j can have up to K×K sources, and so forth. As the order N and the number of states K both grow, the number of transition weights explodes beyond the point of practicality for explicit specification. These weights must instead be supplied either by algorithm or by analysis of physical sequence data. Such data sources must be comprehensive enough so that the process doesn't simply spit back literal subsequences that are discovered.

The Markov program described above provided an Nth-order option. However Petr Kotik has not employed it to date, and I doubt he ever will. Kotik has demonstrated to me that higher-order matrices are not necessary for Nth-order contextual sensitivity. It all depends upon how a state is defined. For a traditional musical example, the chromatic scale degree F♮ can be used in a variety of roles: F♮ might be a chord tone which can be quitted in any manner. F♮ might be a upper neighbor note on the way downward to E♮ or E♭. Or F♮ might be a lower neighbor note on the way upward to G♮ via F♯. Although all of these roles involve the same scale degree, F♮, each role introduces a different set of transitions.

Selection Modes

The MarkovMatrix.SelectionMode enumeration identifies three approaches which MarkovMatrix can use to select transitions between sources and targets.

Some readers may object on the premise that a Markov chain is a stochastic process, so its not really a Markov chain if the mechanism for selecting transitions between states is other than weighted random selection. However, Markov himself gathered statistics on letter successions in Eugene Onegin, and Russian spelling, however complicated, is hardly random. Indeed the field of stochastic processes generally models natural phenomena, with randomness standing in for natural mechanisms which are not singly predictable but which in combination produce consistent statistical distributions. I'm just abstracting out the "singly predictable" part.

Operation

The following example illustrates the operation of Markov chains by controlling a succession of musical pitches. When preparing this example, I did not follow the Index/Supply design by holding pitches separately in an array of Pitch entities. Rather I simple created text strings combining the letter name, accidental, and octave number along with additional distinguishing characters when two states referenced the same pitch. These text strings were deposited in the object field of the corresponding MarkovState instance.

State 0 of my matrix is "C4:C5" which indicates doubling middle C at the octave. This is the anchor point of the chain. There are many transitions leading away from state 0. Other states have very few transitions, and these move along paths leading inevitably back to state 0.

Except leading away from "C4:C5", motion is always stepwise upward, C major chord tones (C E G) are joined with passing C-major scale notes (D F A B). Between chord tones and passing tones, optional chromatic interventions can happen (C♯ D♯ F♯ G♯ A♯). Because the motion is upward, the alterations are always sharps, never flats. I contemplated an additional set of descending pitches with always flats, never sharps. This would have confused the example by doubling the number of states, but feel free to try it out yourself. Likewise, states incorporating both pitches and durations would raise the state count by an order of magnitude — even more when one realizes that to make this work one would need also to incorporated where the duration is positioned within a measure.

An additional constraint is that no more than one chromatic pitch may intervene between any two chord tones. This constraint is important for purposes of demonstration because the complication it introduces must needs be addressed by distinguishing different roles played (humorless pun intended) by the same scale degree.

There are four pathways leading from C to E in ascending steps:

FromToWeight
"C4:C5""C#4"1
"C4:C5""D4"2
"C#4""D%4"1
"D4""D#4"1
"D4""E4"1
"D%4""E4"1
"D#4""E4"1
Table 1(a)

Expressing the three valid pathways in terms of transitions requires drawing a distinction: "D4" on one hand indicates the D♮ that comes directly from C. "D%4" on the other hand represents the D♮ that comes via C♯. The various transitions needed to configure these pathways are listed in Table 1(a) at right. I assigned weight 2 to the transition from "C4:C5" to "D4" because "D4" appears in two pathways while "D%" appears in only one. Suppose I instead wanted the pathways {C D♮ E} and {C D♮ D♯ E} to happen twice as often as the pathway {C C♯ D♮ E}. Then it would be necessary to assign weight 4 to the transition from "C4:C5" to "D4".

C can transition directly to E, which can also be approached indirectly through D. There are three pathways leading from E to G in ascending steps:

FromToWeight
"C4:C5""E4"3
"E4""F4"2
"E4""F#4"1
"F4""F#4"1
"F4""G4"1
"F#4""G4"1
Table 1(b)

Since none of these three pathways conditions the use of a chromatic alteration upon the non-use of a previous alteration, no distinction need be made between states referencing F♮. The various transitions needed to configure these three valid pathways are listed in Table 1(b) at right, which also records the transition from "C4:C5" directly to "E4". I assigned weight 2 to the transition from "E4" to "F4" because "F4" appears in two pathways while "F#" appears in only one. I assigned weight 3 to the transition from "C4:C5" to "E4" to balance the combined weights for "C4:C5" to "C#4" and "C4:C5" to "D4" from Table 1(a).

C can transition directly to G, which can also be approached indirectly through E. There are four pathways leading from G to C in ascending steps:

FromToWeight
"C4:C5""G4"3
"G4""G#4"1
"G4""A4"2
"G#4""A%"1
"A4""A#4"1
"A4""B"1
"A%""B"1
"A#4""B"1
"B""C4:C5"1
Table 1(c)

To configure this requires drawing a distinction between "A4" on one hand, representing the A♮ that comes directly from G, and "A%" on the other hand, representing the A♮ that comes via G♯. The various transitions needed to configure these three valid pathways are listed in Table 1(c) at right, which also records the transition from "C4:C5" directly to "G4".. I assigned weight 2 to the transition from "G4" to "A4" because "A4" appears in two pathways while "A%" appears in only one. I assigned weight 3 to the transition from "C4:C5" to "G4" to balance the combined weights for "C4:C5" to "C#4" and "C4:C5" to "D4" from Table 1(a) along with the weight for "C4:C5" to "E4" from Table 1(b).

"C4:C5""C#4""D4""D%4""D#4""E4""F4""F#4""G4""G#4""A4""A%4""A#4""B4"
"C4:C5"1.02.03.03.0
"C#4"1.0
"D4"1.01.0
"D%4"1.0
"D#4"1.0
"E4"2.01.0
"F4"1.01.0
"F#4"1.0
"G4"1.02.0
"G#4"1.0
"A4"1.01.0
"A%4"1.0
"A#4"1.0
"B4"1.0
Matrix 4: Transition Weights for Markov Pitch Succession

Matrix 4 combines transition-weight data from Table 1(a), Table 1(b), and Table 1(c) into one consolidated array. This matrix is most definitely sparse, with only 22 out of 14×14=196 cells populated, or 11%. Populated cells cluster right of the diagonal, reflecting the predominant upward motion by one or two semitones. The transition from "B4" to "C4:C5" is the wrap-around continuation of this trend. The diagonal itself is wholly unpopulated; states in this matrix do not transition to themselves.

StateC4:C5C#4D4D%4D#4E4F4F#4G4G#4A4A%4A#4B4
Weight 0.148 0.0164 0.0329 0.0164 0.0164 0.0985 0.0656 0.0656 0.148 0.0491 0.0983 0.0491 0.0491 0.147
Combined 0.148 0.0164 0.0493 0.0164 0.0985 0.0656 0.0656 0.148 0.0491 0.1474 0.0491 0.147
Table 2: Steady-State Weights for Markov Pitch Succession

A steady-state analysis of Matrix 4 appears in Table 2. The analysis gives a weight of 0.148 for "C4:C5", the anchor state. This is the largest weight, and its inverse of 6.8 suggests that "C4:C5" will recur in about every 7th chain element. State "G4" has the same weight, as does state "B4" and the combination of "A4" with "A%4" (essentially). These shared weights happen because the pathways back to "C4:C5" always pass through "G4". By contrast, pathways through "E4" happen 0.0985/0.148=66% of the time, while pathways through "D4" happen only 0.0493/0.148=33% of the time. The weights for "C#4" and "D#4" are each 1/3 of the combined weight for D♮; likewise the weights for "G#4" and "A#4" are each 1/3 of the combined weight for A♮. The weights for "F4" and "F#4" are each 2/3 of the weight for "E4".

C4:C5E4G4D4E4D4E4D4E4E4E4D4D4G4C#4E4G4G4C#4E4G4E4D4G4E4
F4A4E4F#4E4F4E4F#4F4F4E4E4A4D%4F4A4G#4D%4F4A4F4E4G#4F#4
F#4A#4F4G4F4F#4F#4G4G4G4F#4F#4A#4E4F#4A#4A%4E4F#4B4G4F4A%4G4
G4B4F#4A4F#4G4G4A4A4A4G4G4B4F4G4B4B4F4G4C4:C5G#4F#4B4A4
G#4C4:C5G4B4G4A4A4B4A#4A#4G#4A4C4:C5F#4A4C4:C5C4:C5G4A4A%4G4C4:C5A#4
A%4G#4C4:C5G#4A#4A#4C4:C5B4B4A%4B4G4A#4G#4A#4B4A4B4
B4A%4A%4B4B4C4:C5C4:C5B4C4:C5A4B4A%4B4C4:C5B4C4:C5
C4:C5B4B4C4:C5C4:C5C4:C5A#4C4:C5B4C4:C5C4:C5
C4:C5C4:C5B4C4:C5
C4:C5
Table 3a: Chain Generated According to Matrix 4 using SelectionMode.RANDOM

Table 3(a) presents one chain generated by MarkovMatrix using the transition weights detailed in Matrix 4 with SelectionMode.RANDOM. The order of elements runs down each column before proceeding to the right. Each column breaks off after a return to the anchor state, "C3:C4". The analysis columns worth of chain elements were enough to fill the page width; overflow was discarded.

C4:C5E4G4D4G4E4D4G4C#4E4E4G4D4E4G4E4C#4D4G4E4G4D4G4E4C#4
F4A4D#4A4F4E4A4D%4F#4F4G#4D#4F4A4F4D%4E4G#4F4G#4E4A4F#4D%4
F#4B4E4A#4G4F4B4E4G4F#4A%4E4G4B4G4E4F4A%4F#4A%4F4A#4G4E4
G4C4:C5F#4B4A4F#4C4:C5F4A4G4B4F#4A4C4:C5G#4F#4F#4B4G4B4G4B4A4F4
A4G4C4:C5B4G4G4A#4A4C4:C5G4A#4A%4G4G4C4:C5A4C4:C5A4C4:C5B4F#4
A#4G#4C4:C5G#4G#4B4A#4A4B4B4A4A4B4A#4C4:C5G4
B4A%4A%4A%4C4:C5B4B4C4:C5C4:C5A#4B4C4:C5B4G#4
C4:C5B4B4B4C4:C5C4:C5B4C4:C5C4:C5A%4
C4:C5C4:C5C4:C5C4:C5B4
C4:C5
Table 3b: Chain Generated According to Matrix 4 using SelectionMode.BALANCED_TRANSITIONS

Table 3(b) presents an alternative chain generated by MarkovMatrix using the transition weights detailed in Matrix 4 with SelectionMode.BALANCED_TRANSITIONS. Over 22 columns worth of chain elements was generated, but only that much is shown.

Pathway RANDOM BALANCED
G A♮ B58
G G♯ A♮ B88
G A♮ A♯ B118
Table 4
Wait Time RANDOM BALANCED
162
255
338
433
513
620
700
810
Table 5

The difference between the random chain in Table 3(a) and the balanced chain in Table 3(b) is summarized in the two tables shown at right.

To create Table 4, I went through each column of chain elements and determined whether the pathway from G back to C was {G A♮ B}, {G G♯ A♮ B}, or {G A♮ A♯ B}. The pathway counts wildly differed in the random chain; however these counts were all the same in the balanced chain. The summary counts only tell part of the story. In the random chain, pathway {G A♮ A♯ B} recurs four times within the five cycles from column 7 to column 11. In the balanced chain, by contrast, pathways {G A♮ B}, {G G♯ A♮ B}, and {G A♮ A♯ B} each appear once in columns 2-4. Same goes for columns 5-7. For columns 8-10. For 11-13. For 14-16. For 17-19. For 20-22. Also for 23-25.

Observing these differences led me to consider pathway recurrance in general, hence Table 5. To create this table, I wrote a little program which counted how many columns it takes for each pathway type to get back to itself. I'm calling this column count the wait time, though this term is not wholly appropriate since while it definitely involves waiting, it only indirectly involves time. In the random chain, adjacent columns shared the same pathway type (wait time is 1 column) in 6 instances. Five of these pathway repeats involve {G A♮ A♯ B}, which is not surprising since according to Table 4, this pathway type dominates the random chain. The pathway most underrepresented in the random chain is {G A♮ A♯ B}. This appears in columns 5, 9, 13, 21, and 23. The average wait time for this pathway specifically is 24/5≈5 chain elements. Compare this average to actual wait times of 4 (close), 4 (close), 8 (not close), and 2 (not close).

Since all pathway types are equally represented in the balanced chain (8 instances), the average wait time calculates out to 24/8=3. The balanced wait times in Table 5 actually do cluster around this value. These numbers are also consistent with consecutive 3-column frames representing all three pathway types.

Having brought this exercise to its conclusion, it will now be fruitful to make an observation about the left-to-right, iterative nature of Markov chains. Chomsky judged the Markov model as inadequate for modeling syntactic structures because the Markov model is only sensitive to prior context. But is this judgement actually valid? The from/to organization of the matrix certainly suggests so in an explicit sense. However the above analysis of chain pathways reveals that where the chain is coming from and where the chain is going are matters of perspective.

Coding

The classes presented here were designed to support the graphical user interface implemented for the Markov program. Several features, most notably the TypeOfMatrix class (Listing 1) and the methods for copying some or all of one matrix's content into a second matrix, are more readily justified in this light.

/**
 * The {@link TypeOfMatrix} class provides a bass class for matrix types.
 * Overrides to {@link TypeOfMatrix#createMatrix(WriteableEntity, int)},
 * and {@link TypeOfMatrix#createState(MarkovMatrix, int)}
 * @author Charles Ames
 */
public abstract class TypeOfMatrix {
   private String name;
   private static SortedMap<String, TypeOfMatrix> matrixTypes = null;
   private static TypeOfMatrix defaultMatrixType = null;
   /**
    * Constructor for {@link TypeOfMatrix} instances.
    * @param name The instance name, which may not be null or blank and
    * which must be unique for each instance.
    */
   protected TypeOfMatrix(String name) {
      this.name = name;
      StringMethods.checkName(name, false, false);
      if (null == matrixTypes)
         matrixTypes = new TreeMap<String, TypeOfMatrix>();
      if (matrixTypes.containsKey(name))
         throw new IllegalArgumentException(
            "There is alreay a matrix type named " + name);
      matrixTypes.put(name, this);
   }
   /**
    * Get the name.
    * @return The assigned name.
    */
   public String getName() {
      return name;
   }
   @Override
   public String toString() {
      return name;
   }
   /**
    * Get the matrix types.
    * @return A collection of {@link TypeOfMatrix} instances,
    * indexed by type name.
    */
   public static SortedMap<String, TypeOfMatrix> getMatrixTypes() {
      return matrixTypes;
   }
   /**
    * Find the {@link TypeOfMatrix} instance with the indicated
    * name.
    * @param name The indicated name.
    * @return The {@link TypeOfMatrix} instance with the indicated
    * name.
    * @throws RuntimeException when no {@link TypeOfMatrix}
    * instance has the indicated name.
    */
   public static TypeOfMatrix find(String name) {
      TypeOfMatrix result;
      if (name.equals("Pitch"))
         result = matrixTypes.get("Notes");
      else
         result = matrixTypes.get(name);
      if (null == result)
         throw new RuntimeException(
            "Invalid matrix type [" + name + "]");
      return result;
   }
   /**
    * Test if a {@link TypeOfMatrix} instances exists
    * with the indicated name.
    * @param name The indicated name.
    * @return True if a {@link TypeOfMatrix} instances
    * exists with the indicated name; false otherwise.
    */
   public static boolean exists(String name) {
      if (null == matrixTypes) return false;
      TypeOfMatrix matrixType = matrixTypes.get(name);
      return (null != matrixType);
   }
   /**
    * Create a matrix whose class is an extension of
    * {@link MarkovMatrix}.
    * @param container The {@link WriteableEntity} to which
    * the matrix will belong.
    * @param order The number of previous states upon which
    * new-state selection is conditioned.
    * @return The newly created {@link MarkovMatrix} instance.
    */
   public abstract MarkovMatrix createMatrix(
         WriteableEntity container, int order);
   /**
    * Create a state whose class is an extension of {@link MarkovState}.
    * @param matrix The matrix to which the state will belong.
    * @param id The state id.
    * @return The newly created {@link MarkovState} instance.
    */
   public abstract MarkovState createState(MarkovMatrix matrix, int id);
   /**
    * Get a {@link TypeOfMatrix} instance named Default where
    * {@link TypeOfMatrix#createMatrix(WriteableEntity, int)}
    * returns a {@link MarkovMatrix} instance and {@link
    * TypeOfMatrix#createState(MarkovMatrix, int)}
    * returns a {@link MarkovState} instance.
    * @return The default {@link TypeOfMatrix} instance.
    */
   public static TypeOfMatrix getDefaultMatrixType() {
      if (null == defaultMatrixType) {
         defaultMatrixType = new TypeOfMatrix("Default") {
            @Override
            public MarkovMatrix createMatrix(
                  WriteableEntity container, int order) {
               MarkovMatrix matrix = new MarkovMatrix(container);
               matrix.setType(this);
               matrix.setOrder(order);
               return matrix;
            }
            @Override
            public MarkovState createState(
                  MarkovMatrix matrix, int id) {
               return new MarkovState(matrix, id);
            }

         };
      }
      return defaultMatrixType;
   }
}
Listing 1: The TypeOfMatrix base class.

The Markov program manages two flavors of matrix: text matrices which output chains as delimiter-separated value files, and note matrices whose output is destined for MusicXML. These specialty matrices are implemented by extending the present MarkovMatrix with states extending the present MarkovState. Since MarkovMatrix needs to know what specialty-state class to instantiate when requested to add a new state, I have introduced the TypeOfMatrix class (Listing 1) as a way of injecting this knowledge.

TypeOfMatrix instances are identified by name, and their collection is managed by the class itself in the static SortedMap<Integer, TypeOfMatrix> named matrixTypes. Each TypeOfMatrix instance has uniquely coded createMatrix() and createState() methods.

One specific TypeOfMatrix is created upon request with the name "Default". For this default TypeOfMatrix, createMatrix() creates raw MarkovMatrix instances while createState() creates raw MarkovState instances. You can obtain the default TypeOfMatrix instance by calling TypeOfMatrix.getDefaultMatrixType().

The method TypeOfMatrix.find(String TypeOfMatrix) will return the TypeOfMatrix instance with the indicated name, if such a type exists.

There are several classes which together implement a composite design pattern with a single MarkovMatrix instance (Listing 2) at the apex of a hierarchy.

Each MarkovMatrix instance contains many child MarkovState instances. The MarkovState class is explained further under States below.

A MarkovState itself contains a set of MarkovSource instances — just one for 1st-order matrices, but potentially many for higher-order matrices. Every MarkovSource contains a set of MarkovTransition instances. Each MarkovTransition under a given MarkovSource references a different MarkovState. The MarkovSource and MarkovSource classes are explained further under Transitions below.

Every MarkovMatrix manages the set of MarkovChain instances generated by that particular matrix. Each MarkovChain is a list of MarkovState references. The MarkovChain class is explained further under Data below.

/**
 * The {@link MarkovMatrix} class implements the matrix of conditional
 * probabilities (weights) which can be used to generate a Markov chain.
 * @author Charles Ames
 */
public class MarkovMatrix
extends WriteableEntity implements Indexer {
   /**
    * Modes of selecting successive states when generating a chain.
    * @author Charles Ames
    */
   public enum SelectionMode {
      /**
       * Choose next state by weighted randomness.
       */
      RANDOM {
         @Override
         protected void initializeUsages(MarkovMatrix markovMatrix) {
            // Do nothing
         }
         @Override
         protected MarkovTransition chooseNextTransition(MarkovMatrix matrix, double accent) {
            MarkovSource currentSource = matrix.getCurrentSource();
            if (null == currentSource)
               throw new RuntimeException("No previous source");
            MarkovTransition bestTransition = null;
            double transitionTotal = currentSource.getTransitionTotal();
            if (MathMethods.TINY > transitionTotal)
               throw new RuntimeException(
                  "No choosable transition from " + currentSource.getKey().toString());
            Collection<MarkovTransition> transitions = currentSource.getTransitions().values();
            double u = SharedRandomGenerator.getSingleton().getRandom().nextDouble();
            double chooser = u * transitionTotal;
            for (MarkovTransition transition : transitions) {
               double weight = transition.getWeight();
               if (chooser <= weight) {
                  bestTransition = transition;
                  break;
               }
               chooser -= weight;
            }
            if (null == bestTransition) {
               throw new RuntimeException(
                  "No choosable transition from " + currentSource.getKey().toString());
            }
            return bestTransition;
         }
      },
      /**
       * Choose next state by statistical feedback upon state usages.
       */
      BALANCED_STATES {
         @Override
         protected void initializeUsages(MarkovMatrix matrix) {
            for (MarkovState state : matrix.states.values()) {
               state.setUsage(0.);
            }
         }
         @Override
         protected MarkovTransition chooseNextTransition(MarkovMatrix matrix, double accent) {
            MarkovSource currentSource = matrix.getCurrentSource();
            if (null == currentSource)
               throw new RuntimeException("No previous source");
            MarkovTransition bestTransition = null;
            double transitionTotal = currentSource.getTransitionTotal();
            if (MathMethods.TINY > transitionTotal)
               throw new RuntimeException(
                  "No choosable transition from " + currentSource.getKey().toString());
            Collection<MarkovTransition> transitions = currentSource.getTransitions().values();
            double chooser = Double.MAX_VALUE;
            double bestWeight = 1.;
            double heterogeneity = matrix.getHeterogeneity();
            for (MarkovTransition transition : transitions) {
               MarkovState target = transition.getTarget();
               double weight = transition.getWeight() / transitionTotal;
               double u = SharedRandomGenerator.getSingleton().getRandom().nextDouble() - 0.5;
               double preference = target.getUsage() + ((accent + heterogeneity * u) / weight);
               if (preference < chooser) {
                  bestTransition = transition;
                  chooser = preference;
                  bestWeight = weight;
               }
            }
            if (null == bestTransition) {
               throw new RuntimeException(
                  "No choosable transition from " + currentSource.getKey().toString());
            }
            bestTransition.getTarget().incrementUsage(accent / bestWeight);
            matrix.normalizeStateUsages();
            return bestTransition;
         }
      },
      /**
       * Choose next state by statistical feedback upon transition usages.
       */
      BALANCED_TRANSITIONS {
         @Override
         protected void initializeUsages(MarkovMatrix matrix) {
            for (MarkovSource source : matrix.sources.values()) {
               for (MarkovTransition transition : source.getTransitions().values()) {
                  transition.setUsage(0.);
               }
            }
         }
         @Override
         protected MarkovTransition chooseNextTransition(MarkovMatrix matrix, double accent) {
            MarkovSource currentSource = matrix.getCurrentSource();
            if (null == currentSource)
               throw new RuntimeException("No previous source");
            MarkovTransition bestTransition = null;
            double transitionTotal = currentSource.getTransitionTotal();
            if (MathMethods.TINY > transitionTotal)
               throw new RuntimeException(
                  "No choosable transition from " + currentSource.getKey().toString());
            Collection<MarkovTransition> transitions = currentSource.getTransitions().values();
            double chooser = Double.MAX_VALUE;
            double bestWeight = 1.;
            double heterogeneity = matrix.getHeterogeneity();
            for (MarkovTransition transition : transitions) {
               double weight = transition.getWeight() / transitionTotal;
               double u = SharedRandomGenerator.getSingleton().getRandom().nextDouble() - 0.5;
               double preference = transition.getUsage() + ((accent + heterogeneity * u) / weight);
               if (preference < chooser) {
                  bestTransition = transition;
                  chooser = preference;
                  bestWeight = weight;
               }
            }
            if (null == bestTransition) {
               throw new RuntimeException(
                  "No choosable transition from " + currentSource.getKey().toString());
            }
            bestTransition.incrementUsage(accent / bestWeight);
            currentSource.normalizeTransitionUsages();
            return bestTransition;
         }
      };
      /**
       * Zero out the usage for each state.
       * @param matrix Which {@link MarkovMatrix} instance is initializing.
       */
      protected abstract void initializeUsages(MarkovMatrix matrix);
      /**
       * Choose the next transition available to the matrix's current source.
       * @param matrix Which {@link MarkovMatrix} instance is choosing.
       * @param accent Stress associated with the current sequence element.
       * @return The chosen {@link MarkovTransition} instance.
       */
      protected abstract MarkovTransition chooseNextTransition(MarkovMatrix matrix, double accent);
   }
   /**
    * Number of consecutive states to define matrix source.
    */
   private int order;
   /**
    * Type of matrix:
    */
   private TypeOfMatrix matrixType;
   /**
    * Stress for upcoming selection.
    */
   private double stress;
   private int cycleCount;
   private double sourceTotal;
   private double steadyStateTotal;
   private MarkovSource currentSource;
   //private boolean active;
   /**
    * Override flag.  When true, the initial state id of a generated chain
    * has been prescribed manually.  When false, the initial value will be
    * selected by the {@link #chooseFirst()} method.
    */
   private boolean override;
   /**
    * Maximum chain length.
    */
   private int maxChainLength;
   /**
    * Seed for the random number generator prior to generating a chain.  Must
    * not be negative.  When 0, the random seed is sampled from the system
    * clock.
    */
   private long seed;
   /**
    * Output file format.
    */
   private OutputFormat outputFormat;
   /**
    * Mode for selecting chain elements: {@link SelectionMode#RANDOM},
    * {@link SelectionMode#BALANCED_STATES}, or
    * {@link SelectionMode#BALANCED_TRANSITIONS}.
    */
   private SelectionMode selectionMode;
   /**
    * Controls bluntness of knife-edge for
    * {@link SelectionMode#BALANCED_STATES}
    * and {@link SelectionMode#BALANCED_TRANSITIONS} selection modes.
    * Must be positive.
    * Values significantly smaller than unity (e.g. 0.1) introduce
    * randomness only when choices are in balance.  Values on the order
    * of unity (or greater) increase chances of selecting over-
    * balanced choices.
    */
   private double heterogeneity;
   private IntArray scratchKey;
   private SortedMap<Integer, MarkovState> states;
   private SortedMap<IntArray, MarkovSource> sources;
   private boolean steadyStateAnalyzed;
   private int indexDigits = 1;
   private DecimalFormat indexFormat = new DecimalFormat("0");
   /**
    * Constructor for {@link MarkovMatrix} instances.
    * @param container The matrix container (may be null).
    */
   protected MarkovMatrix(WriteableEntity container) {
      super(container);
      this.setIDQuality(Entity.AttributeQuality.MODIFIABLE);
      this.setNameQuality(Entity.AttributeQuality.MODIFIABLE);
      this.order = Integer.MIN_VALUE;
      this.scratchKey = null;
      this.sourceTotal = 0.;
      this.states = new TreeMap<Integer, MarkovState>();
      this.sources = new TreeMap<IntArray, MarkovSource>();
      this.selectionMode = SelectionMode.RANDOM;
      this.steadyStateTotal = 0.;
      this.stress = 1.0;
      this.heterogeneity = 0.1;
      this.steadyStateAnalyzed = false;
      this.outputFormat = null;
      this.indexDigits = 1;
      this.indexFormat = new DecimalFormat("0");
      this.currentSource = null;
   }
   @Override
   public boolean setName(String name) {
      boolean result = false;
      String oldName = getOldName();
      if (name.equals(oldName)) return result;
      result = super.setName(name);
      return result;
   }
   /**
    * Setter for {@link #matrixType}.
    * @param matrixType The intended {@link #matrixType} value.
    * @throws UnsupportedOperationException when {@link #matrixType}
    * was previously initialized.
    */
   public void setType(TypeOfMatrix matrixType) {
      if (null != this.matrixType)
         throw new UnsupportedOperationException(
            "Matrix type previously initialized");
      if (null == matrixType)
         throw new IllegalArgumentException(
            "Null argument");
      this.matrixType = matrixType;
   }
   /**
    * Get the matrix type.
    * @return The assigned matrix type.
    */
   public TypeOfMatrix getType() {
      if (null == matrixType)
         throw new UninitializedException(
            "Matrix type uninitialized");
      return matrixType;
   }
   /**
    * Setter for {@link #order}.
    * @param order The intended {@link #order} value.
    * @throws IllegalArgumentException when the matrix order
    * is not positive.
    * @throws UnsupportedOperationException when {@link #order}
    * was previously initialized.
    */
   public void setOrder(int order) {
      if (Integer.MIN_VALUE != this.order)
         throw new UnsupportedOperationException(
            "Order previously initialized");
      if (1 > order)
         throw new IllegalArgumentException(
            "Argument not positive.");
      this.order = order;
      this.scratchKey = allocateSourceKey();
   }
   /**
    * Getter for {@link #order}.
    * @return The assigned {@link #order} value.
    * @throws UninitializedException when {@link #order}
    * has not been initialized.
    */
   public int getOrder() {
      if (Integer.MIN_VALUE == order)
         throw new UninitializedException(
            "Matrix order uninitialized");
      return order;
   }
   /**
    * Test if the matrix is first order.
    * @return True if the matrix is first order; false otherwise.
    * @throws UninitializedException when {@link #order} has not
    * been initialized.
    */
   public boolean isFirstOrder() {
      return 1 == getOrder();
   }
   /**
    * Getter for {@link #currentSource}.
    * @return The assigned {@link #currentSource} value.
    */
   protected MarkovSource getCurrentSource() {
      return currentSource;
   }
   /**
    * Setter for {@link #order}.
    * @param source The intended {@link #currentSource} value.
    */
   protected void setCurrentSource(MarkovSource source) {
      this.currentSource = source;
   }
   /**
    * Copy this matrix into the target container.
    * @param targetContainer The target container.
    * @param name The matrix name in the target container.
    * @return The newly created {@link MarkovMatrix} instance.
    */
   public MarkovMatrix copy(WriteableEntity targetContainer,
         String name) {
      getType().createMatrix(targetContainer, getOrder());
      MarkovMatrix target = new MarkovMatrix(targetContainer);
      target.setName(name);
      target.copyFrom(this, CopyOption.CLEAR);
      return target;
   }
   /**
    * Copy this matrix into the target container, using the same
    * name.
    * @param targetContainer The target container.
    * @return The newly created {@link MarkovMatrix} instance.
    */
   public MarkovMatrix copy(WriteableEntity targetContainer) {
      return copy(targetContainer, getName());
   }
   /**
    * Create an {@link IntArray} instance sized to the order of
    * this matrix.
    * @return The newly allocated {@link IntArray} instance.
    */
   public IntArray allocateSourceKey() {
      return new IntArray(getOrder());
   }
   /**
    * Find the {@link MarkovSource} instance corresponding to the
    * indicated sequence of states.
    * @param states The indicated sequence of states.
    * @return The {@link MarkovSource} instance corresponding
    * to the indicated sequence of states.
    * @throws IllegalArgumentException when the state array length
    * is incompatible with the matrix order.
    * @throws NoSuchObjectException when no source possesses the
    * indicated sequence of states.
    */
   public MarkovSource findSource(List<MarkovState> states) {
      if (states.size() != getOrder()) {
         throw new IllegalArgumentException(
            "State sequence length " + states.size()
            + " incompatible with matrix order " + order);
      }
      for (int position = 0; position < order; position++) {
         scratchKey.insertMember(states.get(position).getID(), position);
      }
      MarkovSource source = getSource(scratchKey);
      if (null == source)
         throw new NoSuchObjectException(
            "No source found with key " + scratchKey.toString());
      return source;
   }
   @Override
   public void makeDirty() {
      try {
         WriteableEntity container = (WriteableEntity)getContainer();
         container.makeDirty();
      }
      catch (NoSuchObjectException e) {
      }
      this.steadyStateAnalyzed = false;
   }
   /**
    * Check at least one state having no transitions to other
    * states.
    * @return true if one or more states cannot transition to
    * any other state; false otherwise.
    */
   public boolean hasTerminalState() {
      for (MarkovState state : states.values()) {
         if (state.isTerminal()) return true;
      }
      return false;
   }
   /**
    * Getter for {@link #override}.
    * @return The assigned {@link #override} value.
    */
   public boolean isOverride() {
      return override;
   }
   /**
    * Setter for {@link #override}.
    * @param override The intended {@link #override} value.
    */
   public void setOverride(boolean override) {
      this.override = override;
   }
   /**
    * Getter for {@link #maxChainLength}.
    * @return The assigned {@link #maxChainLength} value.
    */
   public int getMaxChainLength() {
      return maxChainLength;
   }

   /**
    * Setter for {@link #maxChainLength}.
    * @param limit The intended {@link #maxChainLength} value.
    * @return true if the value differs from the old value;
    * false otherwise.
    */
   public boolean setMaxChainLength(int limit) {
      boolean changed = false;
      if (this.maxChainLength != limit) {
         this.maxChainLength = limit;
         changed = true;
         makeDirty();
      }
      return changed;
   }
   /**
    * Getter for {@link #seed}.
    * @return The assigned {@link #seed} value.
    */
   public long getSeed() {
      return seed;
   }
   /**
    * Setter for {@link #seed}.
    * @param seed The intended {@link #seed} value.
    * @return true if the value differs from the old value;
    * false otherwise.
    */
   public boolean setSeed(long seed) {
      boolean changed = false;
      if (this.seed != seed) {
         this.seed = seed;
         makeDirty();
         changed = true;
      }
      return changed;
   }
   /**
    * Getter for {@link #outputFormat}.
    * @return The assigned {@link #outputFormat} value.
    */
   public final OutputFormat getOutputFormat() {
      return outputFormat;
   }
   /**
    * Setter for {@link #outputFormat}.
    * @param outputFormat The intended {@link #outputFormat}
    * value.
    * @return true if the value differs from the old value;
    * false otherwise.
    */
   public boolean setOutputFormat(OutputFormat outputFormat) {
      boolean changed = false;
      if (this.outputFormat != outputFormat) {
         this.outputFormat = outputFormat;
         makeDirty();
         changed = true;
      }
      return changed;
   }
   /**
    * Check if the indicated file is consistent with
    * {@link #outputFormat}.
    * @param file The indicated file.
    * @return True if the indicated file is consistent with
    * {@link #outputFormat}; false otherwise.
    */
   public final String checkFileExtension(File file) {
      String actualFileExtension = FileMethods.getFileExtension(file);
      String expectedFileExtension = getOutputFormat().getExtension();
      if (!expectedFileExtension.equals(actualFileExtension)) {
         throw new IllegalArgumentException(
            "File extension is [" + actualFileExtension
            + "], expected " + expectedFileExtension);
      }
      return actualFileExtension;
   }
   /**
    * Getter for {@link #selectionMode}.
    * @return The assigned {@link #selectionMode} value.
    */
   public SelectionMode getSelectionMode() {
      return selectionMode;
   }
   /**
    * Setter for {@link #selectionMode}.
    * @param selectionMode The intended {@link #selectionMode} value.
    * @return true if the value differs from the old value; false otherwise.
    */
   public boolean setSelectionMode(SelectionMode selectionMode) {
      boolean changed = false;
      if (this.selectionMode != selectionMode) {
         this.selectionMode = selectionMode;
         makeDirty();
         changed = true;
      }
      return changed;
   }
   /**
    * Getter for {@link #stress}.
    * @return The assigned {@link #stress} value.
    * @throws UninitializedException when the {@link #stress}
    * value is uninitialized.
    */
   public double getStress() {
      if (Double.isNaN(stress))
         throw new UninitializedException("Stress not initialized");
      return stress;
   }
   /**
    * Setter for {@link #stress}.
    * @param stress The intended {@link #stress} value.
    * @return True if {@link #stress} has changed; false otherwise.
    * @throws IllegalArgumentException when the argument is not positive.
    */
   public boolean setStress(double stress) {
      if (stress < MathMethods.TINY)
         throw new IllegalArgumentException(
            "Stress must be positive");
      if (this.stress != stress) {
         this.stress = stress;
         makeDirty();
         return true;
      }
      return false;
   }
   /**
    * Getter for {@link #heterogeneity}.
    * @return The assigned {@link #heterogeneity} value.
    */
   public double getHeterogeneity() {
      return heterogeneity;
   }
   /**
    * Setter for {@link #heterogeneity}.
    * @param heterogeneity The intended {@link #heterogeneity} value.
    * @return true if the value differs from the old value;
    * false otherwise.
    */
   public boolean setHeterogeneity(double heterogeneity) {
      if (heterogeneity < MathMethods.TINY)
         throw new IllegalArgumentException(
            "Heterogeneity must be positive");
      boolean changed = false;
      if (this.heterogeneity != heterogeneity)  {
         this.heterogeneity = heterogeneity;
         changed = true;
         makeDirty();
      }
      return changed;
   }

   @Override
   public int getLimit() {
      if (0 == states.size()) return 0;
      return states.lastKey() + 1;
   }
   /**
    * Get all matrix states.
    * @return A collection of {@link MarkovState} instances,
    * indexed by state id.
    */
   public SortedMap<Integer, MarkovState> getStates() {
      return states;
   }
   /**
    * Get all matrix sources.
    * @return A collection of {@link MarkovSource} instances,
    * indexed by state-id arrays.
    */
   public SortedMap<IntArray, MarkovSource> getSources() {
      return sources;
   }
   /**
    * Create a new {@link MarkovChain} instance.
    * @return The newly created {@link MarkovChain} instance.
    */
   public MarkovChain createChain() {
      return new MarkovChain(this);
   }
   /**
    * Get the matrix state with the indicated id.
    * @param id The indicated id.
    * @return The matrix state with the indicated id.
    * @throws NoSuchObjectException when there is no
    * state with the indicated id.
    */
   public MarkovState getState(int id) {
      MarkovState state = states.get(id);
      if (null == state) {
         throw new NoSuchObjectException(
            "There is no state with id [" + id + "]");
      }
      if (state.getID() != id)
         throw new RuntimeException("Programmer error");
      return state;
   }
   /**
    * Test if the matrix contains a state with the indicated id.
    * @param id The indicated id.
    * @return True if the matrix contains a state with the
    * indicated id; false otherwise.
    */
   public boolean hasState(int id) {
      return getStates().containsKey(id);
   }
   /**
    * Create a new state with the indicated id.
    * @param id The indicated id.
    * @return The newly created state.
    * @throws IllegalArgumentException when there is already a
    * state with the indicated id.
    */
   public MarkovState createState(int id) {
      if (hasState(id)) {
         throw new IllegalArgumentException(
            "There is already a state with id [" + id + "]");
      }
      MarkovState result = matrixType.createState(this, id);
      states.put(id, result);
      refreshIndexFormat();
      makeDirty();
      return result;
   }
   /**
    * Test if the number of digits in the number of states has changed
    * and, if so, rebuild the format to reflect this.
    */
   private void refreshIndexFormat() {
      int magnitude = states.size();
      int digits;
      if (0 == magnitude)
         digits = 1;
      else
         digits = 1 + (int) Math.floor(Math.log10(magnitude));
      if (indexDigits != digits) {
         indexDigits = digits;
         String pattern = StringUtils.repeat("0",digits);
         indexFormat = new DecimalFormat(pattern);
      }
   }
   /**
    * Getter for {@link #indexFormat}.
    * @return A {@link DecimalFormat} with enough 0-padded digits to
    * accommodate all state indices.
    */
   public DecimalFormat getIndexFormat() {
      return indexFormat;
   }
   /**
    * Remove the indicated state.
    * @param state The indicated state.
    */
   public void removeState(MarkovState state) {
      if (this != state.getContainer())
         throw new IllegalArgumentException(
            "State does not belong to this matrix");
      int id = state.getID();
      // first remove all transitions to the state that's going away
      for (MarkovSource source : sources.values()) {
         if (source.getTransitions().containsKey(id))
            source.removeTransition(id);
      }
      // next delete all sources that reference the state.
      Iterator<MarkovSource> iterator = sources.values().iterator();
      while (iterator.hasNext()) {
         MarkovSource source = iterator.next();
         if (source.getKey().contains(id)) {
            iterator.remove();
            source.dispose();
         }
      }
      states.remove(id);
      state.dispose();
      refreshIndexFormat();
      makeDirty();
   }
   /**
    * Remove the state with the indicated id.
    * @param id The indicated id.
    */
   public void removeState(int id) {
      MarkovState state = getState(id);
      removeState(state);
   }
   /**
    * Remove the indicated source.
    * @param source The indicated source.
    * @throws IllegalArgumentException when the argument is null.
    */
   public void removeSource(MarkovSource source) {
      if (null == source)
         throw new IllegalArgumentException("Null source");
      source.detach();
      source.dispose();
      makeDirty();
   }
   /**
    * Get an array covering all the {@link MarkovState} instances of
    * this matrix.
    * @return An array of {@link MarkovState} instances.
    */
   public MarkovState[] getStateArray() {
      int size = states.size();
      MarkovState[] result = new MarkovState[size];
      int index = 0;
      for (MarkovState state : states.values()) {
         result[index++] = state;
      }
      return result;
   }
   /**
    * Create a new {@link MarkovSource} instance from the indicated key.
    * @param key identifies a particular combination of states.
    * @return The newly created {@link MarkovSource} instance.
    * @throws RuntimeException When the particular combination of states
    * already exists.
    */
   public MarkovSource createSource(IntArray key) {
      MarkovSource source = new MarkovSource(this);
      for (int position = 0; position < order; position++) {
         int stateID = key.memberAt(position);
         MarkovState state = states.get(stateID);
         source.addState(state);
      }
      source.attach();
      return source;
   }
   /**
    * Get the matrix source with the indicated key.
    * @param key The indicated key.
    * @return The matrix source with the indicated id.
    * @throws NoSuchObjectException when there is no source with the indicated key.
    */
   public MarkovSource getSource(IntArray key) {
      if (null == key)
         throw new IllegalArgumentException("Null source key");
      MarkovSource source = sources.get(key);
      if (null == source) {
         throw new NoSuchObjectException(
            "There is no source with state sequence [" + key.toString() + "]");
      }
      return source;
   }
   /**
    * Enumerate undefined sources.
    * @param keys The receiving collection of {@link IntArray} instances.
    * The method starts out by clearing this list.
    */
   public void enumerateUndefinedSources(List<IntArray> keys) {
      keys.clear();
      enumerateUndefinedSources(keys, scratchKey.itemCount(), scratchKey);
   }
   /**
    * Enumerate undefined sources.
    * @param keys The receiving collection of {@link IntArray} instances.
    * The method starts out by clearing this list.
    * @param right How many key positions to fill.  May be
    * workingKey.itemCount when the rightmost state is unknown or
    * workingKey.itemCount-1 when the rightmost state has been filled
    * in to workingKey.
    * @param workingKey An {@link IntArray} instance whose array length
    * corresponds to the order of this matrix.
    * @throws IllegalArgumentException when the right argument is not either
    * workingKey.itemCount() or workingKey.itemCount()-1.
    */
   public void enumerateUndefinedSources(List<IntArray> keys,
         int right, IntArray workingKey) {
      SortedMap<Integer, MarkovState> states = getStates();
      int itemCount = workingKey.itemCount();
      if (right < itemCount - 1 || right > itemCount) {
         throw new IllegalArgumentException(
            "Right " + right + " itemCount " + itemCount);
      }
      if (0 == states.size()) return;
      int limit = states.lastKey();
      int iterationLimit = 0;
      int n = 1;
      for (int position = 0; position < right; position++) {
         n *= (limit+1);
         iterationLimit += n;
      }
      int position = 0;
      workingKey.insertMember(-1, position);
      int iterationCount = 0;
      boolean done = false;
      for (;;) {
         int stateID = workingKey.memberAt(position) + 1;
         workingKey.insertMember(stateID, position);
         while (stateID > limit) {
            --position;
            if (position < 0) {
               done = true;
               break;
            }
            stateID = workingKey.memberAt(position) + 1;
            workingKey.insertMember(stateID, position);
         }
         if (done) break;
         if (states.containsKey(stateID)) {
            ++position;
            if (position == right) {
               if (!sources.containsKey(workingKey)) {
                  IntArray undefinedKey = workingKey.copy();
                  keys.add(undefinedKey);
               }
               --position;
            }
            else {
               workingKey.insertMember(-1, position);
            }
         }
         if (++iterationCount > iterationLimit)
            throw new RuntimeException(
               "Iteration count exceeds limit of " + iterationLimit);
      }
   }
   /**
    * Get the next available state id.
    * @return The highest existing state id, plus 1.  0 if the matrix
    * presently has no states.
    */
   public int getNextStateID() {
      if (0 == states.size()) {
         return 0;
      }
      else {
         return states.lastKey() + 1;
      }
   }
   /**
    * Change the id of a state with the indicated old id to the
    * indicated new id.
    * @param oldID The indicated old id.
    * @param newID The indicated new id.
    */
   public void changeStateID(int oldID, int newID) {
      if (oldID == newID) return;
      if (!hasState(oldID))
         throw new IllegalArgumentException(
            "There is no state with id [" + oldID + "]");
      int maxID = getNextStateID();
      if (0 > newID || newID > maxID)
         throw new IllegalArgumentException(
            "New state id [" + newID + "] out of range from 0 to " + maxID);
      if (hasState(newID)) {
         throw new IllegalArgumentException(
            "There is already a state with id [" + newID + "]");
      }
      List<MarkovSource> affectedSources = new ArrayList<MarkovSource>();
      MarkovState state = getState(oldID);
      for (MarkovSource source : sources.values()) {
         boolean flag = false;
         for (int position = 0; position < order; position++) {
            if (state == source.getState(position)) {
               flag = true;
               break;
            }
         }
         if (flag) {
            affectedSources.add(source);
         }
      }
      states.remove(oldID);
      state.setID(newID);
      states.put(newID, state);
      for (MarkovSource source : affectedSources) {
         source.detach();
         source.attach();
      }
      for (MarkovSource source : sources.values()) {
         SortedMap<Integer, MarkovTransition> sourceTransitions = source.getTransitions();
         MarkovTransition transition = sourceTransitions.get(oldID);
         if (null != transition) {
            sourceTransitions.remove(oldID);
            sourceTransitions.put(newID, transition);
         }
      }
   }
   /**
    * Compact the state id's so that they start with 0 and continue without gaps.
    */
   public void resequenceStates() {
      if (0 == states.size()) return;
      int newID = 0;
      int firstID = states.firstKey();
      int lastID = states.lastKey();
      for (int oldID = firstID; oldID <= lastID; oldID++) {
         if (!hasState(oldID)) continue;
         changeStateID(oldID, newID);
         newID++;
      }
   }
   /**
    * Increase state id's by one, starting with the indicated value.
    * Use this method for closing in gaps after one or more states have been removed.
    * @param id The indicated value.
    */
   public void shiftStatesDown(int id) {
      int highID = id - 1;
      while (hasState(highID + 1)) ++highID;
      shiftStatesDown(id, highID);
   }
   protected void shiftStatesDown(int lowID, int highID) {
      if (lowID > highID) return;
      if (0 == states.size()) return;
      int nextID = highID + 1;
      for (int lastID = highID; lastID >= lowID; lastID--) {
         if (!hasState(lastID)) continue;
         changeStateID(lastID, nextID);
         nextID--;
      }
   }
   /**
    * Decrease state id's by one, starting with the indicated value.
    * Use this method to free up an id that you wish to assign to a new state.
    * @param id The indicated value.
    */
   public void shiftStatesUp(int id) {
      if (0 < states.size()) {
         shiftStatesUp(id, states.lastKey());
      }
   }
   protected void shiftStatesUp(int lowID, int highID) {
      if (lowID > highID) return;
      int lastID = Integer.MAX_VALUE;
      for (int nextID = lowID; nextID <= highID; nextID++) {
         if (!hasState(nextID)) {
            if (Integer.MAX_VALUE == lastID) lastID = nextID;
            continue;
         }
         if (Integer.MAX_VALUE > lastID) {
            changeStateID(nextID, lastID);
            lastID++;
         }
      }
   }
   /**
    * Dereference the state with the indicated old id and change to the new id.
    * This method enhances {@link #changeStateID(int, int)} by
    * pushing down any state already  using the new value.
    * @param oldID The state's original value.
    * @param newID The state's new value.
    */
   public void moveState(int oldID, int newID) {
      if (newID == oldID) return;
      MarkovState state = getState(oldID);
      if (null == state)
         throw new IllegalArgumentException("Invalid state id " + oldID);
      boolean shifted = false;
      if (hasState(newID)) {
         shiftStatesDown(newID);
         shifted = true;
      }
      changeStateID(state.getID(), newID);
      if (shifted) resequenceStates();
   }
   /**
    * Copy states, but NOT sources or transitions, from the source matrix
    * into the current matrix.
    * @param source The source matrix.
    * @param option Indicates how to handle overlaps between corresponding states.
    */
   public void copyStates(MarkovMatrix source, CopyOption option) {
      if (CopyOption.CLEAR == option)
      {
         states.clear();
      }
      for (MarkovState sourceState : source.states.values()) {
         int sourceID = sourceState.getID();
         MarkovState targetState;
         if (hasState(sourceID)) {
            if (CopyOption.OVERWRITE != option) continue;
            targetState = states.get(sourceID);
         }
         else {
            targetState = createState(sourceID);
         }
         targetState.copyObject(sourceState);
      }
   }
   /**
    * Copy sources, but NOT transitions, from the reference matrix into
    * the current matrix.
    * It is assumed that {@link #copyStates(MarkovMatrix, CopyOption)} has just
    * previously been called.
    * @param reference The source matrix.
    * @param option Indicates how to handle overlaps between corresponding states.
    */
   public void copySources(MarkovMatrix reference, CopyOption option) {
      if (CopyOption.CLEAR == option)
      {
         sources.clear();
      }
      for (MarkovSource referenceSource : reference.sources.values()) {
         IntArray key = referenceSource.getKey();
         MarkovSource targetSource = sources.get(key);
         if (null != targetSource) {
            if (CopyOption.OVERWRITE != option) continue;
            targetSource.detach();
         }
         else {
            targetSource = new MarkovSource(this);
         }
         for (int position = 0; position < order; position++) {
            int stateID = referenceSource.getState(position).getID();
            MarkovState state = states.get(stateID);
            targetSource.addState(state);
         }
         targetSource.attach();
      }
   }
   /**
    * Copy states and their transitions from the reference matrix into the current matrix.
    * @param reference The reference matrix.
    * @param option Indicates how to handle overlaps between corresponding
    * states or corresponding transitions.
    * @throws IllegalArgumentException When the reference matrix type is inconsistent.
    * @throws IllegalArgumentException When the reference matrix order is inconsistent.
    */
   public final void copyFrom(MarkovMatrix reference, CopyOption option) {
      if (getType() != reference.getType()) {
         throw new IllegalArgumentException("Inconsistent matrix type");
      }
      if (getOrder() != reference.getOrder()) {
         throw new IllegalArgumentException("Inconsistent matrix order");
      }
      setMaxChainLength(reference.getMaxChainLength());
      setSeed(reference.getSeed());
      setSelectionMode(reference.getSelectionMode());
      setHeterogeneity(reference.getHeterogeneity());
      outputFormat = reference.getOutputFormat();
      this.override = option.chooseField(reference.override, this.override);
      copyStates(reference, option);
      copySources(reference, option);
      for (MarkovSource source : sources.values()) {
         IntArray key = source.getKey();
         SortedMap<IntArray, MarkovSource> referenceSources = reference.getSources();
         if (referenceSources.containsKey(key)) {
            MarkovSource referenceSource = referenceSources.get(key);
            source.copyTransitions(referenceSource, option);
         }
      }
   }
   /**
    * Get the sum of all source starting weights.
    * @return The sum of all source starting weights.
    */
   public double getSourceTotal() {
      return sourceTotal;
   }
   protected void incrementStateTotal(double increment) {
      this.sourceTotal += increment;
   }
   /**
    * Choose a source randomly, based on the the matrix's source starting weights.
    * @return The selected source.
    * @throws RuntimeException when no source has a positive starting weight.
    */
   public MarkovSource chooseFirst() {
      double u = SharedRandomGenerator.getSingleton().getRandom().nextDouble();
      double chooser = u * sourceTotal;
      MarkovSource currentSource = null;
      for (MarkovSource source : sources.values()) {
         double weight = source.getWeight();
         if (chooser <= weight) {
            currentSource = source;
            break;
         }
         chooser -= weight;
      }
      if (null == currentSource)
         throw new RuntimeException("No choosable starting source");
      setCurrentSource(currentSource);
      return currentSource;
   }
   /**
    * Test if the argument is appropriate for use as a weight.
    * @param weight The method argument.
    * @throws IllegalArgumentException if the argument is not positive.
    */
   public static void checkWeight(double weight) {
      if (0. > weight) {
         throw new IllegalArgumentException("Weight may not be negative");
      }
   }
   /**
    * Determine if the selection cycle is ongoing.
    * @return True if the selection cycle is ongoing; false otherwise.
    */
   public boolean hasNextSource() {
      if (null == currentSource) return false;
      double transitionTotal = currentSource.getTransitionTotal();
      return MathMethods.TINY < transitionTotal;
   }
   /**
    * Choose next source, based on the selection mode and the current source's transition weights.
    * @return The selected next source.
    * @throws RuntimeException when the source has no positive transition weights.
    */
   public MarkovSource nextSource() {
      MarkovTransition bestTransition = getSelectionMode().chooseNextTransition(this, getStress());
      return getNextSource(bestTransition.getTarget());
   }
   /**
    * Get the {@link MarkovSource} instance which results from merging
    * the selected {@link MarkovState} instance with the previous {@link MarkovSource}.
    * @param nextState The selected {@link MarkovState} instance.
    * @return The new current {@link MarkovSource} instance.
    */
   public MarkovSource getNextSource(MarkovState nextState) {
      MarkovSource currentSource = getCurrentSource();
      deriveNextKey(scratchKey, currentSource.getKey(), nextState.getID());
      MarkovSource nextSource = sources.get(scratchKey);
      setCurrentSource(nextSource);
      return nextSource;
   }
   /**
    * Transfer all but the first id in fromKey to all but the rightmost position in destKey,
    * then fill in the rightmost position with newID.
    * @param destKey The destination key.
    * @param fromKey The reference key.
    * @param newID The newly selected state id.
    */
   public static void deriveNextKey(IntArray destKey, IntArray fromKey, int newID) {
      int order = destKey.itemCount();
      if (order != fromKey.itemCount())
         throw new IllegalArgumentException("Keys incompatible.");
      for (int position = 1; position < order; position++) {
         destKey.insertMember(fromKey.memberAt(position), position-1);
      }
      destKey.insertMember(newID, order-1);
   }
   /**
    * This method is called by {@link #next()}.
    */
   public void activate() {
      if (!override) {
         SharedRandomGenerator.getSingleton().getRandom().setSeed(seed);
         chooseFirst();
      }
   }
   /**
    * Generate a chain
    * @param chain The {@link MarkovChain} instance which will hold the resulting sequence of
    * {@link MarkovState} references.
    * @param progressMonitor The optional progress monitor, which monitors percent completion and which
    * calling processes can use to abort calculation.
    * @param firstSource The starting {@link MarkovSource} instance, which determines the opening
    * sequence of states.
    */
   public void generate(MarkovChain chain, ProgressMonitor progressMonitor, MarkovSource firstSource) {
      getSelectionMode().initializeUsages(this);
      int limit = getMaxChainLength();
      if (0 == limit) limit = Integer.MAX_VALUE;
      if (null != progressMonitor)  {
         progressMonitor.setActivityMessage("Generating chain");
         progressMonitor.setGoal(limit);
         progressMonitor.start();
      }
      chain.clear();
      SharedRandomGenerator.getSingleton().getRandom().setSeed(getSeed());
      MarkovSource source;
      if (null == firstSource) {
         source = chooseFirst();
      }
      else {
         source = firstSource;
      }
      int index = 0;
      for (int position = 0; position < this.getOrder(); position++) {
         MarkovState state = source.getState(position);
         chain.append(state);
         ++index;
      }
      while (isActive()) {
         if (null != progressMonitor) {
            if (!progressMonitor.setCurrent(index)) {
               break;
            }
         }
         MarkovTransition transition
            = getSelectionMode().chooseNextTransition(this, 1.);
         MarkovState target = transition.getTarget();
         getNextSource(target);
         chain.append(transition);
         if (++index >= limit) break;
      }
   }
   /**
    * Test if this matrix is in the process of generating a sequence.
    * @return True if this matrix is in the process of generating a sequence; false otherwise.
    */
   public boolean isActive() {
      if (null == currentSource) return false;
      return !currentSource.isTerminal();
   }
   @Override
   public Integer next() {
      if (null == currentSource) activate();
      if (!override)
         nextSource();
      int result = currentSource.getRightmostState().getID();
      override = false;
      return result;
   }
   /**
    * Get a text description identifying this matrix.
    * @return A text description identifying this matrix.
    */
   public String getText() {
      return super.getText();
   }
   /**
    * Get an HTML summary of state activity.
    * @return HTML-formatted text summarizing state activity.
    */
   public String getSteadyStateSummary() {
      if (!steadyStateAnalyzed)
         throw new UnsupportedOperationException("Unanalyzed matrix.");
      HtmlBuilder builder = new HtmlBuilder();
      builder.beginTable(null);
      builder.addTableCaption("<b>Steady-State Summary</b>", null);
      builder.beginTableHeader(null);
      builder.beginTableRow(null);
      builder.addTableHeaderCell("State", "align=\"left\"");
      builder.addTableHeaderCell("Weight", "align=\"right\"");
      builder.endTableRow();
      builder.endTableHeader();
      builder.beginTableBody(null);
      for (MarkovState state : getStates().values()) {
         builder.beginTableRow(null);
         builder.addTableCell(state.toString(), "align=\"left\"");
         builder.addTableCell(MathMethods.formatDouble(state.getSteadyStateWeight()/steadyStateTotal, 3), "align=\"right\"");
         builder.endTableRow();
      }
      builder.endTableBody();
      builder.endTable();
      return builder.toString();
   }
   /**
    * Describe this matrix as an HTML table.
    * @return A string of HTML-formatted text.
    */
   public String toHtml() {
      HtmlBuilder builder = new HtmlBuilder();
      builder.beginTable(null);
      appendTableHeader(builder);
      builder.beginTableBody(null);
      for (MarkovSource source : sources.values()) {
         source.appendHtmlRow(builder);
      }
      builder.endTableBody();
      builder.endTable();
      return builder.toString();
   }
   protected void appendTableHeader(HtmlBuilder builder) {
      builder.beginTableHeader(null);
      builder.beginTableRow(null);
      for (int position = 0; position < order; position++) {
         builder.addTableCell(null, null);
      }
      String attributes = "colspan=" + states.size();
      appendProbabilityHeaderCell(builder, 0, attributes);
      builder.endTableRow();
      builder.beginTableRow(null);
      for (int position = 0; position < order; position++) {
         int offset = position - order;
         appendProbabilityHeaderCell(builder, offset, null);
      }
      for (MarkovState state : states.values()) {
         builder.addTableHeaderCell(state.getObject().toString(), null);
      }
      builder.endTableRow();
      builder.endTableHeader();
   }
   protected static void appendProbabilityHeaderCell(
         HtmlBuilder builder, int offset, String attributes) {
      builder.beginTableHeaderCell(attributes);
      builder.append("<i>P</i>");
      builder.beginSubscript(null);
      builder.append("<i>k</i>");
      if (0 != offset)
         builder.append(Integer.toString(offset));
      builder.endSubscript();
      builder.endTableHeaderCell();
   }
   /**
    * Get steady-state analyzed flag.
    * @return True if steady-state analyzed; false otherwise.
    */
   public boolean isSteadyStateAnalyzed() {
      return steadyStateAnalyzed;
   }
   /**
    * Begin with equal numbers of each source.
    * Each iteration executes parallel transitions, one for each {@link MarkovSource}, to
    * discover how from source amounts flow into new source amounts.  The source amounts
    * are then summed by state.
    * @param limit Number of iterations.
    * @param progressMonitor The progress monitor, which monitors percent completion
    * and which calling processes can use to abort calculation.
    */
   public void analyzeSteadyStateAmounts(int limit, ProgressMonitor progressMonitor) {
      cycleCount = 0;
      initializeAmounts();
      iterateSteadyStateTransitions(limit, progressMonitor);
   }
   /**
    * Execute parallel transitions, one for each {@link MarkovSource}, to discover how
    * from source amounts flow into new source amounts.
    * Don't call this function without first calling {@link #setToAmounts(double)} with
    * an argument of unity.
    * @param limit Number of iterations.
    * @param progressMonitor The progress monitor, which monitors percent completion and which
    * calling processes can use to abort calculation.
    * @throws IllegalArgumentException when the limit is not positive.
    */
   public void iterateSteadyStateTransitions(int limit, ProgressMonitor progressMonitor) {
      if (1 > limit)
         throw new IllegalArgumentException("Iteration limit not positive");
      if (null != progressMonitor) {
         progressMonitor.setGoal(limit);
         progressMonitor.start();
      }
      for (int index = 0; index < limit; index++) {
         if (null != progressMonitor) {
            if (!progressMonitor.setCurrent(index)) break;
         }
         cycleSteadyStateTransitions();
         evaluateSteadyStateWeights();
      }
      if (null != progressMonitor) {
         if(progressMonitor.isRunning()) progressMonitor.complete();
      }
   }
   /**
    * Begin with equal numbers of each source.
    * Each iteration executes parallel transitions, one for each {@link MarkovSource}, to discover how
    * from source amounts flow into new source amounts.  Iterations continue until largest difference
    * between from amount and to amount satisfies threshold.
    * The source amounts are then summed by state.
    * @param threshold Maximum fromAmount/toAmount difference.
    * @param progressMonitor The optional progress monitor, which monitors percent completion and which
    * calling processes can use to abort calculation.
    */
   public void analyzeSteadyStateTransitions(double threshold, ProgressMonitor progressMonitor) {
      cycleCount = 0;
      initializeAmounts();
      boolean flag = false;
      int limit = 10 * states.size() * states.size();
      if (null != progressMonitor) {
         progressMonitor.setGoal(limit);
         progressMonitor.start();
      }
      for (int k = 1; k <= limit; k++) {
         if (null != progressMonitor) {
            if (!progressMonitor.setCurrent(k)) {
               progressMonitor.error("Killed by user.");
               return;
            }
         }
         cycleSteadyStateTransitions();
         flag = true;
         for (MarkovState state : states.values()) {
            double fromAmount = 0.;
            double toAmount = 0.;
            for (MarkovSource source : state.getSources().values()) {
               fromAmount += source.getFromAmount();
               toAmount += source.getToAmount();
            }
            double diff = Math.abs((fromAmount - toAmount) / toAmount);
            if (diff > threshold) {
               if (null != progressMonitor) {
                  String message = "Iteration " + k + " State " + state.toString()
                  + " diff " + MathMethods.formatDouble(diff, 3);
                  progressMonitor.setActivityMessage(message);
               }
               flag = false;
               break;
            }
         }
         if (flag) break;
      }
      evaluateSteadyStateWeights();
      if (null != progressMonitor) progressMonitor.complete();
   }
   private void cycleSteadyStateTransitions() {
      shiftAmounts();
      double fromTotal = 0.;
      for (MarkovSource fromSource : sources.values()) fromTotal += fromSource.getFromAmount();
      for (MarkovSource fromSource : sources.values()) {
         double transitionTotal = fromSource.getTransitionTotal();
         double fromAmount = fromSource.getFromAmount();
         if (0. == fromAmount) continue;
         for (MarkovTransition transition : fromSource.getTransitions().values()) {
            boolean flag = false;
            MarkovState toState = transition.getTarget();
            deriveNextKey(scratchKey, fromSource.getKey(), toState.getID());
            MarkovSource toSource = sources.get(scratchKey);
            double transitionWeight = transition.getWeight();
            if (0. < transitionWeight) {
               double toWeight = transitionWeight / transitionTotal;
               double toIncrement = fromAmount * toWeight;
               double toAmount = toSource.getToAmount() + toIncrement;
               toSource.setToAmount(toAmount);
               flag = true;
            }
            if (!flag)
               throw new RuntimeException(fromSource.getText() + " has no targets");
         }
      }
      int cycleLimit = 1000;
      if (++cycleCount > cycleLimit)
         throw new RuntimeException(
               "No steady-state convergence after " + cycleLimit + " iterations");
//      HtmlBuilder builder = new HtmlBuilder();
//      builder.beginTableRow(null);
//      builder.addTableCell(Integer.toString(cycleCount), null);
//      double total = 0.;
//      for (MarkovSource source : sources.values()) {
//         total += source.getToAmount();
//      }
//      for (MarkovSource source : sources.values()) {
//         builder.addTableCell(MathMethods.df3.format(source.getToAmount()/total), null);
//      }
//      builder.endTableRow();
//      System.out.print(builder.toString());
      double toTotal = 0.;
      for (MarkovSource toSource : sources.values()) {
         toTotal += toSource.getToAmount();
      }
      if (!MathMethods.haveSmallDifference(fromTotal, toTotal))
         throw new RuntimeException("From total " + fromTotal + " To total " + toTotal);
   }
   private void evaluateSteadyStateWeights() {
      for (MarkovState state : states.values()) {
         double weight = 0.;
         for (MarkovSource source : state.getSources().values()) {
            weight += source.getToAmount();
         }
         state.setSteadyStateWeight(weight);
      }
      this.steadyStateAnalyzed = true;
   }
   /**
    * Set all source to-amounts to the indicated value (usually zero).
    * @param amount The indicated value.
    */
   public void setToAmounts(double amount) {
      for (MarkovSource source : sources.values()) {
         source.setToAmount(amount);
      }
   }
   /**
    * Set all source to-amounts to the source's starting weight.
    */
   public void initializeAmounts() {
      for (MarkovSource source : sources.values()) {
         source.setToAmount(source.getWeight());
      }
   }
   protected void shiftAmounts() {
      for (MarkovSource source : sources.values()) {
         double amount = source.getToAmount();
         double weight = source.getWeight();
         if (weight > 0.) amount += (weight / source.getContainer().getSourceTotal());
         source.setFromAmount(amount);
         source.setToAmount(0.);
      }
   }
   /**
    * Adjust state usages so that they average to zero.
    */
   public void normalizeStateUsages() {
      double total = 0;
      for (MarkovState state : states.values()) {
         total += state.getUsage();
      }
      if (Math.abs(total) < MathMethods.TINY) return;
      double increment = -total / states.size();
      for (MarkovState state : states.values()) {
         state.incrementUsage(increment);
      }
   }
   /**
    * Get the steady-state total.
    * @return The steady-state total.
    */
   public double getSteadyStateTotal() {
      return steadyStateTotal;
   }
   protected void incrementSteadyStateTotal(double increment) {
      steadyStateTotal += increment;
   }
   @Override
   public boolean hasNext() {
      return hasNextSource();
   }
   @Override
   public boolean hasReset() {
      return true;
   }
   @Override
   public void reset() {
      throw new UnsupportedOperationException("Method not implemented");
   }
   /**
    * Get the smallest transition weight for any source in this matrix.
    * @return The smallest transition weight for any source in this matrix.
    */
   public MarkovTransition getRarestTransition() {
      if (!steadyStateAnalyzed) {
         throw new UnsupportedOperationException("Unanalyzed matrix.");
      }
      MarkovTransition result = null;
      double minWeight = Double.MAX_VALUE;
      for (MarkovState state : states.values()) {
         double totalAmount = state.getTotalToAmount();
         for (MarkovSource source : sources.values()) {
            double sourceWeight = source.getToAmount() / totalAmount;
            for (MarkovTransition transition : source.getTransitions().values()) {
               double weight = transition.getWeight();
               if (MathMethods.TINY > weight) continue;
               double transitionWeight = weight / source.getTransitionTotal();
               double jointWeight = sourceWeight * transitionWeight;
               if (jointWeight < minWeight) {
                  minWeight = jointWeight;
                  result = transition;
               }
            }
         }
      }
      return result;
   }
   /**
    * Convert the indicated text into a double-precision weight.
    * @param text The indicated text.
    * @return A double-precision value.
    */
   public static double parseWeightText(String text) {
      double result;
      try {
         result = Double.valueOf(text);
      }
      catch (NumberFormatException e) {
         String message = "Invalid weight [" + text + "]";
         throw new IllegalArgumentException(message);
      }
      if (0 > result) {
         String message = "Weight may not be negative";
         throw new IllegalArgumentException(message);
      }
      return result;
   }
   /**
    * Convert the indicated text into an integer index.
    * @param text The indicated text.
    * @return An integer index.
    */
   public static int parseIndexText(String text) {
      int result;
      try {
         result = Integer.valueOf(text);
      }
      catch (NumberFormatException e) {
         String message = "Invalid index [" + text + "]";
         throw new IllegalArgumentException(message);
      }
      if (0 >= result) {
         String message = "Index must be positive";
         throw new IllegalArgumentException(message);
      }
      return result;
   }
   /**
    * Dump an itemization of this matrix to standard out.
    */
   public void dump() {
      System.out.println(this.getClass().getSimpleName() + " source total " + getSourceTotal());
      SortedMap<Integer, MarkovState> matrixStates = getStates();
      for (int stateKey : matrixStates.keySet()) {
         MarkovState state =  matrixStates.get(stateKey);
         String stateClassName = state.getClass().getSimpleName();
         System.out.println("   " + stateClassName + " position " + stateKey + " id " + state.getID());
         SortedMap<IntArray, MarkovSource> stateSources = state.getSources();
         for (IntArray sourceKey : stateSources.keySet()) {
            MarkovSource source = stateSources.get(sourceKey);
            String sourceClassName = source.getClass().getSimpleName();
            System.out.println("   " + sourceClassName + " position " + sourceKey
               + " key " + source.getKey() + " weight " + source.getWeight()
               + " transition total " + source.getTransitionTotal());
            SortedMap<Integer, MarkovTransition> sourceTransitions = source.getTransitions();
            for (int targetKey : sourceTransitions.keySet()) {
               MarkovTransition transition = sourceTransitions.get(targetKey);
               String transitionClassName = transition.getClass().getSimpleName();
               MarkovState targetState = transition.getTarget();
               int targetID = targetState.getID();
               double targetWeight = transition.getWeight();
               System.out.println("         " + transitionClassName + " position "
                  + targetKey + " target " + targetID + " weight " + targetWeight);
            }
         }
      }
   }
   /**
    * Check the integrity of the matrix-wide source collection versus
    * the state-specific source collections.
    */
   public void checkIntegrity() {
      for (IntArray key : sources.keySet()) {
         MarkovSource source = sources.get(key);
         IntArray itemKey = source.getKey();
         if (!itemKey.equals(key))
            throw new RuntimeException(
               "Inconsistent keys [" + key + "] [" + itemKey + "]");
         int rightmostStateID = key.memberAt(key.itemCount()-1);
         MarkovState rightmostState = getState(rightmostStateID);
         SortedMap<IntArray, MarkovSource> stateSources = rightmostState.getSources();
         if (!stateSources.containsKey(key))
            throw new RuntimeException(
               "State " +  rightmostStateID + " has no source with key [" + key + "]");
         MarkovSource stateSource = stateSources.get(key);
         if (!stateSource.equals(source))
            throw new RuntimeException(
               "State " +  rightmostStateID + " has different source with key [" + key + "]");
      }
   }
   /**
    * Run this matrix, saving results in the indicated file.
    * @param chain An entity which receives the sequence of {@link MarkovState} references.
    * @param file The indicated file.
    * @param progressMonitor Allows the user interface to track the progress of the run and to
    * abort the run if desired.
    */
   public void run(MarkovChain chain, File file, ProgressMonitor progressMonitor) {
      throw new UnsupportedOperationException("Method not implemented");
   }
   @Override
   public double minGraphValue() {
      throw new UnsupportedOperationException("Method not supported");
   }
   @Override
   public double maxGraphValue() {
      throw new UnsupportedOperationException("Method not supported");
   }
}
Listing 2: The MarkovMatrix implementation class.

The type hierarchy for MarkovMatrix is:

The life cycle of a MarkovMatrix instance has three phases: Construction, Analysis, and Generation.

Construction begins by obtaining a TypeOfMatrix, and calling its createMatrix() method; for example:

TypeOfMatrix matrixType
TypeOfMatrix.getDefaultMatrixType();
MarkovMatrix matrix
matrixType.createMatrix(null, 1);

instantiates a new first-order MarkovMatrix() with no container and with the default matrix type.

Construction continues with the creation of MarkovState, MarkovSource, and MarkovTransition instances.

To fully construct a MarkovMatrix, it's good practice first to set up the complete MarkovState collection before moving on to source and transition details. This is advised because both MarkovSource MarkovTransition classes make reference to states which are not their direct containers. Once all the MarkovState have been defined, one can then begin to flesh out the MarkovSource and MarkovTransition instances under each MarkovState.

The SortedMap<Integer, MarkovState> named states manages the collection of MarkovState instances (Listing 3) under a MarkovMatrix. Among other things, states enforces id-uniqueness.

  • Method hasState(int id) tests if an existing MarkovState is registered with states under the indicated id.
  • Method getState(int id) retrieves an existing MarkovState from states.
  • Method createState(int id) creates a new MarkovState which auto-registers itself with states.
  • Method removeState(MarkovState state) removes the indicated MarkovState from states.

The SortedMap<IntArray, MarkovSource> named sources tracks the full collection of MarkovSource instances (Listing 3) under a MarkovMatrix. This full collection is not to be confused with MarkovState.sources, which contains only sources whose rightmost state coincides with the parent MarkovState. Among other things, MarkovMatrix.sources enforces matrix-wide key-uniqueness.

Analysis is an optional stage during which a matrix's steady-state weights are computed and evaluated. There are two flavors of method MarkovMatrix.analyzeSteadyStates() which implement this step. The analyzeSteadyStates(int limit, ProgressMonitor progressMonitor) flavor cycles though the calculations shown above through limit iterations. The analyzeSteadyStates(double threshold, ProgressMonitor progressMonitor) flavor cycles though the calculations shown above until no average steady-state amount changes by more than threshold (the number of iterations may be on the order of 1./threshold).

Once analyzeSteadyStates() has run to completion, the steady-state average for state k may be obtained by calling MarkovMatrix.getState(k).getSteadyStateWeight(). To express the steady-state average as a probability, divide it by MarkovMatrix.getSteadyStateTotal().

Generation happens in cycles. Each cycle begins with a preparation stage which begins with an optional call to setFirstSource() and completes with a call to reset(). Then follows an iteration stage. Following the Index/Supply design, iteration begins with the first pair of calls to hasNext(), and — if hasNext() returns true — to next(). Iteration continues until hasNext() returns false.

Notice that the hasNext() method wraps hasNextState(), while the next() method wraps nextState(). If you wish to encapsulate "supply" options directly into the state, you can employ an alternative iteration cycle using calls to hasNextState(), and — if hasNextState() returns true — to nextState().

Internally, chain generation is implemented neither as a succesion of integer indices nor as a succession of states, but rather as a succession of MarkovSource instances. At the center of the whole process stands the MarkovMatrix.currentSource field.

  • If setFirstSource() was called, reset() sets currentSource to firstSource. Otherwise, reset() employs weighted random selection to initialize currentSource. The field MarkovMatrix.chainIndex, which tracks the current chain position, is initialized to 0.
  • While MarkovMatrix.chainIndex < MarkovMatrix.order, method nextState() returns currentSource.states[orderIndex]. (The currentSource field still holds the MarkovSource assigned by reset().)
  • Thereafter, nextState() calls chooseNextTransition() to determine where the chain should go next. This determination is guided by the matrix selectionMode. The MarkovTransition returned from chooseNextTransition() has a getTarget() method, which will indicate the upcoming state in the chain. However its still necessary to infer the upcoming value of MarkovMatrix.currentSource. The nextState() method delegates this problem to MarkovMatrix.inferNextState().
  • Since in 1st-order matrices sources are one-for-one with states, the inference is easy.
  • For higher-order matrices sources, inferNextState() derives a new source key by left-shifting the id's in the existing source key, then inserting the new state id as the rightmost key element.
/**
 * The {@link MarkovState} class defines a potential stage in the progress of a markov chain.  Each {@link MarkovState}
 * has a unique integer identifier.
 * @author Charles Ames
 */
public class MarkovState extends WriteableEntity {
   private Object object;
   private SortedMap<IntArray, MarkovSource> sources;
   /**
    * Steady state weight.  This weight is only valid after a call
    * either to {@link MarkovState#setSteadyStateWeight(double)} or to
    * {@link MarkovMatrix#analyzeSteadyStateAmounts(int, ProgressMonitor)}.
    */
   private double steadyStateWeight;
   private int count;
   private double usage;
   private static MarkovState[] stateArray;
   private IntArray scratchKey;
   private String displayText = null;

   protected MarkovState(MarkovMatrix matrix, int id) {
      super(matrix);
      setIDQuality(Entity.AttributeQuality.MODIFIABLE);
      setID(id);
      this.sources = new TreeMap<IntArray, MarkovSource>();
      if (null == scratchKey) {
         int order = matrix.getOrder();
         scratchKey = matrix.allocateSourceKey();
         stateArray = new MarkovState[order];
      }
      this.object = null;
      this.setSteadyStateWeight(1.);
      this.setUsage(0.);
   }
   @Override
   public void wipe() {
      object = null;
      sources = null;
      super.wipe();
   }
   @Override
   public String toString() {
      if (null == displayText) {
         Object o = getObject();
         if (null == o)
            displayText = "#" + (getID() + 1);
         else
            displayText = "#" + (getID() + 1) + " " + o.toString();
      }
      return displayText;
   }
   /**
    * Returns a representation of the state's object not including
    * the state id.
    * @return The object value description.
    */
   public String objectString() {
      return getObject().toString();
   }
   /**
    * Get the matrix to which this state belongs.
    * @return The matrix to which this state belongs.
    */
   public MarkovMatrix getContainer() {
      return (MarkovMatrix) super.getContainer();
   }
   /**
    * Get the sources with this as rightmost state.
    * @return A collection of {@link MarkovSource} instances, indexed by key.
    */
   public SortedMap<IntArray, MarkovSource> getSources() {
      return sources;
   }
   /**
    * Get the sole state source for a first-order matrix.
    * If the source does not exist, create it.
    * @return The sole state source for a first-order matrix.
    * @throws UnsupportedOperationException when the matrix is not first-order.
    */
   public MarkovSource getSoleSource() {
      if (!getContainer().isFirstOrder()) throw new UnsupportedOperationException("Weights accessible only for first-order matrices");
      if (0 == sources.size()) {
         createAllSources();
      }
      return sources.get(sources.firstKey());
   }
   @Override
   public boolean setID(int id) {
      if (0 > id)
         throw new IllegalArgumentException("Negative id");
      SortedMap<Integer, MarkovState> map = getContainer().getStates();
      int oldID = getOldID();
      if (oldID == id) return false;
      if (0 <= oldID)
         map.remove(oldID);
      super.setID(id);
      map.put(id, this);
      displayText = null;
      return true;
   }
   /**
    * Get the sum of the child {@link MarkovSource} to amounts.
    * @return The sum of the child {@link MarkovSource} to amounts.
    * @throws UnsupportedOperationException when the matrix is unanalyzed.
    */
   public double getTotalToAmount() {
      if (!getContainer().isSteadyStateAnalyzed())
         throw new UnsupportedOperationException("Unanalyzed matrix");
      double total = 0.;
      for (MarkovSource source : sources.values()) {
         total += source.getToAmount();
      }
      return total;
   }
   /**
    * Check for terminal condition; that is, having no transitions to other states.
    * @return true if this state can transition to any other state; false otherwise.
    */
   public boolean isTerminal() {
      // local sources collection contains only sources whose rightmost
      // state is itself.
      int sourceCount = sources.size();
      int terminalCount = 0;
      for (MarkovSource source : sources.values()) {
         if (source.isTerminal()) terminalCount++;
      }
      if (0 == terminalCount) return false;
      if (sourceCount == terminalCount) return true;
      throw new RuntimeException("State " + getID() + " has " + terminalCount + " terminal source of " + sourceCount);
   }
   /**
    * Get the object.
    * @return The assigned object.
    */
   public Object getObject() {
      return object;
   }
   /**
    * Set the object.
    * @param object The intended object.
    * @return True if the object changed; false otherwise.
    */
   public boolean setObject(Object object) {
      if (null == object)
         throw new IllegalArgumentException("Null object");
      boolean changed = false;
      if (null == this.object || !this.object.equals(object)) {
         this.object = object;
         changed = true;
         makeDirty();
         displayText = null;
      }
      return changed;
   }
   /**
    * Clear the object.
    */
   public void clearObject() {
      this.object = null;
   }
   /**
    * Copy the object from the indicated reference state.
    * @param reference The indicated reference state.
    */
   public void copyObject(MarkovState reference) {
      object = reference.object;
   }
   /**
    * Create sources for all approaches to this state;
    */
   public void createAllSources() {
      int length = scratchKey.itemCount();
      MarkovMatrix matrix = getContainer();
      if (1 == length) {
         scratchKey.insertMember(getID(), 0);
         if (!sources.containsKey(scratchKey)) {
            MarkovSource source = matrix.createSource(scratchKey);
            source.setWeight(1.);
         }
      }
      else {
         SortedMap<Integer, MarkovState> states = matrix.getStates();
         int limit = states.lastKey();
         int right = length - 1;
         scratchKey.insertMember(getID(), right);
         stateArray[right] = this;
         int position;
         for (position = 0; position < right; position++) {
            scratchKey.insertMember(0, position);
         }
         position = right - 1;
         for (;;) {
            int stateID = scratchKey.memberAt(position);
            scratchKey.insertMember(stateID + 1, position);
            MarkovState state = states.get(stateID);
            if (null != state) {
               stateArray[position] = state;
               if (0 > --position) {
                  if (!sources.containsKey(scratchKey)) {
                     MarkovSource source = matrix.createSource(scratchKey);
                     source.setWeight(1.);
                  }
               }
            }
            if (scratchKey.memberAt(position) >= limit) {
               if (++position >= right) break;
            }
         }
      }
   }
   /**
    * Enumerate undefined sources which approach this state;
    * @param keys The receiving collection of {@link IntArray} instances.  The method starts out by clearing this list.
    */
   public void enumerateUndefinedSources(List<IntArray> keys) {
      keys.clear();
      int length = scratchKey.itemCount();
      MarkovMatrix matrix = getContainer();
      int right = length - 1;
      scratchKey.insertMember(getID(), right);
      matrix.enumerateUndefinedSources(keys, right, scratchKey);
   }
   /**
    * Fill the destination list with all the sources which do not presently transition to the target state.
    * @param list The destination list
    * @param targetState The target state.
    */
   public void getUntargetedSourceArray(List<MarkovSource> list, MarkovState targetState) {
      list.clear();
      for (MarkovSource source : sources.values()) {
         boolean flag = false;
         for (MarkovTransition transition : source.getTransitions().values()) {
            if (targetState == transition.getTarget()) {
               flag = true;
               break;
            }
         }
         if (!flag)
         {
            list.add(source);
         }
      }
   }
   /**
    * Get the state with the next lower id.
    * @return The state with the next lower id.  Null if not found.
    */
   public MarkovState predecessor() {
      SortedMap<Integer, MarkovState> states = getContainer().getStates();
      int firstKey = states.firstKey();
      MarkovState result = null;
      int id = getID();
      for (int predecessorID = id; --predecessorID >= firstKey;) {
         result = states.get(predecessorID);
         if (null != result) break;
      }
      return result;
   }
   /**
    * Get the state with the next higher id.
    * @return The state with the next higher id.  Null if not found.
    */
   public MarkovState successor() {
      SortedMap<Integer, MarkovState> states = getContainer().getStates();
      int lastKey = states.lastKey();
      MarkovState result = null;
      int id = getID();
      for (int successorID = id; ++successorID <= lastKey;) {
         result = states.get(successorID);
         if (null != result) break;
      }
      return result;
   }
   /**
    * Setter for the steady state weight.
    * @param weight The intended steady-state weight.
    * @throws IllegalArgumentException when the weight is negative.
    */
   public void setSteadyStateWeight(double weight) {
      MarkovMatrix.checkWeight(weight);
      MarkovMatrix matrix = getContainer();
      matrix.incrementSteadyStateTotal(-this.steadyStateWeight);
      this.steadyStateWeight = weight;
      matrix.incrementSteadyStateTotal(this.steadyStateWeight);
   }
   /**
    * Getter for {@link #steadyStateWeight}.
    * @return The calculated {@link #steadyStateWeight} amount.
    */
   public double getSteadyStateWeight() {
      return steadyStateWeight;
   }
   /**
    * Getter for the state usage count.
    * @return The accumulated usage count.
    */
   public int getCount() {
      return count;
   }
   /**
    * Setter for the state usage count.
    * @param count The intended state usage count.
    */
   public void setCount(int count) {
      this.count = count;
   }
   /**
    * Increase state usage count by 1.
    */
   public void incrementCount() {
      this.count++;
   }
   /**
    * Getter for the state usage quantity.
    * @return The accumulated state usage quantity.
    */
   public double getUsage() {
      return usage;
   }
   /**
    * Setter for the state usage quantity.
    * @param usage The intended state usage quantity.
    */
   public void setUsage(double usage) {
      this.usage = usage;
   }
   /**
    * Increase state usage quantity by the indicated increment.
    * @param increment The indicated increment.
    */
   public void incrementUsage(double increment) {
      this.usage += increment;
   }
   /**
    * Increase usage by the accent times the reciprocal of the steady-state weight.
    * @param accent The contextual emphasis associated with the event.  Use 1 if unsure.
    * @throws IllegalArgumentException when accent is not positive.
    */
   public void updateSteadyStateUsage(double accent) {
      if (accent < MathMethods.TINY) throw new IllegalArgumentException("Accent not positive");
      incrementUsage(accent * getContainer().getSteadyStateTotal() / steadyStateWeight);
   }
}
Listing 3: The MarkovState implementation class.

States

The type hierarchy for MarkovState is:

The integer id associated with each MarkovState instance is superclass-maintained property. The setID() method override takes responsibility for registering each MarkovState instance id with the parent MarkovMatrix, and for updating this registration whenever the instance id changes.

Users who consider the Index/Supply design too detached can use the object field to embed a "supply" element directly into the present MarkovState instance.

The SortedMap<IntArray, MarkovSource> named sources manages the collection of MarkovSource instances whose rightmost state equals the present MarkovState. This collection is not to be confused with MarkovMatrix.sources, which tracks sources matrix-wide.

  • Method hasSource(IntArray key) tests if an existing MarkovSource is registered with MarkovState.sources under the indicated name.
  • Method getSoleSource() retrieves the sole MarkovSource from MarkovState.sources for a MarkovState within a 1st-order MarkovMatrix.
  • Method getSource(IntArray key) retrieves an existing MarkovSource from MarkovState.sources.
  • Method createSource(IntArray key) creates a new MarkovSource which auto-registers itself with both MarkovState.sources and MarkovMatrix.sources.
  • Method removeSource(MarkovSource source) removes the indicated MarkovSource from both MarkovState.sources and MarkovMatrix.sources. All source transitions are disposed of.
/**
 * The {@link MarkovSource} class identifies a unique sequence of {@link MarkovState}
 * instances, where the sequence length equals the order of the enclosing matrix.  Each
 * {@link MarkovSource} is uniquely identified by an {@link IntArray} whose elements
 * are {@link MarkovState} identifiers.
 * @author Charles Ames
 */
public class MarkovSource extends WriteableEntity {
   private IntArray key;
   /**
    * The starting weight associated with this source; used to select the initial
    * source of a chain.
    */
   private double weight;
   private double transitionTotal;
   /**
    * Steady-state amount entering analysis cycle.
    */
   private double fromAmount;
   /**
    * Steady-state amount leaving into analysis cycle.
    */
   private double toAmount;
   /**
    * Accumulated steady-state amount over all analysis cycles.
    */
   private double sumAmount;
   private SortedMap<Integer, MarkovTransition> transitions;
   private int position;
   private MarkovState[] states;
   private String displayWithID;
   private String displayWithoutID;
   protected MarkovSource(MarkovMatrix matrix) {
      super(matrix);
      this.transitions = new TreeMap<Integer, MarkovTransition>();
      this.weight = 0.;
      this.transitionTotal = 0.;
      this.key = null;
      this.states = new MarkovState[matrix.getOrder()];
      this.displayWithID = null;
      this.displayWithoutID = null;
      this.position = 0;
   }
   @Override
   public MarkovMatrix getContainer() {
      return (MarkovMatrix) super.getContainer();
   }
   /**
    * Get this source's unique key.
    * @return An integer array containing id's for each state constituting
    * the source.
    */
   public IntArray getKey() {
      if (null == key) {
         refreshKey();
      }
      return key;
   }
   /**
    * Create a text description of a source from the indicated key.
    * @param matrix The matrix which defines the source states.
    * @param key The indicated key.
    * @return A text description of a source from the indicated key.
    */
   public static String keyToString(MarkovMatrix matrix, IntArray key) {
      StringBuilder builder = new StringBuilder();
      builder.append(key.toString(1));
      int length = key.itemCount();
      for (int position = 0; position < length; position++) {
         builder.append(' ');
         int stateID = key.memberAt(position);
         builder.append(matrix.getState(stateID).objectString());
      }
      return builder.toString();
   }
   @Override
   public String toString() {
      if (null == displayWithID) {
         StringBuilder builder = new StringBuilder();
         int order = getContainer().getOrder();
         for (int position = 0; position < order; position++) {
            if (0 < position) builder.append(' ');
            builder.append(getState(position).toString());
         }
         displayWithID = builder.toString();
      }
      return displayWithID;
   }
   /**
    * Describe the elements of this source without state id.
    * @return A description of the source.
    */
   public String toObjectString() {
      if (null == displayWithoutID) {
         StringBuilder builder = new StringBuilder();
         int order = getContainer().getOrder();
         for (int position = 0; position < order; position++) {
            if (0 < position) builder.append(' ');
            builder.append(getState(position).objectString());
         }
         displayWithoutID = builder.toString();
      }
      return displayWithoutID;
   }
   /**
    * Refresh this source's unique key.
    */
   public void refreshKey() {
      MarkovMatrix matrix = getContainer();
      int order = matrix.getOrder();
      if (null == key) key = matrix.allocateSourceKey();
      for (int position = 0; position < order; position++) {
         MarkovState state = states[position];
         if (null == state)
            throw new NoSuchObjectException(
               "Null state for index " + position);
         key.insertMember(state.getID(), position);
      }
   }
   @Override
   public String getText() {
      return super.getText() + " [" + getKey().toString() + "]";
   }
   private void checkPosition(int position) {
      int order = getContainer().getOrder();
      if (0 > position || order <= position) {
         throw new IllegalArgumentException(
            "Position " + position + " out of range from 0 to " + (order-1));
      }
   }
   /**
    * Get The {@link MarkovState} reference in the indicated position.
    * @param position the indicated position.
    * @return The {@link MarkovState} reference in the indicated position.
    * @throws IllegalArgumentException when position out of range from
    * 0 (inclusive) to matrix order (exclusive).
    * @throws NoSuchObjectException when state in indicated position is null.
    */
   public MarkovState getState(int position) {
      checkPosition(position);
      MarkovState state = states[position];
      if (null == state)
         throw new NoSuchObjectException("Null state for index " + position);
      return state;
   }
   /**
    * Get The {@link MarkovState} reference in the rightmost position.
    * @return The {@link MarkovState} reference in the rightmost position.
    * @throws NoSuchObjectException when state in rightmost position is null.
    */
   public MarkovState getRightmostState() {
      int position = getContainer().getOrder() - 1;
      return getState(position);
   }
   /**
    * Add the indicated {@link MarkovState} to this source's state list in the
    * next available position.
    * @param state The indicated state.
    * @throws IllegalArgumentException When the position falls outside the range
    * from 0 to order-1.
    * @throws RuntimeException When all state positions have been filled.
    * @throws RuntimeException When a particular combination of states already
    * exists.
    */
   public void addState(MarkovState state) {
      checkPosition(position);
      states[position++] = state;
   }
   /**
    * Check if this source references the indicated state.
    * @param state The indicated state.
    * @return True if the indicated state is listed; false otherwise.
    */
   public boolean referencesState(MarkovState state) {
      for (MarkovState refState : states) {
         if (state == refState) return true;
      }
      return false;
   }
   /**
    * Insert the present {@link MarkovSource} into the matrix's source
    * collection.
    * @throws ObjectAlreadyExistsException if a source collection already
    * contains an with the same state sequence.
    */
   protected void attach() {
      refreshKey();
      SortedMap<IntArray, MarkovSource> matrixSources = getContainer().getSources();
      SortedMap<IntArray, MarkovSource> stateSources = getRightmostState().getSources();
      IntArray key = getKey();
      if (matrixSources.containsKey(key))
         throw new ObjectAlreadyExistsException(
            "Matrix already has a source with key [" + key.toString() + "]");
      matrixSources.put(key, this);
      // this test should never succeed.
      if (stateSources.containsKey(key))
         throw new RuntimeException(
            "State already has a source with key [" + key.toString() + "]");
      stateSources.put(key, this);
      makeDirty();
      this.displayWithID = null;
      this.displayWithoutID = null;
   }
   /**
    * Remove the present {@link MarkovSource} from the matrix's source
    * collection.
    * @throws NoSuchObjectException if the collection contains no item with
    * the same state sequence.
    */
   protected void detach() {
      MarkovMatrix matrix = getContainer();
      SortedMap<IntArray, MarkovSource> matrixSources = matrix.getSources();
      MarkovState rightmostState = getRightmostState();
      SortedMap<IntArray, MarkovSource> stateSources = rightmostState.getSources();
      IntArray key = getKey();
      if (!matrixSources.containsKey(key))
         throw new NoSuchObjectException(
            "There is no source with key [" + key.toString() + "]");
      matrixSources.remove(key);
      stateSources.remove(key);
   }
   /**
    * Get the sum of transition weights.
    * @return The sum of transition weights.
    */
   public double getTransitionTotal() {
      return transitionTotal;
   }
   protected void incrementTransitionTotal(double increment) {
      if (Double.isNaN(increment))
         throw new RuntimeException("Programmer error");
      this.transitionTotal += increment;
   }
   /**
    * Getter for {@link #weight}.
    * @return The assigned {@link #weight} value.
    */
   public double getWeight() {
      return weight;
   }
   /**
    * Setter for {@link #weight}
    * @param weight The intended {@link #weight} value.
    * @return True if the value changed; false otherwise.
    * @throws IllegalArgumentException when the argument is negative.
    */
   public boolean setWeight(double weight) {
      MarkovMatrix.checkWeight(weight);
      if (this.weight != weight) {
         MarkovMatrix matrix = getContainer();
         matrix.incrementStateTotal(-this.weight);
         this.weight = weight;
         matrix.incrementStateTotal(this.weight);
         makeDirty();
         return true;
      }
      return false;
   }
   /**
    * Get the transitions from this source.
    * @return A collection of {@link MarkovTransition} instances,
    * indexed by target state ID.
    */
   public SortedMap<Integer, MarkovTransition> getTransitions() {
      return transitions;
   }
   /**
    * Test if this source has a transition targeting the state
    * with the indicated id.
    * @param targetID The indicated target state id.
    * @return True if this source has a transition targeting
    * the state with the
    * indicated id, and the transition has positive weight;
    * false otherwise.
    */
   public boolean hasTransition(int targetID) {
      MarkovTransition transition = transitions.get(targetID);
      if (null == transition) return false;
      return MathMethods.TINY < transition.getWeight();
   }
   /**
    * Get the transition from this source targeting the state
    * with the indicated id.
    * @param targetID The indicated target state id.
    * @return The from this source to the indicated target state.
    * @throws NoSuchObjectException when this source has no
    * transition to the indicated target state.
    */
   public MarkovTransition getTransition(int targetID) {
      MarkovTransition result = transitions.get(targetID);
      if (null == result) {
         throw new NoSuchObjectException(
            "There is no transition to state [" + targetID + "]");
      }
      if (result.getTarget().getID() != targetID)
         throw new RuntimeException("Programmer error");
      return result;
   }
   /**
    * Create a {@link MarkovTransition} from this source to the
    * indicated target state.
    * @param target The indicated target state.
    * @return The newly created {@link MarkovTransition}.
    * @throws IllegalArgumentException when the target state is null.
    */
   public MarkovTransition createTransition(MarkovState target) {
      if (null == target)
         throw new IllegalArgumentException("Null target");
      int targetID = target.getID();
      MarkovTransition result = new MarkovTransition(this, target);
      if (transitions.containsKey(targetID))
         throw new ObjectAlreadyExistsException(
            "Source " + key + " already contains state #" + targetID);
      transitions.put(targetID, result);
      makeDirty();
      return result;
   }
   /**
    * Create transitions to all states not presently targeted by this source.
    */
   public void createAllTransitions() {
      for (MarkovState targetState : getContainer().getStates().values()) {
         int targetID = targetState.getID();
         if (!hasTransition(targetID)) {
            MarkovTransition transition = createTransition(targetState);
            transition.setWeight(1.);
         }
      }
   }
   /**
    * Remove the transition sourced from here and targeting the state with
    * the indicated id.
    * @param targetID The indicated target state id.
    */
   public void removeTransition(int targetID) {
      MarkovTransition transition = getTransition(targetID);
      removeTransition(transition);
   }
   /**
    * Remove the indicated transition.
    * @param transition The indicated target state transition.
    */
   public void removeTransition(MarkovTransition transition) {
      int id = transition.getTarget().getID();
      transition.setWeight(0); // decrements sumOfTransitionWeights
      transitions.remove(id);
      transition.dispose();
      makeDirty();
   }
   /**
    * Copy the transitions from the indicated source state to the current
    * state.
    * @param reference The indicated source state.
    * @param option Indicates how to handle overlaps between corresponding
    * transitions.
    */
   public void copyTransitions(MarkovSource reference, CopyOption option) {
      if (CopyOption.CLEAR == option)
      {
         transitions.clear();
      }
      for (MarkovTransition sourceTransition : reference.getTransitions().values()) {
         MarkovState referenceTargetState = sourceTransition.getTarget();
         int targetID = referenceTargetState.getID();
         double targetWeight = sourceTransition.getWeight();
         if (transitions.containsKey(targetID)) {
            if (CopyOption.OVERWRITE == option) {
               MarkovTransition targetTransition = getTransition(targetID);
               targetTransition.setWeight(targetWeight);
            }
         } else {
            MarkovMatrix matrix = getContainer();
            if (matrix.getStates().containsKey(targetID)) {
               MarkovState state = matrix.getState(targetID);
               MarkovTransition targetTransition = createTransition(state);
               targetTransition.setWeight(targetWeight);
            }
         }
      }
   }
   /**
    * Assemble all the states which this source presently does not target.
    * @return An array of {@link MarkovState} instances.
    */
   public MarkovState[] getUntargetedStateArray() {
      List<MarkovState> list = new ArrayList<MarkovState>();
      MarkovMatrix matrix = getContainer();
      for (MarkovState state : matrix.getStates().values()) {
         if (!hasTransition(state.getID())) {
            list.add(state);
         }
      }
      MarkovState[] result = new MarkovState[list.size()];
      int index = 0;
      for (MarkovState state : list) {
         result[index++] = state;
      }
      return result;
   }
   /**
    * Getter for the steady-state from amount.
    * @return The assigned steady-state from amount.
    */
   public double getFromAmount() {
      return fromAmount;
   }
   /**
    * Setter for the steady-state from amount.
    * @param amount The intended steady-state from amount.
    */
   protected void setFromAmount(double amount) {
      if (amount < 0.)
         throw new IllegalArgumentException("Negative argument");
      if (Double.isNaN(amount))
         throw new IllegalArgumentException("Argument is NaN");
      this.fromAmount = amount;
   }
   /**
    * Getter for {@link #toAmount}.
    * @return The assigned {@link #toAmount} value.
    */
   public double getToAmount() {
      return toAmount;
   }
   /**
    * Setter for {@link #toAmount}.
    * @param amount The intended {@link #toAmount} value.
    */
   protected void setToAmount(double amount) {
      if (amount < 0.)
         throw new IllegalArgumentException("Negative argument");
      if (Double.isNaN(amount))
         throw new IllegalArgumentException("Argument is NaN");
      this.toAmount = amount;
   }
   /**
    * Getter for {@link #sumAmount}.
    * @return The assigned {@link #sumAmount} value.
    */
   public double getSumAmount() {
      return sumAmount;
   }
   /**
    * Setter for {@link #sumAmount}.
    * @param amount The intended {@link #sumAmount} value.
    */
   public void setSumAmount(double amount) {
      this.sumAmount = amount;
   }
   /**
    * Adjust transition usages so that they average to zero.
    */
   public void normalizeTransitionUsages() {
      double total = 0;
      for (MarkovTransition transition : transitions.values()) {
         total += transition.getUsage();
      }
      if (Math.abs(total) < MathMethods.TINY) return;
      double increment = -total / transitions.size();
      for (MarkovTransition transition : transitions.values()) {
         transition.incrementUsage(increment);
      }
   }
   /**
    * Check for terminal condition; that is, having no transitions to states.
    * @return true if this source can transition to any state; false otherwise.
    */
   public boolean isTerminal() {
      if (MathMethods.TINY > getTransitionTotal()) return true;
      if (0 != getTransitions().size()) return false;
      return true;
   }
}
Listing 4: The MarkovSource implementation class.

Transitions

The type hierarchy for MarkovSource is:

Each MarkovSource instance is uniquely identified under a MarkovMatrix by its key field. The IntArray class wraps an Integer array whose length here coincides with the order of the matrix. What's special about IntArray is that it implements Java's java.lang.Comparable interface, which allows IntArray instances to serve as keys into MarkovState.sources and MarkovMatrix.sources.

  • Under a 1st-order matrix, MarkovSource instances are one-for-one with MarkovState. The key array for any source contains just one element, which is the id of the parent state. 1st-order sources are created automatically; they may not be removed.
  • Under a 2nd-order matrix, the rightmost key element (element #1) contains the id of the parent state, while the leftmost key element (element #0) can be any state id; the chain-generating process knows it has the right source when the chain's previous-but-one state id coincides with key element #0 and the chain's previous state id coincides with key element #1.

The downside of dereferencing state id's into source keys is that it complicates changing state id's. There is no setKey() method. State id's may only be changed through a call to MarkovMatrix.changeStateID(int oldID, int newID). This method first compiles a matrix-wide list of MarkovSource instances affected by the change. Having such a list will be necessary since insersions and deletions are not permitted to a Java collection undergoing iteration. A call to MarkovState.setID() effects the actual change. Then each affected MarkovSource goes through a key-refreshing process:

  • A call to MarkovSource.detch() temporarily removes the source from MarkovState.sources and MarkovMatrix.sources.
  • A call to MarkovSource.attach() first defers to MarkovSource.refreshKey() which iterates through MarkovSource.states, replacing key elements with up-to-date state ids. Once MarkovSource.refreshKey() completes, the MarkovSource is re-registered with MarkovState.sources and MarkovMatrix.sources.

The SortedMap<Integer, MarkovTransition> named transitions manages the collection of MarkovTransition instances. The integer which keys into this collection identifies the MarkovState which the transition targets.

  • Method hasTransition(int targetID) tests if an existing MarkovTransition is registered with transitions under the indicated targetID.
  • Method getTransition/span>(int targetID) retrieves an existing MarkovTransition from transitions.
  • Method createTransition(MarkovState target) creates a new MarkovTransition which auto-registers itself with transitions.
  • Method removeTransition(int targetID) removes the MarkovTransition to the state indicated by targetID from transitions.

The MarkovSource.weight field holds the likelihood that this particular MarkovSource will be selected to begin a chain. Its value is normally established by a call to MarkovSource.setWeight() during a matrix's construction phase.

/**
 * The {@link MarkovTransition} defines the probability (weight) that a source
 * state will make a transition to a target state.  Each {@link MarkovTransition} instance
 * combines a {@link MarkovSource} reference with a target {@link MarkovState} reference.
 * The combination of the {@link MarkovSource}'s unique {@link IntArray} and the target
 * {@link MarkovState}'s unique identifier must itself be unique.
 * @author Charles Ames
 */
public class MarkovTransition extends WriteableEntity {
   private MarkovState target;
   /**
    * The relative frequency (relative to other transitions from the same source)
    * of transitioning from the source to this particular target.
    */
   private double weight;
   private int count;
   private double usage;
   protected MarkovTransition(MarkovSource source, MarkovState target) {
      super(source);
      if (null == target)
         throw new IllegalArgumentException("Null target");
      this.target = target;
      this.weight = 0.;
   }
   @Override
   public void wipe() {
      target = null;
      super.wipe();
   }
   @Override
   public String getText() {
      return super.getText() + " [" + getContainer().getKey().toString()
         + ":" + target.getID() + "]";
   }
   @Override
   public MarkovSource getContainer() {
      return (MarkovSource) super.getContainer();
   }
   /**
    * Getter for {@link #weight}.
    * @return The assigned {@link #weight} value.
    */
   public double getWeight() {
      if (Double.isNaN(weight))
         throw new UninitializedException("Weight not initialized");
      return weight;
   }
   /**
    * Setter for {@link #weight}.
    * @param weight The intended {@link #weight} value.
    * @return True if the value changed; false otherwise.
    * @throws IllegalArgumentException when the argument is negative.
    */
   public boolean setWeight(double weight) {
      MarkovMatrix.checkWeight(weight);
      if (!MathMethods.haveSmallDifference(this.weight, weight)) {
         MarkovSource source = getContainer();
         source.incrementTransitionTotal(-this.weight);
         this.weight = weight;
         source.incrementTransitionTotal(this.weight);
         makeDirty();
         return true;
      }
      return false;
   }
   /**
    * Get the transition usage count.
    * @return The assigned transition usage count.
    */
   public int getCount() {
      return count;
   }
   /**
    * Set the transition usage count.
    * @param count The intended transition usage count.
    */
   public void setCount(int count) {
      this.count = count;
   }
   /**
    * Increment usage count by 1.
    */
   public void incrementCount() {
      this.count++;
   }
   /**
    * Get the transition usage.
    * @return The assigned transition usage.
    */
   public double getUsage() {
      return usage;
   }
   /**
    * Set the transition usage.
    * @param usage The intended transition usage.
    */
   public void setUsage(double usage) {
      this.usage = usage;
   }
   /**
    * Increase usage by the indicated increment.
    * @param increment The indicated increment.
    */
   public void incrementUsage(double increment) {
      this.usage += increment;
   }
   /**
    * Get the target state.
    * @return The target state.
    */
   public MarkovState getTarget() {
      return target;
   }
}
Listing 5: The MarkovTransition implementation class.

The type hierarchy for MarkovTransition is:

Each MarkovTransition instance is uniquely identified under a MarkovSource by its target field. This field references the MarkovState which will be returned by MarkovTransition.nextState(), should MarkovSource.chooseNextTransition() select this particular instance.

Guiding chooseNextTransition() in its selection process are the weight and usage fields.

The weight field influences all three selection modes; its value is normally established by a call to MarkovTransition.setWeight() during a matrix's construction phase. There is also the option of changing transition weights dynamically while the chain is being generated, but this option is not for the faint at heart.

The usage field is employed solely by SelectionMode.BALANCED_TRANSITIONS. This statistic advances each time the transition is selected.

/**
 * The {@link MarkovChain} class implements a sequence of {@link MarkovState} instances.
 * @author Charles Ames
 */
public class MarkovChain {
   private static final String TABLE_BORDER_COLLAPSE
      = HtmlBuilder.buildAttribute("style",
         HtmlBuilder.buildStylePropertyDeclaration("border-collapse","collapse"));
   private static final String CELL_ALIGN_RIGHT = HtmlBuilder.buildAttribute("style",
         HtmlBuilder.buildStylePropertyDeclaration("padding","0")
         + HtmlBuilder.buildStylePropertyDeclaration("margin","0")
         + HtmlBuilder.buildStylePropertyDeclaration("text-align","right"));
   private static final String CELL_ALIGN_LEFT = HtmlBuilder.buildAttribute("style",
         HtmlBuilder.buildStylePropertyDeclaration("padding","0")
         + HtmlBuilder.buildStylePropertyDeclaration("margin","0")
         + HtmlBuilder.buildStylePropertyDeclaration("text-align","left"));
   MarkovMatrix matrix;
   List<MarkovState> states;
   /**
    * Constructor for {@link MarkovChain} instances.
    * @param matrix The {@link MarkovMatrix} whose states compose the chain.
    */
   protected MarkovChain(MarkovMatrix matrix) {
      if (null == matrix) throw new IllegalArgumentException("Null argument");
      this.matrix = matrix;
      states = new ArrayList<MarkovState>();
   }
   /**
    * Clear the chain content and reinitialize matrix statistics.
    */
   public void clear() {
      states.clear();
      for (MarkovState state : matrix.getStates().values()) {
         state.setCount(0);
         for (MarkovSource source : state.getSources().values()) {
            for (MarkovTransition transition : source.getTransitions().values()) {
               transition.setCount(0);
            }
         }
      }
   }
   /**
    * Get the matrix which defines the states of this chain.
    * @return The matrix which defines the states of this chain.
    */
   public MarkovMatrix getMatrix() {
      return matrix;
   }
   /**
    * Get the sequence of states.
    * @return A collection of {@link MarkovState} references.
    */
   public List<MarkovState> getStates() {
      return states;
   }
   /**
    * Append the indicated state to the chain.
    * @param state The indicated state.
    */
   public void append(MarkovState state) {
      if (null == state) throw new IllegalArgumentException("Null argument");
      if (matrix != state.getContainer()) {
         throw new UnsupportedOperationException("State belongs to wrong matrix");
      }
      states.add(state);
      state.incrementCount();
   }
   /**
    * Append the indicated transition to the chain.
    * @param transition The indicated transition.
    */
   public void append(MarkovTransition transition) {
      if (null == transition) throw new IllegalArgumentException("Null argument");
      MarkovState target = transition.getTarget();
      if (matrix != target.getContainer()) {
         throw new UnsupportedOperationException("State belongs to wrong matrix");
      }
      append(target);
      transition.incrementCount();
   }
   /**
    * Get the chain length;
    * @return The chain length;
    */
   public int getLength() {
      return states.size();
   }
   /**
    * Get an HTML summary of state activity.
    * @return HTML-formatted text summarizing state activity.
    */
   public String getStateSummary() {
      HtmlBuilder builder = new HtmlBuilder();
      buildStateSummary(builder);
      return builder.toString();
   }
   /**
    * Build an HTML summary of state activity.
    * @param builder The HTML builder, which may already have content.
    */
   public void buildStateSummary(HtmlBuilder builder) {
      int length = states.size();
      if (0 == length)
         throw new UnsupportedOperationException("No states in chain");
      builder.beginBold(null);
      builder.append("State Summary");
      builder.endBold();
      builder.beginTable(TABLE_BORDER_COLLAPSE);
      builder.beginTableHeader(null);
      builder.beginTableRow(null);
      if (!matrix.isSteadyStateAnalyzed()) {
         builder.addTableHeaderCell("State", CELL_ALIGN_LEFT);
         builder.addTableHeaderCell("Count", CELL_ALIGN_RIGHT);
         builder.addTableHeaderCell("Ratio", CELL_ALIGN_RIGHT);
      }
      else {
         builder.addTableHeaderCell("State", CELL_ALIGN_LEFT);
         builder.addTableHeaderCell("Weight", CELL_ALIGN_RIGHT);
         builder.addTableHeaderCell("Count", CELL_ALIGN_RIGHT);
         builder.addTableHeaderCell("Ratio", CELL_ALIGN_RIGHT);
         builder.addTableHeaderCell("", CELL_ALIGN_LEFT);
      }
      builder.endTableRow();
      builder.endTableHeader();
      builder.beginTableBody(null);
      double actualTotal = (double) length;
      double idealTotal = matrix.getSteadyStateTotal();
      double matrixDiff = 0.;
      for (MarkovState state : matrix.getStates().values()) {
         double actualWeight = state.getCount() / actualTotal;
         builder.beginTableRow(null);
         if (!matrix.isSteadyStateAnalyzed()) {
            builder.addTableCell(state.toString(), CELL_ALIGN_LEFT);
            builder.addTableCell(Integer.toString(state.getCount()), CELL_ALIGN_RIGHT);
            builder.addTableCell(MathMethods.formatDouble(actualWeight, 3), CELL_ALIGN_RIGHT);
         }
         else {
            double idealWeight = state.getSteadyStateWeight() / idealTotal;
            double stateDiff = actualWeight - idealWeight;
            matrixDiff += stateDiff;
            int dev = (int) (100. * stateDiff / idealWeight);
            builder.addTableCell(state.toString(), CELL_ALIGN_LEFT);
            builder.addTableCell(MathMethods.formatDouble(idealWeight, 3), CELL_ALIGN_RIGHT);
            builder.addTableCell(Integer.toString(state.getCount()), CELL_ALIGN_RIGHT);
            builder.addTableCell(MathMethods.formatDouble(actualWeight, 3), CELL_ALIGN_RIGHT);
            builder.addTableCell("&nbsp;" + Integer.toString(dev)+"%", CELL_ALIGN_LEFT);
         }
         builder.endTableRow();
      }
      builder.endTableBody();
      builder.endTable();
      if (!matrix.isSteadyStateAnalyzed()) {
         builder.beginBold(null);
         builder.append("Overall conformance: ");
         builder.endBold();
         int dev = (int) (100. * matrixDiff / actualTotal);
         builder.append(Integer.toString(dev)+"%");
         builder.appendLineBreak();
      }
   }
   /**
    * Get an HTML summary of transition activity.
    * @return HTML-formatted text summarizing transition activity.
    */
   public String getTransitionSummary() {
      HtmlBuilder builder = new HtmlBuilder();
      buildTransitionSummary(builder);
      return builder.toString();
   }
   /**
    * Build an HTML summary of transition activity.
    * @param builder The HTML builder, which may already have content.
    */
   public void buildTransitionSummary(HtmlBuilder builder) {
      int length = states.size();
      if (0 == length)
         throw new UnsupportedOperationException("No states in chain");
      builder.beginBold(null);
      builder.append("Transition Summary");
      builder.endBold();
      builder.beginTable(TABLE_BORDER_COLLAPSE);
      builder.beginTableHeader(null);
      builder.beginTableRow(null);
      builder.addTableHeaderCell("Source", CELL_ALIGN_LEFT);
      builder.addTableHeaderCell("Target", CELL_ALIGN_LEFT);
      builder.addTableHeaderCell("Weight", CELL_ALIGN_RIGHT);
      builder.addTableHeaderCell("Count", CELL_ALIGN_RIGHT);
      builder.addTableHeaderCell("Ratio", CELL_ALIGN_RIGHT);
      builder.addTableHeaderCell("", CELL_ALIGN_RIGHT);
      builder.endTableRow();
      builder.endTableHeader();
      builder.beginTableBody(null);
      for (MarkovState state : matrix.getStates().values()) {
         for (MarkovSource source : state.getSources().values()) {
            double idealTotal = source.getTransitionTotal();
            double actualTotal = 0.;
            for (MarkovTransition transition : source.getTransitions().values()) {
               actualTotal += transition.getCount();
            }
            boolean flag = true;
            for (MarkovTransition transition : source.getTransitions().values()) {
               double idealWeight = transition.getWeight()/idealTotal;
               int actualCount = transition.getCount();
               double actualWeight = transition.getCount()/actualTotal;
               int dev = (int) (100. * (actualWeight - idealWeight) / idealWeight);
               builder.beginTableRow(null);
               builder.beginTableCell(CELL_ALIGN_LEFT);
               if (flag) {
                  builder.append(source.toString());
                  flag = false;
               }
               else {
                  builder.appendNonBreakingSpace();
               }
               builder.endTableCell();
               builder.addTableCell(transition.getTarget().toString(), CELL_ALIGN_LEFT);
               builder.addTableCell(MathMethods.formatDouble(idealWeight, 3), CELL_ALIGN_RIGHT);
               builder.addTableCell(Integer.toString(actualCount), CELL_ALIGN_RIGHT);
               builder.addTableCell(MathMethods.formatDouble(actualWeight, 3), CELL_ALIGN_RIGHT);
               builder.beginTableCell(CELL_ALIGN_LEFT);
               builder.appendNonBreakingSpace();
               builder.append(Integer.toString(dev)+"%");
               builder.endTableCell();
               builder.endTableRow();
            }
         }
      }
      builder.endTableBody();
      builder.endTable();
   }
}
Listing 6: The MarkovChain implementation class.

Data

The MarkovChain class has no type hierarchy. The only properties of this data entity are the MarkovMatrix field named matrix and the List<MarkovState> named states. The matrix field references the generating matrix. The states list holds references to MarkovState instances in the order by which they were selected.

MarkovChain instances are created with a call to MarkovMatrix.createChain()) and populated with a call to MarkovMatrix.generate(MarkovChain chain, ProgressMonitor progressMonitor).

Comments

  1. The present text is adapted from my Leonardo Music Journal article from 1992, "A Catalog of Sequence Generators". The heading is "Markov Chains", p. 67. The subject was explored further in an earlier Leonardo article from 1989, "The Markov Process as a Compositional Model: A Survey and Tutorial", Vol. 22, No.2, pp. 175-187.
  2. Statistical feedback is explained in my Perspectives of New Music article from 1990, "Statistics and Compositional Balance".

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