Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
name = "TransitionMatrices"
uuid = "057c4241-e127-4181-840e-6b4b92e6eef5"
version = "0.5.0"
version = "0.6.0"
authors = ["Gabriel Wu <wuzihua@pku.edu.cn> and contributors"]

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

[weakdeps]
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
GenericFFT = "a8297547-1b15-4a5a-a998-a2ac5f1cef28"
Quadmath = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd"

[extensions]
TransitionMatricesDoubleFloatsExt = "DoubleFloats"
TransitionMatricesGenericFFTExt = "GenericFFT"
TransitionMatricesQuadmathExt = "Quadmath"

[compat]
Arblib = "1"
DoubleFloats = "1"
Expand Down
2 changes: 2 additions & 0 deletions benchmark/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
TransitionMatrices = "057c4241-e127-4181-840e-6b4b92e6eef5"

[compat]
BenchmarkTools = "1"
DoubleFloats = "1"
julia = "1.10"
1 change: 1 addition & 0 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

using BenchmarkTools
using TransitionMatrices
using DoubleFloats: Double64 # weak dep of TransitionMatrices; load explicitly for the precision benchmark

const SUITE = BenchmarkGroup()

Expand Down
42 changes: 42 additions & 0 deletions ext/TransitionMatricesDoubleFloatsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module TransitionMatricesDoubleFloatsExt

# Double64 (DoubleFloats) support, loaded on demand. DoubleFloats is a *weak* dependency:
# the core package never needs Double64 (it is a purely user-opt-in numeric type), so the
# Double64-specific shims live here and activate only when the user has `using DoubleFloats`.
# (Arblib is a hard dependency, so it is available to this extension.)

using TransitionMatrices
using DoubleFloats: Double64
using Arblib: Arblib

Base.precision(::Type{Complex{Double64}}) = 106

function Arblib.set!(arb::Arblib.ArbLike, val::Double64)
Arblib.set!(arb, BigFloat(val))
end

# Workaround for DoubleFloats v1.9.x: `cbrt` (and thus its alias `∛`) on a `Double64`
# returns the raw `(hi, lo)` component tuple instead of a `Double64` (the upstream
# `cbrt_db_db` forgets to wrap its result; `sqrt` is unaffected). This breaks every `∛`
# call reached with a `Double64` argument, e.g. `volume_equivalent_radius` and the
# Riccati-Bessel term estimators.
#
# The fix is behavior-gated: the guard below calls the upstream `cbrt` before our method
# exists, so it installs the workaround only while upstream is still broken. Once a fixed
# DoubleFloats returns a `Double64` here, this block is skipped at load time and the
# upstream method is used unchanged — no sticky shadowing, no dead code.
if !(cbrt(Double64(8.0)) isa Double64)
# Specialized to `Double64` (more specific than upstream's parametric method);
# recovers full precision with two Newton steps from a Float64 seed. `cbrt(Float64(a))`
# dispatches to Base, so there is no recursion.
function Base.cbrt(x::Double64)
iszero(x) && return x
a = abs(x)
y = Double64(cbrt(Float64(a)))
y = (2y + a / (y * y)) / 3
y = (2y + a / (y * y)) / 3
return x < 0 ? -y : y
end
end

end
27 changes: 27 additions & 0 deletions ext/TransitionMatricesGenericFFTExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module TransitionMatricesGenericFFTExt

# Generic-type azimuthal FFT for the n-fold IITM, loaded on demand. GenericFFT is a *weak*
# dependency: ComplexF64 uses FFTW and Acb uses Arblib.dft! (both hard deps), so only the
# Complex{Double64} / Complex{BigFloat} FFT path needs GenericFFT. Without it, those types
# fall back to the direct azimuthal sum (slower but correct) — see `_iitm_fft_capable`.

using TransitionMatrices
using GenericFFT: plan_fft

# Make non-Float64 complex-float types FFT-capable. ComplexF64 keeps its more-specific core
# method (FFTW); Acb keeps Arblib.dft!.
TransitionMatrices._iitm_fft_capable(::Type{<:Complex{<:AbstractFloat}}) = true

