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
16 changes: 4 additions & 12 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,7 @@ updates:
# github-actions:
# patterns:
# - "*"
- package-ecosystem: "julia"
directories:
- "/"
- "/docs"
- "/test"
schedule:
interval: "weekly"
# To group all Julia dependency updates into a single PR, uncomment the following:
# groups:
# julia-dependencies:
# patterns:
# - "*"
# Julia dependency updates are handled by CompatHelper
# (.github/workflows/CompatHelper.yml) — the registry-aware Julia-ecosystem
# standard — so a "julia" package-ecosystem is intentionally NOT declared here
# to avoid duplicate PRs. Do not re-add one.
8 changes: 8 additions & 0 deletions .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
# Least-privilege: scope this workflow's GITHUB_TOKEN to exactly what
# CompatHelper needs (read the repo, open the compat PR) instead of the
# repo-wide `write` default. PR *creation* is additionally gated by the
# repo setting "Allow GitHub Actions to create and approve pull requests"
# (Settings > Actions > General > Workflow permissions), enabled 2026-06-09.
permissions:
contents: write
pull-requests: write
jobs:
CompatHelper:
runs-on: ubuntu-latest
Expand Down
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,49 @@ All notable changes to **DFMethods.jl** will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.3] — 2026-06-09

### Added

- **Full SciML common-solver keyword support in `solve` / `init`.**
`DFProjection` now honors the standard SciMLBase nonlinear-solve
keywords that map onto its callback-based stopping system (previously
only `abstol` and `maxiters` were honored; `reltol` and others were
silently ignored):
- `reltol` → `RelResidualTol` (`‖F(z_k)‖ ≤ reltol·‖F(x_0)‖`). New
`DFProjection` field `reltol::Float64 = 0.0` (`0` disables).
- `maxtime` → `MaxTime` (wall-clock seconds). New `DFProjection` field
`maxtime::Float64 = Inf` (`Inf` / `nothing` disables).
- `abstol` → `AbsResidualTol`, `maxiters` → `MaxIters` (unchanged).
Each defaults to the matching `DFProjection` field and is overridden
per-`solve`. All other standard keywords (`termination_condition`,
`internalnorm`, `alias_u0`, `show_trace`, `store_trace`, `trace_level`)
are accepted and absorbed without error, so
`solve(prob, ::DFProjection; any_standard_kwarg…)` never throws. The
default stopping rule is unchanged for code that doesn't set `reltol` /
`maxtime` (still `AnyOf(AbsResidualTol(abstol), MaxIters(maxiters))`).

### Changed

- **`solve` warns on non-convergence (SciML convention).** A non-`Success`
(early) exit now emits a warning unless `verbose = false` is passed.
Earlier releases never warned; solver results are unchanged — only
diagnostic output differs.
- **`SciMLBase` compatibility widened to allow 3.x** — `[compat]` bound
`SciMLBase = "2.53"` → `"2.53, 3"`. The package's SciMLBase surface is
limited to stable API (`NonlinearProblem`, `isinplace`, `ReturnCode`,
`NLStats`, `build_solution`, `__solve`) and is unaffected by the
2.x → 3.x major bump; the full test suite plus docs build pass against
SciMLBase 3.x across Julia 1.10 / 1.12 / pre on x64 + x86. The v2.x
range is retained, so this is a drop-in widening for existing v0.3.2
user code.

### Compatibility

- Julia ≥ 1.10 (unchanged).
- `SciMLBase` v2.53+ **or** v3.x (widened this cycle; previously v2.x only).
- `CommonSolve` v0.2.x, `LineSearch` v0.1.x (unchanged).

## [0.3.2] — 2026-05-25

### Added
Expand Down Expand Up @@ -360,6 +403,7 @@ for constrained nonlinear equations $F(x) = 0$ on a closed convex set $X$.
- `SciMLBase` v2.x
- `CommonSolve` v0.2.x

[0.3.3]: https://github.com/mmogib/DFMethods.jl/releases/tag/v0.3.3
[0.3.2]: https://github.com/mmogib/DFMethods.jl/releases/tag/v0.3.2
[0.3.1]: https://github.com/mmogib/DFMethods.jl/releases/tag/v0.3.1
[0.3.0]: https://github.com/mmogib/DFMethods.jl/releases/tag/v0.3.0
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ authors:
orcid: "https://orcid.org/0000-0002-1367-646X"
email: mshahrani@kfupm.edu.sa
affiliation: "King Fahd University of Petroleum and Minerals"
version: 0.3.2
date-released: "2026-05-25"
version: 0.3.3
date-released: "2026-06-09"
repository-code: "https://github.com/mmogib/DFMethods.jl"
url: "https://mmogib.github.io/DFMethods.jl/stable/"
license: MIT
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "DFMethods"
uuid = "5fd0b45f-fdf3-4567-a8b1-5e033765ff5d"
authors = ["Mohammed Alshahrani <mshahrani@kfupm.edu.sa>"]
version = "0.3.2"
version = "0.3.3"

