Description
Motivation
Currently, the semantics of the C++ library is such that, from the user's perspective, computations always appear as if they are evaluated at the point of assignment:
Tensor<double> a, b;
a(0) = 1.0;
a(1) = 2.0;
b = a(i);
std::cout << b << std::endl; // Prints out 3
a(2) = 3.0;
std::cout << b << std::endl; // Still prints out 3, since `a` is modified after `b` has already been assigned
This makes sense when a user is relying on the lazy evaluation mechanism to trigger computations. However, this is not as intuitive when computations are triggered by explicitly invoking evaluate
(or assemble
and compute
), since users tend to expect that the computation will actually be (re)performed whenever evaluate
is invoked:
Tensor<double> a, b;
a(0) = 1.0;
a(1) = 2.0;
b = a(i);
b.evaluate();
std::cout << b << std::endl; // Prints out 3
a(2) = 3.0;
b.evaluate();
std::cout << b << std::endl; // Still prints out 3, since `a` is modified after `b` has already been assigned
Proposed Changes
This RFC proposes that users be able to specify whether computations should semantically happen at the point of assignment or whenever evaluate
/assemble
/compute
is explicitly invoked. Specifically, the C++ library would expose a new global function:
void setEvaluateAtAssign(bool evalAtAssign);
which the user may invoke before any computation is defined. If setEvaluateAtAssign
is invoked with the argument evalAtAssign
being true, then any computation that is defined afterwards will appear as if it is evaluated at the point of assignment (i.e., as in the examples above). On the other hand, if setEvaluateAtAssign
is invoked with the argument evalAtAssign
being false, then any computation that is defined afterwards will be (re)performed whenever evaluate
/assemble
/compute
is explicitly invoked:
Tensor<double> a, b;
setEvaluateAtAssign(false);
a(0) = 1.0;
a(1) = 2.0;
b = a(i);
b.evaluate();
std::cout << b << std::endl; // Prints out 3
a(2) = 3.0;
b.evaluate();
std::cout << b << std::endl; // Prints out 6
A prototype of this RFC is implemented in the eval_at_assign
branch of the repo.
This feature can be particularly useful in, for instance, iterative applications where the structure of the output does not change (I believe this is the use case @stkaplan was interested in?):
Tensor<double> y, x, A;
y(i) = A(i,j) * x(j);
y.assemble();
while(...) {
// Make some modifications to x...
x.insert(...);
// Recompute y with modified x
y.compute();
}
As pointed out in #408, this feature can also be useful for benchmarking.