Skip to content

Commit af068ab

Browse files
mikeingoldgithub-actions[bot]JoshuaLampert
authored
v0.14-DEV: Refactor changing FP to a keyword argument (#91)
* Make uses of Line, Plane, and Ray explicit * Tidying up * Code cleanup * Bugfix * Bugfix * Convert FP to kwarg with pass-through to workers * Convert FP to kwarg * Convert FP to kwarg * Qualify use of Meshes.segments [skip ci] * Convert FP to kwarg * Convert FP to kwarg, wrapper no longer required for lineintegral * Change comma to semicolon * Convert FP to kwarg * Update tests * Fix erroneous leftover code fragment * Apply suggestions from JuliaFormatter code review [skip ci] Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Tighten up line lengths * Add missing kwargs * Drop unbound type parameter * apply format suggestions Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * default integration rules for `lineintegral`, `surfaceintegral`, and `volumeintegral` * drop unbound type parameter * fix default integration rule * fix Floats to Vectors * Qualify usages of Meshes area and volume * Qualify usage of Meshes degree and clarify plane * Remove unused explicit import - probably mistaken for I from IntegrationRule * Convert Meshes and Unitful to imports * Kill unitdirection function * Split differentiation functions into separate file * Improve docstring for jacobian * Rename GaussKronrod-specific workers --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Joshua Lampert <[email protected]> Co-authored-by: Joshua Lampert <[email protected]>
1 parent d784136 commit af068ab

17 files changed

+279
-454
lines changed

src/MeshIntegrals.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
module MeshIntegrals
22
using CoordRefSystems: CoordRefSystems, CRS
3-
using LinearAlgebra: LinearAlgebra, I, norm, ×,
4-
using Meshes: Meshes, Line, Plane, Ray, area, degree, plane, segments, volume
5-
using Unitful: Unitful
3+
using LinearAlgebra: LinearAlgebra, norm, ×,
64

75
import FastGaussQuadrature
86
import HCubature
7+
import Meshes
98
import QuadGK
9+
import Unitful
1010

1111
include("utils.jl")
12+
13+
include("differentiation.jl")
1214
export jacobian
1315

1416
include("integration_rules.jl")

src/differentiation.jl

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
################################################################################
2+
# Jacobian and Differential Elements
3+
################################################################################
4+
5+
"""
6+
jacobian(geometry, ts; ε=1e-6)
7+
8+
Calculate the Jacobian of a geometry at some parametric point `ts` using a simple
9+
finite-difference approximation with step size `ε`.
10+
11+
# Arguments
12+
- `geometry`: some `Meshes.Geometry` of N parametric dimensions
13+
- `ts`: a parametric point specified as a vector or tuple of length N
14+
- `ε`: step size to use for the finite-difference approximation
15+
"""
16+
function jacobian(
17+
geometry,
18+
ts;
19+
ε = 1e-6
20+
)
21+
T = eltype(ts)
22+
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+
if ts[n] < T(0.01)
27+
return ∂ₙr_right!(εv, ts, n)
28+
elseif T(0.99) < ts[n]
29+
return ∂ₙr_left!(εv, ts, n)
30+
else
31+
return ∂ₙr_central!(εv, ts, n)
32+
end
33+
end
34+
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))
64+
end
65+
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(
101+
bz::Meshes.BezierCurve,
102+
t
103+
)
104+
# Parameter t restricted to domain [0,1] by definition
105+
if t < 0 || t > 1
106+
throw(DomainError(t, "b(t) is not defined for t outside [0, 1]."))
107+
end
108+
109+
# Aliases
110+
P = bz.controls
111+
N = Meshes.degree(bz)
112+
113+
# Ensure that this implementation is tractible: limited by ability to calculate
114+
# binomial(N, N/2) without overflow. It's possible to extend this range by
115+
# converting N to a BigInt, but this results in always returning BigFloat's.
116+
N <= 1028 || error("This algorithm overflows for curves with ⪆1000 control points.")
117+
118+
# Generator for Bernstein polynomial functions
119+
B(i, n) = t -> binomial(Int128(n), i) * t^i * (1 - t)^(n - i)
120+
121+
# Derivative = N Σ_{i=0}^{N-1} sigma(i)
122+
# P indices adjusted for Julia 1-based array indexing
123+
sigma(i) = B(i, N - 1)(t) .* (P[(i + 1) + 1] - P[(i) + 1])
124+
return N .* sum(sigma, 0:(N - 1))
125+
end

src/integral.jl

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
################################################################################
44

