Runtime evaluator
The runtime evaluator (see RuntimeEvaluator) is an engine to evaluate scalar mathematical expressions at runtime.
Summary
RuntimeEvaluator takes an expression in stack machine form at construction time and is then capable of compiling and executing it. The expression can be directly supplied in stack machine form, or it be derived from an infix expression (a mathematical expression in the usual form) by using MathExpressionParser.
The RuntimeEvaluator supports scalar operands of any type.
Usage
The steps required to use the evaluator are the following: - the evaluator is instantiated and fed with the expression it will be required to evaluate - the evaluator internal variable database is initialised by calling the ExtractVariable method - variable properties are set by using the APIs - the expression is compiled by calling the Compile() method - the expression is executed by calling the Execute() method
Instantiating the evaluator
The expression must first be fed to the RuntimeEvaluator at construction time, either typed directly:
StreamString rpnCode = "READ A\n"
"READ B\n"
"ADD\n"
"WRITE ret\n"
;
RuntimeEvaluator expression(rpnCode);
or by converting an infix expression (e.g. via the MathExpressionParser):
StreamString infixExpr = "ret = A + B;"
MathExpressionParser parser(infixExpr);
parser.Parse();
RuntimeEvaluator expression(parser.GetStackMachineExpression());
Initialising the evaluator
First of all, the RuntimeEvaluator must know what variables are contained
in the expression. This is done by calling the ExtractVariables()
method. After that, variable properties can be set by using
RuntimeEvaluator variable managing APIs.
ret = expression.ExtractVariables();
Setting up variables
Variable properties must then be set. Variables properties are:
- type (can be one of MARTe2 supported types: Unsigned32Bit
, Float64Bit
etc.)
- location (the memory location where the variable value will be held)
Types must be set for every input variable by using the
SetInputVariableType
and SetOutputVaribleType
methods:
ret = expression.SetInputVariableType("theta", Float64Bit);
Output variable types can be set with SetOutputVariableType if needed. However, this is not compulsory as RuntimeEvaluator will assign them the output type of the last operation.
Memory locations can be set by using the SetInputVariableMemory and SetOutputVariableMemory methods.
float64 y;
ret = expression.SetOutputVariableMemory("y", &y);
In case an external location for a variable is set, the variable is considered external and any modification of its value will happen to the specified memory address.
In case an external location for a variable is not set, the variable is considered internal (this is the default behavior). The RuntimeEvaluator will be responsible for allocating space for all the internal variables. The memory location of internal variables will be available after compilation by calling the GetInputVariableMemory and GetOutputVariableMemory methods.
Compiling
After setting up the desired properties for variables, the expression can be compiled:
ret = expression.Compile();
From now on, variable values can be set and the expression can be executed.
Executing
Before executing, values of each variable can be updated. External variable values are updated simply by updating the memory they have been set to follow:
float64 theta = 5.0;
ret = expression.SetInputVariableMemory("theta", &theta);
ret = expression.Compile();
theta = 10.0;
Internal variables can be modified by retrieving a pointer to them:
ret = expression.SetInputVariableType("theta", Float64Bit);
float64* ptr;
ptr = (float64*)expression.GetInputVariableMemory("theta");
*ptr = 10.0;
Each time the expression is executed, all output variables are updated. If they have been set external, their values are directly available:
float64 y;
expression.SetOutputVariableMemory("y", &y);
...
expression.Compile();
...
expression.Execute();
REPORT_ERROR(ErrorManagement::Information, "Value of y is: %f", y);
if they are internal, GetOutputVariableMemory
shall be used to
retrieve their addresses and obtain the final value.
Variable values can also be updated in the middle of the expression:
StreamString rpnCode = "READ A\n" // first
"READ B\n"
"ADD\n"
"WRITE A\n" // output variable A is set
"READ A\n" // second
"WRITE ret\n";
In the example above, the former READ A command reads from the input variable A, while the latter reads from the output variable A that was just set by the WRITE A command.
Variable values can be updated any time, and when invoked the
Execute()
method will recalculate the value of output variable
based on the current value of input variables.
Further details
RuntimeEvaluator is a stack machine that reads instructions from a stack machine expression and executes them in a last in-first out fashion. All operations rely on an internal stack: operands are retrieved from the stack and results are placed in the stack.
Working principle
The RuntimeEvaluator scans the input stack machine code and the variable
types. Combination of code and types during Compile()
produces
a list of calls to functions with specific types (the “pseudocode”)
that will be executed during Execute()
.
Functions that will be called must be present in the functionRecords
array, an array that holds all the available functions that
RuntimeEvaluator can call. functionRecords
is an array of
RuntimeEvaluatorFunction objects.
When the RuntimeEvaluator executes an operation, it actually calls the
corresponding function in functionRecords
, or better calls the
RuntimeEvaluatorFunction::ExecuteFunction()
method of that function
and passes itself to the method as the argument.
The operation is then executed by RuntimeEvaluatorFunction, which is
responsible for managing RuntimeEvaluator internal stack by using
Pop()
, Push()
and Peek()
methods. See
RuntimeEvaluatorFunction documentation for further details.
Supported operators
This is a table of all supported operators:
Operator |
Meaning |
---|---|
|
Pushes the value of variable var from memory to the top of the stack |
|
Pops the top of the stack and writes its value to variable var |
|
Push a constant of value val and type type to the top of the stack |
|
Casts the top of the stack to type type |
|
AND operation between top two elements of the stack |
|
OR operation between top two elements of the stack |
|
XOR operation between top two elements of the stack |
|
Greater than operation between top two elements of the stack |
|
Less than operation between top two elements of the stack |
|
Greater or equal operation between top two elements of the stack |
|
Less or equal operation between top two elements of the stack |
|
Equal operation between top two elements of the stack |
|
Not equal operation between top two elements of the stack |
|
Sum between top two elements of the stack |
|
Subtraction between top two elements of the stack |
|
Multiplication between top two elements of the stack |
|
Division between top two elements of the stack |
|
Sine operation on the top of the stack |
|
Cosine operation on the top of the stack |
|
Power operation between top two elements of the stack |
Hint
Comparison of floating-point types may often be implementation-dependent. The EQ and NEQ operators use the SafeMath::IsEqual() instead of the standard == operator in order to be safely portable: this implementation of the comparison operation takes into account the floating-point operand granularity (machine epsilon) to achieve a safely portable equality check.
Adding new functions
New operations can be made available to the RuntimeEvaluator by
adding a RuntimeEvaluatorFunction to the functionRecords
as follows:
void NewOperation(RuntimeEvaluator &evaluator) {
float32 x1,x2,x3;
evaluator.Pop(x1);
evaluator.Pop(x2);
x3 = x2 + x1;
evaluator.Push(x3);
}
TypeDescriptor types[] = {Float32Bit, Float32Bit, Float32Bit};
RuntimeEvaluatorFunction newAdd("NEWADD", 2, 1, types, NewOperation);
RegisterFunction(newAdd);
The function above pops two float32
element from the stack, sums them and then pushes the result to the stack. Upon calling the RegisterFunction
function, the function itself becomes available to the RuntimeEvaluator by using the command NEWADD
.
Examples
Example usage with the following expression:
y = pow(sin(theta), 2) + pow(cos(theta), 2)
(for a more extendive example see below).
#include "RuntimeEvaluator.h"
bool ret;
StreamString rpnCode = "READ theta\n"
"SIN\n"
"CONST int64 2\n"
"POW\n"
"READ theta\n"
"COS\n"
"CONST int64 2\n"
"POW\n"
"ADD\n"
"WRITE y\n"
;
RuntimeEvaluator expression(rpnCode);
ret = expression.ExtractVariables();
float64 theta;
float64 y;
if (ret) {
ret &= expression.SetInputVariableType("theta", Float64Bit);
ret &= expression.SetInputVariableMemory("theta", &theta);
}
if (ret) {
ret &= expression.SetOutputVariableType("y", Float64Bit);
ret &= expression.SetOutputVariableMemory("y", &y);
}
if (ret) {
ret = expression.Compile();
}
// now variable values can be set
theta = 3.14;
if (ret) {
ret = expression.Execute();
}
// result is now available
if (y == 1.0) {
REPORT_ERROR(ErrorManagement::Information, "OK!");
}
The following example shows how to use RuntimeEvaluator to perform runtime calculations.
1/**
2 * @file RuntimeEvaluatorExample1.cpp
3 * @brief Source file for class RuntimeEvaluatorExample1
4 * @date 01/07/2020
5 * @author Nicolo Ferron
6 *
7 * @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
8 * the Development of Fusion Energy ('Fusion for Energy').
9 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved
10 * by the European Commission - subsequent versions of the EUPL (the "Licence")
11 * You may not use this work except in compliance with the Licence.
12 * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
13 *
14 * @warning Unless required by applicable law or agreed to in writing,
15 * software distributed under the Licence is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
17 * or implied. See the Licence permissions and limitations under the Licence.
18
19 * @details This source file contains the definition of all the methods for
20 * the class LoggerServiceExample1 (public, protected, and private). Be aware that some
21 * methods, such as those inline could be defined on the header file, instead.
22 */
23
24#define DLL_API
25
26/*---------------------------------------------------------------------------*/
27/* Standard header includes */
28/*---------------------------------------------------------------------------*/
29
30/*---------------------------------------------------------------------------*/
31/* Project header includes */
32/*---------------------------------------------------------------------------*/
33#include "AdvancedErrorManagement.h"
34#include "ErrorLoggerExample.h"
35#include "MathExpressionParser.h"
36#include "RuntimeEvaluator.h"
37#include "StreamString.h"
38
39/*---------------------------------------------------------------------------*/
40/* Static definitions */
41/*---------------------------------------------------------------------------*/
42
43/*---------------------------------------------------------------------------*/
44/* Method definitions */
45/*---------------------------------------------------------------------------*/
46
47int main(int argc, char **argv) {
48 using namespace MARTe;
49 // Enables the REPORT_ERROR macro
50 SetErrorProcessFunction(&ErrorProcessExampleFunction);
51
52 bool ok;
53 StreamString expression, stackMachineExpr;
54 float64 F, m1, m2, G;
55 float64 sunMass = 1.988e+30;
56 float64 earthMass = 5.972e+24;
57 float64 moonMass = 7.346e+22;
58 RuntimeEvaluator *evaluator = NULL_PTR(RuntimeEvaluator *);
59
60 expression = "F = G*((m1*m2)/pow(d, 2));";
61
62 REPORT_ERROR_STATIC(ErrorManagement::Information,
63 "Expression:\n%s", expression.Buffer());
64
65 // RuntimeEvaluator accepts expressions in stack machine instructions
66 expression.Seek(0u);
67 MathExpressionParser mathParser(expression);
68 ok = mathParser.Parse();
69 if (ok) {
70 stackMachineExpr = mathParser.GetStackMachineExpression();
71
72 REPORT_ERROR_STATIC(ErrorManagement::Information,
73 "Expression in stack machine instruction form:\n%s", stackMachineExpr.Buffer());
74 }
75
76 // Instantiate the evaluator object
77 if (ok) {
78 evaluator = new RuntimeEvaluator(stackMachineExpr);
79 ok = evaluator->ExtractVariables();
80 }
81
82 if (ok) {
83 // Types of all variables must be specified
84 ok &= evaluator->SetOutputVariableType("F", Float64Bit);
85
86 ok &= evaluator->SetInputVariableType("G", Float64Bit);
87 ok &= evaluator->SetInputVariableType("m1", Float64Bit);
88 ok &= evaluator->SetInputVariableType("m2", Float64Bit);
89 ok &= evaluator->SetInputVariableType("d", Float64Bit);
90
91 // A variable can be set to an external location
92 ok &= evaluator->SetOutputVariableMemory("F", &F);
93
94 ok &= evaluator->SetInputVariableMemory("G", &G);
95 ok &= evaluator->SetInputVariableMemory("m1", &m1);
96 ok &= evaluator->SetInputVariableMemory("m2", &m2);
97 // Notice that address of variable d is not set and will have to be retrieved
98 }
99
100 // Now the expression can be compiled
101 if (ok) {
102 ok = evaluator->Compile();
103 }
104
105 // From now on, values can be updated at any time
106 if (ok) {
107 // External variables (G, m1, m2)
108 G = 6.674e-11;
109 m1 = earthMass;
110 m2 = sunMass;
111
112 // Internal variable (d)
113 *((float64*)evaluator->GetInputVariableMemory("d")) = 1.496e+11;
114 }
115
116 // The expession is executed and the value of F becomes available
117 if (ok) {
118 ok = evaluator->Execute();
119 }
120
121 if (ok) {
122 REPORT_ERROR_STATIC(ErrorManagement::Information,
123 "Avg. gravitational force between Sun and Earth is: %.4e N.", F);
124 }
125
126 // Values can be changed and the expression evaluated again
127 if (ok) {
128 m2 = moonMass;
129 *((float64*)evaluator->GetInputVariableMemory("d")) = 3.85e+8;
130 }
131
132 if (ok) {
133 ok = evaluator->Execute();
134 }
135
136 if (ok) {
137 REPORT_ERROR_STATIC(ErrorManagement::Information,
138 "Avg. gravitational force between Moon and Earth is: %.4e N.", F);
139 }
140
141 if (!ok) {
142 REPORT_ERROR_STATIC(ErrorManagement::FatalError, "Evaluation failed.");
143 }
144
145 return 0;
146}
Instructions on how to compile and execute the example can be found here.