diff --git a/src/CellData/CellData.jl b/src/CellData/CellData.jl index ecbbfc6ed..7eacc931e 100644 --- a/src/CellData/CellData.jl +++ b/src/CellData/CellData.jl @@ -61,6 +61,7 @@ export CellDof export get_normal_vector export get_cell_measure export Interpolable +export KDTreeSearch export make_inverse_table export compute_cell_points_from_vector_of_points diff --git a/src/CellData/CellFields.jl b/src/CellData/CellFields.jl index 4ca12fd59..5dc8dc2f0 100644 --- a/src/CellData/CellFields.jl +++ b/src/CellData/CellFields.jl @@ -256,74 +256,49 @@ function distance(polytope::ExtrusionPolytope, inv_cmap::Field, x::Point) end end -function return_cache(f::CellField,x::Point) - trian = get_triangulation(f) - cache1 = _point_to_cell_cache(trian) - - cell_f = get_array(f) - cell_f_cache = array_cache(cell_f) - cf = testitem(cell_f) - f_cache = return_cache(cf,x) - cache2 = cell_f_cache, f_cache, cell_f, f - - return cache1,cache2 -end - -function _point_to_cell_cache(trian::Triangulation) - model = get_background_model(trian) - topo = get_grid_topology(model) - vertex_coordinates = Geometry.get_vertex_coordinates(topo) - kdtree = KDTree(map(nc -> SVector(Tuple(nc)), vertex_coordinates)) - D = num_cell_dims(trian) - vertex_to_cells = get_faces(topo, 0, D) - cell_to_ctype = get_cell_type(trian) - ctype_to_reffe = get_reffes(trian) - ctype_to_polytope = map(get_polytope, ctype_to_reffe) - cell_map = get_cell_map(trian) - table_cache = array_cache(vertex_to_cells) - cache1 = kdtree, vertex_to_cells, cell_to_ctype, ctype_to_polytope, cell_map, table_cache -end - function _point_to_cell!(cache, x::Point) - kdtree, vertex_to_cells, cell_to_ctype, ctype_to_polytope, cell_map, table_cache = cache - - # Find nearest vertex - id,dist = nn(kdtree, SVector(Tuple(x))) - - # Find all neighbouring cells - cells = getindex!(table_cache,vertex_to_cells,id) - @assert !isempty(cells) - - # Calculate the distance from the point to all the cells. Without - # round-off, and with non-overlapping cells, the distance would be - # negative for exactly one cell and positive for all other ones. Due - # to round-off, the distance can be slightly negative or slightly - # positive for points near cell boundaries, in particular near - # vertices. In this case, choose the cell with the smallest - # distance, and check that the distance (if positive) is at most at - # round-off level. - T = eltype(dist) - function cell_distance(cell::Integer) - ctype = cell_to_ctype[cell] - polytope = ctype_to_polytope[ctype] - cmap = cell_map[cell] - inv_cmap = inverse_map(cmap) - return distance(polytope, inv_cmap, x) - end - # findmin, without allocating an array - cell = zero(eltype(cells)) - dist = T(Inf) - for jcell in cells - jdist = cell_distance(jcell) - if jdist < dist - cell = jcell - dist = jdist + searchmethod, kdtree, vertex_to_cells, cell_to_ctype, ctype_to_polytope, cell_map, table_cache = cache + + # Loop over the first m.num_nearest_vertex + for (id,dist) in zip(knn(kdtree, SVector(Tuple(x)), searchmethod.num_nearest_vertices, true)...) + + # Find all neighbouring cells + cells = getindex!(table_cache,vertex_to_cells,id) + @assert !isempty(cells) + + # Calculate the distance from the point to all the cells. Without + # round-off, and with non-overlapping cells, the distance would be + # negative for exactly one cell and positive for all other ones. Due + # to round-off, the distance can be slightly negative or slightly + # positive for points near cell boundaries, in particular near + # vertices. In this case, choose the cell with the smallest + # distance, and check that the distance (if positive) is at most at + # round-off level. + T = eltype(dist) + function cell_distance(cell::Integer) + ctype = cell_to_ctype[cell] + polytope = ctype_to_polytope[ctype] + cmap = cell_map[cell] + inv_cmap = inverse_map(cmap) + return distance(polytope, inv_cmap, x) end + # findmin, without allocating an array + cell = zero(eltype(cells)) + dist = T(Inf) + for jcell in cells + jdist = cell_distance(jcell) + if jdist < dist + cell = jcell + dist = jdist + end + end + + dist ≤ 1000eps(T) && return cell + end - # Ensure the point is inside one of the cells, up to round-off errors - @check dist ≤ 1000eps(T) "Point is not inside any cell" - return cell + # Output error message if cell not found + @check false "Point $x is not inside any cell" end function evaluate!(cache,f::CellField,x::Point) @@ -374,7 +349,7 @@ end # Efficient version: function evaluate!(cache,f::CellField,point_to_x::AbstractVector{<:Point}) cache1,cache2 = cache - kdtree, vertex_to_cells, cell_to_ctype, ctype_to_polytope, cell_map = cache1 + searchmethod, kdtree, vertex_to_cells, cell_to_ctype, ctype_to_polytope, cell_map = cache1 cell_f_cache, f_cache, cell_f, f₀ = cache2 @check f === f₀ "Wrong cache" @@ -391,7 +366,8 @@ function evaluate!(cache,f::CellField,point_to_x::AbstractVector{<:Point}) end function compute_cell_points_from_vector_of_points(xs::AbstractVector{<:Point}, trian::Triangulation, domain_style::PhysicalDomain) - cache1 = _point_to_cell_cache(trian) + searchmethod = KDTreeSearch() + cache1 = _point_to_cell_cache(searchmethod,trian) x_to_cell(x) = _point_to_cell!(cache1, x) point_to_cell = map(x_to_cell, xs) ncells = num_cells(trian) @@ -623,7 +599,7 @@ function _to_common_domain(a::CellField...) Cannote operate cellfields defined over more than 2 different triangulations at this moment. """ - @notimplemented + @notimplemented end map(i->change_domain(i,target_trian,target_domain),a) end @@ -815,7 +791,12 @@ function (a::SkeletonPair{<:CellField})(x) end # Interpolable struct -struct KDTreeSearch end +struct KDTreeSearch + num_nearest_vertices::Int + function KDTreeSearch(;num_nearest_vertices=1) + new(num_nearest_vertices) + end +end struct Interpolable{M,A} <: Function uh::A @@ -826,5 +807,35 @@ struct Interpolable{M,A} <: Function end end -return_cache(a::Interpolable,x::Point) = return_cache(a.uh,x) +(a::Interpolable)(x) = evaluate(a,x) evaluate!(cache,a::Interpolable,x::Point) = evaluate!(cache,a.uh,x) +return_cache(f::CellField,x::Point) = return_cache(Interpolable(f),x) + +function return_cache(a::Interpolable,x::Point) + f = a.uh + trian = get_triangulation(f) + cache1 = _point_to_cell_cache(a.searchmethod,trian) + + cell_f = get_array(f) + cell_f_cache = array_cache(cell_f) + cf = testitem(cell_f) + f_cache = return_cache(cf,x) + cache2 = cell_f_cache, f_cache, cell_f, f + + return cache1,cache2 +end + +function _point_to_cell_cache(searchmethod::KDTreeSearch,trian::Triangulation) + model = get_background_model(trian) + topo = get_grid_topology(model) + vertex_coordinates = Geometry.get_vertex_coordinates(topo) + kdtree = KDTree(map(nc -> SVector(Tuple(nc)), vertex_coordinates)) + D = num_cell_dims(trian) + vertex_to_cells = get_faces(topo, 0, D) + cell_to_ctype = get_cell_type(trian) + ctype_to_reffe = get_reffes(trian) + ctype_to_polytope = map(get_polytope, ctype_to_reffe) + cell_map = get_cell_map(trian) + table_cache = array_cache(vertex_to_cells) + cache1 = searchmethod, kdtree, vertex_to_cells, cell_to_ctype, ctype_to_polytope, cell_map, table_cache +end diff --git a/test/FESpacesTests/CellFieldsTests.jl b/test/FESpacesTests/CellFieldsTests.jl index 97038eb13..e3e5023ea 100644 --- a/test/FESpacesTests/CellFieldsTests.jl +++ b/test/FESpacesTests/CellFieldsTests.jl @@ -139,6 +139,24 @@ source_model = CartesianDiscreteModel((0,1,0,1),(2,2)) for pt in pts @test gh(pt) ≈ fh(pt) end + + # Deformed mesh + function map(x) + if x[1]≈1.0 && x[2]≈1.0 + x = VectorValue(0.6,0.6) + end + x + end + model = CartesianDiscreteModel((0,1,0,1),(1,1),map=map) |> simplexify + reffe = ReferenceFE(lagrangian,Float64,1) + V = FESpace(model,reffe) + f(x) = x[1]+x[2] + u = interpolate_everywhere(f,V) + x = VectorValue(0.45,0.45) + @test_throws AssertionError u(x) + sm=KDTreeSearch(num_nearest_vertices=2) + ux = Interpolable(u;searchmethod=sm)(x) + @test ux == 0.9 end @testset "Test interpolation RT" begin @@ -162,4 +180,3 @@ end end # module -