# Fresh GenericFFT plan each call: a BigFloat plan bakes in precision-specific twiddles and
# must not be cached/reused across precision changes (no FFTW flags here).
function TransitionMatrices._azimuthal_fft_plan(::Type{CT}, Nφ, Nϑ) where {CT <: Complex{<:AbstractFloat}}
return plan_fft(zeros(CT, Nφ, Nϑ), 1)
end

# GenericFFT's plan supports `plan * A` (allocating), not in-place mul!.
function TransitionMatrices._apply_forward_dft!(spectrum::Matrix{CT}, contrast::Matrix{CT},
plan) where {CT <: Complex{<:AbstractFloat}}
spectrum .= plan * contrast
end

end
25 changes: 25 additions & 0 deletions ext/TransitionMatricesQuadmathExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module TransitionMatricesQuadmathExt

# Float128 (Quadmath) support, loaded on demand. Quadmath is a *weak* dependency: the
# core package never needs Float128 (it is a purely user-opt-in numeric type), so the
# Float128 ⇄ Arb glue lives here and activates only when the user has `using Quadmath`.
# (Arblib is a hard dependency, so it is available to this extension.)

using TransitionMatrices
using Quadmath: Quadmath, Float128, ComplexF128
using Arblib: Arblib, ArbLike

Base.convert(::Type{Float128}, x::ArbLike) = Float128(BigFloat(x))
Quadmath.Float128(x::ArbLike) = Float128(BigFloat(x))
Base.precision(::Type{ComplexF128}) = 113

# Quadmath implements `precision(::Type{Float128})` but not the *instance* method
# `precision(::Float128)` (it routes to a missing `_precision_with_base_2`). Define it so
# generic `precision(x)` works on Float128 values (Float128 has a 113-bit significand).
Base.precision(::Float128) = 113

function Arblib.set!(arb::Arblib.ArbLike, val::Float128)
Arblib.set!(arb, BigFloat(val))
end

end
2 changes: 1 addition & 1 deletion packages/EBCMPrecisionLossEstimators/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ TransitionMatrices = "057c4241-e127-4181-840e-6b4b92e6eef5"
[compat]
MLJ = "0.23"
MLJXGBoostInterface = "0.3"
TransitionMatrices = "0.5"
TransitionMatrices = "0.6"
julia = "1.8"
25 changes: 10 additions & 15 deletions src/IITM/fourier.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ _iitm_ldiv(𝐌::AbstractMatrix{<:Union{Arb, Acb}}, 𝐗) = inv(𝐌) * 𝐗
_iitm_ldiv(𝐌, 𝐗) = 𝐌 \ 𝐗

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

struct _AzimuthalFourierWorkspace{CT, P}
contrast::Matrix{CT}
Expand Down Expand Up @@ -49,11 +52,8 @@ function _azimuthal_fft_plan(::Type{ComplexF64}, Nφ, Nϑ)
end
end

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

# ---------- Workspace constructors ----------

