From beb791c4d5198560c5e2c1536d7595e4ad72dc8e Mon Sep 17 00:00:00 2001 From: Stefan de Lange Date: Thu, 30 Jul 2026 11:57:13 +0200 Subject: [PATCH 1/6] Differentiate with respect to refraction model parameters A dual valued pressure or temperature previously needed the observer lifted to the same dual type, and even then the keyword constructors rejected it. Three layers now promote instead: - HUGHES, BENNETT, SG2 and SPARefraction take mixed argument types. The keyword form that @kwdef generates routes through SPARefraction's three argument constructor, so it promotes too. - SolPos and ApparentSolPos promote over their fields, so apparent angles that carry a dual can join geometric angles that do not. - The vector paths size their StructVector from the observer's element type promoted against the refraction model's, since the observer's precision alone gives a container the results cannot convert into. The same type constructors stay more specific, so existing calls dispatch exactly as before. Tests cover the four parametric models against finite differences for both pressure and temperature, the promoting constructors, and the vector path's element type. Interpolated gains autodiff coverage as well: it is absent from test_algorithms() and so had none, though it already worked. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KhdBYRmHazfXZHWCRWt5fT --- docs/src/guides/autodiff.md | 15 ++++ src/Positioning/Positioning.jl | 46 +++++++++- src/Refraction/Refraction.jl | 11 +++ src/Refraction/bennett.jl | 5 ++ src/Refraction/hughes.jl | 5 ++ src/Refraction/sg2.jl | 5 ++ src/Refraction/spa.jl | 14 ++- test/positioning/test-autodiff.jl | 136 ++++++++++++++++++++++++++++++ 8 files changed, 231 insertions(+), 6 deletions(-) diff --git a/docs/src/guides/autodiff.md b/docs/src/guides/autodiff.md index 3cf8d6d8..5ad7fd6c 100644 --- a/docs/src/guides/autodiff.md +++ b/docs/src/guides/autodiff.md @@ -27,6 +27,21 @@ derivatives. Time is a `DateTime`, not a number, so derivatives with respect to are not available through this route. Differentiate through a wrapper that maps a number to a `DateTime` if you need them. +A refraction model's own parameters are differentiable inputs too. The model types and +the result types promote, so a dual valued pressure needs no change to the observer: + +```@example autodiff +ForwardDiff.derivative( + p -> solar_position(Observer(45.0, 10.0), dt, PSA(), HUGHES(p, 10.0)).apparent_elevation, + 101325.0, +) # degrees of apparent elevation per pascal +``` + +[`Interpolated`](@ref SolarPosition.Positioning.Interpolated) differentiates as well. Its +interpolants cover only the geocentric quantities, which depend on time alone, so the +duals travel through the topocentric half it shares with the wrapped algorithm and the +derivatives match that algorithm's to roundoff. + ## DifferentiationInterface Because the differentiability comes from the code being generic rather than from any diff --git a/src/Positioning/Positioning.jl b/src/Positioning/Positioning.jl index 95bc0b00..89c0af67 100644 --- a/src/Positioning/Positioning.jl +++ b/src/Positioning/Positioning.jl @@ -182,6 +182,30 @@ struct ApparentSolPos{T} <: AbstractApparentSolPos where {T <: Real} apparent_zenith::T end +# the element type promotes over the fields, so a position whose apparent angles carry a +# ForwardDiff.Dual from a differentiated refraction parameter can still be built from +# geometric angles that do not +SolPos(azimuth::Real, elevation::Real, zenith::Real) = + SolPos{promote_type(typeof(azimuth), typeof(elevation), typeof(zenith))}( + azimuth, elevation, zenith, +) + +function ApparentSolPos( + azimuth::Real, + elevation::Real, + zenith::Real, + apparent_elevation::Real, + apparent_zenith::Real, + ) + T = promote_type( + typeof(azimuth), typeof(elevation), typeof(zenith), + typeof(apparent_elevation), typeof(apparent_zenith), + ) + return ApparentSolPos{T}( + azimuth, elevation, zenith, apparent_elevation, apparent_zenith, + ) +end + Base.show(io::IO, obs::SolPos) = print( io, "SolPos(azimuth=$(obs.azimuth)°, elevation=$(obs.elevation)°, zenith=$(obs.zenith)°)", @@ -292,7 +316,8 @@ at that precision: - **`Float16`**: unusable — algorithm coefficients overflow its range, so results are silently `NaN`. Use `Float32` or wider. - **`ForwardDiff.Dual`** and other `Real` number types work too, so solar positions are - differentiable with respect to latitude, longitude, and altitude. + differentiable with respect to latitude, longitude, and altitude, and with respect to a + refraction model's parameters such as pressure and temperature. See also: [`solar_position!`](@ref), [`Observer`](@ref), [`PSA`](@ref), [`NOAA`](@ref) """ @@ -382,7 +407,9 @@ function solar_position( alg::SolarAlgorithm = PSA(), refraction::RefractionAlgorithm = DefaultRefraction(), ) where {T <: Real} - RetType = result_type(typeof(alg), typeof(refraction), T) + RetType = result_type( + typeof(alg), typeof(refraction), _result_eltype(T, refraction), + ) pos = StructArrays.StructVector{RetType}(undef, length(dts)) solar_position!(pos, obs, dts, alg, refraction) return pos @@ -394,7 +421,9 @@ function solar_position( alg::SolarAlgorithm = PSA(), refraction::RefractionAlgorithm = DefaultRefraction(), ) where {T <: Real} - RetType = result_type(typeof(alg), typeof(refraction), T) + RetType = result_type( + typeof(alg), typeof(refraction), _result_eltype(T, refraction), + ) pos = StructArrays.StructVector{RetType}(undef, length(dts)) solar_position!(pos, obs, dts, alg, refraction) return pos @@ -406,7 +435,9 @@ function solar_position( alg::SolarAlgorithm = PSA(), refraction::RefractionAlgorithm = DefaultRefraction(), ) where {T <: Real} - RetType = result_type(typeof(alg), typeof(refraction), T) + RetType = result_type( + typeof(alg), typeof(refraction), _result_eltype(T, refraction), + ) pos = StructArrays.StructVector{RetType}(undef, length(dts)) solar_position!(pos, obs, dts, alg, refraction) return pos @@ -480,6 +511,13 @@ result_type(::Type{<:SolarAlgorithm}, ::Type{NoRefraction}, ::Type{T}) where {T} result_type(::Type{<:SolarAlgorithm}, ::Type{<:RefractionAlgorithm}, ::Type{T}) where {T} = ApparentSolPos{T} +# element type of the container the vector paths preallocate. Differentiating with respect +# to a refraction parameter makes the apparent angles duals while the observer stays +# Float64, so the observer's precision alone would give a container the results cannot +# convert into +_result_eltype(::Type{T}, refraction::RefractionAlgorithm) where {T <: Real} = + promote_type(T, Refraction.refraction_eltype(typeof(refraction))) + include("utils.jl") include("timebase.jl") include("deltat.jl") diff --git a/src/Refraction/Refraction.jl b/src/Refraction/Refraction.jl index cf4fb0ff..09ae33d1 100644 --- a/src/Refraction/Refraction.jl +++ b/src/Refraction/Refraction.jl @@ -67,6 +67,17 @@ function refraction(model::RefractionAlgorithm, elevation::T) where {T <: Real} return _refraction(model, elevation) end +_real_or_bottom(::Type{T}) where {T} = T <: Real ? T : Union{} + +# Promotion of a refraction model's numeric parameter types. Differentiating with respect +# to a parameter such as pressure makes that field a dual number, and promoting an +# observer's element type with this keeps the dual alive through the code paths that +# preallocate a result container from the observer's precision alone. promote_type of no +# arguments is Union{}, so a model without numeric fields lands there and leaves the +# observer's element type untouched. +refraction_eltype(::Type{R}) where {R <: RefractionAlgorithm} = + promote_type(map(_real_or_bottom, fieldtypes(R))...) + include("hughes.jl") include("archer.jl") include("bennett.jl") diff --git a/src/Refraction/bennett.jl b/src/Refraction/bennett.jl index 2e08cb56..c6a66861 100644 --- a/src/Refraction/bennett.jl +++ b/src/Refraction/bennett.jl @@ -54,6 +54,11 @@ end BENNETT{T}() where {T <: Real} = BENNETT{T}(T(101325), T(12)) BENNETT() = BENNETT{Float64}() +# mixed argument types promote, so a ForwardDiff.Dual pressure alongside a Float64 +# temperature gives a dual valued model instead of a MethodError +BENNETT(pressure::Real, temperature::Real) = + BENNETT{promote_type(typeof(pressure), typeof(temperature))}(pressure, temperature) + function _refraction(model::BENNETT{T}, elevation_deg::Real) where {T <: Real} # convert pressure from Pascal to hPa pressure_hPa = model.pressure / T(100.0) diff --git a/src/Refraction/hughes.jl b/src/Refraction/hughes.jl index 52b6477a..4c3e70b4 100644 --- a/src/Refraction/hughes.jl +++ b/src/Refraction/hughes.jl @@ -73,6 +73,11 @@ end HUGHES{T}() where {T <: Real} = HUGHES{T}(T(101325), T(10)) HUGHES() = HUGHES{Float64}() +# mixed argument types promote, so a ForwardDiff.Dual pressure alongside a Float64 +# temperature gives a dual valued model instead of a MethodError +HUGHES(pressure::Real, temperature::Real) = + HUGHES{promote_type(typeof(pressure), typeof(temperature))}(pressure, temperature) + function _refraction(model::HUGHES{T}, elevation_deg::Real) where {T <: Real} # this avoids numerical instability at very high elevations if elevation_deg > T(85.0) diff --git a/src/Refraction/sg2.jl b/src/Refraction/sg2.jl index a85c64b2..800fc643 100644 --- a/src/Refraction/sg2.jl +++ b/src/Refraction/sg2.jl @@ -60,6 +60,11 @@ end SG2{T}() where {T <: Real} = SG2{T}(T(101325), T(12)) SG2() = SG2{Float64}() +# mixed argument types promote, so a ForwardDiff.Dual pressure alongside a Float64 +# temperature gives a dual valued model instead of a MethodError +SG2(pressure::Real, temperature::Real) = + SG2{promote_type(typeof(pressure), typeof(temperature))}(pressure, temperature) + function _refraction(model::SG2{T}, elevation_deg::Real) where {T <: Real} # Convert pressure from Pascal to hPa (hectopascal) pressure_hPa = model.pressure / T(100.0) diff --git a/src/Refraction/spa.jl b/src/Refraction/spa.jl index 20382084..5fba8dcb 100644 --- a/src/Refraction/spa.jl +++ b/src/Refraction/spa.jl @@ -62,8 +62,18 @@ apparent_elevation = elevation + refraction_correction atmos_refract::T = -0.5667 end -SPARefraction(pressure::T, temperature::T) where {T <: Real} = - SPARefraction{T}(pressure, temperature, T(-0.5667)) +# mixed argument types promote, so a ForwardDiff.Dual pressure alongside a Float64 +# temperature gives a dual valued model instead of a MethodError. The keyword constructor +# that @kwdef generates routes through the three argument form, so it promotes too. +function SPARefraction(pressure::Real, temperature::Real) + T = promote_type(typeof(pressure), typeof(temperature)) + return SPARefraction{T}(pressure, temperature, T(-0.5667)) +end + +function SPARefraction(pressure::Real, temperature::Real, atmos_refract::Real) + T = promote_type(typeof(pressure), typeof(temperature), typeof(atmos_refract)) + return SPARefraction{T}(pressure, temperature, atmos_refract) +end function _refraction(model::SPARefraction{T}, elevation_deg::Real) where {T <: Real} # Convert pressure from Pascal to hPa/mbar diff --git a/test/positioning/test-autodiff.jl b/test/positioning/test-autodiff.jl index dd416e2b..e09ed15a 100644 --- a/test/positioning/test-autodiff.jl +++ b/test/positioning/test-autodiff.jl @@ -1,6 +1,8 @@ """Automatic differentiation support via ForwardDiff duals""" using ForwardDiff +using Interpolations +using StructArrays: StructArrays @testset "ForwardDiff" begin dt = DateTime(2024, 6, 21, 9, 30) @@ -69,4 +71,138 @@ using ForwardDiff @test Observer(45.0f0, 10.0) isa Observer{Float64} @test Observer(ForwardDiff.Dual(45.0, 1.0), 10.0).latitude isa ForwardDiff.Dual end + + @testset "Interpolated" begin + # the interpolants only cover the geocentric, time-only quantities, so the duals + # travel through the shared topocentric half and must match the wrapped algorithm + span = (DateTime(2024, 6, 1), DateTime(2024, 7, 1)) + itp = Interpolated(SPA(); tspan = span) + + @testset "Derivative matches finite differences: $name" for (name, mk, x0, h) in ( + ("latitude", lat -> Observer(lat, 10.0), 45.0, 1.0e-4), + ("longitude", lon -> Observer(45.0, lon), 10.0, 1.0e-4), + # the altitude sensitivity is ~1e-10 deg/m, so a small step is pure + # finite-difference noise + ("altitude", alt -> Observer(45.0, 10.0, alt), 100.0, 100.0), + ) + for (field, refr) in + ((:elevation, NoRefraction()), (:apparent_elevation, SPARefraction())) + f = x -> getproperty(solar_position(mk(x), dt, itp, refr), field) + ad = ForwardDiff.derivative(f, x0) + fd = (f(x0 + h) - f(x0 - h)) / 2h + @test isfinite(ad) + @test ad ≈ fd rtol = 1.0e-6 + end + end + + @testset "Gradient and Hessian track the wrapped SPA" begin + f = alg -> x -> solar_position( + Observer(x[1], x[2]), dt, alg, NoRefraction(), + ).elevation + x0 = [45.0, 10.0] + @test ForwardDiff.gradient(f(itp), x0) ≈ + ForwardDiff.gradient(f(SPA()), x0) atol = 1.0e-12 + hess = ForwardDiff.hessian(f(itp), x0) + @test hess ≈ ForwardDiff.hessian(f(SPA()), x0) atol = 1.0e-12 + @test hess[1, 2] ≈ hess[2, 1] atol = 1.0e-14 + end + + @testset "Vectorized and in-place paths carry duals" begin + dts = [dt, dt + Hour(1), dt + Hour(2)] + obs = Observer(ForwardDiff.Dual(45.0, 1.0), 10.0) + pos = solar_position(obs, dts, itp, NoRefraction()) + @test eltype(pos.elevation) <: ForwardDiff.Dual + @test length(pos) == 3 + # the partials must equal the scalar derivative at each time + for (i, t) in enumerate(dts) + scalar = ForwardDiff.derivative( + lat -> solar_position( + Observer(lat, 10.0), t, itp, NoRefraction(), + ).elevation, 45.0, + ) + @test ForwardDiff.partials(pos.elevation[i], 1) == scalar + end + buf = similar(pos) + solar_position!(buf, obs, dts, itp, NoRefraction()) + @test buf.elevation == pos.elevation + end + + @testset "Fallback outside the span matches the wrapped algorithm" begin + outside = DateTime(2024, 8, 15, 9, 30) + fb = Interpolated(SPA(); tspan = span, out_of_range = :fallback) + f = alg -> lat -> solar_position( + Observer(lat, 10.0), outside, alg, NoRefraction(), + ).elevation + @test ForwardDiff.derivative(f(fb), 45.0) == + ForwardDiff.derivative(f(SPA()), 45.0) + @test_throws ArgumentError solar_position( + Observer(ForwardDiff.Dual(45.0, 1.0), 10.0), outside, itp, NoRefraction(), + ) + end + end + + # a refraction parameter is a differentiable input like any other, so the models and + # the result types promote a dual-valued parameter against a Float64 observer + @testset "Refraction parameters" begin + dual = ForwardDiff.Dual(101325.0, 1.0) + + @testset "Mixed argument types promote: $(nameof(M))" for M in + (HUGHES, BENNETT, SG2, SPARefraction) + @test M(dual, 10.0) isa M{typeof(dual)} + @test M(101325.0, ForwardDiff.Dual(10.0, 1.0)) isa + M{ForwardDiff.Dual{Nothing, Float64, 1}} + # the same-type constructors keep their element type unchanged + @test M(101325.0, 10.0) isa M{Float64} + @test M(101325.0f0, 10.0f0) isa M{Float32} + end + + @testset "Keyword constructor promotes" begin + @test SPARefraction(pressure = dual) isa SPARefraction{typeof(dual)} + @test SPARefraction(temperature = dual) isa SPARefraction{typeof(dual)} + @test SPARefraction(atmos_refract = dual) isa SPARefraction{typeof(dual)} + @test SPARefraction() isa SPARefraction{Float64} + end + + @testset "Result types promote over their fields" begin + @test SolPos(1.0, dual, 3) isa SolPos{typeof(dual)} + @test ApparentSolPos(1.0, 2.0, 3.0, dual, dual) isa + ApparentSolPos{typeof(dual)} + @test SolPos(1.0, 2.0, 3.0) isa SolPos{Float64} + @test ApparentSolPos(1.0f0, 2.0f0, 3.0f0, 4.0f0, 5.0f0) isa + ApparentSolPos{Float32} + end + + # a Float64 observer with a dual-valued model: no need to lift the observer + obs = Observer(45.0, 10.0) + + @testset "Derivative w.r.t. $param: $(nameof(M))" for M in + (HUGHES, BENNETT, SG2, SPARefraction), + (param, mk, x0, h) in ( + (:pressure, (p, t) -> M(p, t), 101325.0, 1.0), + (:temperature, (t, p) -> M(p, t), 10.0, 1.0e-2), + ) + + other = param === :pressure ? 10.0 : 101325.0 + f = x -> solar_position(obs, dt, PSA(), mk(x, other)).apparent_elevation + ad = ForwardDiff.derivative(f, x0) + fd = (f(x0 + h) - f(x0 - h)) / 2h + @test isfinite(ad) + @test ad ≈ fd rtol = 1.0e-6 + end + + @testset "Vectorized path preallocates the promoted element type" begin + dts = [dt, dt + Hour(1)] + model = HUGHES(dual, 10.0) + pos = solar_position(obs, dts, PSA(), model) + @test pos isa StructArrays.StructVector{ApparentSolPos{typeof(dual)}} + @test eltype(pos.apparent_elevation) <: ForwardDiff.Dual + # the geometric angles have no dual dependence, the apparent ones do + @test all(iszero, ForwardDiff.partials.(pos.elevation, 1)) + @test all(!iszero, ForwardDiff.partials.(pos.apparent_elevation, 1)) + for (i, t) in enumerate(dts) + @test pos.apparent_elevation[i] == + solar_position(obs, t, PSA(), model).apparent_elevation + end + end + end end From f8ba478084f281cd895e1283cca4b55a0cf36575 Mon Sep 17 00:00:00 2001 From: Stefan de Lange Date: Thu, 30 Jul 2026 12:35:55 +0200 Subject: [PATCH 2/6] Show where the solver samples the solar forcing The section on making the error controller see the forcing asserted that declaring discontinuities and adding a quadrature state both help, without showing what either buys. It now separates the two. The almanac sunrise and the model's own kink are seven minutes apart in Amsterdam at the solstice, because transit_sunrise_sunset returns the upper limb at -0.8333 degrees while max(0, sind(elevation)) breaks at geometric zero. The guide bisects inside the almanac bracket for the time the model actually breaks at, then draws every solver step as a rug under the forcing across four variants, with a zoom on sunrise where the mismatch is visible. A measured table against a reltol = 1e-13 reference gives the ranking. Kink placement is worth a factor of 125 on the temperature at slightly less cost than declaring nothing, since the rejections it avoids more than pay for the two steps it forces. The quadrature state is worth only a factor of two here and earns its keep only when the integrand depends on a state, which the guide now says, along with the trapezoid alternative for when it does not. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KhdBYRmHazfXZHWCRWt5fT --- docs/src/guides/modelingtoolkit.md | 194 ++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 18 deletions(-) diff --git a/docs/src/guides/modelingtoolkit.md b/docs/src/guides/modelingtoolkit.md index 5052791a..996e609c 100644 --- a/docs/src/guides/modelingtoolkit.md +++ b/docs/src/guides/modelingtoolkit.md @@ -296,16 +296,40 @@ query uses the state interpolant, whose accuracy is set by the solver tolerances The embedded error estimator only controls the error of integrating the states it is given. Two distinct failure modes follow, each with its own fix. -The first is nonsmoothness. Solar forcing models clip at the horizon, typically with -`max(0, ...)`, and a step that spans sunrise or sunset sees a kink, rejects, and -thrashes. The fix is to tell the solver where the kinks are. -[`transit_sunrise_sunset`](@ref) computes them, and `d_discontinuities` passes them -in, converted to simulation seconds. +The first is nonsmoothness. Solar forcing models clip at the horizon with `max(0, ...)`, +and a step spanning sunrise or sunset hits a kink and rejects. `d_discontinuities` fixes +that, but only with the times the model actually breaks at. +[`transit_sunrise_sunset`](@ref) returns the almanac event, when the sun's upper limb +reaches −0.8333° with refraction allowed for, whereas `max(0, sind(elevation))` breaks +when the geometric elevation crosses zero. In Amsterdam at the solstice the two are +seven minutes apart, so the declared discontinuity lands where nothing happens. The +almanac event brackets the geometric one, so bisect inside it: -The second is smooth blindness. A state with a large time constant filters the -forcing, so the controller sees little state error and takes steps that under resolve -the forcing's integral. The fix is to add the integral as a state, here `E_sol`, so -the quadrature of the forcing enters the error budget directly: +```@example mtk +events = transit_sunrise_sunset(obs, t0) +almanac = [Dates.value(dt - t0) / 1000 for dt in (events.sunrise, events.sunset)] + +function elevation_root(lo, hi) + elevation(x) = solar_position( + obs, t0 + Millisecond(round(Int, 1000x)), PSA(), NoRefraction() + ).elevation + for _ in 1:60 + mid = (lo + hi) / 2 + elevation(lo) * elevation(mid) <= 0 ? (hi = mid) : (lo = mid) + end + return (lo + hi) / 2 +end + +kinks = [elevation_root(a - 1800, a + 1800) for a in almanac] +(kinks .- almanac) ./ 60 # minutes from the almanac event to the model's kink +``` + +The second failure mode is smooth blindness. A state with a large time constant filters +the forcing, so the controller sees little state error and under resolves the forcing's +integral. Adding that integral as a state, here `E_sol`, puts its quadrature into the +error budget. A vector `abstol` matched to `unknowns(sys)` then scales the tolerance to +physical units, since the default 1e-6 on a joule count reaching 2.5e7 is far tighter +than the problem needs: ```@example mtk @parameters C = 5.0e5 k = 25.0 @@ -327,18 +351,152 @@ pmap = [ ] prob = ODEProblem(sys, pmap, (0.0, 86400.0)) -events = transit_sunrise_sunset(obs, t0) -kinks = [Dates.value(dt - t0) / 1000 for dt in (events.sunrise, events.sunset)] +abstol = [isequal(u, E_sol) ? 1.0e-1 : 1.0e-6 for u in unknowns(sys)] +sol = solve(prob; d_discontinuities = kinks, reltol = 1.0e-5, abstol) +(steps = length(sol.t), rejected = sol.stats.nreject, daily_insolation = sol[sys.E_sol][end]) +``` + +Where the solver put its steps says more than a step count does. A loose `reltol` of +1e-5 keeps a day to a countable number of steps, so each can be drawn as a vertical line +under the forcing. The first three rows vary only the declared kink times; the fourth +adds the quadrature state to the best of them: + +```@example mtk +using CairoMakie: Figure, Axis, Label, Point2f, RGBf, Relative, colgap!, colsize!, + hidespines!, hidexdecorations!, hideydecorations!, linesegments!, lines!, + rowgap!, rowsize!, text!, vlines! + +# the same house without the quadrature state, for comparison +@named house_filtered = System(eqs[1:2], t; systems = [sun]) +sys_f = mtkcompile(house_filtered) +prob_f = ODEProblem( + sys_f, + [ + sys_f.sun.observer => obs, + sys_f.sun.t0 => t0, + sys_f.sun.algorithm => PSA(), + sys_f.sun.refraction => NoRefraction(), + ], + (0.0, 86400.0), +) + +variants = [ + ("no kinks declared", solve(prob_f; reltol = 1.0e-5), RGBf(0, 0.447, 0.698)), + ( + "almanac kinks", + solve(prob_f; d_discontinuities = almanac, reltol = 1.0e-5), + RGBf(0.902, 0.624, 0), + ), + ( + "model kinks", + solve(prob_f; d_discontinuities = kinks, reltol = 1.0e-5), + RGBf(0, 0.62, 0.451), + ), + ("model kinks + quadrature", sol, RGBf(0.8, 0.475, 0.655)), +] + +ts = range(0, 86400; length = 3000) +Qs = collect(sol(ts; idxs = sys.Q)) +rug!(ax, y, s, c) = linesegments!( + ax, [Point2f(x / 3600, y + dy) for x in s.t for dy in (-0.36, 0.36)]; + color = c, linewidth = 1.5, +) + +fig = Figure(size = (980, 540)) +zoom = (3.22, 3.62) # a window around sunrise + +ax_day = Axis(fig[1, 1]; ylabel = "Solar heat gain (W)") +ax_rug = Axis( + fig[2, 1]; + xlabel = "Time of day (hours)", xticks = 0:3:24, + yticks = ( + 1:4, + [ + "$n\n$(length(s.t)) steps, $(s.stats.nreject) rejected" + for (n, s, _) in reverse(variants) + ], + ), +) +ax_zoom = Axis(fig[1, 2]; title = "sunrise, zoomed", titlesize = 12) +ax_zoomrug = Axis(fig[2, 2]; xlabel = "Time of day (hours)", xticks = 3.3:0.1:3.6) + +for ax in (ax_day, ax_zoom) + lines!(ax, ts ./ 3600, Qs; color = :grey25, linewidth = 2) + hidexdecorations!(ax; grid = false) +end +for ax in (ax_day, ax_rug) + vlines!(ax, kinks ./ 3600; color = :grey55, linestyle = :dash, linewidth = 1) +end +for ax in (ax_zoom, ax_zoomrug) + vlines!(ax, almanac[1] / 3600; color = :grey55, linestyle = :dot, linewidth = 1.5) + vlines!(ax, kinks[1] / 3600; color = :grey55, linestyle = :dash, linewidth = 1.5) + hideydecorations!(ax; grid = false) +end +for (y, (_, s, c)) in zip(4:-1:1, variants) + rug!(ax_rug, y, s, c) + rug!(ax_zoomrug, y, s, c) +end -sol = solve(prob; d_discontinuities = kinks, reltol = 1.0e-8) -(steps = length(sol.t), daily_insolation = sol[sys.E_sol][end]) +text!( + ax_zoom, almanac[1] / 3600, 30; text = " almanac\n sunrise", + align = (:left, :bottom), fontsize = 10, color = :grey35, +) +text!( + ax_zoom, kinks[1] / 3600, 100; text = " model\n kink", + align = (:left, :bottom), fontsize = 10, color = :grey35, +) + +ax_day.limits = ((0, 24), (-40, 830)) +ax_rug.limits = ((0, 24), (0.4, 4.6)) +ax_zoom.limits = (zoom, (-40, 830)) +ax_zoomrug.limits = (zoom, (0.4, 4.6)) +for ax in (ax_day, ax_rug, ax_zoom, ax_zoomrug) + hidespines!(ax, :t, :r) +end + +Label( + fig[0, 1:2], "Where the solver steps over one day (reltol = 1e-5)"; + fontsize = 15, font = :bold, padding = (0, 0, 4, 0), +) +rowsize!(fig.layout, 1, Relative(0.36)) +colsize!(fig.layout, 2, Relative(0.26)) +rowgap!(fig.layout, 6) +colgap!(fig.layout, 14) +fig ``` -A vector `abstol` matched to `unknowns(sys)` gives the quadrature state a tolerance -in its own physical units when it should not share the default. Whatever combination -you settle on, verify it once against a reference solve at `reltol = 1e-10` and -compare the quantities you care about. That check, not the step count, is what shows -the recipe is sufficient. +The top row declares nothing and steps across the day at near constant spacing, +indifferent to whether the sun is up. The second declares the almanac times, and the +zoom shows why that barely helps: the dotted line sits where the forcing is still flat, +so the step is spent on a discontinuity that is not there and the real kink at the dashed +line is met unprepared. The third lands a step exactly on the break. The fourth changes +the sampling very little. + +Against a `reltol = 1e-13` reference, where the error columns are the largest room +temperature deviation over the day and the relative error in daily insolation: + +| variant | steps | rejected | `f` evals | max ΔT_room | rel. err. insolation | +|:---|---:|---:|---:|---:|---:| +| no kinks declared | 21 | 1 | 129 | 1.7e-2 K | — | +| almanac kinks | 23 | 0 | 137 | 7.7e-3 K | — | +| model kinks | 21 | 0 | 125 | 1.4e-4 K | — | +| model kinks + quadrature | 21 | 0 | 125 | 7.0e-5 K | 4.3e-8 | + +Almost all of the benefit comes from one change. Bisecting for the model's own kink times +buys a factor of 125 on the temperature and costs slightly less than declaring nothing, +since the rejections it avoids more than pay for the two steps it forces. The almanac +times alone buy a factor of two. + +The quadrature state does less than it appears to. `T_room` was never the under resolved +state, so it gains only a factor of two; what the extra state controls is `E_sol`, and it +earns its keep only when the integrand depends on a state. Here `Q` depends on time and +parameters alone, so the dense output evaluates it exactly and a trapezoid rule over the +filtered solve reaches 1.1e-5 relative error at a hundred query points and 2.4e-7 at a +thousand, for no solver cost. Add the state when the integral feeds back into the +dynamics; post-process when it does not. + +Whatever you settle on, verify it once against a reference solve at `reltol = 1e-10`. +That check, not the step count, is what shows the recipe is sufficient. ## Implementation Details From 9936d25959d880d9e30be4f2a06b58d5b6fd3d09 Mon Sep 17 00:00:00 2001 From: Stefan de Lange Date: Thu, 30 Jul 2026 12:45:31 +0200 Subject: [PATCH 3/6] Space the table delimiters for markdownlint MD060 requires the compact table style, which wants a space on each side of every pipe. The new table's delimiter row was written without them. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KhdBYRmHazfXZHWCRWt5fT --- docs/src/guides/modelingtoolkit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/guides/modelingtoolkit.md b/docs/src/guides/modelingtoolkit.md index 996e609c..49ca3274 100644 --- a/docs/src/guides/modelingtoolkit.md +++ b/docs/src/guides/modelingtoolkit.md @@ -476,7 +476,7 @@ Against a `reltol = 1e-13` reference, where the error columns are the largest ro temperature deviation over the day and the relative error in daily insolation: | variant | steps | rejected | `f` evals | max ΔT_room | rel. err. insolation | -|:---|---:|---:|---:|---:|---:| +| :--- | ---: | ---: | ---: | ---: | ---: | | no kinks declared | 21 | 1 | 129 | 1.7e-2 K | — | | almanac kinks | 23 | 0 | 137 | 7.7e-3 K | — | | model kinks | 21 | 0 | 125 | 1.4e-4 K | — | From ec60e90264e85d5334cb9eda3a1e307b7a5893fb Mon Sep 17 00:00:00 2001 From: Stefan de Lange Date: Thu, 30 Jul 2026 13:05:45 +0200 Subject: [PATCH 4/6] Stop tracking the generated test CondaPkg environment test/.CondaPkg is a CondaPkg.jl generated environment, not shareable state. Nothing declares it, since there is no test/CondaPkg.toml, and nothing under test/ uses CondaPkg, PythonCall or PyCall. The tracked meta file is a binary cache holding absolute paths from whichever machine last resolved it. docs/.CondaPkg was already ignored, so the pattern is generalised rather than extended one directory at a time. The files stay on disk and CondaPkg regenerates them on demand. benchmark/.CondaPkg is tracked for the same reason and has the same problem, but benchmark/python.jl does use CondaPkg, so it is left alone here. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KhdBYRmHazfXZHWCRWt5fT --- .gitignore | 2 +- test/.CondaPkg/.gitattributes | 2 - test/.CondaPkg/.gitignore | 4 - test/.CondaPkg/meta | Bin 866 -> 0 bytes test/.CondaPkg/pixi.lock | 851 ---------------------------------- test/.CondaPkg/pixi.toml | 17 - 6 files changed, 1 insertion(+), 875 deletions(-) delete mode 100644 test/.CondaPkg/.gitattributes delete mode 100644 test/.CondaPkg/.gitignore delete mode 100644 test/.CondaPkg/meta delete mode 100644 test/.CondaPkg/pixi.lock delete mode 100644 test/.CondaPkg/pixi.toml diff --git a/.gitignore b/.gitignore index 5322dafd..11baeb20 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,6 @@ node_modules .vscode/settings.json **.old play/Project.toml -docs/.CondaPkg/ +**/.CondaPkg/ *.mem **/Manifest-*.toml diff --git a/test/.CondaPkg/.gitattributes b/test/.CondaPkg/.gitattributes deleted file mode 100644 index 887a2c18..00000000 --- a/test/.CondaPkg/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# SCM syntax highlighting & preventing 3-way merges -pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/test/.CondaPkg/.gitignore b/test/.CondaPkg/.gitignore deleted file mode 100644 index 740bb7d1..00000000 --- a/test/.CondaPkg/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ - -# pixi environments -.pixi -*.egg-info diff --git a/test/.CondaPkg/meta b/test/.CondaPkg/meta deleted file mode 100644 index 728d60fc6e18a110d57b289f914910989b8ae2f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 866 zcmbV~-A=+V7=|_e#c1LMc%r8|`fY3y4uC{C@XRa`Njxm0j6&CrwIkEJ@J5W+;}v)r zcF-=wKZrY6+i&mlz3tnbY%QO+m)H0MzhB>(HH<|=7.5.0 - constrains: - - openmp_impl 9999 - arch: x86_64 - platform: linux - license: BSD-3-Clause - license_family: BSD - size: 23621 - timestamp: 1650670423406 -- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 - md5: 2934f256a8acfe48f6ebb4fce6cde29c - depends: - - python >=3.9 - - typing-extensions >=4.0.0 - license: MIT - license_family: MIT - size: 18074 - timestamp: 1733247158254 -- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py314h2e6c369_1.conda - sha256: 1ca9b0d228362574bd503f557152eb3ca2eb8bf70663b3a755d8020202e03885 - md5: 6f7d83cec0fff00ca9a772034411fa12 - depends: - - python - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python_abi 3.14.* *_cp314 - constrains: - - __glibc >=2.17 - arch: x86_64 - platform: linux - license: Apache-2.0 - license_family: APACHE - size: 292368 - timestamp: 1762497706993 -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 - md5: 51a19bba1b8ebfb60df25cde030b7ebc - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - arch: x86_64 - platform: linux - license: bzip2-1.0.6 - license_family: BSD - size: 260341 - timestamp: 1757437258798 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.1.4-hbd8a1cb_0.conda - sha256: b5974ec9b50e3c514a382335efa81ed02b05906849827a34061c496f4defa0b2 - md5: bddacf101bb4dd0e51811cb69c7790e2 - depends: - - __unix - license: ISC - size: 146519 - timestamp: 1767500828366 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py314h4a8dc5f_1.conda - sha256: c6339858a0aaf5d939e00d345c98b99e4558f285942b27232ac098ad17ac7f8e - md5: cf45f4278afd6f4e6d03eda0f435d527 - depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - pycparser - - python >=3.14,<3.15.0a0 - - python_abi 3.14.* *_cp314 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 300271 - timestamp: 1761203085220 -- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 - md5: 962b9857ee8e7018c22f2776ffa0b2d7 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - size: 27011 - timestamp: 1733218222191 -- conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.11.3-pyhcf101f3_0.conda - sha256: 0760286714848e8af7aade73da886e9f5cfe4126e6d10501f10135e50e94c2c0 - md5: fed521ea8b179bf63e9986b3108ed5f3 - depends: - - python >=3.10 - - colorama >=0.4.6 - - dunamai >=1.7.0 - - funcy >=1.17 - - jinja2 >=3.1.5 - - jinja2-ansible-filters >=1.3.1 - - packaging >=23.0 - - pathspec >=0.9.0 - - plumbum >=1.6.9 - - prompt-toolkit <3.0.52 - - pydantic >=2.4.2 - - pygments >=2.7.1 - - pyyaml >=5.3.1 - - questionary >=1.8.1 - - eval-type-backport >=0.1.3,<0.3.0 - - platformdirs >=4.3.6 - - typing_extensions >=4.0.0,<5.0.0 - - python - license: MIT - license_family: MIT - size: 54229 - timestamp: 1769272985745 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.4-py314h7fe84b3_0.conda - sha256: 90738c26981732357d71b97df1994a1a74f87701468d61e19755af7d9e35edf8 - md5: afabda22fe5163200fc59f31b58d9e6a - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.14 - - libgcc >=14 - - openssl >=3.5.5,<4.0a0 - - python >=3.14,<3.15.0a0 - - python_abi 3.14.* *_cp314 - constrains: - - __glibc >=2.17 - arch: x86_64 - platform: linux - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD - size: 1719239 - timestamp: 1769650654007 -- conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.25.0-pyhd8ed1ab_0.conda - sha256: 16d78408ed520b0951425cdfae280392813794e29ea6d3cb95f6c479347c4e1c - md5: 4b6cc280feef9e74ffe9ea1c875b5f8c - depends: - - packaging >=20.9 - - python >=3.9 - license: MIT - license_family: MIT - size: 30075 - timestamp: 1751991836363 -- conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda - sha256: 05ffdcb83903c159bfbb78a07fbcce6fd6dda41df9c55ed75e0eb1db5528048f - md5: 879479fda1dddb002fdc4885cea33740 - depends: - - eval_type_backport >=0.2.2,<0.2.3.0a0 - - python >=3.9 - license: MIT - license_family: MIT - size: 6662 - timestamp: 1734857849281 -- conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda - sha256: 2d721421a60676216e10837a240c75e2190e093920a4016a469fa9a62c95ab5f - md5: 8681d7f876da5e66a1c7fce424509383 - depends: - - python >=3.9 - constrains: - - eval-type-backport >=0.2.2,<0.2.3.0a0 - license: MIT - license_family: MIT - size: 11520 - timestamp: 1734857840035 -- conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda - sha256: 4a3e3e86b7b49aaa2a0faa57da2bcf39f7ee858499e8335132f83bfeed79191e - md5: 84f8955e99a8944fdee49da39edb0add - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - size: 30249 - timestamp: 1734381235500 -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 - md5: 186a18e3ba246eccfc7cff00cd19a870 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 12728445 - timestamp: 1767969922681 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - sha256: acc1d991837c0afb67c75b77fdc72b4bf022aac71fedd8b9ea45918ac9b08a80 - md5: c85c76dc67d75619a92f51dfbce06992 - depends: - - python >=3.9 - - zipp >=3.1.0 - constrains: - - importlib-resources >=6.5.2,<6.5.3.0a0 - license: Apache-2.0 - license_family: APACHE - size: 33781 - timestamp: 1736252433366 -- conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.1-pyhd8ed1ab_0.conda - sha256: 5a4e3a01f626c8de15ddada622d364e94ff28e8d6bdedf1665442ef03a4e0140 - md5: 3a804714ed59be1969ffca10f703ec2a - depends: - - python >=3.10 - license: BSD-2-Clause - license_family: BSD - size: 132825 - timestamp: 1760146119847 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b - md5: 04558c96691bed63104678757beb4f8d - depends: - - markupsafe >=2.0 - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - size: 120685 - timestamp: 1764517220861 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda - sha256: a36789229ca9ce5315265b9d425abce9acb4691f5864aea69e935020545a9acb - md5: 974c5b3e353f031cfcf2365c9d375926 - depends: - - jinja2 - - python >=3.9 - - pyyaml - license: BSD-2-Clause - license_family: BSD - size: 20315 - timestamp: 1734906203051 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 - md5: 12bd9a3f089ee6c9266a37dab82afabd - depends: - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - binutils_impl_linux-64 2.45.1 - arch: x86_64 - platform: linux - license: GPL-3.0-only - license_family: GPL - size: 725507 - timestamp: 1770267139900 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - sha256: 1e1b08f6211629cbc2efe7a5bca5953f8f6b3cae0eeb04ca4dacee1bd4e2db2f - md5: 8b09ae86839581147ef2e5c5e229d164 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - expat 2.7.3.* - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 76643 - timestamp: 1763549731408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 - md5: a360c33a5abe61c07959e449fa1453eb - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 58592 - timestamp: 1769456073053 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - sha256: 0caed73aac3966bfbf5710e06c728a24c6c138605121a3dacb2e03440e8baa6a - md5: 264fbfba7fb20acf3b29cde153e345ce - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - constrains: - - libgomp 15.1.0 h767d61c_5 - - libgcc-ng ==15.1.0=*_5 - arch: x86_64 - platform: linux - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 824191 - timestamp: 1757042543820 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - sha256: f54bb9c3be12b24be327f4c1afccc2969712e0b091cdfbd1d763fb3e61cda03f - md5: 069afdf8ea72504e48d23ae1171d951c - depends: - - libgcc 15.1.0 h767d61c_5 - arch: x86_64 - platform: linux - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 29187 - timestamp: 1757042549554 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda - sha256: 125051d51a8c04694d0830f6343af78b556dd88cc249dfec5a97703ebfb1832d - md5: dcd5ff1940cd38f6df777cac86819d60 - depends: - - __glibc >=2.17,<3.0.a0 - arch: x86_64 - platform: linux - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 447215 - timestamp: 1757042483384 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb - md5: c7c83eecbb72d88b940c249af56c8b17 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - xz 5.8.2.* - arch: x86_64 - platform: linux - license: 0BSD - size: 113207 - timestamp: 1768752626120 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - sha256: fe171ed5cf5959993d43ff72de7596e8ac2853e9021dec0344e583734f1e0843 - md5: 2c21e66f50753a083cbe6b80f38268fa - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - arch: x86_64 - platform: linux - license: BSD-2-Clause - license_family: BSD - size: 92400 - timestamp: 1769482286018 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 - md5: a587892d3c13b6621a6091be690dbca2 - depends: - - libgcc-ng >=12 - arch: x86_64 - platform: linux - license: ISC - size: 205978 - timestamp: 1716828628198 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.2-hf4e2dac_0.conda - sha256: 04596fcee262a870e4b7c9807224680ff48d4d0cc0dac076a602503d3dc6d217 - md5: da5be73701eecd0e8454423fd6ffcf30 - depends: - - __glibc >=2.17,<3.0.a0 - - icu >=78.2,<79.0a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - arch: x86_64 - platform: linux - license: blessing - size: 942808 - timestamp: 1768147973361 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - sha256: 0f5f61cab229b6043541c13538d75ce11bd96fb2db76f94ecf81997b1fde6408 - md5: 4e02a49aaa9d5190cb630fa43528fbe6 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc 15.1.0 h767d61c_5 - arch: x86_64 - platform: linux - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 3896432 - timestamp: 1757042571458 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - sha256: 7b8cabbf0ab4fe3581ca28fe8ca319f964078578a51dd2ca3f703c1d21ba23ff - md5: 8bba50c7f4679f08c861b597ad2bda6b - depends: - - libstdcxx 15.1.0 h8f9b012_5 - arch: x86_64 - platform: linux - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 29233 - timestamp: 1757042603319 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.3-h5347b49_0.conda - sha256: 1a7539cfa7df00714e8943e18de0b06cceef6778e420a5ee3a2a145773758aee - md5: db409b7c1720428638e7c0d509d3e1b5 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - arch: x86_64 - platform: linux - license: BSD-3-Clause - license_family: BSD - size: 40311 - timestamp: 1766271528534 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 - md5: edb0dca6bc32e4f4789199455a1dbeb8 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - constrains: - - zlib 1.3.1 *_2 - arch: x86_64 - platform: linux - license: Zlib - license_family: Other - size: 60963 - timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/noarch/markupsafe-3.0.3-pyh7db6752_0.conda - sha256: e0cbfea51a19b3055ca19428bd9233a25adca956c208abb9d00b21e7259c7e03 - md5: fab1be106a50e20f10fe5228fd1d1651 - depends: - - python >=3.10 - constrains: - - jinja2 >=3.0.0 - track_features: - - markupsafe_no_compile - license: BSD-3-Clause - license_family: BSD - size: 15499 - timestamp: 1759055275624 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 - md5: 47e340acb35de30501a76c7c799c41d7 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - arch: x86_64 - platform: linux - license: X11 AND BSD-3-Clause - size: 891641 - timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.5-h35e630c_1.conda - sha256: 752b58a5886596fd791e559f8f35f1f4304f924410a3ff47cf999fc89ee5a610 - md5: f146d5fd47c519c44fe459300a170ae3 - depends: - - __glibc >=2.17,<3.0.a0 - - ca-certificates - - libgcc >=14 - arch: x86_64 - platform: linux - license: Apache-2.0 - license_family: Apache - size: 3115300 - timestamp: 1769560205194 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 - md5: b76541e68fea4d511b1ac46a28dcd2c6 - depends: - - python >=3.8 - - python - license: Apache-2.0 - license_family: APACHE - size: 72010 - timestamp: 1769093650580 -- conda: https://conda.anaconda.org/conda-forge/noarch/paramiko-4.0.0-pyhd8ed1ab_0.conda - sha256: ce76d5a1fc6c7ef636cbdbf14ce2d601a1bfa0dd8d286507c1fd02546fccb94b - md5: 1a884d2b1ea21abfb73911dcdb8342e4 - depends: - - bcrypt >=3.2 - - cryptography >=3.3 - - invoke >=2.0 - - pynacl >=1.5 - - python >=3.9 - license: LGPL-2.1-or-later - license_family: LGPL - size: 159896 - timestamp: 1755102147074 -- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - sha256: 29ea20d0faf20374fcd61c25f6d32fb8e9a2c786a7f1473a0c3ead359470fbe1 - md5: 2908273ac396d2cd210a8127f5f1c0d6 - depends: - - python >=3.10 - license: MPL-2.0 - license_family: MOZILLA - size: 53739 - timestamp: 1769677743677 -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda - sha256: 04c64fb78c520e5c396b6e07bc9082735a5cc28175dbe23138201d0a9441800b - md5: 1bd2e65c8c7ef24f4639ae6e850dacc2 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - size: 23922 - timestamp: 1764950726246 -- conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda - sha256: 972e0c1c9f0b1763d482e885732baef7f9a0cbe8686f5e2c6bdd88838619a59a - md5: 7fd2e38f4e608f5ebd80f871352c5495 - depends: - - python >=3.10 - - pywin32-on-windows - - paramiko - - importlib_resources - - python - license: MIT - license_family: MIT - size: 103911 - timestamp: 1761911487086 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b - md5: d17ae9db4dc594267181bd199bf9a551 - depends: - - python >=3.9 - - wcwidth - constrains: - - prompt_toolkit 3.0.51 - license: BSD-3-Clause - license_family: BSD - size: 271841 - timestamp: 1744724188108 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - sha256: 936189f0373836c1c77cd2d6e71ba1e583e2d3920bf6d015e96ee2d729b5e543 - md5: 1e61ab85dd7c60e5e73d853ea035dc29 - depends: - - prompt-toolkit >=3.0.51,<3.0.52.0a0 - license: BSD-3-Clause - license_family: BSD - size: 7182 - timestamp: 1744724189376 -- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 - md5: 12c566707c80111f9799308d9e265aef - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - size: 110100 - timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda - sha256: 868569d9505b7fe246c880c11e2c44924d7613a8cdcc1f6ef85d5375e892f13d - md5: c3946ed24acdb28db1b5d63321dbca7d - depends: - - typing-inspection >=0.4.2 - - typing_extensions >=4.14.1 - - python >=3.10 - - typing-extensions >=4.6.1 - - annotated-types >=0.6.0 - - pydantic-core ==2.41.5 - - python - license: MIT - license_family: MIT - size: 340482 - timestamp: 1764434463101 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py314h2e6c369_1.conda - sha256: 7e0ae379796e28a429f8e48f2fe22a0f232979d65ec455e91f8dac689247d39f - md5: 432b0716a1dfac69b86aa38fdd59b7e6 - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.14.* *_cp314 - constrains: - - __glibc >=2.17 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 1943088 - timestamp: 1762988995556 -- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a - md5: 6b6ece66ebcae2d5f326c77ef2c5a066 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - size: 889287 - timestamp: 1750615908735 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py314h5bd0f2a_0.conda - sha256: 86093b979cb28efb72bb03693857d300abd16cba91b50120a403bdd85c8629a9 - md5: 11c74c76b6a6e1dd89c5fe98d65ed37f - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.4.1 - - libgcc >=14 - - libsodium >=1.0.20,<1.0.21.0a0 - - python >=3.14,<3.15.0a0 - - python_abi 3.14.* *_cp314 - - six - arch: x86_64 - platform: linux - license: Apache-2.0 - license_family: Apache - size: 1163036 - timestamp: 1767324013517 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.3-h32b2ec7_100_cp314.conda - build_number: 100 - sha256: ff087b19d158644d3b0708eca10a5e40d692cdc8e95f53715f4490c6959f3768 - md5: b40594d5da041824087eebe12228af42 - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.3,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - liblzma >=5.8.2,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.51.2,<4.0a0 - - libuuid >=2.41.3,<3.0a0 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.5,<4.0a0 - - python_abi 3.14.* *_cp314 - - readline >=8.3,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - - zstd >=1.5.7,<1.6.0a0 - arch: x86_64 - platform: linux - license: Python-2.0 - size: 36529771 - timestamp: 1770271970971 - python_site_packages_path: lib/python3.14/site-packages -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - build_number: 8 - sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 - md5: 0539938c55b6b1a59b560e843ad864a4 - constrains: - - python 3.14.* *_cp314 - license: BSD-3-Clause - license_family: BSD - size: 6989 - timestamp: 1752805904792 -- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - md5: 2807a0becd1d986fe1ef9b7f8135f215 - depends: - - __unix - - python >=2.7 - license: BSD-3-Clause - license_family: BSD - size: 4856 - timestamp: 1646866525560 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda - sha256: b318fb070c7a1f89980ef124b80a0b5ccf3928143708a85e0053cde0169c699d - md5: 2035f68f96be30dc60a5dfd7452c7941 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.14,<3.15.0a0 - - python_abi 3.14.* *_cp314 - - yaml >=0.2.5,<0.3.0a0 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 202391 - timestamp: 1770223462836 -- conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda - sha256: 0604c6dff3af5f53e34fceb985395d08287137f220450108a795bcd1959caf14 - md5: 34fa231b5c5927684b03bb296bb94ddc - depends: - - prompt_toolkit >=2.0,<4.0 - - python >=3.10 - license: MIT - license_family: MIT - size: 31257 - timestamp: 1757356458097 -- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - sha256: 12ffde5a6f958e285aa22c191ca01bbd3d6e710aa852e00618fa6ddc59149002 - md5: d7d95fc8287ea7bf33e0e7116d2b95ec - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - ncurses >=6.5,<7.0a0 - arch: x86_64 - platform: linux - license: GPL-3.0-only - license_family: GPL - size: 345073 - timestamp: 1765813471974 -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d - md5: 3339e3b65d58accf4ca4fb8748ab16b3 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - size: 18455 - timestamp: 1753199211006 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac - md5: cffd3bdd58090148f4cfcd831f4b26ab - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - constrains: - - xorg-libx11 >=1.8.12,<2.0a0 - arch: x86_64 - platform: linux - license: TCL - license_family: BSD - size: 3301196 - timestamp: 1769460227866 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 - md5: a0a4a3035667fc34f29bfbd5c190baa6 - depends: - - python >=3.10 - - typing_extensions >=4.12.0 - license: MIT - license_family: MIT - size: 18923 - timestamp: 1764158430324 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d - depends: - - python >=3.10 - - python - license: PSF-2.0 - license_family: PSF - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - sha256: 1d30098909076af33a35017eed6f2953af1c769e273a0626a04722ac4acaba3c - md5: ad659d0a2b3e47e38d829aa8cad2d610 - license: LicenseRef-Public-Domain - size: 119135 - timestamp: 1767016325805 -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.5.3-pyhd8ed1ab_0.conda - sha256: 2395599ec9e37e6f21838bb26e7f2336fa03a4b1460ba10897ec856b21ac7d59 - md5: 36432484e9ce3b073a51bf138767a593 - depends: - - python >=3.10 - license: MIT - license_family: MIT - size: 70539 - timestamp: 1769858722627 -- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad - md5: a77f85f77be52ff59391544bfe73390a - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 85189 - timestamp: 1753484064210 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae - md5: 30cd29cb87d819caead4d55184c1d115 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - size: 24194 - timestamp: 1764460141901 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 - md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 - depends: - - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 - arch: x86_64 - platform: linux - license: BSD-3-Clause - license_family: BSD - size: 601375 - timestamp: 1764777111296 diff --git a/test/.CondaPkg/pixi.toml b/test/.CondaPkg/pixi.toml deleted file mode 100644 index 6aa29ae7..00000000 --- a/test/.CondaPkg/pixi.toml +++ /dev/null @@ -1,17 +0,0 @@ -[dependencies] -openssl = ">=3, <3.6" -libstdcxx = ">=3.4,<=15.1" -libstdcxx-ng = ">=3.4,<=15.1" -copier = "*" - - [dependencies.python] - channel = "conda-forge" - build = "*cp*" - version = ">=3.10,!=3.14.0,!=3.14.1,<4" - -[project] -name = ".CondaPkg" -platforms = ["linux-64"] -channels = ["conda-forge"] -channel-priority = "strict" -description = "automatically generated by CondaPkg.jl" From 1e6558d2e523cf74e1d038e68fea36559bee3ede Mon Sep 17 00:00:00 2001 From: Stefan de Lange Date: Thu, 30 Jul 2026 13:14:38 +0200 Subject: [PATCH 5/6] Document that a refraction model's precision promotes the result The docstring and both guides stated that the result element type follows the Observer element type. That was only true because a mismatched refraction model used to be a MethodError, and it is now the promotion of the two, since the apparent angles are computed at the model's precision. Promotion is kept rather than narrowing the model's parameters to the observer's type, because narrowing would have to special case ForwardDiff.Dual to avoid destroying derivatives. Type stability tests grow to cover the case. The vector path sizes its StructVector from a type computed at call time, so it has to infer to a concrete element type or the whole vector API goes unstable, and that path had no coverage: the existing testset only exercised scalar calls with NoRefraction and DefaultRefraction, neither of which can widen. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KhdBYRmHazfXZHWCRWt5fT --- docs/src/guides/precision.md | 18 +++++++++ docs/src/index.md | 4 +- src/Positioning/Positioning.jl | 6 ++- test/positioning/test-typestability.jl | 51 +++++++++++++++++++++++++- 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/docs/src/guides/precision.md b/docs/src/guides/precision.md index bafe5e33..98124ca7 100644 --- a/docs/src/guides/precision.md +++ b/docs/src/guides/precision.md @@ -25,6 +25,24 @@ A magnitude-safe time base keeps full intra-day resolution at every precision. T carried as an exact integer day count since J2000 plus a fraction of a day, so low precision types never ride on the ~2.45e6 Julian Date. +## Refraction models carry their own precision + +A refraction model's pressure and temperature have an element type of their own, and the +apparent angles are computed at it. The result type is the promotion of the two, so a +narrow observer paired with a default model widens: + +```@example precision +( + typeof(solar_position(obs32, dt, PSA(), HUGHES())), # Float64 pressure widens + typeof(solar_position(obs32, dt, PSA(), HUGHES{Float32}())), # matched, stays narrow + typeof(solar_position(obs32, dt, PSA(), ARCHER())), # no parameters to widen +) +``` + +Construct the model at the observer's precision to keep the result narrow. Models without +parameters never widen anything, and `DefaultRefraction` builds its model at the observer's +precision, so the default path is unaffected. + ## Supported types - `Float64` is the default and the reference. Every algorithm agrees with a 256-bit diff --git a/docs/src/index.md b/docs/src/index.md index 4a267d32..564c40b6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -91,7 +91,9 @@ accuracy and implementation status. The computation runs at the precision of the [`Observer{T}`](@ref SolarPosition.Positioning.Observer) element type. `Float32`, -`Float64`, `Float128`, and `BigFloat` are supported. See the +`Float64`, `Float128`, and `BigFloat` are supported. A refraction model's own parameter +type promotes with the observer's, so build the model at the same precision to keep a +narrow result narrow. See the [Numeric Precision](@ref numeric-precision) guide for measured accuracy and runtime of every algorithm at each precision, including multithreaded benchmarks. diff --git a/src/Positioning/Positioning.jl b/src/Positioning/Positioning.jl index 89c0af67..fba01cdb 100644 --- a/src/Positioning/Positioning.jl +++ b/src/Positioning/Positioning.jl @@ -301,7 +301,11 @@ pos_noaa = solar_position(obs, dt, NOAA()) # Floating-Point Precision The result element type follows the `Observer{T}` element type `T`, and the computation runs -at that precision: +at that precision. A refraction model carrying a wider parameter type widens the result with +it, since the apparent angles are computed at the model's precision: `Observer{Float32}` with +the default `HUGHES()` and its `Float64` pressure gives an `ApparentSolPos{Float64}`. +Construct the model at the observer's precision, as in `HUGHES{Float32}()`, to keep the +result narrow. Models without parameters such as `ARCHER` never widen anything. - **`Float64`**: the default, with reference accuracy for every algorithm. - **`BigFloat`**: genuine extended precision for every algorithm — use a higher `setprecision` for more correct digits. Note that ΔT is only known to about a second and diff --git a/test/positioning/test-typestability.jl b/test/positioning/test-typestability.jl index 322021ac..1ec915b4 100644 --- a/test/positioning/test-typestability.jl +++ b/test/positioning/test-typestability.jl @@ -2,8 +2,9 @@ using SolarPosition: Observer, solar_position, SolPos, ApparentSolPos, PSA, NOAA, Walraven, USNO, SPA -using SolarPosition.Refraction: NoRefraction, DefaultRefraction -using Dates: DateTime +using SolarPosition.Refraction: NoRefraction, DefaultRefraction, HUGHES, BENNETT, SG2, + SPARefraction, ARCHER, MICHALSKY +using Dates: DateTime, Hour @testset "Type stability across precisions" begin dt = DateTime(2026, 6, 2, 18, 17, 23) @@ -23,3 +24,49 @@ using Dates: DateTime end end end + +@testset "Type stability with explicit refraction models" begin + dt = DateTime(2026, 6, 2, 18, 17, 23) + dts = [dt, dt + Hour(1)] + algorithms = last.(test_algorithms()) + models = ( + NoRefraction(), DefaultRefraction(), HUGHES(), BENNETT(), SG2(), + SPARefraction(), ARCHER(), MICHALSKY(), + ) + + # the vector path sizes its StructVector from a type computed at call time, so it must + # infer to a concrete element type or the whole vector API goes unstable + obs = Observer(40.0, -105.0) + @testset "$(nameof(typeof(alg))) / $(nameof(typeof(model)))" for alg in algorithms, + model in models + + p = @inferred solar_position(obs, dt, alg, model) + @test p isa Union{SolPos{Float64}, ApparentSolPos{Float64}} + + v = @inferred solar_position(obs, dts, alg, model) + @test isconcretetype(eltype(v)) + @test eltype(v) === typeof(p) + end + + # A refraction model carries its own parameter type and the apparent angles are + # computed at it, so the result type is the promotion of the two. Matched precision and + # parameterless models keep the observer's element type. + @testset "Refraction parameters promote the result element type" begin + obs32 = Observer(40.0f0, -105.0f0) + for r in (HUGHES(), BENNETT(), SG2(), SPARefraction()) + @test solar_position(obs32, dt, PSA(), r) isa ApparentSolPos{Float64} + @test eltype(solar_position(obs32, dts, PSA(), r)) === ApparentSolPos{Float64} + end + for r in ( + HUGHES{Float32}(), BENNETT{Float32}(), SG2{Float32}(), + SPARefraction{Float32}(), ARCHER(), MICHALSKY(), + ) + @test solar_position(obs32, dt, PSA(), r) isa ApparentSolPos{Float32} + @test eltype(solar_position(obs32, dts, PSA(), r)) === ApparentSolPos{Float32} + end + # DefaultRefraction builds its model at the observer's precision, so it never widens + @test solar_position(obs32, dt, SPA(), DefaultRefraction()) isa + ApparentSolPos{Float32} + @test solar_position(obs32, dt, PSA(), NoRefraction()) isa SolPos{Float32} + end +end From d159c46aadb3c3b0077d762e6246db4626a18e32 Mon Sep 17 00:00:00 2001 From: Stefan de Lange Date: Thu, 30 Jul 2026 13:18:15 +0200 Subject: [PATCH 6/6] Stop tracking the generated benchmark CondaPkg environment benchmark/python.jl does use CondaPkg, but the tracked environment cannot be what serves it: pixi.toml and pixi.lock contain no reference to solposx or pandas, the two packages python.jl installs. What is tracked is the Copier template's own environment of copier, openssl and libstdcxx, the same stale content that test/.CondaPkg held, and its meta file is a binary cache of absolute paths from another machine. CondaPkg resolves the real dependencies on demand, and there is no tracked benchmark/CondaPkg.toml declaring anything. The **/.CondaPkg/ pattern added in the previous commit already covers this directory, and the files stay on disk. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KhdBYRmHazfXZHWCRWt5fT --- benchmark/.CondaPkg/.gitattributes | 2 - benchmark/.CondaPkg/.gitignore | 4 - benchmark/.CondaPkg/meta | Bin 876 -> 0 bytes benchmark/.CondaPkg/pixi.lock | 793 ----------------------------- benchmark/.CondaPkg/pixi.toml | 17 - 5 files changed, 816 deletions(-) delete mode 100644 benchmark/.CondaPkg/.gitattributes delete mode 100644 benchmark/.CondaPkg/.gitignore delete mode 100644 benchmark/.CondaPkg/meta delete mode 100644 benchmark/.CondaPkg/pixi.lock delete mode 100644 benchmark/.CondaPkg/pixi.toml diff --git a/benchmark/.CondaPkg/.gitattributes b/benchmark/.CondaPkg/.gitattributes deleted file mode 100644 index 887a2c18..00000000 --- a/benchmark/.CondaPkg/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# SCM syntax highlighting & preventing 3-way merges -pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/benchmark/.CondaPkg/.gitignore b/benchmark/.CondaPkg/.gitignore deleted file mode 100644 index 740bb7d1..00000000 --- a/benchmark/.CondaPkg/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ - -# pixi environments -.pixi -*.egg-info diff --git a/benchmark/.CondaPkg/meta b/benchmark/.CondaPkg/meta deleted file mode 100644 index 55f68b05555b9b0a56559572e052798d6eaf9291..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 876 zcmbu7Pfx-y7{)dJi_ydn;6<+reK$4<2Ov=n9$f;Gcr2rAg|01YN2Z^}Pv+TA;mHl! zh4>E=cUaqZ&+mQOr#stOJ+B`R-FN(U+c#^7MM8jK9ucL<1alaZ1t`IA3jOjO2t_p& z98DRF2oK`~r!zo3!N)k5MSvvDDGWox;ab7MMQ#Ju`Qavh*(H;(9P1^I{A&m0O&>AJvm8Y{0~OvT!r## L*L?m#W<5Uvapcnx diff --git a/benchmark/.CondaPkg/pixi.lock b/benchmark/.CondaPkg/pixi.lock deleted file mode 100644 index 0723ba7b..00000000 --- a/benchmark/.CondaPkg/pixi.lock +++ /dev/null @@ -1,793 +0,0 @@ -version: 6 -environments: - default: - channels: - - url: https://conda.anaconda.org/conda-forge/ - packages: - linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.11.3-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.3-py313heb322e3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.25.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/paramiko-4.0.0-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py313h843e2db_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h07c4f96_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.10-hc97d973_100_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.5.3-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda -packages: -- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 - md5: d7c89558ba9fa0495403155b64376d81 - license: None - size: 2562 - timestamp: 1578324546067 -- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - build_number: 16 - sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 - md5: 73aaf86a425cc6e73fcf236a5a46396d - depends: - - _libgcc_mutex 0.1 conda_forge - - libgomp >=7.5.0 - constrains: - - openmp_impl 9999 - license: BSD-3-Clause - license_family: BSD - size: 23621 - timestamp: 1650670423406 -- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - sha256: e0ea1ba78fbb64f17062601edda82097fcf815012cf52bb704150a2668110d48 - md5: 2934f256a8acfe48f6ebb4fce6cde29c - depends: - - python >=3.9 - - typing-extensions >=4.0.0 - license: MIT - license_family: MIT - size: 18074 - timestamp: 1733247158254 -- conda: https://conda.anaconda.org/conda-forge/linux-64/bcrypt-5.0.0-py313h843e2db_1.conda - sha256: cdf7496797af275f8bb5edd270aa06303dde623c7dd3a5941b0ce085d8f4cdc5 - md5: b59ec3796cba93d0db5f71e361176f27 - depends: - - python - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - arch: x86_64 - platform: linux - license: Apache-2.0 - license_family: APACHE - size: 292710 - timestamp: 1762497710166 -- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda - sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 - md5: 51a19bba1b8ebfb60df25cde030b7ebc - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: bzip2-1.0.6 - license_family: BSD - size: 260341 - timestamp: 1757437258798 -- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.11.12-hbd8a1cb_0.conda - sha256: b986ba796d42c9d3265602bc038f6f5264095702dd546c14bc684e60c385e773 - md5: f0991f0f84902f6b6009b4d2350a83aa - depends: - - __unix - license: ISC - size: 152432 - timestamp: 1762967197890 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py313hf46b229_1.conda - sha256: 2162a91819945c826c6ef5efe379e88b1df0fe9a387eeba23ddcf7ebeacd5bd6 - md5: d0616e7935acab407d1543b28c446f6f - depends: - - __glibc >=2.17,<3.0.a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - pycparser - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 298357 - timestamp: 1761202966461 -- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 - md5: 962b9857ee8e7018c22f2776ffa0b2d7 - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - size: 27011 - timestamp: 1733218222191 -- conda: https://conda.anaconda.org/conda-forge/noarch/copier-9.11.3-pyhcf101f3_0.conda - sha256: 0760286714848e8af7aade73da886e9f5cfe4126e6d10501f10135e50e94c2c0 - md5: fed521ea8b179bf63e9986b3108ed5f3 - depends: - - python >=3.10 - - colorama >=0.4.6 - - dunamai >=1.7.0 - - funcy >=1.17 - - jinja2 >=3.1.5 - - jinja2-ansible-filters >=1.3.1 - - packaging >=23.0 - - pathspec >=0.9.0 - - plumbum >=1.6.9 - - prompt-toolkit <3.0.52 - - pydantic >=2.4.2 - - pygments >=2.7.1 - - pyyaml >=5.3.1 - - questionary >=1.8.1 - - eval-type-backport >=0.1.3,<0.3.0 - - platformdirs >=4.3.6 - - typing_extensions >=4.0.0,<5.0.0 - - python - license: MIT - license_family: MIT - size: 54229 - timestamp: 1769272985745 -- conda: https://conda.anaconda.org/conda-forge/linux-64/cryptography-46.0.3-py313heb322e3_1.conda - sha256: beb4f2fa46bf3d550bf5bf2a07796be14cbe73ceebe43b28e634ee778b547e99 - md5: 4e6278c519f2766ea707361f81b33364 - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.14 - - libgcc >=14 - - openssl >=3.5.4,<4.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - arch: x86_64 - platform: linux - license: Apache-2.0 AND BSD-3-Clause AND PSF-2.0 AND MIT - license_family: BSD - size: 1723198 - timestamp: 1764805305302 -- conda: https://conda.anaconda.org/conda-forge/noarch/dunamai-1.25.0-pyhd8ed1ab_0.conda - sha256: 16d78408ed520b0951425cdfae280392813794e29ea6d3cb95f6c479347c4e1c - md5: 4b6cc280feef9e74ffe9ea1c875b5f8c - depends: - - packaging >=20.9 - - python >=3.9 - license: MIT - license_family: MIT - size: 30075 - timestamp: 1751991836363 -- conda: https://conda.anaconda.org/conda-forge/noarch/eval-type-backport-0.2.2-pyhd8ed1ab_0.conda - sha256: 05ffdcb83903c159bfbb78a07fbcce6fd6dda41df9c55ed75e0eb1db5528048f - md5: 879479fda1dddb002fdc4885cea33740 - depends: - - eval_type_backport >=0.2.2,<0.2.3.0a0 - - python >=3.9 - license: MIT - license_family: MIT - size: 6662 - timestamp: 1734857849281 -- conda: https://conda.anaconda.org/conda-forge/noarch/eval_type_backport-0.2.2-pyha770c72_0.conda - sha256: 2d721421a60676216e10837a240c75e2190e093920a4016a469fa9a62c95ab5f - md5: 8681d7f876da5e66a1c7fce424509383 - depends: - - python >=3.9 - constrains: - - eval-type-backport >=0.2.2,<0.2.3.0a0 - license: MIT - license_family: MIT - size: 11520 - timestamp: 1734857840035 -- conda: https://conda.anaconda.org/conda-forge/noarch/funcy-2.0-pyhd8ed1ab_1.conda - sha256: 4a3e3e86b7b49aaa2a0faa57da2bcf39f7ee858499e8335132f83bfeed79191e - md5: 84f8955e99a8944fdee49da39edb0add - depends: - - python >=3.9 - license: BSD-3-Clause - license_family: BSD - size: 30249 - timestamp: 1734381235500 -- conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.5.2-pyhd8ed1ab_0.conda - sha256: acc1d991837c0afb67c75b77fdc72b4bf022aac71fedd8b9ea45918ac9b08a80 - md5: c85c76dc67d75619a92f51dfbce06992 - depends: - - python >=3.9 - - zipp >=3.1.0 - constrains: - - importlib-resources >=6.5.2,<6.5.3.0a0 - license: Apache-2.0 - license_family: APACHE - size: 33781 - timestamp: 1736252433366 -- conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.1-pyhd8ed1ab_0.conda - sha256: 5a4e3a01f626c8de15ddada622d364e94ff28e8d6bdedf1665442ef03a4e0140 - md5: 3a804714ed59be1969ffca10f703ec2a - depends: - - python >=3.10 - license: BSD-2-Clause - license_family: BSD - size: 132825 - timestamp: 1760146119847 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda - sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b - md5: 04558c96691bed63104678757beb4f8d - depends: - - markupsafe >=2.0 - - python >=3.10 - - python - license: BSD-3-Clause - license_family: BSD - size: 120685 - timestamp: 1764517220861 -- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-ansible-filters-1.3.2-pyhd8ed1ab_1.conda - sha256: a36789229ca9ce5315265b9d425abce9acb4691f5864aea69e935020545a9acb - md5: 974c5b3e353f031cfcf2365c9d375926 - depends: - - jinja2 - - python >=3.9 - - pyyaml - license: BSD-2-Clause - license_family: BSD - size: 20315 - timestamp: 1734906203051 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45-default_hbd61a6d_104.conda - sha256: 9e191baf2426a19507f1d0a17be0fdb7aa155cdf0f61d5a09c808e0a69464312 - md5: a6abd2796fc332536735f68ba23f7901 - depends: - - __glibc >=2.17,<3.0.a0 - - zstd >=1.5.7,<1.6.0a0 - constrains: - - binutils_impl_linux-64 2.45 - license: GPL-3.0-only - license_family: GPL - size: 725545 - timestamp: 1764007826689 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.3-hecca717_0.conda - sha256: 1e1b08f6211629cbc2efe7a5bca5953f8f6b3cae0eeb04ca4dacee1bd4e2db2f - md5: 8b09ae86839581147ef2e5c5e229d164 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - constrains: - - expat 2.7.3.* - license: MIT - license_family: MIT - size: 76643 - timestamp: 1763549731408 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h9ec8514_0.conda - sha256: 25cbdfa65580cfab1b8d15ee90b4c9f1e0d72128f1661449c9a999d341377d54 - md5: 35f29eec58405aaf55e01cb470d8c26a - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: MIT - license_family: MIT - size: 57821 - timestamp: 1760295480630 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_5.conda - sha256: 0caed73aac3966bfbf5710e06c728a24c6c138605121a3dacb2e03440e8baa6a - md5: 264fbfba7fb20acf3b29cde153e345ce - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - constrains: - - libgomp 15.1.0 h767d61c_5 - - libgcc-ng ==15.1.0=*_5 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 824191 - timestamp: 1757042543820 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_5.conda - sha256: f54bb9c3be12b24be327f4c1afccc2969712e0b091cdfbd1d763fb3e61cda03f - md5: 069afdf8ea72504e48d23ae1171d951c - depends: - - libgcc 15.1.0 h767d61c_5 - arch: x86_64 - platform: linux - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 29187 - timestamp: 1757042549554 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_5.conda - sha256: 125051d51a8c04694d0830f6343af78b556dd88cc249dfec5a97703ebfb1832d - md5: dcd5ff1940cd38f6df777cac86819d60 - depends: - - __glibc >=2.17,<3.0.a0 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 447215 - timestamp: 1757042483384 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda - sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 - md5: 1a580f7796c7bf6393fddb8bbbde58dc - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - constrains: - - xz 5.8.1.* - license: 0BSD - size: 112894 - timestamp: 1749230047870 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb9d3cd8_0.conda - sha256: 3aa92d4074d4063f2a162cd8ecb45dccac93e543e565c01a787e16a43501f7ee - md5: c7e925f37e3b40d893459e625f6a53f1 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: BSD-2-Clause - license_family: BSD - size: 91183 - timestamp: 1748393666725 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.20-h4ab18f5_0.conda - sha256: 0105bd108f19ea8e6a78d2d994a6d4a8db16d19a41212070d2d1d48a63c34161 - md5: a587892d3c13b6621a6091be690dbca2 - depends: - - libgcc-ng >=12 - arch: x86_64 - platform: linux - license: ISC - size: 205978 - timestamp: 1716828628198 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_0.conda - sha256: 6f0e8a812e8e33a4d8b7a0e595efe28373080d27b78ee4828aa4f6649a088454 - md5: 2e1b84d273b01835256e53fd938de355 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libzlib >=1.3.1,<2.0a0 - license: blessing - size: 938979 - timestamp: 1764359444435 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_5.conda - sha256: 0f5f61cab229b6043541c13538d75ce11bd96fb2db76f94ecf81997b1fde6408 - md5: 4e02a49aaa9d5190cb630fa43528fbe6 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc 15.1.0 h767d61c_5 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 3896432 - timestamp: 1757042571458 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_5.conda - sha256: 7b8cabbf0ab4fe3581ca28fe8ca319f964078578a51dd2ca3f703c1d21ba23ff - md5: 8bba50c7f4679f08c861b597ad2bda6b - depends: - - libstdcxx 15.1.0 h8f9b012_5 - license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 29233 - timestamp: 1757042603319 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.41.2-h5347b49_1.conda - sha256: 030447cf827c471abd37092ab9714fde82b8222106f22fde94bc7a64e2704c40 - md5: 41f5c09a211985c3ce642d60721e7c3e - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - license: BSD-3-Clause - size: 40235 - timestamp: 1764790744114 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 - md5: edb0dca6bc32e4f4789199455a1dbeb8 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - constrains: - - zlib 1.3.1 *_2 - license: Zlib - license_family: Other - size: 60963 - timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py313h3dea7bd_0.conda - sha256: a530a411bdaaf0b1e4de8869dfaca46cb07407bc7dc0702a9e231b0e5ce7ca85 - md5: c14389156310b8ed3520d84f854be1ee - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - constrains: - - jinja2 >=3.0.0 - arch: x86_64 - platform: linux - license: BSD-3-Clause - license_family: BSD - size: 25909 - timestamp: 1759055357045 -- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 - md5: 47e340acb35de30501a76c7c799c41d7 - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - license: X11 AND BSD-3-Clause - size: 891641 - timestamp: 1738195959188 -- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.4-h26f9b46_0.conda - sha256: e807f3bad09bdf4075dbb4168619e14b0c0360bacb2e12ef18641a834c8c5549 - md5: 14edad12b59ccbfa3910d42c72adc2a0 - depends: - - __glibc >=2.17,<3.0.a0 - - ca-certificates - - libgcc >=14 - license: Apache-2.0 - license_family: Apache - size: 3119624 - timestamp: 1759324353651 -- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.0-pyhcf101f3_0.conda - sha256: c1fc0f953048f743385d31c468b4a678b3ad20caffdeaa94bed85ba63049fd58 - md5: b76541e68fea4d511b1ac46a28dcd2c6 - depends: - - python >=3.8 - - python - license: Apache-2.0 - license_family: APACHE - size: 72010 - timestamp: 1769093650580 -- conda: https://conda.anaconda.org/conda-forge/noarch/paramiko-4.0.0-pyhd8ed1ab_0.conda - sha256: ce76d5a1fc6c7ef636cbdbf14ce2d601a1bfa0dd8d286507c1fd02546fccb94b - md5: 1a884d2b1ea21abfb73911dcdb8342e4 - depends: - - bcrypt >=3.2 - - cryptography >=3.3 - - invoke >=2.0 - - pynacl >=1.5 - - python >=3.9 - license: LGPL-2.1-or-later - license_family: LGPL - size: 159896 - timestamp: 1755102147074 -- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.0.4-pyhd8ed1ab_0.conda - sha256: 29ea20d0faf20374fcd61c25f6d32fb8e9a2c786a7f1473a0c3ead359470fbe1 - md5: 2908273ac396d2cd210a8127f5f1c0d6 - depends: - - python >=3.10 - license: MPL-2.0 - license_family: MOZILLA - size: 53739 - timestamp: 1769677743677 -- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.5.1-pyhcf101f3_0.conda - sha256: 04c64fb78c520e5c396b6e07bc9082735a5cc28175dbe23138201d0a9441800b - md5: 1bd2e65c8c7ef24f4639ae6e850dacc2 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - size: 23922 - timestamp: 1764950726246 -- conda: https://conda.anaconda.org/conda-forge/noarch/plumbum-1.10.0-pyhcf101f3_0.conda - sha256: 972e0c1c9f0b1763d482e885732baef7f9a0cbe8686f5e2c6bdd88838619a59a - md5: 7fd2e38f4e608f5ebd80f871352c5495 - depends: - - python >=3.10 - - pywin32-on-windows - - paramiko - - importlib_resources - - python - license: MIT - license_family: MIT - size: 103911 - timestamp: 1761911487086 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b - md5: d17ae9db4dc594267181bd199bf9a551 - depends: - - python >=3.9 - - wcwidth - constrains: - - prompt_toolkit 3.0.51 - license: BSD-3-Clause - license_family: BSD - size: 271841 - timestamp: 1744724188108 -- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.51-hd8ed1ab_0.conda - sha256: 936189f0373836c1c77cd2d6e71ba1e583e2d3920bf6d015e96ee2d729b5e543 - md5: 1e61ab85dd7c60e5e73d853ea035dc29 - depends: - - prompt-toolkit >=3.0.51,<3.0.52.0a0 - license: BSD-3-Clause - license_family: BSD - size: 7182 - timestamp: 1744724189376 -- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 - md5: 12c566707c80111f9799308d9e265aef - depends: - - python >=3.9 - - python - license: BSD-3-Clause - license_family: BSD - size: 110100 - timestamp: 1733195786147 -- conda: https://conda.anaconda.org/conda-forge/noarch/pydantic-2.12.5-pyhcf101f3_1.conda - sha256: 868569d9505b7fe246c880c11e2c44924d7613a8cdcc1f6ef85d5375e892f13d - md5: c3946ed24acdb28db1b5d63321dbca7d - depends: - - typing-inspection >=0.4.2 - - typing_extensions >=4.14.1 - - python >=3.10 - - typing-extensions >=4.6.1 - - annotated-types >=0.6.0 - - pydantic-core ==2.41.5 - - python - license: MIT - license_family: MIT - size: 340482 - timestamp: 1764434463101 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pydantic-core-2.41.5-py313h843e2db_1.conda - sha256: b15568ddc03bd33ea41610e5df951be4e245cd61957cbf8c2cfd12557f3d53b5 - md5: f27c39a1906771bbe56cd26a76bf0b8b - depends: - - python - - typing-extensions >=4.6.0,!=4.7.0 - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - - python_abi 3.13.* *_cp313 - constrains: - - __glibc >=2.17 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 1940186 - timestamp: 1762989000579 -- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a - md5: 6b6ece66ebcae2d5f326c77ef2c5a066 - depends: - - python >=3.9 - license: BSD-2-Clause - license_family: BSD - size: 889287 - timestamp: 1750615908735 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pynacl-1.6.2-py313h07c4f96_0.conda - sha256: 28c2b34c8a5542631b47ef48ccfb32fbf19eab5b46cd39ac0bf546af0c337d31 - md5: 69b90b328f1a57bc4bbffcaa990128cf - depends: - - __glibc >=2.17,<3.0.a0 - - cffi >=1.4.1 - - libgcc >=14 - - libsodium >=1.0.20,<1.0.21.0a0 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - six - arch: x86_64 - platform: linux - license: Apache-2.0 - license_family: Apache - size: 1192806 - timestamp: 1767324184629 -- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.10-hc97d973_100_cp313.conda - build_number: 100 - sha256: fbc193c94b72b09b1e5b4ae7d65764b90bb9e6e4d672481d91d8769ab20e909e - md5: 9e9d4de52c55af3563a00981fb39cbed - depends: - - __glibc >=2.17,<3.0.a0 - - bzip2 >=1.0.8,<2.0a0 - - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.7.3,<3.0a0 - - libffi >=3.5.2,<3.6.0a0 - - libgcc >=14 - - liblzma >=5.8.1,<6.0a0 - - libmpdec >=4.0.0,<5.0a0 - - libsqlite >=3.51.1,<4.0a0 - - libuuid >=2.41.2,<3.0a0 - - libzlib >=1.3.1,<2.0a0 - - ncurses >=6.5,<7.0a0 - - openssl >=3.5.4,<4.0a0 - - python_abi 3.13.* *_cp313 - - readline >=8.2,<9.0a0 - - tk >=8.6.13,<8.7.0a0 - - tzdata - license: Python-2.0 - size: 37104697 - timestamp: 1764755723771 - python_site_packages_path: lib/python3.13/site-packages -- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-8_cp313.conda - build_number: 8 - sha256: 210bffe7b121e651419cb196a2a63687b087497595c9be9d20ebe97dd06060a7 - md5: 94305520c52a4aa3f6c2b1ff6008d9f8 - constrains: - - python 3.13.* *_cp313 - license: BSD-3-Clause - license_family: BSD - size: 7002 - timestamp: 1752805902938 -- conda: https://conda.anaconda.org/conda-forge/noarch/pywin32-on-windows-0.1.0-pyh1179c8e_3.tar.bz2 - sha256: 6502696aaef571913b22a808b15c185bd8ea4aabb952685deb29e6a6765761cb - md5: 2807a0becd1d986fe1ef9b7f8135f215 - depends: - - __unix - - python >=2.7 - license: BSD-3-Clause - license_family: BSD - size: 4856 - timestamp: 1646866525560 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda - sha256: ef7df29b38ef04ec67a8888a4aa039973eaa377e8c4b59a7be0a1c50cd7e4ac6 - md5: f256753e840c3cd3766488c9437a8f8b - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - python >=3.13,<3.14.0a0 - - python_abi 3.13.* *_cp313 - - yaml >=0.2.5,<0.3.0a0 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 201616 - timestamp: 1770223543730 -- conda: https://conda.anaconda.org/conda-forge/noarch/questionary-2.1.1-pyhd8ed1ab_0.conda - sha256: 0604c6dff3af5f53e34fceb985395d08287137f220450108a795bcd1959caf14 - md5: 34fa231b5c5927684b03bb296bb94ddc - depends: - - prompt_toolkit >=2.0,<4.0 - - python >=3.10 - license: MIT - license_family: MIT - size: 31257 - timestamp: 1757356458097 -- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c - md5: 283b96675859b20a825f8fa30f311446 - depends: - - libgcc >=13 - - ncurses >=6.5,<7.0a0 - license: GPL-3.0-only - license_family: GPL - size: 282480 - timestamp: 1740379431762 -- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d - md5: 3339e3b65d58accf4ca4fb8748ab16b3 - depends: - - python >=3.9 - - python - license: MIT - license_family: MIT - size: 18455 - timestamp: 1753199211006 -- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_ha0e22de_103.conda - sha256: 1544760538a40bcd8ace2b1d8ebe3eb5807ac268641f8acdc18c69c5ebfeaf64 - md5: 86bc20552bf46075e3d92b67f089172d - depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libzlib >=1.3.1,<2.0a0 - constrains: - - xorg-libx11 >=1.8.12,<2.0a0 - license: TCL - license_family: BSD - size: 3284905 - timestamp: 1763054914403 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c - md5: edd329d7d3a4ab45dcf905899a7a6115 - depends: - - typing_extensions ==4.15.0 pyhcf101f3_0 - license: PSF-2.0 - license_family: PSF - size: 91383 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing-inspection-0.4.2-pyhd8ed1ab_1.conda - sha256: 70db27de58a97aeb7ba7448366c9853f91b21137492e0b4430251a1870aa8ff4 - md5: a0a4a3035667fc34f29bfbd5c190baa6 - depends: - - python >=3.10 - - typing_extensions >=4.12.0 - license: MIT - license_family: MIT - size: 18923 - timestamp: 1764158430324 -- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - sha256: 032271135bca55aeb156cee361c81350c6f3fb203f57d024d7e5a1fc9ef18731 - md5: 0caa1af407ecff61170c9437a808404d - depends: - - python >=3.10 - - python - license: PSF-2.0 - license_family: PSF - size: 51692 - timestamp: 1756220668932 -- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda - sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 - md5: 4222072737ccff51314b5ece9c7d6f5a - license: LicenseRef-Public-Domain - size: 122968 - timestamp: 1742727099393 -- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.5.3-pyhd8ed1ab_0.conda - sha256: 2395599ec9e37e6f21838bb26e7f2336fa03a4b1460ba10897ec856b21ac7d59 - md5: 36432484e9ce3b073a51bf138767a593 - depends: - - python >=3.10 - license: MIT - license_family: MIT - size: 70539 - timestamp: 1769858722627 -- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - sha256: 6d9ea2f731e284e9316d95fa61869fe7bbba33df7929f82693c121022810f4ad - md5: a77f85f77be52ff59391544bfe73390a - depends: - - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - arch: x86_64 - platform: linux - license: MIT - license_family: MIT - size: 85189 - timestamp: 1753484064210 -- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae - md5: 30cd29cb87d819caead4d55184c1d115 - depends: - - python >=3.10 - - python - license: MIT - license_family: MIT - size: 24194 - timestamp: 1764460141901 -- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 - md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 - depends: - - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 - license: BSD-3-Clause - size: 601375 - timestamp: 1764777111296 diff --git a/benchmark/.CondaPkg/pixi.toml b/benchmark/.CondaPkg/pixi.toml deleted file mode 100644 index 6aa29ae7..00000000 --- a/benchmark/.CondaPkg/pixi.toml +++ /dev/null @@ -1,17 +0,0 @@ -[dependencies] -openssl = ">=3, <3.6" -libstdcxx = ">=3.4,<=15.1" -libstdcxx-ng = ">=3.4,<=15.1" -copier = "*" - - [dependencies.python] - channel = "conda-forge" - build = "*cp*" - version = ">=3.10,!=3.14.0,!=3.14.1,<4" - -[project] -name = ".CondaPkg" -platforms = ["linux-64"] -channels = ["conda-forge"] -channel-priority = "strict" -description = "automatically generated by CondaPkg.jl"