[deps]
CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2"
Expand All @@ -13,7 +13,7 @@ SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
[compat]
CommonSolve = "0.2"
LineSearch = "0.1"
SciMLBase = "2.53"
SciMLBase = "2.53, 3"
julia = "1.10"

[extras]
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ If you use DFMethods.jl in research, please cite it via the Zenodo DOI:
@software{Alshahrani_DFMethods_2026,
author = {Alshahrani, Mohammed},
title = {{DFMethods.jl: A configurable framework for derivative-free projection methods for constrained nonlinear equations}},
month = may,
month = jun,
year = 2026,
publisher = {Zenodo},
version = {v0.3.2},
version = {v0.3.3},
doi = {10.5281/zenodo.20350220},
url = {https://doi.org/10.5281/zenodo.20350220}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Custom observers subtype [`AbstractCallback`](@ref) and add a method on
```@example quick
using SciMLBase # for ReturnCode

sol_short = solve(prob, DFProjection(); maxiters = 2)
sol_short = solve(prob, DFProjection(); maxiters = 2, verbose = false) # we expect (and inspect) a non-Success retcode
sol_short.retcode == ReturnCode.MaxIters
```

Expand Down
4 changes: 2 additions & 2 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ alg_capped = DFProjection(;
MaxIters(10_000),
MaxTime(0.05)), # 50 ms
)
wall = @elapsed sol = solve(prob, alg_capped)
wall = @elapsed sol = solve(prob, alg_capped; verbose = false) # MaxTime is the expected exit here
(retcode = sol.retcode, iters = sol.stats.nsteps,
wallclock_seconds = round(wall; digits = 3))
```
Expand Down Expand Up @@ -260,7 +260,7 @@ for (pname, n_p, F_p) in problems
x0_p = ones(n_p)
for (lname, ls) in line_searches
sol = solve(NonlinearProblem(F_p, x0_p),
DFProjection(; linesearch = ls))
DFProjection(; linesearch = ls); verbose = false) # some configs may not converge; we record it
push!(results, (
problem = pname,
linesearch = lname,
Expand Down
61 changes: 47 additions & 14 deletions src/algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

"""
DFProjection(; direction, linesearch, inertial, iterate_update,
abstol, maxiters, stopping, ζ, inner_maxiter, maxbt,
callbacks)
abstol, reltol, maxiters, maxtime, stopping, ζ,
inner_maxiter, maxbt, callbacks)

Concrete derivative-free projection algorithm with pluggable components.
The constraint set lives on the **problem**, not on the algorithm — see
Expand All @@ -31,21 +31,26 @@ The constraint set lives on the **problem**, not on the algorithm — see
- `linesearch::LineSearch.AbstractLineSearchAlgorithm` — line search. Default: `ResidualNormBacktrack()`.
- `inertial::AbstractInertialRule` — inertial rule. Default: `Inertial(0.25)`.
- `iterate_update::AbstractIterateUpdate` — post-line-search iterate update strategy. Default: `SolodovSvaiterProjection()`. Alternatives: `DirectUpdate()`, `HalpernUpdate(β)`.
- `abstol::Float64` — residual tolerance used to build the default stopping. Default: `1e-6`.
- `abstol::Float64` — absolute residual tolerance used to build the default stopping. Default: `1e-6`.
- `reltol::Float64` — relative residual tolerance (target `‖F(z_k)‖ ≤ reltol·‖F(x_0)‖`) used to build the default stopping. `0` disables it. Default: `0.0`.
- `maxiters::Int` — outer-iteration cap used to build the default stopping. Default: `2000`.
- `stopping::AbstractStoppingCriterion` — full stopping rule. If not supplied, built as `AnyOf(AbsResidualTol(abstol), MaxIters(maxiters))`.
- `maxtime::Float64` — wall-clock budget in seconds used to build the default stopping. `Inf` disables it. Default: `Inf`.
- `stopping::AbstractStoppingCriterion` — full stopping rule. If not supplied, built from the knobs above as `AnyOf(AbsResidualTol(abstol)[, RelResidualTol(reltol)], MaxIters(maxiters)[, MaxTime(maxtime)])` — the bracketed criteria appear only when `reltol > 0` / `maxtime` is finite.
- `ζ::Float64` — approximate-projection tolerance factor used by `SolodovSvaiterProjection`. Default: `0.5`.
- `inner_maxiter::Int` — max inner-projection iterations (Dykstra). Default: `500`.
- `maxbt::Int` — max line-search backtracks per iteration. Default: `50`.
- `callbacks::Vector{<:AbstractCallback}` — observer callbacks fired during the solve. Default: empty.

# Stopping criteria
`abstol` and `maxiters` are convenience knobs that build the default
stopping rule. For composite or domain-specific criteria, pass
`stopping = AnyOf(RelResidualTol(...), StepNormTol(...), MaxTime(...), …)`;
the `abstol`/`maxiters` fields are still stored (for introspection and
SciMLBase kwarg overrides) but `step!` ignores them in favour of the
supplied `stopping`.
`abstol`, `reltol`, `maxiters`, and `maxtime` are convenience knobs that
build the default stopping rule (mirroring the SciML common-solver
options of the same name). For composite or domain-specific criteria,
pass `stopping = AnyOf(StepNormTol(...), DirectionNormTol(...), …)`; the
knob fields are still stored (for introspection and SciMLBase kwarg
overrides) but `step!` ignores them in favour of the supplied `stopping`.
When a `stopping` rule is supplied explicitly, `solve`-time tolerance
keywords (`abstol`/`reltol`/`maxiters`/`maxtime`) cannot be re-applied and
are reported via a `verbose` warning.
"""
struct DFProjection{Dir<:AbstractSearchDirection,
LS<:LineSearch.AbstractLineSearchAlgorithm,
Expand All @@ -57,8 +62,11 @@ struct DFProjection{Dir<:AbstractSearchDirection,
linesearch::LS
inertial::In
abstol::Float64
reltol::Float64
maxiters::Int
maxtime::Float64
stopping::Stop
auto_stopping::Bool
ζ::Float64
inner_maxiter::Int
maxbt::Int
Expand All @@ -71,23 +79,48 @@ function DFProjection(;
linesearch = ResidualNormBacktrack(),
inertial = Inertial(0.25),
abstol::Real = 1e-6,
reltol::Real = 0.0,
maxiters::Int = 2000,
maxtime::Real = Inf,
stopping::Union{Nothing, AbstractStoppingCriterion} = nothing,
ζ::Real = 0.5,
inner_maxiter::Int = 500,
maxbt::Int = 50,
iterate_update = SolodovSvaiterProjection(),
callbacks::Vector{<:AbstractCallback} = AbstractCallback[],
)
stop = stopping === nothing ?
AnyOf(AbsResidualTol(Float64(abstol)), MaxIters(maxiters)) :
stopping
auto = stopping === nothing
stop = auto ? _default_stopping(abstol, reltol, maxiters, maxtime) : stopping
return DFProjection(direction, linesearch, inertial,
Float64(abstol), maxiters, stop,
Float64(abstol), Float64(reltol), maxiters, Float64(maxtime),
stop, auto,
Float64(ζ), inner_maxiter, maxbt,
iterate_update, callbacks)
end

# Build the default stopping rule from the convenience tolerance/budget
# knobs. `RelResidualTol` is added only when `reltol > 0` and `MaxTime`
# only when `maxtime` is finite, so the common cases `DFProjection()` and
# `DFProjection(; abstol, maxiters)` produce exactly
# `AnyOf(AbsResidualTol(abstol), MaxIters(maxiters))` — byte-identical to
# earlier releases (no behavior change for code that doesn't set reltol /
# maxtime).
function _default_stopping(abstol::Real, reltol::Real, maxiters::Integer, maxtime::Real)
abs_c = AbsResidualTol(Float64(abstol))
iter_c = MaxIters(Int(maxiters))
use_rel = reltol > 0
use_time = isfinite(maxtime)
if use_rel && use_time
return AnyOf(abs_c, RelResidualTol(Float64(reltol)), iter_c, MaxTime(Float64(maxtime)))
elseif use_rel
return AnyOf(abs_c, RelResidualTol(Float64(reltol)), iter_c)
elseif use_time
return AnyOf(abs_c, iter_c, MaxTime(Float64(maxtime)))
else
return AnyOf(abs_c, iter_c)
end
end

# ============================================================================
# DFProjectionCache: mutable state for one solve
# ============================================================================
Expand Down
Loading
Loading