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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ jobs:
- DataDrivenSR
- DataDrivenSparse
- DataDrivenLux
- nopre
version:
- '1'
- 'lts'
- 'pre'
exclude:
- group: nopre
version: 'pre'
steps:
- uses: actions/checkout@v6
- uses: julia-actions/setup-julia@v2
Expand Down
3 changes: 3 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[default.extend-words]
# Pre-existing typos in public API (breaking to fix)
expresssion = "expresssion"

# Julia-specific functions
indexin = "indexin"
findfirst = "findfirst"
Expand Down
2 changes: 1 addition & 1 deletion docs/src/libs/datadrivendmd/example_02.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sys = ODEProblem(f, u0, tspan)
sol = solve(sys, Tsit5(), saveat = 0.05);

# We could use the `DESolution` to define our problem, but here we want to use the data for didactic purposes.
# For a [`ContinuousDataDrivenProblem`](@ref DataDrivenProblem), we need either the state trajectory and the timepoints or the state trajectory and its derivate.
# For a [`ContinuousDataDrivenProblem`](@ref DataDrivenProblem), we need either the state trajectory and the timepoints or the state trajectory and its derivative.

X = Array(sol)
t = sol.t
Expand Down
14 changes: 13 additions & 1 deletion src/problem/type.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,15 @@ end

Base.eltype(::AbstractDataDrivenProblem{T}) where {T} = T

# Internal constructor that is type-stable once cType and probType are known
# This function barrier ensures the inner construction is fully specialized
function _construct_datadrivenproblem(
::Val{cType}, ::Val{probType}, dType::Type{T}, X, t, DX, Y, U, p, name
) where {cType, probType, T}
promoted = _promote(X, t, DX, Y, U, p)
return DataDrivenProblem{T, cType, probType}(promoted..., name)
end

function DataDrivenProblem(
probType, X, t, DX, Y, U, p; name = gensym(:DDProblem),
kwargs...
Expand All @@ -131,7 +140,10 @@ function DataDrivenProblem(
end
end

return DataDrivenProblem{dType, cType, probType}(_promote(X, t, DX, Y, U, p)..., name)
# Use function barrier with Val types for type stability
return _construct_datadrivenproblem(
Val(cType), Val(probType), dType, X, t, DX, Y, U, p, name
)
end

function remake_problem(
Expand Down
5 changes: 3 additions & 2 deletions test/basis/basis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ end
@test size(basis_2) == (5,)
# Note: Order may differ due to internal Symbolics representation
# The linear_independent basis extracts terms which may be reordered
@test basis_2([1.0; 2.0; π], [0.0; 1.0]) ≈ [1.0; -1.0; π; 2.0; 1.0]
@test basis([1.0; 2.0; π], [0.0; 1.0]) ≈ [1.0; 2.0; π; -1.0; 5 * π + 2.0; 1.0]
# Use sorted comparison to handle non-deterministic ordering
@test sort(basis_2([1.0; 2.0; π], [0.0; 1.0])) ≈ sort([1.0; -1.0; π; 2.0; 1.0])
@test sort(basis([1.0; 2.0; π], [0.0; 1.0])) ≈ sort([1.0; 2.0; π; -1.0; 5 * π + 2.0; 1.0])

@test size(basis) == size(basis_2) .+ (1,)
push!(basis_2, sin(u[2]))
Expand Down
7 changes: 7 additions & 0 deletions test/nopre/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[deps]
DataDrivenDiffEq = "2445eb08-9709-466a-b3fc-47e12bd697a2"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
JET = "0.9, 0.10, 0.11"
69 changes: 69 additions & 0 deletions test/nopre/jet_tests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using DataDrivenDiffEq
using JET
using Test

# Note: JET analysis on symbolic packages can be slow and may report many
# false positives from the underlying symbolic infrastructure (Symbolics.jl,
# ModelingToolkit.jl). This test file focuses on core DataDrivenDiffEq
# functionality with concrete types to catch actual type stability issues.
#
# We use @test_opt with target_modules to only check DataDrivenDiffEq code,
# and we use broken=true for tests that detect expected polymorphic behavior
# (like problem type dispatch) rather than actual bugs.

@testset "JET Static Analysis" begin
@testset "Basis generator type stability" begin
# Test basis generators with concrete types
# These should be fully type-stable as they don't involve symbolic computation
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.polynomial_basis(
2, 3
)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.monomial_basis(2, 3)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.chebyshev_basis(2, 3)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.sin_basis(2, 3)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.cos_basis(2, 3)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.fourier_basis(2, 3)
end

@testset "Problem accessor type stability" begin
X = rand(2, 10)
t = collect(1.0:10.0)
DX = rand(2, 10)

prob = ContinuousDataDrivenProblem(X, t, DX)

# Test simple accessor functions that should be type-stable
# These accessors only check type parameters, not field values
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.is_autonomous(prob)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.is_discrete(prob)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.is_continuous(prob)
@test_opt target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq.is_direct(prob)
# Note: has_timepoints checks isempty on AbstractVector field, which has
# expected runtime dispatch due to the abstract field type design choice
end

@testset "Internal constructor type stability" begin
# Test the internal type-stable constructor directly
X = rand(2, 10)
t = collect(1.0:10.0)
DX = rand(2, 10)
Y = Matrix{Float64}(undef, 0, 0)
U = Matrix{Float64}(undef, 0, 0)
p = Float64[]

# The internal constructor has runtime dispatch in _promote on Julia lts
# due to broadcasting with abstract element types. Fixed in Julia 1.11+.
@test_opt broken = (VERSION < v"1.11") target_modules = (DataDrivenDiffEq,) DataDrivenDiffEq._construct_datadrivenproblem(
Val(false),
Val(DataDrivenDiffEq.DDProbType(3)),
Float64,
X,
t,
DX,
Y,
U,
p,
:test
)
end
end
13 changes: 13 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ function activate_subpkg_env(subpkg)
return Pkg.instantiate()
end

function activate_nopre_env()
Pkg.activate(joinpath(@__DIR__, "nopre"))
Pkg.develop(PackageSpec(path = dirname(@__DIR__)))
return Pkg.instantiate()
end

@time begin
if GROUP == "All" || GROUP == "Core" || GROUP == "Downstream"
@testset "All" begin
Expand All @@ -41,6 +47,13 @@ end
include("./commonsolve/commonsolve.jl")
end
end
elseif GROUP == "nopre"
# nopre tests are excluded from Julia pre-release versions in CI
# to avoid failures from upstream changes (e.g., JET type inference)
activate_nopre_env()
@safetestset "JET Static Analysis" begin
include("nopre/jet_tests.jl")
end
else
dev_subpkg(GROUP)
subpkg_path = joinpath(dirname(@__DIR__), "lib", GROUP)
Expand Down
Loading