55
"""
6-
integral(f, geometry)
7-
integral(f, geometry, rule)
8-
integral(f, geometry, rule, FP)
6+
integral(f, geometry[, rule]; FP=Float64)
97
108
Numerically integrate a given function `f(::Point)` over the domain defined by
119
a `geometry` using a particular numerical integration `rule` with floating point
@@ -24,23 +22,13 @@ contrast, increasing `FP` to e.g. `BigFloat` will typically increase precision
2422
function integral end
2523

2624
# If only f and geometry are specified, select default rule
27-
function integral(
28-
f::F,
29-
geometry::G
30-
) where {F <: Function, G <: Meshes.Geometry}
31-
N = Meshes.paramdim(geometry)
32-
rule = (N == 1) ? GaussKronrod() : HAdaptiveCubature()
33-
_integral(f, geometry, rule)
34-
end
35-
36-
# with rule and T specified
3725
function integral(
3826
f::F,
3927
geometry::G,
40-
rule::I,
41-
FP::Type{T} = Float64
42-
) where {F <: Function, G <: Meshes.Geometry, I <: IntegrationRule, T <: AbstractFloat}
43-
_integral(f, geometry, rule, FP)
28+
rule::I = Meshes.paramdim(geometry) == 1 ? GaussKronrod() : HAdaptiveCubature();
29+
kwargs...
30+
) where {F <: Function, G <: Meshes.Geometry, I <: IntegrationRule}
31+
_integral(f, geometry, rule; kwargs...)
4432
end
4533

4634
################################################################################
@@ -51,25 +39,25 @@ end
5139
function _integral(
5240
f,
5341
geometry,
54-
rule::GaussKronrod,
55-
FP::Type{T} = Float64
56-
) where {T <: AbstractFloat}
57-
# Run the appropriate integral type
42+
rule::GaussKronrod;
43+
kwargs...
44+
)
45+
# Pass through to dim-specific workers in next section of this file
5846
N = Meshes.paramdim(geometry)
5947
if N == 1
60-
return _integral_1d(f, geometry, rule, FP)
48+
return _integral_gk_1d(f, geometry, rule; kwargs...)
6149
elseif N == 2
62-
return _integral_2d(f, geometry, rule, FP)
50+
return _integral_gk_2d(f, geometry, rule; kwargs...)
6351
elseif N == 3
64-
return _integral_3d(f, geometry, rule, FP)
52+
return _integral_gk_3d(f, geometry, rule; kwargs...)
6553
end
6654
end
6755

6856
# GaussLegendre
6957
function _integral(
7058
f,
7159
geometry,
72-
rule::GaussLegendre,
60+
rule::GaussLegendre;
7361
FP::Type{T} = Float64
7462
) where {T <: AbstractFloat}
7563
N = Meshes.paramdim(geometry)
@@ -94,7 +82,7 @@ end
9482
function _integral(
9583
f,
9684
geometry,
97-
rule::HAdaptiveCubature,
85+
rule::HAdaptiveCubature;
9886
FP::Type{T} = Float64
9987
) where {T <: AbstractFloat}
10088
N = Meshes.paramdim(geometry)
@@ -103,7 +91,7 @@ function _integral(
10391

10492
# HCubature doesn't support functions that output Unitful Quantity types
10593
# Establish the units that are output by f
106-
testpoint_parametriccoord = fill(FP(0.5), N)
94+
testpoint_parametriccoord = zeros(FP, N)
10795
integrandunits = Unitful.unit.(integrand(testpoint_parametriccoord))
10896
# Create a wrapper that returns only the value component in those units
10997
uintegrand(uv) = Unitful.ustrip.(integrandunits, integrand(uv))
@@ -118,20 +106,20 @@ end
118106
# Specialized GaussKronrod Methods
119107
################################################################################
120108

121-
function _integral_1d(
109+
function _integral_gk_1d(
122110
f,
123111
geometry,
124-
rule::GaussKronrod,
112+
rule::GaussKronrod;
125113
FP::Type{T} = Float64
126114
) where {T <: AbstractFloat}
127115
integrand(t) = f(geometry(t)) * differential(geometry, (t))
128116
return QuadGK.quadgk(integrand, zero(FP), one(FP); rule.kwargs...)[1]
129117
end
130118

131-
function _integral_2d(
119+
function _integral_gk_2d(
132120
f,
133121
geometry2d,
134-
rule::GaussKronrod,
122+
rule::GaussKronrod;
135123
FP::Type{T} = Float64
136124
) where {T <: AbstractFloat}
137125
integrand(u, v) = f(geometry2d(u, v)) * differential(geometry2d, (u, v))
@@ -140,10 +128,10 @@ function _integral_2d(
140128
end
141129

142130
# Integrating volumes with GaussKronrod not supported by default
143-
function _integral_3d(
131+
function _integral_gk_3d(
144132
f,
145133
geometry,
146-
rule::GaussKronrod,
134+
rule::GaussKronrod;
147135
FP::Type{T} = Float64
148136
) where {T <: AbstractFloat}
149137
error("Integrating this volume type with GaussKronrod not supported.")

0 commit comments

Comments
 (0)