Skip to content

Commit e969029

Browse files
mikeingoldgithub-actions[bot]JoshuaLampert
authored
Rewrite of jacobian approximation (#96)
* Add tests for 1D Box * Add units * Adjust rtol * Make auto tests verbose * Add tests for 2D Box * Apply format suggestions [skip ci] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Tupleify * Add tests for 3D Box * Condense functions * Remove reusable buffer * Remove unsupported GK rule on 3D Box * Convert ev to a generated ntuple * Implement non-allocating jacobian * Add explicit rtol * Simplify Box constructors * Remove old Box tests and obsolete lines * Add dimension check and fix type stability issue * Whitespace fixes [skip ci] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Explicit namespace * Remove broken status * Add test for jacobian error condition * Cleaner abstraction/iterators * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Fix 4D Box test * Figured out destructuring of anonymous args --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Joshua Lampert <[email protected]>
1 parent b210eb3 commit e969029

File tree

4 files changed

+126
-61
lines changed

4 files changed

+126
-61
lines changed

src/differentiation.jl

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,49 +18,34 @@ function jacobian(
1818
ts::V;
1919
ε = 1e-6
2020
) where {G <: Meshes.Geometry, V <: Union{AbstractVector, Tuple}}
21-
T = eltype(ts)
21+
Dim = Meshes.paramdim(geometry)
22+
if Dim != length(ts)
23+
throw(ArgumentError("ts must have same number of dimensions as geometry."))
24+
end
2225

23-
# Get the partial derivative along the n'th axis via finite difference approximation
24-
# where ts is the current parametric position (εv is a reusable buffer)
25-
function ∂ₙr!(εv, ts, n)
26+
T = eltype(ts)
27+
ε = T(ε)
28+
29+
# Get the partial derivative along the n'th axis via finite difference
30+
# approximation, where ts is the current parametric position
31+
function ∂ₙr(ts, n)
32+
# Build left/right parametric coordinates with non-allocating iterators
33+
left = Iterators.map(((i, t),) -> i == n ? t - ε : t, enumerate(ts))
34+
right = Iterators.map(((i, t),) -> i == n ? t + ε : t, enumerate(ts))
35+
# Select orientation of finite-diff
2636
if ts[n] < T(0.01)
27-
return ∂ₙr_right!(εv, ts, n)
37+
# Right
38+
return (geometry(right...) - geometry(ts...)) / ε
2839
elseif T(0.99) < ts[n]
29-
return ∂ₙr_left!(εv, ts, n)
40+
# Left
41+
return (geometry(ts...) - geometry(left...)) / ε
3042
else
31-
return ∂ₙr_central!(εv, ts, n)
43+
# Central
44+
return (geometry(right...) - geometry(left...)) / 2ε
3245
end
3346
end
3447

35-
# Central finite difference
36-
function ∂ₙr_central!(εv, ts, n)
37-
εv[n] = ε
38-
a = ts .- εv
39-
b = ts .+ εv
40-
return (geometry(b...) - geometry(a...)) / 2ε
41-
end
42-
43-
# Left finite difference
44-
function ∂ₙr_left!(εv, ts, n)
45-
εv[n] = ε
46-
a = ts .- εv
47-
b = ts
48-
return (geometry(b...) - geometry(a...)) / ε
49-
end
50-
51-
# Right finite difference
52-
function ∂ₙr_right!(εv, ts, n)
53-
εv[n] = ε
54-
a = ts
55-
b = ts .+ εv
56-
return (geometry(b...) - geometry(a...)) / ε
57-
end
58-
59-
# Allocate a re-usable ε vector
60-
εv = zeros(T, length(ts))
61-
62-
∂ₙr(n) = ∂ₙr!(εv, ts, n)
63-
return map(∂ₙr, 1:length(ts))
48+
return map(n -> ∂ₙr(ts, n), 1:Dim)
6449
end
6550

6651
function jacobian(

test/auto_tests.jl

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939

4040
# Generate a @testset for item
4141
function autotest(item::SupportItem)
42-
#@assert item.type == coordtype(item.geometry) "Item type mismatch"
43-
4442
N = (item.type == Float32) ? 1000 : 100
4543
algorithm_set = [
4644
(GaussLegendre(N), item.gausslegendre),
@@ -77,15 +75,10 @@ end
7775
pt_n(T) = Point(T(0), T(1), T(0))
7876
pt_w(T) = Point(T(-1), T(0), T(0))
7977
pt_e(T) = Point(T(1), T(0), T(0))
80-
pt_s(T) = Point(T(0), T(-1), T(0))
81-
pt_z(T) = Point(T(0), T(0), T(1))
8278

8379
# Test Geometries
8480
ball2d(T) = Ball(origin2d(T), T(2.0))
8581
ball3d(T) = Ball(origin3d(T), T(2.0))
86-
box1d(T) = Box(Point(T(-1)), Point(T(1)))
87-
box2d(T) = Box(Point(T(-1), T(-1)), Point(T(1), T(1)))
88-
box3d(T) = Box(Point(T(-1), T(-1), T(-1)), Point(T(1), T(1), T(-1)))
8982
circle(T) = Circle(plane_xy(T), T(2.5))
9083
cyl(T) = Cylinder(pt_e(T), pt_w(T), T(2.5))
9184
cylsurf(T) = CylinderSurface(pt_e(T), pt_w(T), T(2.5))
@@ -101,24 +94,19 @@ end
10194
# Name, T type, example, integral,line,surface,volume, GaussLegendre,GaussKronrod,HAdaptiveCubature
10295
SupportItem("Ball{2,$T}", T, ball2d(T), 1, 0, 1, 0, 1, 1, 1),
10396
SupportItem("Ball{3,$T}", T, ball3d(T), 1, 0, 0, 1, 1, 0, 1),
104-
SupportItem("Box{1,$T}", T, box1d(T), 1, 1, 0, 0, 1, 1, 1),
105-
SupportItem("Box{2,$T}", T, box2d(T), 1, 0, 1, 0, 1, 1, 1),
106-
SupportItem("Box{3,$T}", T, box3d(T), 1, 0, 0, 1, 1, 0, 1),
10797
SupportItem("Circle{$T}", T, circle(T), 1, 1, 0, 0, 1, 1, 1),
10898
SupportItem("Cylinder{$T}", T, cyl(T), 1, 0, 0, 1, 1, 0, 1),
10999
SupportItem("CylinderSurface{$T}", T, cylsurf(T), 1, 0, 1, 0, 1, 1, 1),
110100
SupportItem("Disk{$T}", T, disk(T), 1, 0, 1, 0, 1, 1, 1),
111-
# Frustum -- not yet supported
112101
SupportItem("ParaboloidSurface{$T}", T, parab(T), 1, 0, 1, 0, 1, 1, 1),
113-
# SimpleMesh
114102
SupportItem("Sphere{2,$T}", T, sphere2d(T), 1, 1, 0, 0, 1, 1, 1),
115103
SupportItem("Sphere{3,$T}", T, sphere3d(T), 1, 0, 1, 0, 1, 1, 1),
116104
SupportItem("Tetrahedron", T, tetra(T), 1, 0, 0, 1, 0, 1, 0),
117105
SupportItem("Triangle{$T}", T, triangle(T), 1, 0, 1, 0, 1, 1, 1),
118106
SupportItem("Torus{$T}", T, torus(T), 1, 0, 1, 0, 1, 1, 1)
119107
]
120108

121-
@testset "Float64 Geometries" begin
109+
@testset "Float64 Geometries" verbose=true begin
122110
map(autotest, SUPPORT_MATRIX(Float64))
123111
end
124112
end

test/combinations.jl

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,102 @@
3434
@test_throws DomainError jacobian(curve, [1.1])
3535
end
3636

37-
@testitem "Meshes.Box" setup=[Setup] begin
37+
@testitem "Meshes.Box 1D" setup=[Setup] begin
38+
a = π
39+
box = Box(Point(0), Point(a))
40+
41+
function f(p::P) where {P <: Meshes.Point}
42+
t = ustrip(p.coords.x)
43+
sqrt(a^2 - t^2) * u"Ω/m"
44+
end
45+
fv(p) = fill(f(p), 3)
46+
47+
# Scalar integrand
48+
sol = π * a^2 / 4 * u"Ω"
49+
@test integral(f, box, GaussLegendre(100))sol rtol=1e-6
50+
@test integral(f, box, GaussKronrod()) sol
51+
@test integral(f, box, HAdaptiveCubature()) sol
52+
53+
# Vector integrand
54+
vsol = fill(sol, 3)
55+
@test integral(fv, box, GaussLegendre(100))vsol rtol=1e-6
56+
@test integral(fv, box, GaussKronrod()) vsol
57+
@test integral(fv, box, HAdaptiveCubature()) vsol
58+
59+
# Integral aliases
60+
@test lineintegral(f, box) sol
61+
@test_throws "not supported" surfaceintegral(f, box)
62+
@test_throws "not supported" volumeintegral(f, box)
63+
end
64+
65+
@testitem "Meshes.Box 2D" setup=[Setup] begin
66+
a = π
67+
box = Box(Point(0, 0), Point(a, a))
68+
69+
function f(p::P) where {P <: Meshes.Point}
70+
x, y = ustrip.((p.coords.x, p.coords.y))
71+
(sqrt(a^2 - x^2) + sqrt(a^2 - y^2)) * u"Ω/m^2"
72+
end
73+
fv(p) = fill(f(p), 3)
74+
75+
# Scalar integrand
76+
sol = 2a ** a^2 / 4) * u"Ω"
77+
@test integral(f, box, GaussLegendre(100))sol rtol=1e-6
78+
@test integral(f, box, GaussKronrod()) sol
79+
@test integral(f, box, HAdaptiveCubature()) sol
80+
81+
# Vector integrand
82+
vsol = fill(sol, 3)
83+
@test integral(fv, box, GaussLegendre(100))vsol rtol=1e-6
84+
@test integral(fv, box, GaussKronrod()) vsol
85+
@test integral(fv, box, HAdaptiveCubature()) vsol
86+
87+
# Integral aliases
88+
@test_throws "not supported" lineintegral(f, box)
89+
@test surfaceintegral(f, box) sol
90+
@test_throws "not supported" volumeintegral(f, box)
91+
end
92+
93+
@testitem "Meshes.Box 3D" setup=[Setup] begin
94+
a = π
95+
box = Box(Point(0, 0, 0), Point(a, a, a))
96+
97+
function f(p::P) where {P <: Meshes.Point}
98+
x, y, z = ustrip.((p.coords.x, p.coords.y, p.coords.z))
99+
(sqrt(a^2 - x^2) + sqrt(a^2 - y^2) + sqrt(a^2 - z^2)) * u"Ω/m^3"
100+
end
101+
fv(p) = fill(f(p), 3)
102+
103+
# Scalar integrand
104+
sol = 3a^2 ** a^2 / 4) * u"Ω"
105+
@test integral(f, box, GaussLegendre(100))sol rtol=1e-6
106+
@test_throws "not supported" integral(f, box, GaussKronrod())
107+
@test integral(f, box, HAdaptiveCubature()) sol
108+
109+
# Vector integrand
110+
vsol = fill(sol, 3)
111+
@test integral(fv, box, GaussLegendre(100))vsol rtol=1e-6
112+
@test_throws "not supported" integral(fv, box, GaussKronrod())
113+
@test integral(fv, box, HAdaptiveCubature()) vsol
114+
115+
# Integral aliases
116+
@test_throws "not supported" lineintegral(f, box)
117+
@test_throws "not supported" surfaceintegral(f, box)
118+
@test volumeintegral(f, box) sol
119+
end
120+
121+
@testitem "Meshes.Box 4D" setup=[Setup] begin
122+
a = zero(Float64)
123+
b = zero(Float64)
124+
box = Box(Point(a, a, a, a), Point(b, b, b, b))
125+
126+
f = p -> one(Float64)
127+
38128
# Test for currently-unsupported >3D differentials
39-
box4d = Box(Point(zeros(4)...), Point(ones(4)...))
40-
@test integral(f -> one(Float64), box4d)1.0u"m^4" broken=true
129+
@test integral(f, box)1.0u"m^4" broken=true
130+
131+
# Test jacobian with wrong number of parametric coordinates
132+
@test_throws ArgumentError jacobian(box, zeros(2))
41133
end
42134

43135
@testitem "Meshes.Cone" setup=[Setup] begin

test/floating_point_types.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,33 @@ end
1111

1212
@testitem "Alternate floating types" setup=[Setup, BaseAtol] begin
1313
@testset "$FP" for FP in (Float32, BigFloat)
14-
# typeof @test's are currently broken for Float32, see GitHub Issue 74
15-
1614
# Rectangular volume with unit integrand
1715
f = p -> one(FP)
18-
box1d = Box(Point(fill(zero(FP) * u"m", 1)...), Point(fill(one(FP) * u"m", 1)...))
19-
box2d = Box(Point(fill(zero(FP) * u"m", 2)...), Point(fill(one(FP) * u"m", 2)...))
20-
box3d = Box(Point(fill(zero(FP) * u"m", 3)...), Point(fill(one(FP) * u"m", 3)...))
16+
a = zero(FP) * u"m"
17+
b = one(FP) * u"m"
18+
box1d = Box(Point(a), Point(b))
19+
box2d = Box(Point(a, a), Point(b, b))
20+
box3d = Box(Point(a, a, a), Point(b, b, b))
2121

2222
# Check HCubature integrals (same method invoked for all dimensions)
2323
int_HC = integral(f, box1d, HAdaptiveCubature(); FP = FP)
2424
@test int_HCone(FP) * u"m" atol=baseatol[FP] * u"m"
25-
@test typeof(int_HC.val)==FP broken=(FP == Float32)
25+
@test typeof(int_HC.val) == FP
2626

2727
# Check Gauss-Legendre integral in 1D
2828
int_GL_1D = integral(f, box1d, GaussLegendre(100); FP = FP)
2929
@test int_GL_1Done(FP) * u"m" atol=baseatol[FP] * u"m"
30-
@test typeof(int_GL_1D.val)==FP broken=(FP == Float32)
30+
@test typeof(int_GL_1D.val) == FP
3131

3232
# Check Gauss-Legendre integral in 2D
3333
int_GL_2D = integral(f, box2d, GaussLegendre(100); FP = FP)
3434
@test int_GL_2Done(FP) * u"m^2" atol=2baseatol[FP] * u"m^2"
35-
@test typeof(int_GL_2D.val)==FP broken=(FP == Float32)
35+
@test typeof(int_GL_2D.val) == FP
3636

3737
# Check Gauss-Legendre integral in 3D
3838
int_GL_3D = integral(f, box3d, GaussLegendre(100); FP = FP)
3939
@test int_GL_3Done(FP) * u"m^3" atol=3baseatol[FP] * u"m^3"
40-
@test typeof(int_GL_3D.val)==FP broken=(FP == Float32)
40+
@test typeof(int_GL_3D.val) == FP
4141
end
4242
end
4343

0 commit comments

Comments
 (0)