Skip to content

Commit 2a38339

Browse files
authored
Add more integral methods
* Add surfaceintegral method for CylinderSurface using GaussKronrod * Add tests for surfaceintegral on CylinderSurface * Update support matrix * Add temporary debugging * Bugfix in math * Adjust tests * Remove debugging * Add volumeintegral method for Ball{3,T} * Disable complex test due to change in Meshes * Update support matrix * Add more surfaceintegral methods for Sphere{3,T} * Add another volumeintegral method for Ball{3,T} * Bugfix * Move function to another section * Bump version and update support matrix * Remove branch-specific plans
1 parent a76852c commit 2a38339

File tree

5 files changed

+156
-13
lines changed

5 files changed

+156
-13
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MeshIntegrals"
22
uuid = "dadec2fd-bbe0-4da4-9dbe-476c782c8e47"
33
authors = ["Mike Ingold <[email protected]>"]
4-
version = "0.9.4"
4+
version = "0.9.5"
55

66
[deps]
77
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@ Methods are tested to ensure compatibility with
2020
| Symbol | Meaning |
2121
|--------|---------|
2222
| :white_check_mark: | Implemented, passes tests |
23-
| :yellow_square: | Implemented, untested |
23+
| :yellow_square: | Implemented, not yet validated |
2424
| :x: | Not yet implemented |
2525

26-
### Line Integrals
26+
### Integral
27+
| Geometry | Gauss-Legendre | Gauss-Kronrod | H-Adaptive Cubature |
28+
|----------|----------------|---------------|---------------------|
29+
| `Meshes.Box{Dim,T}` | :x: | :x: | :x: |
30+
31+
### Line Integral
2732
| Geometry | Gauss-Legendre | Gauss-Kronrod | H-Adaptive Cubature |
2833
|----------|----------------|---------------|---------------------|
2934
| `Meshes.BezierCurve` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
@@ -35,25 +40,24 @@ Methods are tested to ensure compatibility with
3540
| `Meshes.Segment` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
3641
| `Meshes.Sphere{2,T}` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
3742

38-
### Surface Integrals
43+
### Surface Integral
3944
| Geometry | Gauss-Legendre | Gauss-Kronrod | H-Adaptive Cubature |
4045
|----------|----------------|---------------|-------------------|
4146
| `Meshes.Ball{2,T}` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
4247
| `Meshes.Box{2,T}` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
43-
| `Meshes.CylinderSurface` | :x: | :x: | :x: |
48+
| `Meshes.CylinderSurface` | :x: | :white_check_mark: | :x: |
4449
| `Meshes.Disk` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
4550
| `Meshes.ParaboloidSurface` | :x: | :x: | :x: |
46-
| `Meshes.Sphere{3,T}` | :x: | :x: | :x: |
51+
| `Meshes.Sphere{3,T}` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
4752
| `Meshes.Triangle` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
4853
| `Meshes.Torus` | :x: | :x: | :x: |
4954
| `Meshes.SimpleMesh` | :x: | :x: | :x: |
5055

51-
### Volume Integrals
56+
### Volume Integral
5257
| Geometry | Gauss-Legendre | H-Adaptive Cubature |
5358
|----------|----------------|---------------|
54-
| `Meshes.Ball{3,T}` | :x: | :x: |
59+
| `Meshes.Ball{3,T}` | :white_check_mark: | :white_check_mark: |
5560
| `Meshes.Box{3,T}` | :white_check_mark: | :white_check_mark: |
56-
| `Meshes.Box{Dim,T}` | :x: | :x: |
5761

5862
# Example Usage
5963

src/integral_surface.jl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,30 @@ function surfaceintegral(
127127
return/4) * area(triangle) .* sum(integrand, zip(wws,xxs))
128128
end
129129

