-
Notifications
You must be signed in to change notification settings - Fork 83
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
Get rid of threading and updates to form API's #317
Conversation
I'll also do some experiments with different form API's discussed in #101 but won't promise anything. |
This would be really good. I find it very difficult to debug forms because |
Previously Element.gbasis was returning either 2-tuple or 3-tuple depending on the type of element and its order-attribute. This return value was unpacked and fed to the (bi)linear forms written by the user. For simplifying the return value and enabling experiments with more generic form definitions, we introduced a NamedTuple with optional attributes f, df, ddf (and possibly more added later). This NamedTuple is called DiscreteField and represents a global discrete finite element function (and its derivatives) evaluated at the quadrature points. Small backwards-incompatibility was introduced by the respective changes in Basis.interpolate which now returns DiscreteField object (a NamedTuple with 3 values, i.e. 3-tuple) instead of 2-tuple and some code might depend on that behavior. In the test suite there was only two small changes due to this.
These latest additions allow supporting quite easily this Fenics-type notation: from skfem import *
m = MeshTri()
e = ElementTriP1()
b = InteriorBasis(m, e)
from skfem.assembly.form.bilinear_form import BilinearForm
@BilinearForm
def mass(u, v, w, dx):
return u * v * dx
def inner(u, v):
return u[0] * v[0] + u[1] * v[1]
def grad(u):
return u[1]
@BilinearForm
def dudv(u, v, w, dx):
return inner(grad(u), grad(v)) * dx
A=asm(mass, b)
B=asm(dudv, b) I don't know if it makes sense or not. I don't have much experience with Fenics. Do you think it would make sense from the viewpoint of getting more users try this library? |
What I'm proposing in particular is to add this kind of "Fenics compatibility layer" so that the user could do @BilinearForm
def bilinf(u, v, w, dx):
from skfem.compatibility.fenics import grad, inner
return inner(grad(u), grad(v)) * dx |
Very interesting. More generally I like the idea of having multiple constructors for an internal representation of forms. (This would mean defining e.g. FEniCS/ufl is a substantial effort in how to write these things down. (Other notable ones being FreeFEM and One drawback of UFL (or anything similar) over something more indicial like Does anything ever get done with the |
Now dx is not passed to the form because it is not really used. Currently using @BilinearForm and @LinearForm instead of @bilinear_form and @linear_form use the new set of arguments: u, v, w and v, w. I want to still experiment with changing w to use also DiscreteField. |
I tried to look at UFL docs here: https://fenics.readthedocs.io/projects/ufl/en/latest/manual/internal_representation.html but as you notice they don't help too much in understanding how to use it somewhere else than in Fenics. I'll try to install it after writing this message and play around with it. I'm not going to immediately add any dependency but just look into if and how it could be incorporated in a compatiblity layer. @gdmcbain In @BilinearForm
def bilinf(u, v, w):
return u.df[0] * v.df[0] + u.df[1] + v.df[1] or even @BilinearForm
def bilinf(u, v, w):
return u[1][0] * v[1][0] + u[1][1] + v[1][1] but I guess we would want to use something like @BilinearForm
def bilinf(u, v, w):
from skfem.helpers import d, dot
return dot(d(u), d(v)) to abstract out the attributes and indices, so basic use cases won't need them. Note that it's also possible to use the 'classical' syntax by using By design of skfem, these attributes can mean a bit different things for different types of |
It seems to me that this FFC (Fenics Form Compiler) is used to turn UFL forms into C-code. I hope we don't need to do anything as excessive. |
Suprisingly enough, I was able to do this: from skfem import *
m = MeshTri()
e = ElementTriP1()
b = InteriorBasis(m, e)
from ufl import *
element = FiniteElement("CG", triangle, 1)
U = TrialFunction(element)
V = TestFunction(element)
def fenics_form(form):
def new_form(u, v, w):
def eval_u(x, derivatives=None):
if derivatives is None:
return eval_u.c.f
d, = derivatives
return eval_u.c.df[d]
eval_u.c = u
def eval_v(x, derivatives=None):
if derivatives is None:
return eval_v.c.f
d, = derivatives
return eval_v.c.df[d]
eval_v.c = v
return form(U, V)(None, {U: eval_u, V: eval_v})
return new_form
@BilinearForm
@fenics_form
def mass(u, v):
return u * v
@BilinearForm
@fenics_form
def stiffness(u, v):
return dot(grad(u), grad(v))
M=asm(mass, b)
K=asm(stiffness, b)
(M, K) and it seems to work! |
Basically, when evaluating UFL forms, you just need to provide instructions how to evaluate each symbolic function in the form of a function handle. This probably incurs quite a bit of extra function evaluations but at least we have a proof-of-concept now. |
When porting ex04 to use the new style forms, I realized that having |
Figured out how to bypass that using |
An overview of major internal changes:
@BilinearForm
def bilinf(u, v, w):
return u * v
|
Suprisingly it seems that assembling weak Laplacian got a bit faster:
It's not entirely clear to me why this happened but still a positive thing I suppose. |
Old:
New:
|
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.
Looks good! And a nifty speed-up to boot!
I propose that we simplify the code and get rid of
threading
since it's basically never used and doesn't provide too great improvements to execution time even if someone would use it.