Expand Down Expand Up @@ -102,13 +102,8 @@ function _apply_forward_dft!(spectrum::Matrix{ComplexF64},
mul!(spectrum, plan, contrast)
end

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

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

Expand Down
4 changes: 3 additions & 1 deletion src/IITM/nfold.jl
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ function transition_matrix_iitm(s::AbstractNFoldShape{N, T, CT}, λ, nₘₐₓ,
end

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

# Reference: ComplexF64 (FFTW path)
m_f64 = complex(1.5)
Expand Down
8 changes: 3 additions & 5 deletions src/TransitionMatrices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ module TransitionMatrices

using Arblib
using Arblib: ArbLike, AcbLike, ArbVectorLike, AcbVectorLike, ArbMatrixLike, AcbMatrixLike
using DoubleFloats: Double64
using FastGaussQuadrature: FastGaussQuadrature
import FFTW
using GenericFFT
using ForwardDiff: ForwardDiff
using GenericLinearAlgebra: Diagonal, GenericLinearAlgebra, cond, inv
using LinearAlgebra: lu, mul!
using OffsetArrays: OffsetArray
using Quadmath: Quadmath, Float128, ComplexF128
using Rotations: Angle2d, Rotation, RotMatrix2, RotZYZ
using StableTasks: StableTasks
using StaticArrays: SVector, SMatrix, SArray, @SVector, @SMatrix, @SArray
Expand Down Expand Up @@ -78,7 +75,8 @@ export AbstractShape, AbstractAxisymmetricShape, AbstractNFoldShape, volume,
rmin, rmax, Spheroid, Cylinder, Chebyshev, Prism,
SuperSpheroid, SuperEllipsoid, SuperSpheroidRevolved

# Re-exports
export RotZYZ, Double64, Float128, ComplexF128, Arb, Acb
# Re-exports. Double64 / Float128 / ComplexF128 are NOT re-exported — DoubleFloats and
# Quadmath are weak dependencies; `using DoubleFloats` / `using Quadmath` to access them.
export RotZYZ, Arb, Acb
Comment thread
coderabbitai[bot] marked this conversation as resolved.

end
41 changes: 5 additions & 36 deletions src/compat/index.jl
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
Base.convert(::Type{Float128}, x::ArbLike) = Float128(BigFloat(x))
Quadmath.Float128(x::ArbLike) = Float128(BigFloat(x))
# Float128 (Quadmath) ⇄ Arb conversions live in `ext/TransitionMatricesQuadmathExt.jl`
# (Quadmath is a weak dependency).
Base.round(x::Arb, ::RoundingMode{:Up}) = ceil(BigFloat(x))
Base.abs2(x::AcbLike) = abs2(real(x)) + abs2(imag(x))
Base.complex(::Type{Arb}) = Acb

# Workaround for DoubleFloats v1.9.x: `cbrt` (and thus its alias `∛`) on a
# `Double64` returns the raw `(hi, lo)` component tuple instead of a `Double64`
# (the upstream `cbrt_db_db` forgets to wrap its result; `sqrt` is unaffected).
# This breaks every `∛` call reached with a `Double64` argument, e.g.
# `volume_equivalent_radius` and the Riccati-Bessel term estimators.
#
# The fix is behavior-gated: the guard below calls the upstream `cbrt` before
# our method exists, so it installs the workaround only while upstream is still
# broken. Once a fixed DoubleFloats returns a `Double64` here, this block is
# skipped at load time and the upstream method is used unchanged — no sticky
# shadowing, no dead code. (A package upgrade recompiles this module, so the
# guard is re-evaluated against the new DoubleFloats version.)
if !(cbrt(Double64(8.0)) isa Double64)
# Specialized to `Double64` (more specific than upstream's parametric
# method); recovers full precision with two Newton steps from a Float64
# seed. `cbrt(Float64(a))` dispatches to Base, so there is no recursion.
function Base.cbrt(x::Double64)
iszero(x) && return x
a = abs(x)
y = Double64(cbrt(Float64(a)))
y = (2y + a / (y * y)) / 3
y = (2y + a / (y * y)) / 3
return x < 0 ? -y : y
end
end
# Double64 (DoubleFloats) shims — the `cbrt` workaround, `precision(Complex{Double64})`,
# and `Arblib.set!(::Double64)` — live in `ext/TransitionMatricesDoubleFloatsExt.jl`
# (DoubleFloats is a weak dependency).

function Base.inv(x::Matrix{Arb})
a = ArbMatrix(x)
Expand All @@ -44,8 +22,6 @@ end

Base.precision(::Type{ComplexF32}) = 24
Base.precision(::Type{ComplexF64}) = 53
Base.precision(::Type{Complex{Double64}}) = 106
Base.precision(::Type{ComplexF128}) = 113
Base.precision(::Type{Complex{Arb}}) = precision(Arb)

function Base.precision(::Type{
Expand All @@ -58,13 +34,6 @@ function Arblib.set!(arb::Arblib.ArbLike, dual::ForwardDiff.Dual)
Arblib.set!(arb, dual.value)
end

function Arblib.set!(arb::Arblib.ArbLike, val::Float128)
Arblib.set!(arb, BigFloat(val))
end

function Arblib.set!(arb::Arblib.ArbLike, val::Double64)
Arblib.set!(arb, BigFloat(val))
end

Base.Int64(x::ArbLike) = round(Int64, BigFloat(x))

Expand Down
14 changes: 4 additions & 10 deletions src/shapes/superspheroid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,8 @@ SuperSpheroid(a, c, n, m) = SuperSpheroid{typeof(a), typeof(m)}(a, c, n, m)
# needless detour and, because `BigFloat(::Arb)` uses the *global* precision, silently
# truncate high-precision `T` (Arb, large BigFloat). `precision(x)` reads the instance's
# precision, so Float64/Double64/Float128/BigFloat/Arb shapes each compute at their own.
# Working precision (bits) from the value's own precision, so high-precision shapes
# (Double64, BigFloat, Arb instances) are not truncated. `precision(::Float128)` (the
# instance method) is unimplemented in the current Quadmath/Julia (it routes to a
# missing `_precision_with_base_2`), so Float128 is pinned to its 113-bit mantissa via
# the working type method `precision(Float128)`.
_beta_prec(::Float128) = precision(Float128)
_beta_prec(x::Real) = precision(x)

function _beta_lgamma(x::T, y::T) where {T <: Real}
prec = max(_beta_prec(x), _beta_prec(y))
prec = max(precision(x), precision(y))
lΓx = Arblib.lgamma!(Arb(; prec), Arb(x; prec); prec)
lΓy = Arblib.lgamma!(Arb(; prec), Arb(y; prec); prec)
lΓxy = Arblib.lgamma!(Arb(; prec), Arb(x + y; prec); prec)
Expand Down Expand Up @@ -79,7 +71,9 @@ has_symmetric_plane(::SuperSpheroid) = true

@testitem "SuperSpheroid utility functions" begin
using TransitionMatrices: SuperSpheroid, volume, volume_equivalent_radius,
has_symmetric_plane, Double64, Float128, ComplexF128
has_symmetric_plane
using DoubleFloats: Double64
using Quadmath: Float128, ComplexF128

# n=1 reduces to spheroid: V = 4π/3 * a^2 * c
@testset "n=1 reduction to spheroid" begin
Expand Down
3 changes: 3 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[deps]
Arblib = "fb37089c-8514-4489-9461-98f9c8763369"
DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
GSL = "92c85e6c-cbff-5e0c-80f7-495c94daaecd"
GenericFFT = "a8297547-1b15-4a5a-a998-a2ac5f1cef28"
Quadmath = "be4d8f0f-7fa4-5f49-b795-2f01399ab2dd"
Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
Expand Down
5 changes: 4 additions & 1 deletion test/compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using TransitionMatrices
using Arblib
using ForwardDiff
using Quadmath: Quadmath, Float128, ComplexF128 # weak deps: load explicitly
using DoubleFloats: Double64

@test precision(ComplexF32) == 24
@test precision(ComplexF64) == 53
Expand All @@ -15,7 +17,7 @@

arb = Arb(1.25)
@test convert(Float128, arb) == Float128(1.25)
@test TransitionMatrices.Quadmath.Float128(arb) == Float128(1.25)
@test Quadmath.Float128(arb) == Float128(1.25)
@test Float32(arb) == Float32(1.25)
@test Int64(Arb(2.0)) == 2
@test round(Arb(1.2), RoundUp) == ceil(BigFloat(Arb(1.2)))
Expand Down Expand Up @@ -63,6 +65,7 @@ end

@testitem "cbrt on Double64 returns a Double64 (DoubleFloats v1.9 workaround)" begin
using TransitionMatrices
using DoubleFloats: Double64

# Upstream DoubleFloats returns a (hi, lo) tuple here; ensure our shim wraps it.
@test cbrt(Double64(8.0)) isa Double64
Expand Down
Loading