Skip to content

Commit 8bd66b4

Browse files
mikeingoldgithub-actions[bot]JoshuaLampert
authored
Add support for integrating over PolyArea and Domain types (#182)
* Add tests for SimpleMesh * Add integral method for Domains, generalize aliases to GeometryOrDomain * Define specialization methods for PolyArea * Use explicit namespace for Meshes.Domain * Generalize support_status geometry type * Bugfix, explicit import GeometryOrDomain * Explicit use of non-exported union * Explicit namespace bugfix * Extend supports_autoenzyme to Domains * Formatting changes * Adjust test imports * Add tests for RegularGrid * Add tests for CartesianGrid, add missing ustrip import * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add explicit rtol's, reduce dims to speed up RegularGrid tests * Generalize supports_autoenzyme from utils to Domains * Bugfix update old name * Make partitioned integral codes consistent * Add PolyArea tests * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Bugfix * Bugfix * Reverse hole region coord path * Apply suggestion Co-authored-by: Joshua Lampert <[email protected]> * Mirror GeometryOrDomain internally * Bugfix * Remove unnecessary use of GeometryOrDomain, switch usings to imports * Update docs and merge recent v0.16.3 release changes * Add tests and docs for StructuredGrid * Update test/combinations.jl Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Remove unnecessary method * Docstring and style updates * Update docstrings for consistency and clarity, associated style improvements * Add note to changelog * Apply suggestions from code review Co-authored-by: Joshua Lampert <[email protected]> * Switch to function format due to line length --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Joshua Lampert <[email protected]>
1 parent 5c7a06e commit 8bd66b4

File tree

12 files changed

+216
-80
lines changed

12 files changed

+216
-80
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Adds integral methods for `PolyArea` and subtypes of `Domain` with explicit tests for `CartesianGrid`, `PolyArea`, `RegularGrid`, `SimpleMesh`, and `StructuredGrid`.
13+
14+
### Changed
15+
16+
- Generalizes the alias functions (e.g. `lineintegral`) to also accept `Domain`s.
17+
- Updated docstrings to improve clarity and consistency.
18+
19+
1020
## [0.16.3] - 2025-06-26
1121

1222
### Changed

docs/src/support.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ The following Support Matrix captures the current state of support for all geome
2626
combinations. Entries with a green check mark are fully supported and pass unit tests
2727
designed to check for accuracy.
2828

29-
| `Meshes.Geometry` | `GaussKronrod` | `GaussLegendre` | `HAdaptiveCubature` |
29+
| `Meshes.Geometry/Domain` | `GaussKronrod` | `GaussLegendre` | `HAdaptiveCubature` |
3030
|----------|----------------|---------------|---------------------|
3131
| `Ball` in `𝔼{2}` | ⚠️ |||
3232
| `Ball` in `𝔼{3}` | 🛑 |||
3333
| `BezierCurve` ||||
3434
| `Box` in `𝔼{1}` ||||
3535
| `Box` in `𝔼{2}` | ⚠️ |||
3636
| `Box` in `𝔼{≥3}` | 🛑 |||
37+
| `CartesianGrid` ||||
3738
| `Circle` ||||
3839
| `Cone` | 🛑 |||
3940
| `ConeSurface` | ⚠️ |||
@@ -48,16 +49,18 @@ designed to check for accuracy.
4849
| `ParaboloidSurface` | ⚠️ |||
4950
| `ParametrizedCurve` ||||
5051
| `Plane` ||||
51-
| `Polyarea` | 🛑 | [🎗️](https://github.com/JuliaGeometry/MeshIntegrals.jl/issues/28) | [🎗️](https://github.com/JuliaGeometry/MeshIntegrals.jl/issues/28) |
52+
| `PolyArea` | ⚠️ | | |
5253
| `Pyramid` | ⚠️ |||
5354
| `Quadrangle` | ⚠️ |||
5455
| `Ray` ||||
56+
| `RegularGrid` ||||
5557
| `Ring` ||||
5658
| `Rope` ||||
5759
| `Segment` ||||
58-
| `SimpleMesh` | 🛑 | [🎗️](https://github.com/JuliaGeometry/MeshIntegrals.jl/issues/27) | [🎗️](https://github.com/JuliaGeometry/MeshIntegrals.jl/issues/27) |
60+
| `SimpleMesh` | ⚠️ | | |
5961
| `Sphere` in `𝔼{2}` ||||
6062
| `Sphere` in `𝔼{3}` | ⚠️ |||
63+
| `StructuredGrid` ||||
6164
| `Tetrahedron` | ⚠️ |||
6265
| `Triangle` ||||
6366
| `Torus` | ⚠️ |||
@@ -66,6 +69,5 @@ designed to check for accuracy.
6669
| Symbol | Support Level |
6770
|--------|---------|
6871
|| Supported |
69-
| 🎗️ | Planned to support in the future |
7072
| ⚠️ | Deprecated |
71-
| 🛑 | Not supported |
73+
| 🛑 | Not supported |

ext/MeshIntegralsEnzymeExt.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
module MeshIntegralsEnzymeExt
22

3-
using MeshIntegrals: MeshIntegrals, AutoEnzyme
4-
using Meshes: Meshes
5-
using Enzyme: Enzyme
3+
import MeshIntegrals
4+
import Meshes
5+
import Enzyme
66

77
function MeshIntegrals.jacobian(
88
geometry::Meshes.Geometry,
99
ts::Union{AbstractVector{T}, Tuple{T, Vararg{T}}},
10-
::AutoEnzyme
10+
::MeshIntegrals.AutoEnzyme
1111
) where {T <: AbstractFloat}
1212
Dim = Meshes.paramdim(geometry)
1313
if Dim != length(ts)
@@ -16,9 +16,10 @@ function MeshIntegrals.jacobian(
1616
return Meshes.to.(Enzyme.jacobian(Enzyme.Forward, geometry, ts...))
1717
end
1818

19-
# Supports all geometries except for those that throw errors
20-
# See GitHub Issue #154 for more information
19+
# Supports all geometries/domains by default
2120
MeshIntegrals.supports_autoenzyme(::Type{<:Meshes.Geometry}) = true
21+
MeshIntegrals.supports_autoenzyme(::Type{<:Meshes.Domain}) = true
22+
# except for those that throw errors (see GitHub Issue #154)
2223
MeshIntegrals.supports_autoenzyme(::Type{<:Meshes.BezierCurve}) = false
2324
MeshIntegrals.supports_autoenzyme(::Type{<:Meshes.CylinderSurface}) = false
2425
MeshIntegrals.supports_autoenzyme(::Type{<:Meshes.Cylinder}) = false

src/MeshIntegrals.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import LinearAlgebra
99
import QuadGK
1010
import Unitful
1111

12+
# Same as the internal-only Meshes.GeometryOrDomain
13+
const GeometryOrDomain = Union{Meshes.Geometry, Meshes.Domain}
14+
1215
include("differentiation.jl")
1316
export DifferentiationMethod, FiniteDifference, AutoEnzyme, jacobian
1417

@@ -31,6 +34,7 @@ include("specializations/CylinderSurface.jl")
3134
include("specializations/FrustumSurface.jl")
3235
include("specializations/Line.jl")
3336
include("specializations/Plane.jl")
37+
include("specializations/PolyArea.jl")
3438
include("specializations/Ray.jl")
3539
include("specializations/Ring.jl")
3640
include("specializations/Rope.jl")

src/integral.jl

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,52 @@
33
################################################################################
44

55
"""
6-
integral(f, geometry[, rule]; diff_method=_default_diff_method(geometry, FP), FP=Float64)
6+
integral(f, geometry[, rule]; kwargs...)
77
88
Numerically integrate a given function `f(::Point)` over the domain defined by
99
a `geometry` using a particular numerical integration `rule` with floating point
1010
precision of type `FP`.
1111
1212
# Arguments
1313
- `f`: an integrand function, i.e. any callable with a method `f(::Meshes.Point)`
14-
- `geometry`: some `Meshes.Geometry` that defines the integration domain
14+
- `geometry`: a Meshes.jl `Geometry` or `Domain` that defines the integration domain
1515
- `rule`: optionally, the `IntegrationRule` used for integration (by default
1616
`GaussKronrod()` in 1D and `HAdaptiveCubature()` else)
1717
1818
# Keyword Arguments
19-
- `diff_method::DifferentiationMethod = _default_diff_method(geometry, FP)`: the method to
20-
use for calculating Jacobians that are used to calculate differential elements
21-
- `FP = Float64`: the floating point precision desired.
19+
- `diff_method::DifferentiationMethod`: manually specifies the differentiation method use to
20+
calculate Jacobians within the integration domain.
21+
- `FP = Float64`: manually specifies the desired output floating point precision
2222
"""
2323
function integral end
2424

25+
# Default integration rule to use if unspecified
26+
function _default_rule(geometry)
27+
ifelse(Meshes.paramdim(geometry) == 1, GaussKronrod(), HAdaptiveCubature())
28+
end
29+
2530
# If only f and geometry are specified, select default rule
2631
function integral(
2732
f,
2833
geometry::Geometry,
29-
rule::I = Meshes.paramdim(geometry) == 1 ? GaussKronrod() : HAdaptiveCubature();
34+
rule::I = _default_rule(geometry);
3035
kwargs...
3136
) where {I <: IntegrationRule}
3237
_integral(f, geometry, rule; kwargs...)
3338
end
3439

40+
function integral(
41+
f,
42+
domain::Meshes.Domain,
43+
rule::I = _default_rule(domain);
44+
kwargs...
45+
) where {I <: IntegrationRule}
46+
# Discretize the Domain into primitive geometries, sum the integrals over those
47+
subintegral(geometry) = integral(f, geometry, rule; kwargs...)
48+
subgeometries = collect(Meshes.elements(Meshes.discretize(domain)))
49+
return sum(subintegral, subgeometries)
50+
end
51+
3552
################################################################################
3653
# Integral Workers
3754
################################################################################

src/integral_aliases.jl

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,26 @@ Numerically integrate a given function `f(::Point)` along a line-like `geometry`
99
using a particular numerical integration `rule` with floating point precision of
1010
type `FP`.
1111
12+
This is a convenience wrapper around [`integral`](@ref) that additionally enforces
13+
a requirement that the geometry have one parametric dimension.
14+
1215
Rule types available:
1316
- [`GaussKronrod`](@ref) (default)
1417
- [`GaussLegendre`](@ref)
1518
- [`HAdaptiveCubature`](@ref)
1619
"""
1720
function lineintegral(
1821
f,
19-
geometry::Geometry,
22+
geometry::GeometryOrDomain,
2023
rule::IntegrationRule = GaussKronrod();
2124
kwargs...
2225
)
23-
N = Meshes.paramdim(geometry)
24-
25-
if N == 1
26-
return integral(f, geometry, rule; kwargs...)
27-
else
26+
if (N = Meshes.paramdim(geometry)) != 1
2827
throw(ArgumentError("Performing a line integral on a geometry \
2928
with $N parametric dimensions not supported."))
3029
end
30+
31+
return integral(f, geometry, rule; kwargs...)
3132
end
3233

3334
################################################################################
@@ -41,25 +42,26 @@ Numerically integrate a given function `f(::Point)` along a surface `geometry`
4142
using a particular numerical integration `rule` with floating point precision of
4243
type `FP`.
4344
45+
This is a convenience wrapper around [`integral`](@ref) that additionally enforces
46+
a requirement that the geometry have two parametric dimensions.
47+
4448
Algorithm types available:
4549
- [`GaussKronrod`](@ref)
4650
- [`GaussLegendre`](@ref)
4751
- [`HAdaptiveCubature`](@ref) (default)
4852
"""
4953
function surfaceintegral(
5054
f,
51-
geometry::Geometry,
55+
geometry::GeometryOrDomain,
5256
rule::IntegrationRule = HAdaptiveCubature();
5357
kwargs...
5458
)
55-
N = Meshes.paramdim(geometry)
56-
57-
if N == 2
58-
return integral(f, geometry, rule; kwargs...)
59-
else
59+
if (N = Meshes.paramdim(geometry)) != 2
6060
throw(ArgumentError("Performing a surface integral on a geometry \
6161
with $N parametric dimensions not supported."))
6262
end
63+
64+
return integral(f, geometry, rule; kwargs...)
6365
end
6466

6567
################################################################################
@@ -73,23 +75,24 @@ Numerically integrate a given function `f(::Point)` throughout a volumetric
7375
`geometry` using a particular numerical integration `rule` with floating point
7476
precision of type `FP`.
7577
78+
This is a convenience wrapper around [`integral`](@ref) that additionally enforces
79+
a requirement that the geometry have three parametric dimensions.
80+
7681
Algorithm types available:
7782
- [`GaussKronrod`](@ref)
7883
- [`GaussLegendre`](@ref)
7984
- [`HAdaptiveCubature`](@ref) (default)
8085
"""
8186
function volumeintegral(
8287
f,
83-
geometry::Geometry,
88+
geometry::GeometryOrDomain,
8489
rule::IntegrationRule = HAdaptiveCubature();
8590
kwargs...
8691
)
87-
N = Meshes.paramdim(geometry)
88-
89-
if N == 3
90-
return integral(f, geometry, rule; kwargs...)
91-
else
92+
if (N = Meshes.paramdim(geometry)) != 3
9293
throw(ArgumentError("Performing a volume integral on a geometry \
9394
with $N parametric dimensions not supported."))
9495
end
96+
97+
return integral(f, geometry, rule; kwargs...)
9598
end

src/specializations/BezierCurve.jl

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,11 @@
1313
# integral
1414
################################################################################
1515
"""
16-
integral(f, curve::BezierCurve, rule = GaussKronrod();
17-
diff_method=Analytical(), FP=Float64, alg=Meshes.Horner())
16+
integral(f, curve::BezierCurve[, rule = GaussKronrod()]; kwargs...)
1817
1918
Like [`integral`](@ref) but integrates along the domain defined by `curve`.
2019
21-
# Arguments
22-
- `f`: an integrand function, i.e. any callable with a method `f(::Meshes.Point)`
23-
- `curve`: a `Meshes.BezierCurve` that defines the integration domain
24-
- `rule = GaussKronrod()`: optionally, the `IntegrationRule` used for integration
25-
26-
# Keyword Arguments
27-
- `diff_method::DifferentiationMethod = Analytical()`: the method to use for
28-
calculating Jacobians that are used to calculate differential elements
29-
- `FP = Float64`: the floating point precision desired
20+
# Special Keyword Arguments
3021
- `alg = Meshes.Horner()`: the method to use for parametrizing `curve`. Alternatively,
3122
`alg=Meshes.DeCasteljau()` can be specified for increased accuracy, but comes with a
3223
steep performance cost, especially for curves with a large number of control points.

src/specializations/PolyArea.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
################################################################################
2+
# Specialized Methods for PolyArea
3+
#
4+
# Why Specialized?
5+
# The PolyArea geometry defines a surface bounded by a polygon, but Meshes.jl
6+
# cannot define a parametric function for a polyarea because a general solution
7+
# for one does not exist. However, they can be partitioned into simpler elements
8+
# and integrated separately before finally summing the result.
9+
################################################################################
10+
11+
"""
12+
integral(f, area::PolyArea[, rule = HAdaptiveCubature()]; kwargs...)
13+
14+
Like [`integral`](@ref) but integrates over the surface domain defined by a `PolyArea`.
15+
The surface is first discretized into facets that are integrated independently using
16+
the specified integration `rule`.
17+
"""
18+
function integral(
19+
f,
20+
area::Meshes.PolyArea,
21+
rule::I;
22+
kwargs...
23+
) where {I <: IntegrationRule}
24+
# Partition the PolyArea, sum the integrals over each of those areas
25+
subintegral(area) = integral(f, area, rule; kwargs...)
26+
subgeometries = collect(Meshes.elements(Meshes.discretize(area)))
27+
return sum(subintegral, subgeometries)
28+
end

src/specializations/Ring.jl

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,11 @@
99
################################################################################
1010

1111
"""
12-
integral(f, ring::Ring, rule = GaussKronrod();
13-
diff_method=FiniteDifference(), FP=Float64)
12+
integral(f, ring::Ring[, rule = GaussKronrod()]; kwargs...)
1413
1514
Like [`integral`](@ref) but integrates along the domain defined by `ring`. The
1615
specified integration `rule` is applied independently to each segment formed by
1716
consecutive points in the Ring.
18-
19-
# Arguments
20-
- `f`: an integrand function, i.e. any callable with a method `f(::Meshes.Point)`
21-
- `ring`: a `Ring` that defines the integration domain
22-
- `rule = GaussKronrod()`: optionally, the `IntegrationRule` used for integration
23-
24-
# Keyword Arguments
25-
- `diff_method::DifferentiationMethod = FiniteDifference()`: the method to use for
26-
calculating Jacobians that are used to calculate differential elements
27-
- `FP = Float64`: the floating point precision desired
2817
"""
2918
function integral(
3019
f,
@@ -33,5 +22,6 @@ function integral(
3322
kwargs...
3423
) where {I <: IntegrationRule}
3524
# Convert the Ring into Segments, sum the integrals of those
36-
return sum(segment -> _integral(f, segment, rule; kwargs...), Meshes.segments(ring))
25+
subintegral(segment) = _integral(f, segment, rule; kwargs...)
26+
return sum(subintegral, Meshes.segments(ring))
3727
end

src/specializations/Rope.jl

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,11 @@
99
################################################################################
1010

1111
"""
12-
integral(f, rope::Rope, rule = GaussKronrod();
13-
diff_method=FiniteDifference(), FP=Float64)
12+
integral(f, rope::Rope[, rule = GaussKronrod()]; kwargs...)
1413
1514
Like [`integral`](@ref) but integrates along the domain defined by `rope`. The
1615
specified integration `rule` is applied independently to each segment formed by
1716
consecutive points in the Rope.
18-
19-
# Arguments
20-
- `f`: an integrand function, i.e. any callable with a method `f(::Meshes.Point)`
21-
- `rope`: a `Rope` that defines the integration domain
22-
- `rule = GaussKronrod()`: optionally, the `IntegrationRule` used for integration
23-
24-
# Keyword Arguments
25-
- `diff_method::DifferentiationMethod = FiniteDifference()`: the method to use for
26-
calculating Jacobians that are used to calculate differential elements
27-
- `FP = Float64`: the floating point precision desired
2817
"""
2918
function integral(
3019
f,
@@ -33,5 +22,6 @@ function integral(
3322
kwargs...
3423
) where {I <: IntegrationRule}
3524
# Convert the Rope into Segments, sum the integrals of those
36-
return sum(segment -> integral(f, segment, rule; kwargs...), Meshes.segments(rope))
25+
subintegral(segment) = integral(f, segment, rule; kwargs...)
26+
return sum(subintegral, Meshes.segments(rope))
3727
end

0 commit comments

Comments
 (0)