Inspirel banner

Model synthesis

Model synthesis is a technique that shares some of the properties of both preprocessing and metaprogramming. It is a process that allows the engineer to inject programmable code fragments into the operation body.

The need for such a process comes from the fact that operation bodies are introduced and kept in the so-called held state, which means that they are not evaluated until late in the grammar processing stage. The following example demonstrates that this is indeed the case:

Image
Image
Image
Image

The grammar tree displayed above contains two literals as separate entities and the plusT operation that refers to them. It might seem like an obvious opportunity to simplify it and perform the addition immediately (and just once), so that the actual statement in the body is just:

Image

but keeping the whole operation body unevaluated is intentional, as it allows to construct models without any prior assumptions with regard to how compilers work. This approach also allows users to retain complete control on such aspects by means of preprocessors, which can selectively perform any needed simplifications.

Still, this unevaluated format is not always convenient, for example here the intention was to benefit from Mathematica’s computational abilities and compute the constant value in-line:

Image
Image

There is a grammar error in the above code and the grammar reduction process gets stuck with the following state:

Image
Image

This tree shows that FMT does not know what to do with Factorial (it is not a supported operation) and consequently how to proceed with the assignment.

The model synthesis is a method of selective evaluation of chosen model parts - the part of the model that needs to be forcibly evaluated has to be wrapped in the Synthesize symbol, for example:

Image
Image

The grammar was accepted and the complete tree shows that 5! was indeed evaluated and the result (120) was used in the model in its place:

Image
Image

The Synthesize symbol is not a standard Wolfram function (it is defined in “FMTSymbols`”), but for consistency with other model building blocks like control structures its name starts with capital, which intentionally breaks the recommended naming convention.

The first example from this chapter can be rewritten with this expression:

Image

to force evaluation the evaluation and the use of 3 as a single literal.

The following example is not just a funny exercise, but reveals some important properties of model synthesis:

Image

If the intent was to pick some constant value at random, the goal was achieved, but with a trap:

Image
Image

The same grammar does not necessarily look the same when displayed again:

Image
Image

Similarly, the generated code (here in C++) can be different each time:

Image
#include "my_package.h"

#include <cstdint>

using namespace my_package;

// Operation definitions.

void my_package::op()
{
    std::int32_t temp;
    
    temp = 51;
}

another try:

Image
#include "my_package.h"

#include <cstdint>

using namespace my_package;

// Operation definitions.

void my_package::op()
{
    std::int32_t temp;
    
    temp = 69;
}

The above evaluations demonstrate that model synthesis is performed not just once, when the operation is defined, but every time the grammar is needed. This means that careless models can lead to inconsistent results, as correctness checks and code generators can see essentially different models.

In order to solve this particular problem, the possible approach is to capture the random value and inject it into the model from the external variable:

Image

Now, as long as the myRandomValue symbol does not change, all processing will rely on the same value.

Model synthesis is not only useful for computing constants - complete expressions or even control structures can be computed and injected into the model with this technique, which can complement metaprogramming capabilities described in the previous chapter. For example, 100 data objects generated there with the use of metaprogramming (example repeated):

Image

can be initialized in an operation declared and defined this way:

Image
Image

The generated code (in C++) for this operation (shortened!), looks like:

void my_package::init_signals()
{
    signal1 = 0;
    signal2 = 0;
    signal3 = 0;
    // ...
    signal99 = 0;
    signal100 = 0;
}

Again, in a real project signal names would be obtained from a configuration file or perhaps from an external database.

Model synthesis can require some knowledge of how different operations are internally represented to avoid the trap of unintended evaluation of computed expressions - for example, above the assignment was directly synthesized as assignmentT, as the use of standard Set would cause unintended effects with all involved symbols. Control statements can hide their own, similar traps. Fortunately, it is not necessary to follow the internal representation in all its details, as they will be filled as necessary in normal grammar processing steps. Above, it was necessary to use assignmentT instead of Set, but it was not necessary to refer to objectLhsT or literalT - these details are filled in automatically.

Another example shows how model synthesis can be used to generate code that fills a short array with integer values corresponding to a sine waveform for 8-bit DAC. The list of samples can be obtained with the following expression:

Image
Image

These samples can be validated graphically:

Image
Image

The appropriate model definitions are as follows, starting with the data type for the array object:

Image

The sample array data object can be defined as:

Image

And, finally, declaration and definition of the sample initialization operation:

Image
Image

The final generated code (again, in C++) for the initialization function is:

void my_package::init_samples()
{
    samples[0] = 128;
    samples[1] = 167;
    samples[2] = 202;
    samples[3] = 230;
    samples[4] = 248;
    samples[5] = 255;
    samples[6] = 248;
    samples[7] = 230;
    samples[8] = 202;
    samples[9] = 167;
    samples[10] = 128;
    samples[11] = 88;
    samples[12] = 53;
    samples[13] = 25;
    samples[14] = 7;
    samples[15] = 1;
    samples[16] = 7;
    samples[17] = 25;
    samples[18] = 53;
    samples[19] = 88;
}

Of course, the combination of metaprogamming with model synthesis allows to parameterize the sample array length as well.

As a final note, it is important to state that model synthesis is resolved before calling grammar preprocessors, so that synthesized models can be further processed according to user-defined rules.

Previous: Metaprogramming, next: Grammar checks

See also Table of Contents.