Skip to content

Commit c336d9f

Browse files
authored
Make DoubleFloats, Quadmath & GenericFFT weak dependencies (package extensions) (#14)
Float64-only users no longer load DoubleFloats, Quadmath (and thus the libquadmath C library), or GenericFFT: all three are `[weakdeps]` activated via package extensions, pulled in only when the user does `using DoubleFloats` / `using Quadmath` / `using GenericFFT`. Verified: `using TransitionMatrices` alone loads none of the three extensions, and a Float64 EBCM computation runs unchanged. These are all opt-in extras — the core (Float64, FFTW, Arblib) path never needs them: - ext/TransitionMatricesDoubleFloatsExt.jl: Double64 — precision(Complex{Double64}), Arblib.set!(::Double64), and the Double64 `cbrt`/`∛` workaround. - ext/TransitionMatricesQuadmathExt.jl: Float128 — Float128⇄Arb conversions, precision(ComplexF128), the instance precision(::Float128) fix (Quadmath leaves it unimplemented), and Arblib.set!(::Float128). - ext/TransitionMatricesGenericFFTExt.jl: the generic-type azimuthal FFT for the n-fold IITM — `_iitm_fft_capable(Complex{<:AbstractFloat})`, the GenericFFT plan, and the generic `_apply_forward_dft!`. Without GenericFFT, Complex{Double64}/Complex{BigFloat} n-fold IITM gracefully falls back to the direct azimuthal sum (correct, just no FFT acceleration); ComplexF64 stays on FFTW and Acb on Arblib.dft! (both hard deps). Note: weakening Quadmath alone would have no effect, because DoubleFloats lists Quadmath in its own deps and so transitively loads it — they had to be weakened together for Float64-only loads to drop libquadmath. Arblib stays a hard dependency: the arbitrary-precision + special-function backbone (gausslegendre for non-Float64, factorial(n>20) via gamma! in the Wigner-d functions — reached even on the Float64 path — and Arb/Acb as first-class numeric types in the EBCM Arb-matrix inversion and the IITM Acb FFT backend). Re-exports of Double64 / Float128 / ComplexF128 are dropped (use the respective package); Arb/Acb stay exported. Tests and the benchmark load the weak deps explicitly; test/Project.toml and benchmark/Project.toml gain them. Full suite: 48,290 tests pass.
1 parent 9383750 commit c336d9f

14 files changed

Lines changed: 141 additions & 73 deletions

Project.toml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
name = "TransitionMatrices"
22
uuid = "057c4241-e127-4181-840e-6b4b92e6eef5"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
authors = ["Gabriel Wu <wuzihua@pku.edu.cn> and contributors"]
55

66
[deps]
77
Arblib = "fb37089c-8514-4489-9461-98f9c8763369"
8-
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
98
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
109
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
1110
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
12-
GenericFFT = "a8297547-1b15-4a5a-a998-a2ac5f1cef28"
1311
GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a"
1412
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1513
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
16-
Quadmath = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd"
1714
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
1815
StableTasks = "91464d47-22a1-43fe-8b7f-2d57ee82463f"
1916
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2017
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe"
2118
Wigxjpf = "af901252-fd8a-4391-8647-10b4fde07a1e"
2219

20+
[weakdeps]
21+
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
22+
GenericFFT = "a8297547-1b15-4a5a-a998-a2ac5f1cef28"
23+
Quadmath = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd"
24+
25+
[extensions]
26+
TransitionMatricesDoubleFloatsExt = "DoubleFloats"
27+
TransitionMatricesGenericFFTExt = "GenericFFT"
28+
TransitionMatricesQuadmathExt = "Quadmath"
29+
2330
[compat]
2431
Arblib = "1"
2532
DoubleFloats = "1"

benchmark/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
[deps]
22
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
3+
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
34
TransitionMatrices = "057c4241-e127-4181-840e-6b4b92e6eef5"
45

56
[compat]
67
BenchmarkTools = "1"
8+
DoubleFloats = "1"
79
julia = "1.10"

benchmark/benchmarks.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
using BenchmarkTools
2121
using TransitionMatrices
22+
using DoubleFloats: Double64 # weak dep of TransitionMatrices; load explicitly for the precision benchmark
2223

2324
const SUITE = BenchmarkGroup()
2425

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module TransitionMatricesDoubleFloatsExt
2+
3+
# Double64 (DoubleFloats) support, loaded on demand. DoubleFloats is a *weak* dependency:
4+
# the core package never needs Double64 (it is a purely user-opt-in numeric type), so the
5+
# Double64-specific shims live here and activate only when the user has `using DoubleFloats`.
6+
# (Arblib is a hard dependency, so it is available to this extension.)
7+
8+
using TransitionMatrices
9+
using DoubleFloats: Double64
10+
using Arblib: Arblib
11+
12+
Base.precision(::Type{Complex{Double64}}) = 106
13+
14+
function Arblib.set!(arb::Arblib.ArbLike, val::Double64)
15+
Arblib.set!(arb, BigFloat(val))
16+
end
17+
18+
# Workaround for DoubleFloats v1.9.x: `cbrt` (and thus its alias `∛`) on a `Double64`
19+
# returns the raw `(hi, lo)` component tuple instead of a `Double64` (the upstream
20+
# `cbrt_db_db` forgets to wrap its result; `sqrt` is unaffected). This breaks every `∛`
21+
# call reached with a `Double64` argument, e.g. `volume_equivalent_radius` and the
22+
# Riccati-Bessel term estimators.
23+
#
24+
# The fix is behavior-gated: the guard below calls the upstream `cbrt` before our method
25+
# exists, so it installs the workaround only while upstream is still broken. Once a fixed
26+
# DoubleFloats returns a `Double64` here, this block is skipped at load time and the
27+
# upstream method is used unchanged — no sticky shadowing, no dead code.
28+
if !(cbrt(Double64(8.0)) isa Double64)
29+
# Specialized to `Double64` (more specific than upstream's parametric method);
30+
# recovers full precision with two Newton steps from a Float64 seed. `cbrt(Float64(a))`
31+
# dispatches to Base, so there is no recursion.
32+
function Base.cbrt(x::Double64)
33+
iszero(x) && return x
34+
a = abs(x)
35+
y = Double64(cbrt(Float64(a)))
36+
y = (2y + a / (y * y)) / 3
37+
y = (2y + a / (y * y)) / 3
38+
return x < 0 ? -y : y
39+
end
40+
end
41+
42+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module TransitionMatricesGenericFFTExt
2+
3+
# Generic-type azimuthal FFT for the n-fold IITM, loaded on demand. GenericFFT is a *weak*
4+
# dependency: ComplexF64 uses FFTW and Acb uses Arblib.dft! (both hard deps), so only the
5+
# Complex{Double64} / Complex{BigFloat} FFT path needs GenericFFT. Without it, those types
6+
# fall back to the direct azimuthal sum (slower but correct) — see `_iitm_fft_capable`.
7+
8+
using TransitionMatrices
9+
using GenericFFT: plan_fft
10+
11+
# Make non-Float64 complex-float types FFT-capable. ComplexF64 keeps its more-specific core
12+
# method (FFTW); Acb keeps Arblib.dft!.
13+
TransitionMatrices._iitm_fft_capable(::Type{<:Complex{<:AbstractFloat}}) = true
14+
15+
# Fresh GenericFFT plan each call: a BigFloat plan bakes in precision-specific twiddles and
16+
# must not be cached/reused across precision changes (no FFTW flags here).
17+
function TransitionMatrices._azimuthal_fft_plan(::Type{CT}, Nφ, Nϑ) where {CT <: Complex{<:AbstractFloat}}
18+
return plan_fft(zeros(CT, Nφ, Nϑ), 1)
19+
end
20+
21+
# GenericFFT's plan supports `plan * A` (allocating), not in-place mul!.
22+
function TransitionMatrices._apply_forward_dft!(spectrum::Matrix{CT}, contrast::Matrix{CT},
23+
plan) where {CT <: Complex{<:AbstractFloat}}
24+
spectrum .= plan * contrast
25+
end
26+
27+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module TransitionMatricesQuadmathExt
2+
3+
# Float128 (Quadmath) support, loaded on demand. Quadmath is a *weak* dependency: the
4+
# core package never needs Float128 (it is a purely user-opt-in numeric type), so the
5+
# Float128 ⇄ Arb glue lives here and activates only when the user has `using Quadmath`.
6+
# (Arblib is a hard dependency, so it is available to this extension.)
7+
8+
using TransitionMatrices
9+
using Quadmath: Quadmath, Float128, ComplexF128
10+
using Arblib: Arblib, ArbLike
11+
12+
Base.convert(::Type{Float128}, x::ArbLike) = Float128(BigFloat(x))
13+
Quadmath.Float128(x::ArbLike) = Float128(BigFloat(x))
14+
Base.precision(::Type{ComplexF128}) = 113
15+
16+
# Quadmath implements `precision(::Type{Float128})` but not the *instance* method
17+
# `precision(::Float128)` (it routes to a missing `_precision_with_base_2`). Define it so
18+
# generic `precision(x)` works on Float128 values (Float128 has a 113-bit significand).
19+
Base.precision(::Float128) = 113
20+
21+
function Arblib.set!(arb::Arblib.ArbLike, val::Float128)
22+
Arblib.set!(arb, BigFloat(val))
23+
end
24+
25+
end

packages/EBCMPrecisionLossEstimators/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ TransitionMatrices = "057c4241-e127-4181-840e-6b4b92e6eef5"
1111
[compat]
1212
MLJ = "0.23"
1313
MLJXGBoostInterface = "0.3"
14-
TransitionMatrices = "0.5"
14+
TransitionMatrices = "0.6"
1515
julia = "1.8"

src/IITM/fourier.jl

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ _iitm_ldiv(𝐌::AbstractMatrix{<:Union{Arb, Acb}}, 𝐗) = inv(𝐌) * 𝐗
66
_iitm_ldiv(𝐌, 𝐗) = 𝐌 \ 𝐗
77

88
# Capability predicate: true when the FFT path is available for this complex type.
9-
_iitm_fft_capable(::Type{<:Complex{<:AbstractFloat}}) = true # FFTW / GenericFFT
10-
_iitm_fft_capable(::Type{Acb}) = true # Arblib.dft!
11-
_iitm_fft_capable(::Type) = false # direct fallback
9+
# ComplexF64 → FFTW (hard dep); Acb → Arblib.dft! (hard dep). Other Complex{<:AbstractFloat}
10+
# (Double64/BigFloat) become capable only when GenericFFT is loaded — see
11+
# `ext/TransitionMatricesGenericFFTExt.jl`; without it they use the direct azimuthal sum.
12+
_iitm_fft_capable(::Type{ComplexF64}) = true # FFTW
13+
_iitm_fft_capable(::Type{Acb}) = true # Arblib.dft!
14+
_iitm_fft_capable(::Type) = false # direct fallback (incl. generic float w/o GenericFFT)
1215

1316
struct _AzimuthalFourierWorkspace{CT, P}
1417
contrast::Matrix{CT}
@@ -49,11 +52,8 @@ function _azimuthal_fft_plan(::Type{ComplexF64}, Nφ, Nϑ)
4952
end
5053
end
5154

52-
# GenericFFT path: fresh plan each time (bakes in precision-specific twiddles).
53-
# Do NOT cache — a BigFloat plan would go stale if precision changes.
54-
function _azimuthal_fft_plan(::Type{CT}, Nφ, Nϑ) where {CT <: Complex{<:AbstractFloat}}
55-
return plan_fft(zeros(CT, Nφ, Nϑ), 1) # GenericFFT; no flags kwarg
56-
end
55+
# The GenericFFT plan for non-Float64 complex-float types lives in
56+
# `ext/TransitionMatricesGenericFFTExt.jl` (GenericFFT is a weak dependency).
5757

5858
# ---------- Workspace constructors ----------
5959

@@ -102,13 +102,8 @@ function _apply_forward_dft!(spectrum::Matrix{ComplexF64},
102102
mul!(spectrum, plan, contrast)
103103
end
104104

105-
# ---------- Forward column DFT: generic Complex{<:AbstractFloat} (GenericFFT) ----------
106-
# GenericFFT's DummyFFTPlan only supports `plan * A` (allocating), not mul!.
107-
function _apply_forward_dft!(spectrum::Matrix{CT},
108-
contrast::Matrix{CT},
109-
plan) where {CT <: Complex{<:AbstractFloat}}
110-
spectrum .= plan * contrast
111-
end
105+
# The generic (non-ComplexF64) forward column DFT — used by the GenericFFT plan — lives in
106+
# `ext/TransitionMatricesGenericFFTExt.jl` (GenericFFT is a weak dependency).
112107

113108
# ---------- Fourier coefficient extraction: generic Complex{<:AbstractFloat} ----------
114109

src/IITM/nfold.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,9 @@ function transition_matrix_iitm(s::AbstractNFoldShape{N, T, CT}, λ, nₘₐₓ,
216216
end
217217

218218
@testitem "Generic FFT: Complex{Double64} Prism matches ComplexF64 reference" begin
219-
using TransitionMatrices: Prism, calc_T_iitm, calc_Csca, calc_Cext, Double64
219+
using TransitionMatrices: Prism, calc_T_iitm, calc_Csca, calc_Cext
220+
using DoubleFloats: Double64
221+
using GenericFFT # weak dep: load it so the Complex{Double64} azimuthal FFT path is exercised
220222

221223
# Reference: ComplexF64 (FFTW path)
222224
m_f64 = complex(1.5)

src/TransitionMatrices.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ module TransitionMatrices
22

33
using Arblib
44
using Arblib: ArbLike, AcbLike, ArbVectorLike, AcbVectorLike, ArbMatrixLike, AcbMatrixLike
5-
using DoubleFloats: Double64
65
using FastGaussQuadrature: FastGaussQuadrature
76
import FFTW
8-
using GenericFFT
97
using ForwardDiff: ForwardDiff
108
using GenericLinearAlgebra: Diagonal, GenericLinearAlgebra, cond, inv
119
using LinearAlgebra: lu, mul!
1210
using OffsetArrays: OffsetArray
13-
using Quadmath: Quadmath, Float128, ComplexF128
1411
using Rotations: Angle2d, Rotation, RotMatrix2, RotZYZ
1512
using StableTasks: StableTasks
1613
using StaticArrays: SVector, SMatrix, SArray, @SVector, @SMatrix, @SArray
@@ -78,7 +75,8 @@ export AbstractShape, AbstractAxisymmetricShape, AbstractNFoldShape, volume,
7875
rmin, rmax, Spheroid, Cylinder, Chebyshev, Prism,
7976
SuperSpheroid, SuperEllipsoid, SuperSpheroidRevolved
8077

81-
# Re-exports
82-
export RotZYZ, Double64, Float128, ComplexF128, Arb, Acb
78+
# Re-exports. Double64 / Float128 / ComplexF128 are NOT re-exported — DoubleFloats and
79+
# Quadmath are weak dependencies; `using DoubleFloats` / `using Quadmath` to access them.
80+
export RotZYZ, Arb, Acb
8381

8482
end

0 commit comments

Comments
 (0)