-
-
Notifications
You must be signed in to change notification settings - Fork 213
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
PDESystem boundary specification #526
Comments
Probably good to add @kburns here as well, |
On the spectral side, Dedalus requires that the boundaries be coordinate level sets. So for a disk, polar coordinates are used and the boundary conditions are specified at fixed polar radius ( This works well for the simple domains we handle with global spectral bases (intervals, disks, annuli, spheres, spherical shells, balls, etc.) and products of such. Special attention is needed at the discretization level to deal with corners and coordinate singularities, though. Smooth deformations of those domains can also be defined and incorporated into the PDE, so you can do things like ocean bottom topography by using stretched / terrain-following coordinates. But for anything more complicated, we usually switch to immersed boundary methods. |
Because boundary conditions can be defined at any point in the domain, not just at the outer boundaries, I believe the best way to express them is through implicit expressions. For example:
Unfortunately, it becomes more complex when something like this is needed:
|
Also, if you have a point set (e.g. 3d reconstruction of an airplane wing) through which you can create a surface (or volume), you may need something like this:
|
I would suggest allowing a more "vector-like" representation, while still allowing the existing style for existing domains. xvec = [x,y]
domain = xvec in UnitSquareDomain()
domains = [xvec in domain]
S = surface(domain)
# Existing example
# S[1], ..., S[4] pre-defined and documented for different domain geometries
bcs = [u(S[0]) ~ 0.f0, u(S[1]) ~ -sin(pi*1)*sin(pi*y),
u(S[2]) ~ 0.f0, u(S[3]) ~ -sin(pi*x)*sin(pi*1)]
u(S[0]) == u(x,0) # true
# Dirichlet everywhere
bcs = [u(S) ~ 0.f0]
# Neumann everywhere
n = unit_normal(domain)
bcs = [grad(u(S)).n ~ 0.f0] Spherical example @parameters r
domain = AxisymmetricSphereDomain(0,1)
domains = [r in domain]
S = surface(domain)
n = unit_normal(domain)
# Dirichlet everywhere
bcs = [u(S) ~ 0.f0]
# or equivalently
bcs = [u(1) ~ 0.f0]
# Neumann everywhere
bcs = [grad(u(S)).n ~ 0.f0]
# or equivalently
bcs = [Dr(u(1)) ~ 0.f0] For more complex geometries, in the python-based FEM packages they usually create the domain (and mesh) in a GUI, label the domain boundaries 1-n, then in the code specify what bc to apply on each boundary |
The suggested syntax is in the WIP part of the documentation, i.e. https://mtk.sciml.ai/dev/systems/PDESystem/#Domains-(WIP)-1 t ∈ IntervalDomain(0.0,1.0)
(t,x) ∈ UnitDisk()
[v,w,x,y,z] ∈ VectorUnitBall(5) with a suggested form for LevelSet for now as well. That seems to be equivalent to what @tinosulzer suggested. Now the difficulty here is that each of these objects will need to have an equivalent object for I don't think bcs = [(u(x,y,z,t) ~ 0.f0, (x,y) ∈ boundary(d1), z ∈ boundary(d2))] Even worse, we will need to handle: bcs = [(u(x,y,z,t) ~ 0.f0, (x,y,z) ∈ boundary(d[1])] and such, since there may be multiple boundaries that for a given domain. I like the S = (x,y,z)
n = unit_normal(domain)
grad = Differential(n)
bcs = [(grad(u(S)) ~ 0.f0, S ∈ boundary(d))] seems reasonable. |
There is some initial support for julia> boundary(UnitDisk()) == UnitCircle()
true And it is a goal to make this work in more cases, though right now in spite of its name DomainSets does not actually define a great number of domains yet :-) It would probably need polygons and polytopes in particular. As mentioned above, this approach will likely remain restricted to fairly simple domains and combinations of them. Still, getting all the feasible simple combinations right, with minimal assumptions on later usage, is within scope. Right now, I'm aiming to make DomainIntegrals useful for DomainIntegrals.jl, for the evaluation of integrals on general domains. This means that I am thinking about ways of automatically generating parameterizations in terms of standard intervals and boxes, and it looks like that might work soon. I have not given any thought to friendly notations like shown above. At one point we did have a basic PDE solver using domains from DomainSets: example notebook (probably no longer functional). Nothing here is symbolic or descriptive, it starts from a specific basis of a certain length (like a Fourier series), and everything later on are operators acting on the coefficients in that basis. Still, this solver only required generating points on the boundary, which was relatively straightforward to do. Continuous representations are a little harder. |
To give you an idea, right now I get something like this: julia> d = ProductDomain(0..1, UnitDisk(), 2..3.0)
0.0..1.0 x the 2-dimensional closed unit ball x 2.0..3.0
julia> boundary(d)
the union of 3 domains:
1. : the union of 2 domains:
1. : Point{Float64}(0.0)
2. : Point{Float64}(1.0)
x the 2-dimensional closed unit ball x 2.0..3.0
2. : 0.0..1.0 x the unit circle x 2.0..3.0
3. : 0.0..1.0 x the 2-dimensional closed unit ball x the union of 2 domains:
1. : Point{Float64}(2.0)
2. : Point{Float64}(3.0) The output is messed up, but the result is some kind of union of product domains. The boundary of an interval is currently a union of |
Awesome, that sounds great. And |
Well, glad that this may be helpful! For historical reasons I tend to use julia> d = ProductDomain(0..1, UnitDisk(), 2..3.0)
julia> bnd = boundary(d);
julia> element(bnd, 1)
the union of 2 domains:
1. : Point{Float64}(0.0)
2. : Point{Float64}(1.0)
x the 2-dimensional closed unit ball x 2.0..3.0
julia> element(bnd, 2)
0.0..1.0 x the unit circle x 2.0..3.0
julia> element(bnd, 3)
0.0..1.0 x the 2-dimensional closed unit ball x the union of 2 domains:
1. : Point{Float64}(2.0)
2. : Point{Float64}(3.0)
julia> element(element(bnd, 3), 3)
the union of 2 domains:
1. : Point{Float64}(2.0)
2. : Point{Float64}(3.0) It would probably be better to turn a product domain of unions into a union of product domains, I'll look into that. Still, for boxes and cubes the output is quite horrible at the moment with all the |
What is getindex used for there? |
It is not used in this package, but the way it was designed anything that supports |
It would make a lot of nice things like broadcast work. |
I've opened a separate issue for the julia> [0.5, 0.2, 3] .+ 2*UnitBall()
A mapped domain based on the 3-dimensional closed unit ball Here we think of a domain as being a continuous set of points, and translation is like element-wise addition. I'm mentioning the syntax because it may be relevant in this issue, and suggestions for user-friendly creation of domains are very welcome! I've been meaning to explore something like [ [0.5, 0.2, 3] + 2*x for x ∈ UnitBall()] That seems closer to the kind of syntax we're looking for here. Apart from |
Hmm, that's an interesting use case for that. Cool! |
Awesome. I think we have a general plan. We just need to get the PINNs and MOLFiniteDifference on it in order to solidify it. |
@daanhb how would you do the equivalent of this using domain = IntervalDomain(0.0, 1.0)
domain.lower
domain.upper and, how would you define a |
Hi @tinosulzer, the equivalent would be: julia> using DomainSets
julia> d = 0..1.0
0.0..1.0
julia> infimum(d)
0.0
julia> supremum(d)
1.0
julia> DomainSets.endpoints(d)
(0.0, 1.0)
julia> 2.5 * UnitDisk()
2.5 * UnitDisk()
julia> 2.5 * UnitDisk() .+ [0.5,0.1]
2.5 * UnitDisk() .+ [0.5, 0.10000000000000002]
julia> typeof(ans)
MappedDomain{StaticArrays.SVector{2, Float64}, DomainSets.GenericAffineMap{StaticArrays.SVector{2, Float64}, Float64, StaticArrays.SVector{2, Float64}}, StaticUnitBall{StaticArrays.SVector{2, Float64}, :closed}} Some comments:
The release of DomainSets 0.5 is imminent and it will be a lot more robust. The set of domains it actually supports right now is still pretty limited, but it can be extended either in the package or elsewhere - again, feel free to file issues! |
What other domains are there to include? I see a circle and product domain at least in domains.jl. The The interval notation Another pitfall is that There is a Coming back to your question, recovering the radius of a circle is not actually possible, weirdly enough. Currently you'd have to check the mapped domain to see whether the domain being mapped is a circle, whether the map is linear and diagonal, and whether all diagonal entries are equal... :-) Is there anything I can do here to help moving forward? |
Thanks! For now I am happy with just cuboids, disks and spheres. I like just being able to use For the circle, I'm feeling stupid for asking how to change the radius of a unit disk haha. But it would definitely be useful to have a more general
why not? |
Yeah,
It might not be a problem in practice, depending on what interface you provide here. But I'll add a ball/sphere with radius and center in DomainSets 0.5.1 because it is such a common case (v0.5 is out today). Perhaps the type can stay the same after scaling and translation, because that would preserve the shape. |
Hi there 👋 I'm coming late to the party but.. Note that some of the groundwork in LazySets.jl could be applied to this issue (and vice-versa: we're interested in PDE modeling combined with set-based techniques in Julia so I'm interested to follow and maybe at some point participate in this issue). There is an upcoming JuliaCon workshop for our JuliaReach stack [1]. We've been using [1] https://github.com/JuliaReach/JuliaCon-2021-Workshop-Its-All-Set |
There are some PDEs with integer-valued independent variables which are interesting, like the chemical master equation.
Where should I be looking for the high level overview of the domain interface? |
You could check [1-3]. Now let me state upfront that specialized algorithms to efficiently iterate over complex domains (say, a finite element mesh) are not the focus of LazySets.jl -- the focus is on writing new algorithms for set computations, conversions, approximations, and so on (especially in dimensions higher than 3). The library Meshes.jl will be much better suited for the purpose of mesh generation, exploration, and computational geometry à la CGAL. [1] https://juliareach.github.io/LazySets.jl/dev/man/tour/#A-Tour-of-LazySets |
I don't think we're completely set until we're settled and using the boundary specifications as well: intervals and boxes can be easy on the boundary specifications but things like circles, not so much. |
I think the suggested syntax above should work, in which a variable is declared to be associated with a domain, but indeed it may require some experimenting to get the non-product domains right. It seems easier to use I found that you have some control over this when using the native generator syntax, and I can use this to show what I mean. The generator does not associate a variable with a domain, but it associates a function with a domain. There are several scenarios: julia> using DomainSets
julia> gen = (exp(x) for x in Interval(2,3))
Base.Generator{ClosedInterval{Int64}, typeof(exp)}(exp, 2..3)
julia> gen.f
exp (generic function with 15 methods)
julia> gen.iter
2..3 Here, the generator has two fields A function on a domain with product structure could be specified with a repeated for: julia> gen2 = (exp(x+y) for x in Interval(2,3.5), y in Interval(4, 5.5))
Base.Generator{Base.Iterators.ProductIterator{Tuple{ClosedInterval{Float64}, ClosedInterval{Float64}}}, var"#11#12"}(var"#11#12"(), Base.Iterators.ProductIterator{Tuple{ClosedInterval{Float64}, ClosedInterval{Float64}}}((2.0..3.5, 4.0..5.5)))
julia> gen2.f
#7 (generic function with 1 method)
julia> gen2.f( (0.2, 0.3))
1.6487212707001282
julia> exp(0.2+0.3)
1.6487212707001282
julia> gen2.iter
Base.Iterators.ProductIterator{Tuple{ClosedInterval{Float64}, ClosedInterval{Float64}}}((2.0..3.5, 4.0..5.5))
julia> D = ProductDomain(gen2.iter.iterators...)
(2.0..3.5) × (4.0..5.5)
julia> p = point_in_domain(D)
2-element StaticArrays.SVector{2, Float64} with indices SOneTo(2):
2.75
4.75
julia> gen2.f(p)
1808.0424144560632 Here, the function Alternatively, you can work with vectors to support non-product domains: julia> gen3 = (cos(x[1]+x[2]+x[3]) for x in UnitBall())
Base.Generator{EuclideanUnitBall{3, Float64, :closed}, var"#13#14"}(var"#13#14"(), UnitBall())
julia> gen3.f
#13 (generic function with 1 method)
julia> gen3.iter
UnitBall() Here, the function Finally, the trickiest setting is where you combine both structures, which probably arises e.g. for time-dependent problems: julia> gen4 = (exp(x[1]+x[2]+y) for x in UnitDisk(), y in Interval(2..3.0))
Base.Generator{Base.Iterators.ProductIterator{Tuple{EuclideanUnitBall{2, Float64, :closed}, ClosedInterval{Float64}}}, var"#15#16"}(var"#15#16"(), Base.Iterators.ProductIterator{Tuple{EuclideanUnitBall{2, Float64, :closed}, ClosedInterval{Float64}}}((UnitDisk(), 2.0..3.0)))
julia> gen4.f
#15 (generic function with 1 method)
julia> gen4.iter.iterators
(UnitDisk(), 2.0..3.0)
julia> D1 = ProductDomain(gen4.iter.iterators)
UnitDisk() × (2.0..3.0)
julia> eltype(D1)
StaticArrays.SVector{3, Float64} (alias for StaticArrays.SArray{Tuple{3}, Float64, 1, 3})
julia> D2 = TupleProductDomain(gen4.iter.iterators)
UnitDisk() × (2.0..3.0)
julia> eltype(D2)
Tuple{StaticArrays.SVector{2, Float64}, Float64}
julia> p = point_in_domain(D2)
([0.0, 0.0], 2.5)
julia> gen4.f(p)
12.182493960703473 Now, the function The code for product domains in DomainSets is a little tricky because I wanted to support all possibilities. The good news is that probably makes it flexible enough to get everything to work, the bad news is that it becomes, well, tricky. |
I think the |
Gradients in the normal direction will be required for Neumann BCs. I kind of think the generator use is a little too cute, but I still don't know. |
True, cute, but probably too limited. I wanted to show what considerations arise when treating functions defined on non-product domains - these issues or similar ones will show up one way or another, I think. |
Can we move this issue to PDEBase? @ChrisRackauckas Looks like a lot of stuff that will need to be thought about eventually is documented here. Also PS what is Dedalus? |
@thomasgibson @sandreza @simonbyrne let's discuss how the PDESystem specification needs to change in order to accommodate the types of meshes that are used for Clima.
@KirillZubov @emmanuellujan should stay informed here, since we will need to make sure the PINN and finite difference methods update for any change.
The current PDE system is like:
One of the difficulties that I saw was that, in this form, it's "easy" to know how to identify the boundaries and write a condition to them. However, a breakdown occurs on harder domains. An even simpler example than unstructured meshes is
CircleDomain
, we we can no long write things likeu(1,x) = ... for all x
, since we now have to say things likeu(x,y) = ... for all x^2 + y^2 = 1
which is a fundamentally implicit specification, whereas it can be made explicit only by directly knowing a representation of the boundary.So it looks like we need to be able to query for boundaries of domains and specify the BCs based on them. For example, maybe something like:
That looks a bit awful though, but gets the point across. But the real question is:
The text was updated successfully, but these errors were encountered: