-
Notifications
You must be signed in to change notification settings - Fork 25
Tidy up interpolation tsfc<->firedrake interface #233
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Conversation
So my broad impression is that we want to make |
Suppose I give you just the generated kernel (the loopy thing). And I say "this kernel matches up with interpolating this expression". And then the only thing I permit you to ask of the expression is "what are your coefficients" and "what is your domain". What information do I need to construct the parloop that executes the kernel? |
I would need to match the domain to a PyOP2 iteration set, match the coefficients to the par loop arguments (which we ought to do with something like Sound right or wrong? Also this seems to be an answer to a different question - I am, as far as I'm aware, not in a situation where I have such a kernel - rather I'm trying to work out how you build one. The answer to your question seems one step ahead. |
OK, I see. Basically, inside TSFC, if you want to disentangle the symbolics from the numerics you're only allowed to called UFL methods on the objects. IOW, you want to get to a situation where if you just had a fully symbolic expression, from ufl import *
from tsfc.finatinterface import create_element
V = VectorElement("P", triangle, 1)
mesh = Mesh(V)
V = FunctionSpace(mesh, FiniteElement("P", triangle, 2))
x, y = SpatialCoordinate(mesh)
expr = x*y - y**2 + x
element = create_element(V.ufl_element())
stuff = compile_expression_dual_evaluation(expr, element, coffee=False) |
This example should fail I believe - a function of spatial coordinates surely requires the coordinates data. |
Only when executing, not when compiling. |
So I can execute this import ufl
from tsfc.finatinterface import create_element
from tsfc import compile_expression_dual_evaluation
def test_ufl_only_dependence():
V = ufl.VectorElement("P", ufl.triangle, 1)
mesh = ufl.Mesh(V)
V = ufl.FunctionSpace(mesh, ufl.FiniteElement("P", ufl.triangle, 2))
v = ufl.Coefficient(V)
expr = ufl.inner(v, v)
element = create_element(V.ufl_element())
ast, oriented, needs_cell_sizes, coefficients, _ = compile_expression_dual_evaluation(expr, element, coffee=False) and the |
I think we are talking at cross-purposes. I claim that for generation of an interpolation kernel (which is what this is doing), I should need no global (Firedrake-level) information. But rather I should only need symbolic information (what the elements are, what the expression is, etc...). I proposed one way of checking if this is the case right now is to feed purely symbolic objects (from UFL) into |
Okay - in a discussion with @dham he conjectured that if my expression is one of spatial coordinates or non-trivial pullback, I should need global domain information from the Put another way, how do I deal with
(permalink)? Does a correctly refactored |
It needs a symbolic equivalent of that coefficient. A You shouldn't global domain information (numbers) when you're compiling a kernel. You only need the symbolic information that tells you how numbers are to be interpreted. Line 136 in b043e86
What does that function do? tsfc/tsfc/kernel_interface/firedrake_loopy.py Lines 221 to 229 in b043e86
So it makes a symbolic representation of the coordinate field from the mesh's coordinate element. No Firedrake object required. |
Sorry, edited my comments for tetchiness. |
First step towards dealing with #232. Use fake coordinates coefficient when required instead of data carrying .coordinates firedrake Function.
Tests for non-dependence of compile_expression_dual_evaluation on numerics by checking it works with purely symbolic expressions.
728e728
to
649a451
Compare
Okay so it looks like I've got everything compiling using UFL only. Now I need to work out how to get all the necessary coefficient processing information (which I need, do I need to split coefficients) out of |
So that caller can replace if necessary
44f19bc
to
ed89971
Compare
This now works - I wonder if there's a better way of dealing with the coordinates coefficient but perhaps this is good enough for now? |
@@ -267,7 +267,7 @@ def name_multiindex(multiindex, name): | |||
return builder.construct_kernel(kernel_name, impero_c, index_names, quad_rule) | |||
|
|||
|
|||
def compile_expression_dual_evaluation(expression, to_element, coordinates, *, | |||
def compile_expression_dual_evaluation(expression, to_element, *, |
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.
to_element
should eventually become a UFL cofunction whenever they are implemented
"""Constructs an :class:`ExpressionKernel`. | ||
|
||
:arg return_arg: loopy.GlobalArg for the return value | ||
:arg impero_c: gem.ImperoC object that represents the kernel | ||
:arg index_names: pre-assigned index names | ||
:arg first_coefficient_fake_coords: If true, the kernel's first | ||
coefficient is a constructed UFL coordinate field |
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.
TBH, this is a horrible variable name. I think what this means is that the kernel expects to be fed:
[mesh.coordinates] + extract_coefficients(expression)
Is that right?
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.
Didn't see this comment in time. Yes that is correct
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.
If you have a suggested better interface and/or variable name to say and I can make a new PR
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 in various places in Firedrake we have similar variables to this e.g. for cell sizes. They are called something like needs_cell_sizes
, maybe you just want to rename to needs_coords
.
builder.domain_coordinate[domain] = coords_coefficient | ||
builder.set_cell_sizes(domain) | ||
coefficients = [coords_coefficient] + coefficients | ||
first_coefficient_fake_coords = True | ||
builder.set_coefficients(coefficients) | ||
|
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.
The way coordinate
is taken care of in the above is identical to how we do in KernelBuilder.set_coordinate
, so maybe one potential future change is to make set_coordinate
method also available in ExpressionKernelBuilder
.
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 strongly considered doing this, but didn't want to make a "fake" version of that interface without recreating it more fully. Hopefully once your changes to that interface have landed this can be given another spin.
@@ -173,7 +175,8 @@ def construct_kernel(self, return_arg, impero_c, index_names): | |||
loopy_kernel = generate_loopy(impero_c, args, self.scalar_type, | |||
"expression_kernel", index_names) | |||
return ExpressionKernel(loopy_kernel, self.oriented, self.cell_sizes, | |||
self.coefficients, self.tabulations) | |||
self.coefficients, first_coefficient_fake_coords, |
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.
Another thing I think we should think about next time is how to make ExpressionKernelBuilder
more symmetric to KernelBuilder
. I think instead of returning coefficients
here, we should just call extract_coefficients(expr)
again in Firedrake (this compares to form.coefficients()
called in assemble.py
) and split Function
s if necessary when setting up ParLoop
arguments (again, as is done in assemble.py
).
See issue #232. Coupled with firedrakeproject/firedrake#1861. Please feel free to bikeshed this whilst it's WIP!