Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.
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
3 changes: 3 additions & 0 deletions src/DiffEqDevTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export test_convergence, analyticless_test_convergence, appxtrue!, appxtrue, def

export get_sample_errors

#Tagging and Filtering
export filter_by_tags, exclude_by_tags, get_tags, unique_tags, merge_wp_sets

#Tab Functions
export stability_region, residual_order_condition, check_tableau,
imaginary_stability_interval
Expand Down
121 changes: 111 additions & 10 deletions src/benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ relates to error (precision) across different tolerance settings.
- `name`: Name of the algorithm
- `error_estimate`: Error metric used (e.g., `:final`, `:l2`)
- `N`: Number of tolerance settings tested
- `tags`: Symbolic tags for categorizing this algorithm (e.g., `[:rosenbrock, :stiff, :4th_order]`)

# Example
```julia
Expand All @@ -248,6 +249,7 @@ mutable struct WorkPrecision
name::Any
error_estimate::Any
N::Int
tags::Vector{Symbol}
end

"""
Expand Down Expand Up @@ -294,7 +296,8 @@ end
function WorkPrecision(
prob, alg, abstols, reltols, dts = nothing;
name = nothing, appxsol = nothing, error_estimate = :final,
numruns = 20, seconds = 2, reduction = default_reduction, kwargs...
numruns = 20, seconds = 2, reduction = default_reduction,
tags::Vector{Symbol} = Symbol[], kwargs...
)
N = length(abstols)
errors = Vector{Dict{Symbol, Float64}}(undef, N)
Expand Down Expand Up @@ -417,15 +420,16 @@ function WorkPrecision(
end
return WorkPrecision(
prob, abstols, reltols, _dicts_to_structarray(errors),
times, dts, stats, name, error_estimate, N
times, dts, stats, name, error_estimate, N, tags
)
end

# Work precision information for a BVP
function WorkPrecision(
prob::AbstractBVProblem, alg, abstols, reltols, dts = nothing;
name = nothing, appxsol = nothing, error_estimate = :final,
numruns = 20, seconds = 2, reduction = default_reduction, kwargs...
numruns = 20, seconds = 2, reduction = default_reduction,
tags::Vector{Symbol} = Symbol[], kwargs...
)
N = length(abstols)
errors = Vector{Dict{Symbol, Float64}}(undef, N)
Expand Down Expand Up @@ -547,15 +551,15 @@ function WorkPrecision(
end
return WorkPrecision(
prob, abstols, reltols, _dicts_to_structarray(errors),
times, dts, stats, name, error_estimate, N
times, dts, stats, name, error_estimate, N, tags
)
end

# Work precision information for a nonlinear problem.
function WorkPrecision(
prob::NonlinearProblem, alg, abstols, reltols, dts = nothing; name = nothing,
appxsol = nothing, error_estimate = :l2, numruns = 20, seconds = 2,
reduction = default_reduction, kwargs...
reduction = default_reduction, tags::Vector{Symbol} = Symbol[], kwargs...
)
N = length(abstols)
errors = Vector{Dict{Symbol, Float64}}(undef, N)
Expand Down Expand Up @@ -612,7 +616,7 @@ function WorkPrecision(

return WorkPrecision(
prob, abstols, reltols, _dicts_to_structarray(errors),
times, dts, stats, name, error_estimate, N
times, dts, stats, name, error_estimate, N, tags
)
end

Expand All @@ -636,11 +640,13 @@ function WorkPrecisionSet(
_dts = get(setups[i], :dts, nothing)
filtered_setup = filter(p -> p.first in DiffEqBase.allowedkeywords, setups[i])

_tags = get(setups[i], :tags, Symbol[])
wps[i] = WorkPrecision(
prob, setups[i][:alg], _abstols, _reltols, _dts;
appxsol = appxsol,
error_estimate = error_estimate,
name = names[i], reduction = reduction, kwargs..., filtered_setup...
name = names[i], reduction = reduction,
tags = _tags, kwargs..., filtered_setup...
)
end
return WorkPrecisionSet(
Expand Down Expand Up @@ -792,7 +798,8 @@ function WorkPrecisionSet(
wps = [WorkPrecision(
prob, _abstols[i], _reltols[i],
_dicts_to_structarray(errors[i]),
times[:, i], _dts[i], stats, names[i], error_estimate, N
times[:, i], _dts[i], stats, names[i], error_estimate, N,
get(setups[i], :tags, Symbol[])
)
for i in 1:N]
return WorkPrecisionSet(
Expand Down Expand Up @@ -922,7 +929,8 @@ function WorkPrecisionSet(
stats = nothing
wps = [WorkPrecision(
prob, _abstols[i], _reltols[i], errors[i], times[:, i],
_dts[i], stats, names[i], error_estimate, N
_dts[i], stats, names[i], error_estimate, N,
get(setups[i], :tags, Symbol[])
)
for i in 1:N]
return WorkPrecisionSet(
Expand Down Expand Up @@ -951,11 +959,13 @@ function WorkPrecisionSet(
_dts = get(setups[i], :dts, nothing)
filtered_setup = filter(p -> p.first in DiffEqBase.allowedkeywords, setups[i])

_tags = get(setups[i], :tags, Symbol[])
wps[i] = WorkPrecision(
prob, setups[i][:alg], _abstols, _reltols, _dts;
appxsol = appxsol,
error_estimate = error_estimate,
name = names[i], reduction = reduction, kwargs..., filtered_setup...
name = names[i], reduction = reduction,
tags = _tags, kwargs..., filtered_setup...
)
end
return WorkPrecisionSet(
Expand Down Expand Up @@ -1040,6 +1050,97 @@ function get_sample_errors(
end
end

"""
filter_by_tags(wp_set::WorkPrecisionSet, tags::Symbol...) -> WorkPrecisionSet

Return a new `WorkPrecisionSet` containing only entries whose tags include
ALL of the specified tags (AND logic).

# Example
```julia
setups = [
Dict(:alg => Rosenbrock23(), :tags => [:rosenbrock, :2nd_order]),
Dict(:alg => Rodas5P(), :tags => [:rosenbrock, :5th_order]),
Dict(:alg => TRBDF2(), :tags => [:bdf, :2nd_order]),
]
wp_set = WorkPrecisionSet(prob, abstols, reltols, setups)
rosenbrock_only = filter_by_tags(wp_set, :rosenbrock)
second_order_rosenbrock = filter_by_tags(wp_set, :rosenbrock, :2nd_order)
```
"""
function filter_by_tags(wp_set::WorkPrecisionSet, tags::Symbol...)
isempty(tags) && return wp_set
indices = findall(wp -> all(t -> t in wp.tags, tags), wp_set.wps)
_subset_wps(wp_set, indices)
end

"""
exclude_by_tags(wp_set::WorkPrecisionSet, tags::Symbol...) -> WorkPrecisionSet

Return a new `WorkPrecisionSet` excluding entries that have ANY of the specified tags.

# Example
```julia
no_reference = exclude_by_tags(wp_set, :reference)
```
"""
function exclude_by_tags(wp_set::WorkPrecisionSet, tags::Symbol...)
isempty(tags) && return wp_set
indices = findall(wp -> !any(t -> t in wp.tags, tags), wp_set.wps)
_subset_wps(wp_set, indices)
end

"""
get_tags(wp_set::WorkPrecisionSet) -> Vector{Vector{Symbol}}

Return the tags for each entry in the `WorkPrecisionSet`.
"""
get_tags(wp_set::WorkPrecisionSet) = [wp.tags for wp in wp_set.wps]

"""
unique_tags(wp_set::WorkPrecisionSet) -> Vector{Symbol}

Return all unique tags present across entries in the `WorkPrecisionSet`.
"""
function unique_tags(wp_set::WorkPrecisionSet)
alltags = Symbol[]
for wp in wp_set.wps
append!(alltags, wp.tags)
end
return unique!(sort!(alltags))
end

"""
merge_wp_sets(sets::WorkPrecisionSet...) -> WorkPrecisionSet

Merge multiple `WorkPrecisionSet`s into a single set. All entries are combined.
The metadata (abstols, reltols, prob, error_estimate) is taken from the first set.
"""
function merge_wp_sets(sets::WorkPrecisionSet...)
isempty(sets) && throw(ArgumentError("At least one WorkPrecisionSet is required"))
wps = vcat([s.wps for s in sets]...)
all_setups = vcat([s.setups for s in sets]...)
all_names = vcat([s.names for s in sets]...)
N = length(wps)
first_set = first(sets)
return WorkPrecisionSet(
wps, N, first_set.abstols, first_set.reltols, first_set.prob,
all_setups, all_names, first_set.error_estimate, first_set.numruns
)
end

function _subset_wps(wp_set::WorkPrecisionSet, indices::Vector{Int})
isempty(indices) &&
@warn "No entries match the specified tags. Returning empty WorkPrecisionSet."
wps = wp_set.wps[indices]
setups = wp_set.setups isa AbstractVector ? wp_set.setups[indices] : wp_set.setups
names = wp_set.names isa AbstractVector ? wp_set.names[indices] : wp_set.names
return WorkPrecisionSet(
wps, length(wps), wp_set.abstols, wp_set.reltols, wp_set.prob,
setups, names, wp_set.error_estimate, wp_set.numruns
)
end

Base.length(wp::WorkPrecision) = wp.N
Base.size(wp::WorkPrecision) = length(wp)
Base.getindex(wp::WorkPrecision, i::Int) = wp.times[i]
Expand Down
32 changes: 31 additions & 1 deletion src/plotrecipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,38 @@ end
x = wp_set.error_estimate,
y = :times,
view = :benchmark,
color = nothing
color = nothing,
tags = nothing,
include_tags = nothing,
exclude_tags = nothing
)
# Apply tag-based filtering if specified
if tags !== nothing || include_tags !== nothing || exclude_tags !== nothing
filtered = wp_set
if tags !== nothing
filtered = filter_by_tags(filtered, tags...)
end
if include_tags !== nothing
extra = filter_by_tags(wp_set, include_tags...)
# Merge: filtered + extra (deduplicated)
seen = Set(wp.name for wp in filtered.wps)
extra_wps = [wp for wp in extra.wps if wp.name ∉ seen]
if !isempty(extra_wps)
all_wps = vcat(filtered.wps, extra_wps)
all_names = [wp.name for wp in all_wps]
filtered = WorkPrecisionSet(
all_wps, length(all_wps), filtered.abstols, filtered.reltols,
filtered.prob, filtered.setups, all_names, filtered.error_estimate,
filtered.numruns
)
end
end
if exclude_tags !== nothing
filtered = exclude_by_tags(filtered, exclude_tags...)
end
wp_set = filtered
end

if view == :benchmark
seriestype --> :path
linewidth --> 3
Expand Down
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ end
@time @testset "Error Reduction Function Tests" begin
include("error_reduction_tests.jl")
end
@time @testset "Tagging Tests" begin
include("tagging_tests.jl")
end
Loading
Loading