-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure compile_ufl output has correct free indices #237
Conversation
c84c379
to
f8a89d2
Compare
Sadly it seems that quite a lot of |
f8a89d2
to
340f92b
Compare
340f92b
to
00c2d54
Compare
This removes a shortcut in delta which would return gem.Literal(1.) for Delta(j, j). This shortcut stopped IndexSum(Delta(i, i), i) doing the right thing since it stopped i from being a free index of the expression produced by Delta. Deltas with repeated indices are now replaced with gem.Literal(1.) by gem.optimise.replace_indices_delta.
00c2d54
to
63fe989
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor fixes
Very minor, your commit message for the compile_ufl change:
I think it does fix it, so remove "This attempts to fix that" :). |
Can you add a test that this is doing TRT? e.g. that |
I'm not quite sure of a nice way to set up such a test. Because of the weird interface that |
When an expression given to compile_ufl does not have points in the input PointSet in its expression tree, the resulting gem expression has missing free indices. This fixes that.
63fe989
to
d711e72
Compare
OK, I see. yes I think that's ok. |
This feels to me like scratching your face with your elbow behind your back. Surely, the right way would be that the caller calmly accepts the fact, that the compiled expression may be constant along some of the point set indices, and then it deals with the situation accordingly. Leaving "mathematical one" nodes in the expression with fake index dependencies does not sound like a clean solution. |
So there are two parts here. The early simplification of I agree that using |
Are you advocating not changing the behaviour of |
@dham was the person who first strongly advocated that this was a bug - he may want to add his thoughts |
I suppose
Perhaps. Where is it used? See more in another answer below. |
I would need to see how you exactly use |
I think the |
See this comment in FInAT/FInAT#57 which I am trying to tidy and land. I am currently putting together a tensor of weights If the It seems to me better to get |
Well, yes and no. From a formal point of view, you are right, since To apply optimisations, that invariably simplify the expression, as early as possible, is usually the right thing to do. The benefits are potentially unlocking further optimisations after simplification (which means better optimised output code), and less work for further traverals to do, due to the smaller resulting expression (which means faster compilation). Currently, for interpolation, we just let assignment do the broadcasting. Form assembly with fancy optimisations can be even more complicated. For example, the mass matrix of a vector-CG element is the outer product of the scalar mass matrix with an identity matrix, which means we only broadcast along the diagonal loop, but not onto the entire (dense) result tensor. Is this new use case somehow more special than the previous ones? |
I don't really see why some missing indices would get in the way of some tensor contraction. Since you say in the linked comment that |
Hmmm. The problem is that you get intermediate values that don't have the expected shape, and you then have to somehow implicitly know what has disappeared. In this case, compile_ufl is being used to evaluate an expression at a bunch of points. The expression might also have shape because it's vector or tensor-valued. If some of the shape/indices disappear because of this sort of index cancellation, how do I safely know which indices have disappeared? What if it's a 3D vector valued expression being evaluated at 3 points? I think for this to be safe you have to have explicit broadcasting of some sort. You can then drop the broadcasts before you go to Impero in order to avoid loops, but while you're manipulating GEM I think you need to maintain consistent shapes. |
The problem is that you get intermediate values that don't have the
expected shape, and you then have to somehow implicitly know what has
disappeared. In this case, compile_ufl is being used to evaluate an
expression at a bunch of points. The expression might also have shape
because it's vector or tensor-valued. If some of the shape/indices
disappear because of this sort of index cancellation, how do I safely
know which indices have disappeared? What if it's a 3D vector valued
expression being evaluated at 3 points?
I think we shall consider whether shapes (say, `A` being a 3 x 3 matrix)
and free indices (say, `e` being a scalar expression with free indices
`i` and `j` which both have extent 3) are equivalent and interchangeable,
or somehow different. Considering an expression in isolation, these two
ways of looking at tensor nature seem interchangeable, for they are
convertible: you can wrap up free indices into a tensor shape with
`ComponentTensor` and you can turn tensor shapes into scalars with free
indices by indexing (`Indexed`) them with ‘free’ (i.e. loop) indices.
This consideration, however, raises the question, why do we have both if
they are both expressing the same thing? Is it not redundant to have
both? Is it just an artefact of coming from UFL, which also has both?
There is more to it than that.
Shapes and free indices differ significantly when we look at them under
the aspect of substitutability (Ersetzbarkeit) in a surrounding
expression. Consider an expression such as Ax, uA, or uAx, where A is a
matrix, and u and x are vectors. Then, say, we are able to reduce the
rank of A (to a vector or a scalar), and we want to substitute this
“optimised” A into the original expression. This cannot be done
correctly without updating the surrounding vector operations, that is
whether they are an inner product, outer product, MatVec, MatMult,
multiplication by scalar etc.
This is not a problem, however, for free indices, since indices are
explicitly named rather than merely having an implicit order. Consider
u_i * A_{ij} * x_j. If manage to reduce A’s rank, we can simply
substitute the result back, and u_i * a_i * x_j, u_i * a_j * x_j, or u_i
* a * x_j are all correctly substituted expressions because the matching
indices were explicitly named. It could be that the result of such a
substitution gives reduced rank in the outer expression too, but that
just means we’ve done even more optimisation.
This is why GEM is absolutely strict about shapes; a scalar [shape =
()], a vector of length 1 [shape = (1,)], and a 1 x 1 matrix [shape =
(1, 1)] are all distinct shapes, and they are in no way interchangeable
(unlike MATLAB, for example). Free indices, however, are handled in a
much more loose way. In fact, the reason GEM exists, and I could not
just use a subset of UFL was that in UFL, substituting something with
additional free indices (say, a coefficient with something that depends
on the quadrature index) may be illegal, because in UFL free indices are
really just a different way of looking at shapes.
So the convention I have usually used in API design was to define as
shape that which is absolutely non-negotiable, and define as free
indices those which are possible dependencies on loop indices.
If some of the shape/indices disappear because of this sort of index
cancellation, how do I safely know which indices have disappeared?
For shapes, this ought not happen, because that’s problematic. For free
indices: they are “named”, so you can just look at the expression and
tell which ones have disappeared.
What if it's a 3D vector valued expression being evaluated at 3
points?
It could be a reasonable approach to define the 3D vector value (or
whatever shape it has) as the shape of the expression, and communicate
the point dependency as free indices, so they can disappear as a
dependency if optimisation was successful.
|
We have come to the conclusion that this PR is not necessary, but that the behaviour of GEM with respect to shape being sacred and indices being discardable needs to be documented in a design document for TSFC. I will make an issue. |
expr, = fem.compile_ufl(expression, **config, point_sum=False) | ||
# the point set free indices should now be free indices of the compiled | ||
# expression | ||
assert set(point_set.indices) <= set(expr.free_indices) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wence- there was some discussion yesterday of adding some kind of assertion along these lines, but I can't work out what it should be. Given that the point set indices can disappear, and we don't know what other free indices there might be at this point, what's there left to assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What other free indices might there be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think expr.free_indices \in point_set.indices
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if your expression has an argument there is a free index for that. Might be remembering wrong though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, so what we're expecting is expr.free_indices <= set(chain(point_set.indices, *argument_multiindices)))
I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #241
When an expression given to compile_ufl does not have points in the
input PointSet in its expression tree, the resulting gem expression has
missing free indices. This attempts to fix that.
Todo: