Skip to content

Commit 79ea114

Browse files
mikeingoldJoshuaLampertgithub-actions[bot]
authored
Implement differential and jacobian for Bezier curve integrals (#92)
* Convert Bezier methods from static dl to using derivative * apply format suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix dimension of test point * Add new tests for BezierCurve * Merge derivative with jacobian * Code formatting * Update names * Bugfix - deconstruct t from ts [skip ci] Co-authored-by: Joshua Lampert <[email protected]> * Apply format suggestions [skip ci] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Clarify variable name * Add argument type parameters to differential * Bugfix - missing comma to capture as tuple * Updated path name * Remove old tests * Add an rtol for tests * Update rtol values * Update rtol values * Add jacobian to API page --------- Co-authored-by: Joshua Lampert <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Joshua Lampert <[email protected]>
1 parent af068ab commit 79ea114

File tree

6 files changed

+80
-53
lines changed

6 files changed

+80
-53
lines changed

docs/src/api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ MeshIntegrals.GaussKronrod
2121
MeshIntegrals.GaussLegendre
2222
MeshIntegrals.HAdaptiveCubature
2323
```
24+
25+
## Derivatives
26+
27+
```@docs
28+
MeshIntegrals.jacobian
29+
```

src/differentiation.jl

Lines changed: 38 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ finite-difference approximation with step size `ε`.
1414
- `ε`: step size to use for the finite-difference approximation
1515
"""
1616
function jacobian(
17-
geometry,
18-
ts;
17+
geometry::G,
18+
ts::V;
1919
ε = 1e-6
20-
)
20+
) where {G <: Meshes.Geometry, V <: Union{AbstractVector, Tuple}}
2121
T = eltype(ts)
2222

2323
# Get the partial derivative along the n'th axis via finite difference approximation
@@ -63,44 +63,11 @@ function jacobian(
6363
return map(∂ₙr, 1:length(ts))
6464
end
6565

66-
"""
67-
differential(geometry, ts)
68-
69-
Calculate the differential element (length, area, volume, etc) of the parametric
70-
function for `geometry` at arguments `ts`.
71-
"""
72-
function differential(
73-
geometry,
74-
ts
75-
)
76-
J = jacobian(geometry, ts)
77-
78-
# TODO generalize this with geometric algebra, e.g.: norm(foldl(∧, J))
79-
if length(J) == 1
80-
return norm(J[1])
81-
elseif length(J) == 2
82-
return norm(J[1] × J[2])
83-
elseif length(J) == 3
84-
return abs((J[1] × J[2]) J[3])
85-
else
86-
error("Not implemented yet. Please report this as an Issue on GitHub.")
87-
end
88-
end
89-
90-
################################################################################
91-
# Partial Derivatives
92-
################################################################################
93-
94-
"""
95-
derivative(b::BezierCurve, t)
96-
97-
Determine the vector derivative of a Bezier curve `b` for the point on the
98-
curve parameterized by value `t`.
99-
"""
100-
function derivative(
66+
function jacobian(
10167
bz::Meshes.BezierCurve,
102-
t
103-
)
68+
ts::V
69+
) where {V <: Union{AbstractVector, Tuple}}
70+
t = only(ts)
10471
# Parameter t restricted to domain [0,1] by definition
10572
if t < 0 || t > 1
10673
throw(DomainError(t, "b(t) is not defined for t outside [0, 1]."))
@@ -121,5 +88,35 @@ function derivative(
12188
# Derivative = N Σ_{i=0}^{N-1} sigma(i)
12289
# P indices adjusted for Julia 1-based array indexing
12390
sigma(i) = B(i, N - 1)(t) .* (P[(i + 1) + 1] - P[(i) + 1])
124-
return N .* sum(sigma, 0:(N - 1))
91+
derivative = N .* sum(sigma, 0:(N - 1))
92+
93+
return [derivative]
94+
end
95+
96+
################################################################################
97+
# Differential Elements
98+
################################################################################
99+
100+
"""
101+
differential(geometry, ts)
102+
103+
Calculate the differential element (length, area, volume, etc) of the parametric
104+
function for `geometry` at arguments `ts`.
105+
"""
106+
function differential(
107+
geometry::G,
108+
ts::V
109+
) where {G <: Meshes.Geometry, V <: Union{AbstractVector, Tuple}}
110+
J = jacobian(geometry, ts)
111+
112+
# TODO generalize this with geometric algebra, e.g.: norm(foldl(∧, J))
113+
if length(J) == 1
114+
return norm(J[1])
115+
elseif length(J) == 2
116+
return norm(J[1] × J[2])
117+
elseif length(J) == 3
118+
return abs((J[1] × J[2]) J[3])
119+
else
120+
error("Not implemented yet. Please report this as an Issue on GitHub.")
121+
end
125122
end

src/integral.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ function _integral(
8787
) where {T <: AbstractFloat}
8888
N = Meshes.paramdim(geometry)
8989

90-
integrand(t) = f(geometry(t...)) * differential(geometry, t)
90+
integrand(ts) = f(geometry(ts...)) * differential(geometry, ts)
9191

9292
# HCubature doesn't support functions that output Unitful Quantity types
9393
# Establish the units that are output by f
9494
testpoint_parametriccoord = zeros(FP, N)
9595
integrandunits = Unitful.unit.(integrand(testpoint_parametriccoord))
9696
# Create a wrapper that returns only the value component in those units
97-
uintegrand(uv) = Unitful.ustrip.(integrandunits, integrand(uv))
97+
uintegrand(ts) = Unitful.ustrip.(integrandunits, integrand(ts))
9898
# Integrate only the unitless values
9999
value = HCubature.hcubature(uintegrand, zeros(FP, N), ones(FP, N); rule.kwargs...)[1]
100100

@@ -112,7 +112,7 @@ function _integral_gk_1d(
112112
rule::GaussKronrod;
113113
FP::Type{T} = Float64
114114
) where {T <: AbstractFloat}
115-
integrand(t) = f(geometry(t)) * differential(geometry, (t))
115+
integrand(t) = f(geometry(t)) * differential(geometry, (t,))
116116
return QuadGK.quadgk(integrand, zero(FP), one(FP); rule.kwargs...)[1]
117117
end
118118

src/specializations/BezierCurve.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ function integral(
3333
# Change of variables: x [-1,1] ↦ t [0,1]
3434
t(x) = FP(1 // 2) * x + FP(1 // 2)
3535
point(x) = curve(t(x), alg)
36+
integrand(x) = f(point(x)) * differential(curve, (t(x),))
3637

3738
# Integrate f along curve and apply domain-correction for [-1,1] ↦ [0, length]
38-
return FP(1 // 2) * length(curve) * sum(w .* f(point(x)) for (w, x) in zip(ws, xs))
39+
return FP(1 // 2) * sum(w .* integrand(x) for (w, x) in zip(ws, xs))
3940
end
4041

4142
"""
@@ -56,9 +57,8 @@ function integral(
5657
FP::Type{T} = Float64,
5758
alg::Meshes.BezierEvalMethod = Meshes.Horner()
5859
) where {F <: Function, T <: AbstractFloat}
59-
len = length(curve)
6060
point(t) = curve(t, alg)
61-
integrand(t) = len * f(point(t))
61+
integrand(t) = f(point(t)) * differential(curve, (t,))
6262
return QuadGK.quadgk(integrand, zero(FP), one(FP); rule.kwargs...)[1]
6363
end
6464

@@ -80,16 +80,15 @@ function integral(
8080
FP::Type{T} = Float64,
8181
alg::Meshes.BezierEvalMethod = Meshes.Horner()
8282
) where {F <: Function, T <: AbstractFloat}
83-
len = length(curve)
8483
point(t) = curve(t, alg)
85-
integrand(t) = len * f(point(t[1]))
84+
integrand(ts) = f(point(only(ts))) * differential(curve, ts)
8685

8786
# HCubature doesn't support functions that output Unitful Quantity types
8887
# Establish the units that are output by f
89-
testpoint_parametriccoord = zeros(FP, 3)
88+
testpoint_parametriccoord = zeros(FP, 1)
9089
integrandunits = Unitful.unit.(integrand(testpoint_parametriccoord))
9190
# Create a wrapper that returns only the value component in those units
92-
uintegrand(uv) = Unitful.ustrip.(integrandunits, integrand(uv))
91+
uintegrand(ts) = Unitful.ustrip.(integrandunits, integrand(ts))
9392
# Integrate only the unitless values
9493
value = HCubature.hcubature(uintegrand, zeros(FP, 1), ones(FP, 1); rule.kwargs...)[1]
9594

test/auto_tests.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ end
8383
# Test Geometries
8484
ball2d(T) = Ball(origin2d(T), T(2.0))
8585
ball3d(T) = Ball(origin3d(T), T(2.0))
86-
bezier(T) = BezierCurve([Point(cos(t), sin(t), 0) for t in T(0):T(0.1):T(2π)])
8786
box1d(T) = Box(Point(T(-1)), Point(T(1)))
8887
box2d(T) = Box(Point(T(-1), T(-1)), Point(T(1), T(1)))
8988
box3d(T) = Box(Point(T(-1), T(-1), T(-1)), Point(T(1), T(1), T(-1)))
@@ -102,7 +101,6 @@ end
102101
# Name, T type, example, integral,line,surface,volume, GaussLegendre,GaussKronrod,HAdaptiveCubature
103102
SupportItem("Ball{2,$T}", T, ball2d(T), 1, 0, 1, 0, 1, 1, 1),
104103
SupportItem("Ball{3,$T}", T, ball3d(T), 1, 0, 0, 1, 1, 0, 1),
105-
SupportItem("BezierCurve{$T}", T, bezier(T), 1, 1, 0, 0, 1, 1, 1),
106104
SupportItem("Box{1,$T}", T, box1d(T), 1, 1, 0, 0, 1, 1, 1),
107105
SupportItem("Box{2,$T}", T, box2d(T), 1, 0, 1, 0, 1, 1, 1),
108106
SupportItem("Box{3,$T}", T, box3d(T), 1, 0, 0, 1, 1, 0, 1),

test/combinations.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22
# - All supported combinations of integral(f, ::Geometry, ::IntegrationAlgorithm) produce accurate results
33
# - Invalid applications of integral aliases (e.g. lineintegral) produce a descriptive error
44

5+
@testitem "Meshes.BezierCurve" setup=[Setup] begin
6+
curve = BezierCurve(
7+
[Point(t * u"m", sin(t) * u"m", 0.0u"m") for t in range(-pi, pi, length = 361)]
8+
)
9+
10+
f(x, y, z) = (1 / sqrt(1 + cos(x)^2)) * u"Ω/m"
11+
f(p::Meshes.Point) = f(ustrip(to(p))...)
12+
fv(p) = fill(f(p), 3)
13+
14+
# Scalar integrand
15+
sol = 2π * u"Ω"
16+
@test integral(f, curve, GaussLegendre(100))sol rtol=0.5e-2
17+
@test integral(f, curve, GaussKronrod())sol rtol=0.5e-2
18+
@test integral(f, curve, HAdaptiveCubature())sol rtol=0.5e-2
19+
20+
# Vector integrand
21+
vsol = fill(sol, 3)
22+
@test integral(fv, curve, GaussLegendre(100))vsol rtol=0.5e-2
23+
@test integral(fv, curve, GaussKronrod())vsol rtol=0.5e-2
24+
@test integral(fv, curve, HAdaptiveCubature())vsol rtol=0.5e-2
25+
26+
# Integral aliases
27+
@test lineintegral(f, curve)sol rtol=0.5e-2
28+
@test_throws "not supported" surfaceintegral(f, curve)
29+
@test_throws "not supported" volumeintegral(f, curve)
30+
end
31+
532
@testitem "Meshes.Cone" setup=[Setup] begin
633
r = 2.5u"m"
734
h = 2.5u"m"

0 commit comments

Comments
 (0)