130+
function surfaceintegral(
131+
f,
132+
sphere::Meshes.Sphere{3,T},
133+
settings::GaussLegendre
134+
) where {T}
135+
# Validate the provided integrand function
136+
_validate_integrand(f,3,T)
137+
138+
# Get Gauss-Legendre nodes and weights for a 2D region [-1,1]^2
139+
xs, ws = gausslegendre(settings.n)
140+
wws = Iterators.product(ws, ws)
141+
xxs = Iterators.product(xs, xs)
142+
143+
# Domain transformation: xi,xj [-1,1] ↦ s,t [0,1]
144+
t(xi) = 0.5xi + 0.5
145+
u(xj) = 0.5xj + 0.5
146+
147+
# Integrate the sphere in parametric (t,u)-space [0,1]²
148+
integrand(t,u) = sinpi(t) * f(sphere(1,t,u))
149+
g(((wi,wj), (xi,xj))) = wi * wj * integrand(t(xi),u(xj))
150+
R = sphere.radius
151+
return 0.25 * 2π^2 * R^2 .* sum(g, zip(wws,xxs))
152+
end
153+
130154
################################################################################
131155
# Gauss-Kronrod
132156
################################################################################
@@ -167,6 +191,41 @@ function surfaceintegral(
167191
return area(box) .* outerintegral
168192
end
169193

194+
function surfaceintegral(
195+
f,
196+
cyl::Meshes.CylinderSurface{T},
197+
settings::GaussKronrod
198+
) where {T}
199+
# Validate the provided integrand function
200+
# A CylinderSurface is definitionally embedded in 3D-space
201+
_validate_integrand(f,3,T)
202+
203+
# Integrate the rounded sides of the cylinder's surface
204+
# \int ( \int f(r̄) dz ) dφ
205+
function sides_innerintegral(φ)
206+
sidelength = norm(cyl(φ,1) - cyl(φ,0))
207+
return sidelength * quadgk(z -> f(cyl(φ,z)), 0, 1; settings.kwargs...)[1]
208+
end
209+
sides = (2π * cyl.radius) .* quadgk-> sides_innerintegral(φ), 0, 1; settings.kwargs...)[1]
210+
211+
# Integrate the top and bottom disks
212+
# \int ( \int r f(r̄) dr ) dφ
213+
function disk_innerintegral(φ,plane,z)
214+
# Parameterize the top surface of the cylinder
215+
rimedge = cyl(φ,z)
216+
centerpoint = plane.p
217+
= rimedge - centerpoint
218+
radius = norm(r̄)
219+
point(r) = centerpoint + (r / radius) *
220+
221+
return radius^2 * quadgk(r -> r * f(point(r)), 0, 1; settings.kwargs...)[1]
222+
end
223+
top = 2π .* quadgk-> disk_innerintegral(φ,cyl.top,1), 0, 1; settings.kwargs...)[1]
224+
bottom = 2π .* quadgk-> disk_innerintegral(φ,cyl.bot,0), 0, 1; settings.kwargs...)[1]
225+
226+
return sides + top + bottom
227+
end
228+
170229
function surfaceintegral(
171230
f,
172231
disk::Meshes.Disk{T},
@@ -212,6 +271,22 @@ function surfaceintegral(
212271
return 2.0 * area(triangle) .* outerintegral
213272
end
214273

274+
function surfaceintegral(
275+
f,
276+
sphere::Meshes.Sphere{3,T},
277+
settings::GaussKronrod
278+
) where {T}
279+
# Validate the provided integrand function
280+
_validate_integrand(f,3,T)
281+
282+
# Integrate the sphere in parametric (t,u)-space [0,1]^2
283+
innerintegrand(u) = quadgk(t -> sinpi(t) * f(sphere(1,t,u)), 0, 1)[1]
284+
intval = quadgk(u -> innerintegrand(u), 0, 1, settings.kwargs...)[1]
285+
286+
R = sphere.radius
287+
return 2π^2 * R^2 .* intval
288+
end
289+
215290

216291
################################################################################
217292
# HCubature
@@ -307,3 +382,20 @@ function surfaceintegral(
307382
# Apply a linear domain-correction factor 0.5 ↦ area(triangle)
308383
return 2.0 * area(triangle) .* intval
309384
end
385+
386+
function surfaceintegral(
387+
f,
388+
sphere::Meshes.Sphere{3,T},
389+
settings::HAdaptiveCubature
390+
) where {T}
391+
# Validate the provided integrand function
392+
_validate_integrand(f,3,T)
393+
394+
# Integrate the sphere in parametric (t,u)-space [0,1]^2
395+
integrand(t,u) = sinpi(t) * f(sphere(1,t,u))
396+
integrand(tu) = integrand(tu[1],tu[2])
397+
intval = hcubature(tu -> integrand(tu), [0,0], [1,1], settings.kwargs...)[1]
398+
399+
R = sphere.radius
400+
return 2π^2 * R^2 .* intval
401+
end

src/integral_volume.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22
# Gauss-Legendre
33
################################################################################
44

5+
function volumeintegral(
6+
f,
7+
ball::Meshes.Ball{3,T},
8+
settings::GaussLegendre
9+
) where {T}
10+
# Validate the provided integrand function
11+
_validate_integrand(f,3,T)
12+
13+
# Get Gauss-Legendre nodes and weights for a 2D region [-1,1]^2
14+
xs, ws = gausslegendre(settings.n)
15+
wws = Iterators.product(ws, ws, ws)
16+
xxs = Iterators.product(xs, xs, xs)
17+
18+
# Domain transformation: xi,xj,xk [-1,1] ↦ s,t,u [0,1]
19+
s(xi) = 0.5xi + 0.5
20+
t(xj) = 0.5xj + 0.5
21+
u(xk) = 0.5xk + 0.5
22+
23+
# Integrate the ball in parametric (s,t,u)-space [0,1]³
24+
integrand(s,t,u) = s^2 * sinpi(t) * f(ball(s,t,u))
25+
g(((wi,wj,wk), (xi,xj,xk))) = wi * wj * wk * integrand(s(xi),t(xj),u(xk))
26+
R = ball.radius
27+
return (1/8) * 2π^2 * R^3 .* sum(g, zip(wws,xxs))
28+
end
29+
530
function volumeintegral(
631
f,
732
box::Meshes.Box{3,T},
@@ -34,6 +59,23 @@ end
3459
# HCubature
3560
################################################################################
3661

62+
function volumeintegral(
63+
f,
64+
ball::Meshes.Ball{3,T},
65+
settings::HAdaptiveCubature
66+
) where {T}
67+
# Validate the provided integrand function
68+
_validate_integrand(f,3,T)
69+
70+
# Integrate the ball in parametric (s,t,u)-space [0,1]³
71+
integrand(s,t,u) = s^2 * sinpi(t) * f(ball(s,t,u))
72+
integrand(stu) = integrand(stu[1],stu[2],stu[3])
73+
intval = hcubature(stu -> integrand(stu), [0,0,0], [1,1,1], settings.kwargs...)[1]
74+
75+
R = ball.radius
76+
return 2π^2 * R^3 .* intval
77+
end
78+
3779
function volumeintegral(
3880
f,
3981
box::Meshes.Box{3,T},

test/runtests.jl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ using Test
2323

2424
# Line segments/paths oriented CCW between points
2525
seg_ne = Segment(pt_e, pt_n)
26-
#seg_nw = Segment(pt_n, pt_w)
27-
#seg_sw = Segment(pt_w, pt_s)
28-
#seg_se = Segment(pt_s, pt_e)
2926
line_ne = Line(pt_e, pt_n)
3027
ring_rect = Ring(pt_e, pt_n, pt_w, pt_s)
3128
rope_rect = Rope(pt_e, pt_n, pt_w, pt_s, pt_e)
@@ -44,6 +41,8 @@ using Test
4441
sphere2d = Sphere(origin2d, 2.0)
4542
sphere3d = Sphere(origin3d, 2.0)
4643
triangle = Ngon(pt_e, pt_n, pt_w)
44+
cylsurf = CylinderSurface(pt_e, pt_w, 2.5)
45+
# TODO add test for a non-right-cylinder surface when measure(c) is fixed in Meshes
4746

4847
@testset "Errors Expected on Invalid Methods" begin
4948
f(::Point) = 1.0
@@ -53,7 +52,7 @@ using Test
5352
@test_throws MethodError lineintegral(f, ball3d) # Ball{3,T}
5453
@test_throws MethodError lineintegral(f, box2d) # Box{2,T}
5554
@test_throws MethodError lineintegral(f, box3d) # Box{3,T}
56-
# CylinderSurface
55+
@test_throws MethodError lineintegral(f, cylsurf) # CylinderSurface
5756
@test_throws MethodError lineintegral(f, disk) # Disk
5857
@test_throws MethodError lineintegral(f, triangle) # Ngon{3,Dim,T}
5958
# ParaboloidSurface
@@ -101,9 +100,11 @@ using Test
101100
@test isapprox(surfaceintegral(f, box2d, rule), area(box2d); rtol=1e-6) # Box{2,T}
102101
@test isapprox(surfaceintegral(f, disk, rule), area(disk); rtol=1e-6) # Disk
103102
@test isapprox(surfaceintegral(f, triangle, rule), area(triangle); rtol=1e-6) # Triangle
103+
@test isapprox(surfaceintegral(f, cylsurf, rule), area(cylsurf); rtol=1e-6) # CylinderSurface
104104

105105
# Volume Integrals (skip for GaussKronrod rules)
106106
if rule != GaussKronrod()
107+
@test volumeintegral(f, ball3d, rule) volume(ball3d) # Ball{3,T}
107108
@test volumeintegral(f, box3d, rule) volume(box3d) # Box{3,T}
108109
end
109110
end
@@ -127,15 +128,18 @@ using Test
127128
@test isapprox(surfaceintegral(f, box2d, rule), fill(area(box2d),3); rtol=1e-6) # Box{2,T}
128129
@test isapprox(surfaceintegral(f, disk, rule), fill(area(disk),3); rtol=1e-6) # Disk
129130
@test isapprox(surfaceintegral(f, triangle, rule), fill(area(triangle),3); rtol=1e-6) # Triangle
131+
@test isapprox(surfaceintegral(f, cylsurf, rule), fill(area(cylsurf),3); rtol=1e-6) # CylinderSurface
130132

131133
# Volume Integrals (skip for GaussKronrod rules)
132134
if rule != GaussKronrod()
135+
@test volumeintegral(f, ball3d, rule) fill(volume(ball3d),3) # Ball{3,T}
133136
@test volumeintegral(f, box3d, rule) fill(volume(box3d),3) # Box{3,T}
134137
end
135138
end
136139
end
137140
end
138141

142+
#= Disabled: As of Feb 2024 Meshes seems to now disallow Point{1,Complex}
139143
@testset "Contour Integrals on a Point{1,Complex}-Domain" begin
140144
fc(z::Complex) = 1/z
141145
fc(p::Point{1,ComplexF64}) = fc(p.coords[1])
@@ -148,4 +152,5 @@ using Test
148152
# ∴ \int_C (1/z) dz = 2πi
149153
@test lineintegral(fc, unit_circle_complex, GaussKronrod()) ≈ 2π*im
150154
end
155+
=#
151156
end

0 commit comments

Comments
 (0)