• No se han encontrado resultados

TIROIDITIS INFLAMATORIA O DESTRUCTIVA

Frequently operations require a stream of a particular form, or change the struc-ture of a stream themselves. To facilitate this a group of combinators are needed to manipulate the type or structure of streams. These come in six variations, rep-resenting the transformations between the different structures, see Figure5.1.

Stream v

Stream (List v) List (Stream v)

e.g. transpose e.g. select

e.g. ch

unk e.g. dividee.g. join

e.g. transpose

Figure 5.1: Transitions between common internal structures of streams.

Any number of functions can be created for these six transformation types, however what follows are those that have been found to be useful in the creation of a wide variety of other transformations. Implementations of these functions have been omitted for clarity, but may be found in AppendixB.

70

5.2.1 Stream ⇐⇒stream of lists

There are two main forms of transformation from a stream to a stream of lists windowand chunk. Window is used to provide a stream of recent histories of a stream, by creating a sliding window on the values of the stream that it transforms.

Chunk divides a stream of values into regularly sized blocks, however this relies upon there being enough values in the underlying stream at each point to construct a complete block before it blocks. Variations on both window and chunk exist which vary the sizes of the list produced at each step.

The inverse of window and chunk can be achieved using the standard concat function for streams of lists, however more commonly streams of lists are con-verted into lists through selection strategies. Some common selection strategies remain unnamed such as map best because their operation is clear from the ver-bose implementation. The more complex operation is called simply select and this takes a function that creates a probability distribution from the length of a list. The stream of lists is used to derive a stream of distributions from which can be sampled to select a single element of the original lists1. Simple selection operations can be seen in the variations of iterative improvement, while genetic algorithms make extensive use of distributive selection mechanisms.

5.2.2 Stream⇐⇒list of streams

Two processes exist for converting a stream into a list of streams, the first is the standard functions replicate, which duplicates the stream multiple times to create a list of identical streams. This is of value when applying different transformations to the same source stream. The second action is divide, which takes a finite set of indices and a stream of instances of these indices. It then divides the input stream, based upon a pairing of each value with an index from the stream of indices, and creates one stream for each member of the index set. For example, in a genetic algorithm it is often useful to apply a mutation to a subset of elements in a population. Divide, using a boolean index type can be used to enable this, as will be seen in subsequent sections of this chapter.

join is the reversal of divide, taking the names and stream of name values.

When selecting from the result of replicate to return to a single stream it will

1Due to the stochastic nature of the selection process unsafePerformIO is used to allow se-lection processes to be created self seeded with RNGs. This is the implementation found in the appendices and will be used subsequently in this Thesis.

usually make more sense to convert the list of streams into a stream of lists and consider the operations in the previous section.

5.2.3 List of streams ⇐⇒ stream of lists

Conversion between streams of lists and lists of streams can be achieved using the standard transpose function. When applied to a list of streams transpose will create a single stream, where the first element is a list containing the first element of each input stream. When applied to a stream of lists transpose will create a list of streams, where each stream represents one row of the lists in the original stream. It is usually assumed that the lists in the stream will be of regular size.

5.2.4 Composite Operations

Many of the basic operations of the library introduce timing issues into the streams that they transform. For example, the use of chunk alone will typically suffer from starvation where the operation is looped over a finite source of data. To avoid issues like this the operations are used in pairs, with particular pairs appearing so often that they form their own operations. The code for these operations is also found in AppendixB.

The first function stretch is composed of the operation map(replicate n) and concatenation. The map/replicate pair takes a stream as input and creates a stream of lists, where each list consists of multiple references to the underlying value of the original stream. It is rarely used alone, but is usually found as a component of doMany.

stretch:: Int→ Stream v → Stream v

The doMany operation takes a stream transformation, but applies the operation to each value in the underlying stream multiple times and collects the results as a list. It is composed of chunk and stretch and is most useful when the stream trans-formation being applied uses stateful intrans-formation internally threaded through the computation, such as a perturbation operation using a random number generator.

This allows several perturbations to be seen from each solution in the underlying stream, before being selected between.

doMany:: Int→ (Stream a → Stream b) → Stream a → Stream (List b)

72

The functions divide and join can be paired to divide a stream into multiple substreams, apply a different stream transformation to each substream and then recombine them. For the result to be stable it is important that divide and join share the set of indices and stream of index values, the sharing of which is provided by the function nest. This is then reformulated so that the type of the function is a list of index values and stream transformations paired together, a stream of index values and results in a stream transformation.

nest:: Eq n⇒ List (n, Stream a → Stream b) → Stream n → Stream a→ Stream b

5.2.5 Restart Combinators

Metaheuristic search strategies converge with time, reaching a point where they cease to make significant improvements in the best known solution to the problem.

At this point alternative strategies are used to diversify the search, allowing the strategy to continue. The simplest form of diversification is the random restart, where solutions are generated by a search strategy from one solution and when that strategy begins to struggle it continues from a newly generated solution.

The restart combinators were originally created to support this task, but were subsequently found to be of use more widely in hybridisation, perturbation and neighbourhood implementation and adaptive simulated annealing. Two versions are implemented restart and restartExtract, both sharing the same type:

restart, restartExtract :: (Stream a → Stream a) -- internal strategy

→ (Stream a → Stream Bool) -- restart on

→ Stream a -- seed solutions

→ Stream a

restartprovides as output every solution encountered by the internal strategy until the restart condition is found. restartExtract acts as a stream transformer, giving only the last solution encountered by the internal strategy from each seed.

5.2.6 Combinators for eagerness

The use of streams to express metaheuristics can run the risk of memory leaks caused through the build up of unevaluated Thunks. This problem can be con-trolled by requiring that the solutions in the final stream are evaluated in the order

that they appear in the stream. This can be implemented using a function called push, which makes a stream head strict, that is the first value must be evaluated before any other values are2.

push:: Stream a→ Stream a push(x : xs) = x ‘seq‘ x : push xs

This function is applied to the output of a metaheuristic before applying further operations such as selecting the best of the solutions seen.