EvalEngine - axkr/symja_android_library GitHub Wiki
The org.matheclipse.core.eval.EvalEngine
class in Symja is responsible for the evaluation of expressions. The main methods for evaluating an expressions are evaluate(final IExpr expr)
and evaluateNIL(final IExpr expr)
.
The evaluation process:
If the expression is a literal value, e.g. Integer
, String
, Complex
, etc. then leave it unchanged and return.
Evaluate the head()
of an expression and get its attributes.
Depending on the following attributes:
various arguments are evaluated. However, arguments that have the form:
also specify which arguments are evaluated or not before rewriting and function application.
At the end of this, the internal variables head, attributes
(from head), and elements (of the expression) are set.
Building a New Expression
Construct a new expression using information from the head and the elements gathered in the previous step.
The substeps here are:
- Attempt to flatten sequences in the expression unless the
SequenceHold
orHoldAllComplete
attributes are set in theHead
. - Change
Unevaluated(expr)
toexpr
but mark the expression as being unevaluated. - Flatten expressions involving nested functions if the
Flat
attribute was found in theHead
. - Sort elements if the
Orderless
attribute was found in theHead
.
Setting Up Thread Rewrite for Listable Functions
Threading is needed when the head has the Listable
attribute. The method EvalAttributes.threadList()
rewrites the expression: F({a,b,c,...})
as: {F(a), F(b), F(c), ...}
.
Searching for a Rule in Head to Apply
Search for a rule in Head
that matches the expression.
Applying Rule or Restoring Expression
If a rule was found, apply it getting back an evaluated expression. If the expression is unchanged, return the special value F.NIL
.
Builtin-function application
For builtin-functions the first element (i.e. argument-0), often referred to as the "head" (or Head()
) of an IAST
(Symja's equivalent for function representation), is an IBuiltInSymbol
.
When there are other elements (arguments 1 to n), the expression is assumed to be a Symja function call, where the function name comes from the head. If this is a built-in function, like Plus, Power, Times, ...
the Symja function name is the name of a Java class derived from IFunctionEvaluator
. These Symja function-like classes are described in later sections.
As described in the previous section, before invoking that Symja function, we need to check for a rewrite rule that applies to the Symja function call. If a rule is found, it will return the evaluated right-hand-side of that rule.
These rules get created on loading the module containing a subclass of IFunctionEvaluator
implementing some Symja primitive function. The rules come from a file with the extension *.m
which is translated to Java before the compile time.
When writing a new builtin function, there is an evaluate()
method.
Here is an example for the DiagonalMatrix
primitive taken from the code
private static class DiagonalMatrix extends AbstractFunctionEvaluator {
@Override
public IExpr evaluate(final IAST ast, EvalEngine engine) {
...
...
// the return value F.NIL has the special meaning "return unevaluated":
return F.NIL;
}
@Override
public int[] expectedArgSize(IAST ast) {
// this function allows one or two arguments
return ARGS_1_2;
}
@Override
public void setUp(final ISymbol newSymbol) {
// if necessary set attributes here:
// newSymbol.setAttributes(ISymbol.FLAT | ISymbol.ONEIDENTITY);
}
}
The evaluate()
method above will get called when finding an IAST
whose Head
value is DiagonalMatrix
.
Thread Safety in EvalEngine
The EvalEngine
class (or classes which delegate to EvalEngine
) in Symja is not thread-safe. This means that it cannot be used concurrently by multiple threads without external synchronization. Here are the reasons why EvalEngine
is not thread-safe:
Shared Mutable State
EvalEngine
maintains a mutable state that is shared among all threads. This includes variables, rules, and other settings. If multiple threads modify this state concurrently, it can lead to inconsistent and unpredictable results. This is a classic problem in concurrent programming known as a race condition.
Lack of Synchronization
EvalEngine
does not use any synchronization mechanisms (like synchronized blocks or Lock objects) to control access to its internal state. This means that multiple threads can access and modify the state simultaneously, leading to data races and other concurrency issues.
Thread-Local Storage
EvalEngine
uses thread-local storage for some of its data, which means that each thread has its own copy of this data. While this can help to avoid some concurrency issues, it also means that changes made by one thread are not visible to others. This can lead to inconsistencies if threads need to coordinate their actions or share results.
Non-Atomic Operations
Some operations in EvalEngine
are not atomic, meaning they involve multiple steps that should be completed as a single operation. If another thread interrupts these operations, it can leave the EvalEngine
in an inconsistent state.
In conclusion, while EvalEngine
is a powerful tool for symbolic computation, it is not designed to be used in a multithreaded environment without additional measures to ensure thread safety. If you need to use EvalEngine
in a multi-threaded application, you should ensure that access to the engine is properly synchronized, or consider using separate EvalEngine
instances for each thread.