diff --git a/Project.toml b/Project.toml index d5777fc11..acefa9890 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "NonlinearSolve" uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" authors = ["SciML"] -version = "4.12.0" +version = "4.13.0" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" @@ -85,14 +85,14 @@ LeastSquaresOptim = "0.8.5" LineSearch = "0.1.4" LineSearches = "7.3" LinearAlgebra = "1.10" -LinearSolve = "2.36.1, 3" +LinearSolve = "3.46" MINPACK = "1.2" MPI = "0.20.22" NLSolvers = "0.5" NLsolve = "4.5" NaNMath = "1" NonlinearProblemLibrary = "0.1.2" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" NonlinearSolveFirstOrder = "1.2" NonlinearSolveQuasiNewton = "1.8" NonlinearSolveSpectralMethods = "1.1" @@ -106,6 +106,7 @@ Random = "1.10" ReTestItems = "1.24" Reexport = "1.2.2" ReverseDiff = "1.15" +SciMLLogging = "1.3" SIAMFANLEquations = "1.0.1" SciMLBase = "2.116" SimpleNonlinearSolve = "2.1" @@ -145,6 +146,7 @@ PolyesterForwardDiff = "98d1487c-24ca-40b6-b7ab-df2af84e126b" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReTestItems = "817f1d60-ba6b-4fd5-9520-3cf149f6a823" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" +SciMLLogging = "a6db7da4-7206-11f0-1eab-35f2a5dbe1d1" SIAMFANLEquations = "084e46ad-d928-497d-ad5e-07fa361a48c4" SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" SparseMatrixColorings = "0a514795-09f3-496d-8182-132a7b665d35" @@ -157,4 +159,4 @@ Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [targets] -test = ["Aqua", "BandedMatrices", "BenchmarkTools", "ExplicitImports", "FastLevenbergMarquardt", "FixedPointAcceleration", "Hwloc", "InteractiveUtils", "LeastSquaresOptim", "LineSearches", "MINPACK", "NLSolvers", "NLsolve", "NaNMath", "NonlinearProblemLibrary", "OrdinaryDiffEqTsit5", "PETSc", "Pkg", "PolyesterForwardDiff", "Random", "ReTestItems", "SIAMFANLEquations", "SparseConnectivityTracer", "SparseMatrixColorings", "SpeedMapping", "StableRNGs", "StaticArrays", "Sundials", "Test", "Zygote", "ReverseDiff", "Tracker"] +test = ["Aqua", "BandedMatrices", "BenchmarkTools", "ExplicitImports", "FastLevenbergMarquardt", "FixedPointAcceleration", "Hwloc", "InteractiveUtils", "LeastSquaresOptim", "LineSearches", "MINPACK", "NLSolvers", "NLsolve", "NaNMath", "NonlinearProblemLibrary", "OrdinaryDiffEqTsit5", "PETSc", "Pkg", "PolyesterForwardDiff", "Random", "ReTestItems", "SIAMFANLEquations", "SparseConnectivityTracer", "SparseMatrixColorings", "SpeedMapping", "StableRNGs", "StaticArrays", "Sundials", "Test", "Zygote", "ReverseDiff", "Tracker", "SciMLLogging"] diff --git a/docs/src/basics/solve.md b/docs/src/basics/solve.md index 3bc7df056..15e967d52 100644 --- a/docs/src/basics/solve.md +++ b/docs/src/basics/solve.md @@ -36,3 +36,32 @@ These are exclusively available for native `NonlinearSolve.jl` solvers. level of detail of the trace. (Defaults to `TraceMinimal()`) - `store_trace`: Must be `Val(true)` or `Val(false)`. This controls whether the trace is stored in the solution object. (Defaults to `Val(false)`) + +## Verbosity Controls + +NonlinearSolve.jl provides fine-grained control over diagnostic messages, warnings, and errors +through the `verbose` keyword argument. The verbosity system allows you to control what +information is displayed during the solve process. See [SciMLLogging.jl](https://docs.sciml.ai/SciMLLogging/dev/) for more details. + +```@docs +NonlinearVerbosity +``` + +### Quick Start + +```julia +# Use a preset +solve(prob, alg; verbose = NonlinearVerbosity(SciMLLogging.Standard())) + +# Silence all messages +solve(prob, alg; verbose = NonlinearVerbosity(SciMLLogging.None())) + +# Maximum verbosity +solve(prob, alg; verbose = NonlinearVerbosity(SciMLLogging.All())) + +# Custom configuration +solve(prob, alg; verbose = NonlinearVerbosity( + alias_u0_immutable = SciMLLogging.WarnLevel(), + threshold_state = SciMLLogging.InfoLevel() +)) +``` \ No newline at end of file diff --git a/lib/BracketingNonlinearSolve/Project.toml b/lib/BracketingNonlinearSolve/Project.toml index bfc4e05ad..850fa0201 100644 --- a/lib/BracketingNonlinearSolve/Project.toml +++ b/lib/BracketingNonlinearSolve/Project.toml @@ -30,7 +30,7 @@ ConcreteStructs = "0.2.3" ExplicitImports = "1.10.1" ForwardDiff = "0.10.36, 1" InteractiveUtils = "<0.0.1, 1" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" PrecompileTools = "1.2" Reexport = "1.2.2" SciMLBase = "2.116" diff --git a/lib/BracketingNonlinearSolve/src/BracketingNonlinearSolve.jl b/lib/BracketingNonlinearSolve/src/BracketingNonlinearSolve.jl index b7c16fac3..fd9398832 100644 --- a/lib/BracketingNonlinearSolve/src/BracketingNonlinearSolve.jl +++ b/lib/BracketingNonlinearSolve/src/BracketingNonlinearSolve.jl @@ -5,7 +5,7 @@ using PrecompileTools: @compile_workload, @setup_workload using Reexport: @reexport using CommonSolve: CommonSolve, solve -using NonlinearSolveBase: NonlinearSolveBase, AbstractNonlinearSolveAlgorithm +using NonlinearSolveBase: NonlinearSolveBase, AbstractNonlinearSolveAlgorithm, NonlinearVerbosity, @SciMLMessage, AbstractVerbosityPreset using SciMLBase: SciMLBase, IntervalNonlinearProblem, ReturnCode abstract type AbstractBracketingAlgorithm <: AbstractNonlinearSolveAlgorithm end diff --git a/lib/BracketingNonlinearSolve/src/bisection.jl b/lib/BracketingNonlinearSolve/src/bisection.jl index 5af8f275b..153772d27 100644 --- a/lib/BracketingNonlinearSolve/src/bisection.jl +++ b/lib/BracketingNonlinearSolve/src/bisection.jl @@ -21,7 +21,7 @@ end function SciMLBase.__solve( prob::IntervalNonlinearProblem, alg::Bisection, args...; - maxiters = 1000, abstol = nothing, verbose::Bool = true, kwargs... + maxiters = 1000, abstol = nothing, verbose::NonlinearVerbosity = NonlinearVerbosity(), kwargs... ) @assert !SciMLBase.isinplace(prob) "`Bisection` only supports out-of-place problems." @@ -45,9 +45,9 @@ function SciMLBase.__solve( end if sign(fl) == sign(fr) - verbose && - @warn "The interval is not an enclosing interval, opposite signs at the \ - boundaries are required." + @SciMLMessage("The interval is not an enclosing interval, opposite signs at the \ + boundaries are required.", + verbose, :non_enclosing_interval) return SciMLBase.build_solution( prob, alg, left, fl; retcode = ReturnCode.InitialFailure, left, right ) diff --git a/lib/BracketingNonlinearSolve/src/brent.jl b/lib/BracketingNonlinearSolve/src/brent.jl index a1dbb9641..cce3428ad 100644 --- a/lib/BracketingNonlinearSolve/src/brent.jl +++ b/lib/BracketingNonlinearSolve/src/brent.jl @@ -7,10 +7,20 @@ struct Brent <: AbstractBracketingAlgorithm end function SciMLBase.__solve( prob::IntervalNonlinearProblem, alg::Brent, args...; - maxiters = 1000, abstol = nothing, verbose::Bool = true, kwargs... + maxiters = 1000, abstol = nothing, verbose = NonlinearVerbosity(), kwargs... ) @assert !SciMLBase.isinplace(prob) "`Brent` only supports out-of-place problems." + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + f = Base.Fix2(prob.f, prob.p) left, right = prob.tspan fl, fr = f(left), f(right) @@ -33,9 +43,9 @@ function SciMLBase.__solve( end if sign(fl) == sign(fr) - verbose && - @warn "The interval is not an enclosing interval, opposite signs at the \ - boundaries are required." + @SciMLMessage("The interval is not an enclosing interval, opposite signs at the \ + boundaries are required.", + verbose, :non_enclosing_interval) return SciMLBase.build_solution( prob, alg, left, fl; retcode = ReturnCode.InitialFailure, left, right ) diff --git a/lib/BracketingNonlinearSolve/src/falsi.jl b/lib/BracketingNonlinearSolve/src/falsi.jl index 557fc18fe..edd57cbc6 100644 --- a/lib/BracketingNonlinearSolve/src/falsi.jl +++ b/lib/BracketingNonlinearSolve/src/falsi.jl @@ -7,10 +7,20 @@ struct Falsi <: AbstractBracketingAlgorithm end function SciMLBase.__solve( prob::IntervalNonlinearProblem, alg::Falsi, args...; - maxiters = 1000, abstol = nothing, verbose::Bool = true, kwargs... + maxiters = 1000, abstol = nothing, verbose = NonlinearVerbosity(), kwargs... ) @assert !SciMLBase.isinplace(prob) "`False` only supports out-of-place problems." + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + f = Base.Fix2(prob.f, prob.p) l, r = prob.tspan # don't reuse these variables left, right = prob.tspan @@ -32,9 +42,9 @@ function SciMLBase.__solve( end if sign(fl) == sign(fr) - verbose && - @warn "The interval is not an enclosing interval, opposite signs at the \ - boundaries are required." + @SciMLMessage("The interval is not an enclosing interval, opposite signs at the \ + boundaries are required.", + verbose, :non_enclosing_interval) return SciMLBase.build_solution( prob, alg, left, fl; retcode = ReturnCode.InitialFailure, left, right ) diff --git a/lib/BracketingNonlinearSolve/src/itp.jl b/lib/BracketingNonlinearSolve/src/itp.jl index c733dc25f..2a4af0362 100644 --- a/lib/BracketingNonlinearSolve/src/itp.jl +++ b/lib/BracketingNonlinearSolve/src/itp.jl @@ -58,7 +58,7 @@ end function SciMLBase.__solve( prob::IntervalNonlinearProblem, alg::ITP, args...; - maxiters = 1000, abstol = nothing, verbose::Bool = true, kwargs... + maxiters = 1000, abstol = nothing, verbose::NonlinearVerbosity = NonlinearVerbosity(), kwargs... ) @assert !SciMLBase.isinplace(prob) "`ITP` only supports out-of-place problems." @@ -83,9 +83,9 @@ function SciMLBase.__solve( end if sign(fl) == sign(fr) - verbose && - @warn "The interval is not an enclosing interval, opposite signs at the \ - boundaries are required." + @SciMLMessage("The interval is not an enclosing interval, opposite signs at the \ + boundaries are required.", + verbose, :non_enclosing_interval) return SciMLBase.build_solution( prob, alg, left, fl; retcode = ReturnCode.InitialFailure, left, right ) diff --git a/lib/BracketingNonlinearSolve/src/ridder.jl b/lib/BracketingNonlinearSolve/src/ridder.jl index a647098ad..e0686be95 100644 --- a/lib/BracketingNonlinearSolve/src/ridder.jl +++ b/lib/BracketingNonlinearSolve/src/ridder.jl @@ -7,7 +7,7 @@ struct Ridder <: AbstractBracketingAlgorithm end function SciMLBase.__solve( prob::IntervalNonlinearProblem, alg::Ridder, args...; - maxiters = 1000, abstol = nothing, verbose::Bool = true, kwargs... + maxiters = 1000, abstol = nothing, verbose::NonlinearVerbosity = NonlinearVerbosity(), kwargs... ) @assert !SciMLBase.isinplace(prob) "`Ridder` only supports out-of-place problems." @@ -32,9 +32,9 @@ function SciMLBase.__solve( end if sign(fl) == sign(fr) - verbose && - @warn "The interval is not an enclosing interval, opposite signs at the \ - boundaries are required." + @SciMLMessage("The interval is not an enclosing interval, opposite signs at the \ + boundaries are required.", + verbose, :non_enclosing_interval) return SciMLBase.build_solution( prob, alg, left, fl; retcode = ReturnCode.InitialFailure, left, right ) diff --git a/lib/NonlinearSolveBase/Project.toml b/lib/NonlinearSolveBase/Project.toml index d58050004..41e51ddab 100644 --- a/lib/NonlinearSolveBase/Project.toml +++ b/lib/NonlinearSolveBase/Project.toml @@ -1,7 +1,7 @@ name = "NonlinearSolveBase" uuid = "be0214bd-f91f-a760-ac4e-3421ce2b2da0" authors = ["Avik Pal and contributors"] -version = "2.0" +version = "2.1" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" @@ -21,6 +21,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLJacobianOperators = "19f34311-ddf3-4b8b-af20-060888a46c0e" +SciMLLogging = "a6db7da4-7206-11f0-1eab-35f2a5dbe1d1" SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961" SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" @@ -54,7 +55,6 @@ NonlinearSolveBaseSparseArraysExt = "SparseArrays" NonlinearSolveBaseSparseMatrixColoringsExt = "SparseMatrixColorings" NonlinearSolveBaseTrackerExt = "Tracker" - [compat] ADTypes = "1.9" Adapt = "4.1.0" @@ -66,15 +66,15 @@ CommonSolve = "0.2.4" Compat = "4.15" ConcreteStructs = "0.2.3" DifferentiationInterface = "0.6.16, 0.7" -EnzymeCore = "0.8" Enzyme = "0.13.12" +EnzymeCore = "0.8" ExplicitImports = "1.10.1" FastClosures = "0.3" ForwardDiff = "0.10.36, 1" InteractiveUtils = "<0.0.1, 1" LineSearch = "0.1.4" LinearAlgebra = "1.10" -LinearSolve = "3.15" +LinearSolve = "3.46" Markdown = "1.10" MaybeInplace = "0.1.4" Mooncake = "0.4" @@ -84,6 +84,7 @@ RecursiveArrayTools = "3" ReverseDiff = "1.15" SciMLBase = "2.116" SciMLJacobianOperators = "0.1.1" +SciMLLogging = "1.3.1" SciMLOperators = "1.7" SciMLStructures = "1.5" Setfield = "1.1.2" @@ -92,8 +93,8 @@ SparseMatrixColorings = "0.4.5" StaticArraysCore = "1.4" SymbolicIndexingInterface = "0.3.43" Test = "1.10" -Tracker = "0.2.35" TimerOutputs = "0.5.23" +Tracker = "0.2.35" julia = "1.10" [extras] diff --git a/lib/NonlinearSolveBase/ext/NonlinearSolveBaseLinearSolveExt.jl b/lib/NonlinearSolveBase/ext/NonlinearSolveBaseLinearSolveExt.jl index c6e88e3ab..50512300b 100644 --- a/lib/NonlinearSolveBase/ext/NonlinearSolveBaseLinearSolveExt.jl +++ b/lib/NonlinearSolveBase/ext/NonlinearSolveBaseLinearSolveExt.jl @@ -5,21 +5,22 @@ using ArrayInterface: ArrayInterface using CommonSolve: CommonSolve, init, solve! using LinearSolve: LinearSolve, QRFactorization, SciMLLinearSolveAlgorithm using SciMLBase: ReturnCode, LinearProblem, LinearAliasSpecifier +using SciMLLogging: @SciMLMessage using LinearAlgebra: ColumnNorm -using NonlinearSolveBase: NonlinearSolveBase, LinearSolveJLCache, LinearSolveResult, Utils +using NonlinearSolveBase: NonlinearSolveBase, LinearSolveJLCache, LinearSolveResult, Utils, NonlinearVerbosity function (cache::LinearSolveJLCache)(; A = nothing, b = nothing, linu = nothing, - reuse_A_if_factorization = false, verbose = true, kwargs... + reuse_A_if_factorization = false, kwargs... ) cache.stats.nsolve += 1 update_A!(cache, A, reuse_A_if_factorization) b !== nothing && setproperty!(cache.lincache, :b, b) linu !== nothing && NonlinearSolveBase.set_lincache_u!(cache, linu) - + linres = solve!(cache.lincache) if linres.retcode === ReturnCode.Failure return LinearSolveResult(; linres.u, success = false) diff --git a/lib/NonlinearSolveBase/src/NonlinearSolveBase.jl b/lib/NonlinearSolveBase/src/NonlinearSolveBase.jl index 4baef6c55..43d7e8439 100644 --- a/lib/NonlinearSolveBase/src/NonlinearSolveBase.jl +++ b/lib/NonlinearSolveBase/src/NonlinearSolveBase.jl @@ -28,6 +28,9 @@ import SciMLBase: solve, init, __init, __solve, wrap_sol, get_root_indp, isinpla using SciMLJacobianOperators: JacobianOperator, StatefulJacobianOperator using SciMLOperators: AbstractSciMLOperator, IdentityOperator +using SciMLLogging: @SciMLMessage, AbstractVerbositySpecifier, AbstractVerbosityPreset, AbstractMessageLevel, + None, Minimal, Standard, Detailed, All, Silent, InfoLevel, WarnLevel + using SymbolicIndexingInterface: SymbolicIndexingInterface import SciMLStructures using Setfield: @set! @@ -41,6 +44,7 @@ const SII = SymbolicIndexingInterface include("public.jl") include("utils.jl") +include("verbosity.jl") include("abstract_types.jl") include("common_defaults.jl") @@ -54,6 +58,7 @@ include("tracing.jl") include("wrappers.jl") include("polyalg.jl") + include("descent/common.jl") include("descent/newton.jl") include("descent/steepest.jl") @@ -92,6 +97,8 @@ export DescentResult, SteepestDescent, NewtonDescent, DampedNewtonDescent, Dogle export NonlinearSolvePolyAlgorithm +export NonlinearVerbosity + export pickchunksize end diff --git a/lib/NonlinearSolveBase/src/abstract_types.jl b/lib/NonlinearSolveBase/src/abstract_types.jl index 6b78940f5..a6bfdef7d 100644 --- a/lib/NonlinearSolveBase/src/abstract_types.jl +++ b/lib/NonlinearSolveBase/src/abstract_types.jl @@ -280,6 +280,7 @@ the cache: - `maxtime`: the maximum time limit for the solver. (Optional) - `timer`: the timer for the solver. (Optional) - `total_time`: the total time taken by the solver. (Optional) + - `verbose`: a verbosity object that contains options determining what log messages are emitted. """ abstract type AbstractNonlinearSolveCache <: AbstractNonlinearSolveBaseAPI end diff --git a/lib/NonlinearSolveBase/src/autodiff.jl b/lib/NonlinearSolveBase/src/autodiff.jl index 4a9f2edbc..857e64509 100644 --- a/lib/NonlinearSolveBase/src/autodiff.jl +++ b/lib/NonlinearSolveBase/src/autodiff.jl @@ -24,10 +24,14 @@ function select_forward_mode_autodiff( !(ADTypes.mode(ad) isa ADTypes.ForwardOrReverseMode) && !is_finite_differences_backend(ad) @warn lazy"The chosen AD backend $(ad) is not a forward mode AD. Use with caution." + + @warn "The chosen AD backend $(ad) is not a forward mode AD. Use with caution." + end if incompatible_backend_and_problem(prob, ad) adₙ = select_forward_mode_autodiff(prob, nothing; warn_check_mode) - @warn lazy"The chosen AD backend `$(ad)` does not support the chosen problem. This \ + + @warn "The chosen AD backend `$(ad)` does not support the chosen problem. This \ could be because the backend package for the chosen AD isn't loaded. After \ running autodiff selection detected `$(adₙ)` as a potential forward mode \ backend." @@ -50,14 +54,14 @@ function select_reverse_mode_autodiff( if warn_check_mode && !(ADTypes.mode(ad) isa ADTypes.ReverseMode) && !(ADTypes.mode(ad) isa ADTypes.ForwardOrReverseMode) && !is_finite_differences_backend(ad) - @warn lazy"The chosen AD backend $(ad) is not a reverse mode AD. Use with caution." + @warn "The chosen AD backend $(ad) is not a forward mode AD. Use with caution." end if incompatible_backend_and_problem(prob, ad) adₙ = select_reverse_mode_autodiff(prob, nothing; warn_check_mode) - @warn lazy"The chosen AD backend `$(ad)` does not support the chosen problem. This \ - could be because the backend package for the chosen AD isn't loaded. After \ - running autodiff selection detected `$(adₙ)` as a potential reverse mode \ - backend." + @warn "The chosen AD backend `$(ad)` does not support the chosen problem. This \ + could be because the backend package for the chosen AD isn't loaded. After \ + running autodiff selection detected `$(adₙ)` as a potential forward mode \ + backend." return adₙ end return ad @@ -75,10 +79,10 @@ end function select_jacobian_autodiff(prob::AbstractNonlinearProblem, ad::AbstractADType) if incompatible_backend_and_problem(prob, ad) adₙ = select_jacobian_autodiff(prob, nothing) - @warn lazy"The chosen AD backend `$(ad)` does not support the chosen problem. This \ - could be because the backend package for the chosen AD isn't loaded. After \ - running autodiff selection detected `$(adₙ)` as a potential jacobian \ - backend." + @warn "The chosen AD backend `$(ad)` does not support the chosen problem. This \ + could be because the backend package for the chosen AD isn't loaded. After \ + running autodiff selection detected `$(adₙ)` as a potential forward mode \ + backend." return adₙ end return ad diff --git a/lib/NonlinearSolveBase/src/descent/newton.jl b/lib/NonlinearSolveBase/src/descent/newton.jl index a0aaa91d3..8a968d58f 100644 --- a/lib/NonlinearSolveBase/src/descent/newton.jl +++ b/lib/NonlinearSolveBase/src/descent/newton.jl @@ -35,9 +35,14 @@ function InternalAPI.init( δus = Utils.unwrap_val(shared) ≤ 1 ? nothing : map(2:Utils.unwrap_val(shared)) do i @bb δu_ = similar(u) end + if Utils.unwrap_val(pre_inverted) lincache = nothing else + if haskey(kwargs, :verbose) + linsolve_kwargs = merge((verbose = kwargs[:verbose].linear_verbosity,), linsolve_kwargs) + end + lincache = construct_linear_solver( alg, alg.linsolve, J, Utils.safe_vec(fu), Utils.safe_vec(u); stats, abstol, reltol, linsolve_kwargs... @@ -61,7 +66,6 @@ function InternalAPI.init( δus = Utils.unwrap_val(shared) ≤ 1 ? nothing : map(2:N) do i @bb δu_ = similar(u) end - normal_form = needs_square_A(alg.linsolve, u) if normal_form JᵀJ = transpose(J) * J @@ -72,6 +76,11 @@ function InternalAPI.init( A, b = J, Utils.safe_vec(fu) end + if haskey(kwargs, :verbose) + linsolve_kwargs = merge( + (verbose = kwargs[:verbose].linear_verbosity,), linsolve_kwargs) + end + lincache = construct_linear_solver( alg, alg.linsolve, A, b, Utils.safe_vec(u); stats, abstol, reltol, linsolve_kwargs... @@ -88,7 +97,6 @@ function InternalAPI.solve!( ) δu = SciMLBase.get_du(cache, idx) skip_solve && return DescentResult(; δu) - if preinverted_jacobian(cache) && !normal_form(cache) @assert J!==nothing "`J` must be provided when `preinverted_jacobian = Val(true)`." @bb δu = J × vec(fu) diff --git a/lib/NonlinearSolveBase/src/descent/steepest.jl b/lib/NonlinearSolveBase/src/descent/steepest.jl index 247490957..8ca7d35f6 100644 --- a/lib/NonlinearSolveBase/src/descent/steepest.jl +++ b/lib/NonlinearSolveBase/src/descent/steepest.jl @@ -37,6 +37,12 @@ function InternalAPI.init( @bb δu_ = similar(u) end if Utils.unwrap_val(pre_inverted) + + if haskey(kwargs, :verbose) + linsolve_kwargs = merge( + (verbose = kwargs[:verbose].linear_verbosity,), linsolve_kwargs) + end + lincache = construct_linear_solver( alg, alg.linsolve, transpose(J), Utils.safe_vec(fu), Utils.safe_vec(u); stats, abstol, reltol, linsolve_kwargs... diff --git a/lib/NonlinearSolveBase/src/jacobian.jl b/lib/NonlinearSolveBase/src/jacobian.jl index acc727432..e5889036b 100644 --- a/lib/NonlinearSolveBase/src/jacobian.jl +++ b/lib/NonlinearSolveBase/src/jacobian.jl @@ -234,7 +234,7 @@ function construct_concrete_adtype(f::NonlinearFunction, ad::AbstractADType) else if sparse_or_structured_prototype(f.jac_prototype) if !(sparsity_detector isa NoSparsityDetector) - @warn lazy"`jac_prototype` is a sparse matrix but sparsity = $(f.sparsity) \ + @warn "`jac_prototype` is a sparse matrix but sparsity = $(f.sparsity) \ has also been specified. Ignoring sparsity field and using \ `jac_prototype` sparsity." end diff --git a/lib/NonlinearSolveBase/src/linear_solve.jl b/lib/NonlinearSolveBase/src/linear_solve.jl index 667564255..cf4b14415 100644 --- a/lib/NonlinearSolveBase/src/linear_solve.jl +++ b/lib/NonlinearSolveBase/src/linear_solve.jl @@ -70,11 +70,10 @@ function construct_linear_solver(alg, linsolve, A, b, u; stats, kwargs...) u_fixed = fix_incompatible_linsolve_arguments(A, b, u) @bb u_cache = copy(u_fixed) - linprob = LinearProblem(A, b; u0 = u_cache, kwargs...) - + linprob = LinearProblem(A, b; u0 = u_cache) # unlias here, we will later use these as caches lincache = init( - linprob, linsolve; alias = LinearAliasSpecifier(alias_A = false, alias_b = false)) + linprob, linsolve; alias = LinearAliasSpecifier(alias_A = false, alias_b = false), kwargs...) return LinearSolveJLCache(lincache, linsolve, stats) end diff --git a/lib/NonlinearSolveBase/src/polyalg.jl b/lib/NonlinearSolveBase/src/polyalg.jl index 90ca2ca2e..76edb3033 100644 --- a/lib/NonlinearSolveBase/src/polyalg.jl +++ b/lib/NonlinearSolveBase/src/polyalg.jl @@ -61,6 +61,8 @@ end alias_u0::Bool initializealg + + verbose end function update_initial_values!(cache::NonlinearSolvePolyAlgorithmCache, u0, p) @@ -117,15 +119,25 @@ end function SciMLBase.__init( prob::AbstractNonlinearProblem, alg::NonlinearSolvePolyAlgorithm, args...; stats = NLStats(0, 0, 0, 0, 0), maxtime = nothing, maxiters = 1000, - internalnorm::IN = L2_NORM, alias_u0 = false, verbose = true, + internalnorm::IN = L2_NORM, alias_u0 = false, verbose = NonlinearVerbosity(), initializealg = NonlinearSolveDefaultInit(), kwargs... ) where {IN} if alias_u0 && !ArrayInterface.ismutable(prob.u0) - verbose && @warn "`alias_u0` has been set to `true`, but `u0` is \ - immutable (checked using `ArrayInterface.ismutable`)." + @SciMLMessage("`alias_u0` has been set to `true`, but `u0` is + immutable (checked using `ArrayInterface.ismutable``).", verbose, :alias_u0_immutable) alias_u0 = false # If immutable don't care about aliasing end + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + u0 = prob.u0 u0_aliased = alias_u0 ? copy(u0) : u0 alias_u0 && (prob = SciMLBase.remake(prob; u0 = u0_aliased)) @@ -141,7 +153,7 @@ function SciMLBase.__init( end, alg, -1, alg.start_index, 0, stats, 0.0, maxtime, ReturnCode.Default, false, maxiters, internalnorm, - u0, u0_aliased, alias_u0, initializealg + u0, u0_aliased, alias_u0, initializealg, verbose ) run_initialization!(cache) return cache diff --git a/lib/NonlinearSolveBase/src/solve.jl b/lib/NonlinearSolveBase/src/solve.jl index 0fe813dbd..7632171f8 100644 --- a/lib/NonlinearSolveBase/src/solve.jl +++ b/lib/NonlinearSolveBase/src/solve.jl @@ -45,11 +45,23 @@ problems. https://docs.sciml.ai/SciMLSensitivity/stable/ """ function solve(prob::AbstractNonlinearProblem, args...; sensealg = nothing, - u0 = nothing, p = nothing, wrap = Val(true), kwargs...) + u0 = nothing, p = nothing, wrap = Val(true), verbose = NonlinearVerbosity(), kwargs...) if sensealg === nothing && haskey(prob.kwargs, :sensealg) sensealg = prob.kwargs[:sensealg] end + if verbose isa Bool + # @warn "Using `true` or `false` for `verbose` is being deprecated. Please use a `NonlinearVerbosity` type to specify verbosity settings. + # For details see the verbosity section of the common solver options documentation page." + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + if haskey(prob.kwargs, :alias_u0) @warn "The `alias_u0` keyword argument is deprecated. Please use a NonlinearAliasSpecifier, e.g. `alias = NonlinearAliasSpecifier(alias_u0 = true)`." alias_spec = NonlinearAliasSpecifier(alias_u0 = prob.kwargs[:alias_u0]) @@ -85,6 +97,7 @@ function solve(prob::AbstractNonlinearProblem, args...; sensealg = nothing, args...; alias_u0 = alias_u0, originator = SciMLBase.ChainRulesOriginator(), + verbose, kwargs...)) else solve_up(prob, @@ -94,6 +107,7 @@ function solve(prob::AbstractNonlinearProblem, args...; sensealg = nothing, args...; alias_u0 = alias_u0, originator = SciMLBase.ChainRulesOriginator(), + verbose, kwargs...) end end @@ -165,15 +179,27 @@ end function init( prob::AbstractNonlinearProblem, args...; sensealg = nothing, - u0 = nothing, p = nothing, kwargs...) + u0 = nothing, p = nothing, verbose = NonlinearVerbosity(), kwargs...) if sensealg === nothing && has_kwargs(prob) && haskey(prob.kwargs, :sensealg) sensealg = prob.kwargs[:sensealg] end + if verbose isa Bool + # @warn "Using `true` or `false` for `verbose` is being deprecated. Please use a `NonlinearVerbosity` type to specify verbosity settings. + # For details see the verbosity section of the common solver options documentation page." + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + u0 = u0 !== nothing ? u0 : prob.u0 p = p !== nothing ? p : prob.p - init_up(prob, sensealg, u0, p, args...; kwargs...) + init_up(prob, sensealg, u0, p, args...; verbose, kwargs...) end function init_up(prob::AbstractNonlinearProblem, @@ -221,11 +247,11 @@ function init_call(_prob, args...; merge_callbacks=true, kwargshandle=nothing, end function SciMLBase.__solve( - prob::AbstractNonlinearProblem, alg::AbstractNonlinearSolveAlgorithm, args...; - kwargs... -) + prob::AbstractNonlinearProblem, alg::AbstractNonlinearSolveAlgorithm, args...; kwargs...) cache = SciMLBase.__init(prob, alg, args...; kwargs...) - return CommonSolve.solve!(cache) + sol = CommonSolve.solve!(cache) + + return sol end function CommonSolve.solve!(cache::AbstractNonlinearSolveCache) @@ -375,17 +401,28 @@ end @generated function __generated_polysolve( prob::AbstractNonlinearProblem, alg::NonlinearSolvePolyAlgorithm{Val{N}}, args...; - stats = NLStats(0, 0, 0, 0, 0), alias_u0 = false, verbose = true, + stats = NLStats(0, 0, 0, 0, 0), alias_u0 = false, verbose = NonlinearVerbosity(), initializealg = NonlinearSolveDefaultInit(), kwargs... ) where {N} + + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + sol_syms = [gensym("sol") for _ in 1:N] prob_syms = [gensym("prob") for _ in 1:N] u_result_syms = [gensym("u_result") for _ in 1:N] calls = [quote current = alg.start_index if alias_u0 && !ArrayInterface.ismutable(prob.u0) - verbose && @warn "`alias_u0` has been set to `true`, but `u0` is \ - immutable (checked using `ArrayInterface.ismutable`)." + @SciMLMessage("`alias_u0` has been set to `true`, but `u0` is + immutable (checked using `ArrayInterface.ismutable``).", verbose, :alias_u0_immutable) alias_u0 = false # If immutable don't care about aliasing end end] @@ -529,6 +566,8 @@ end initializealg retcode::ReturnCode.T + + verbose end function get_abstol(cache::NonlinearSolveNoInitCache) @@ -561,11 +600,11 @@ end function SciMLBase.__init( prob::AbstractNonlinearProblem, alg::AbstractNonlinearSolveAlgorithm, args...; - initializealg = NonlinearSolveDefaultInit(), + initializealg = NonlinearSolveDefaultInit(), verbose = NonlinearVerbosity(), kwargs... ) cache = NonlinearSolveNoInitCache( - prob, alg, args, kwargs, initializealg, ReturnCode.Default) + prob, alg, args, kwargs, initializealg, ReturnCode.Default, verbose) run_initialization!(cache) return cache end diff --git a/lib/NonlinearSolveBase/src/verbosity.jl b/lib/NonlinearSolveBase/src/verbosity.jl new file mode 100644 index 000000000..ab4b558f1 --- /dev/null +++ b/lib/NonlinearSolveBase/src/verbosity.jl @@ -0,0 +1,229 @@ +""" + NonlinearVerbosity <: AbstractVerbositySpecifier + +Verbosity configuration for NonlinearSolve.jl solvers, providing fine-grained control over +diagnostic messages, warnings, and errors during nonlinear system solution. + +# Fields + +## Error Control Group +- `non_enclosing_interval`: Messages when interval doesn't enclose root (bracketing methods) +- `alias_u0_immutable`: Messages when aliasing u0 with immutable array +- `linsolve_failed_noncurrent`: Messages when linear solve fails on non-current iteration +- `termination_condition`: Messages about termination conditions + +## Numerical Group +- `threshold_state`: Messages about threshold state in low-rank methods + +## Linear Solver Group +- `linear_verbosity`: Verbosity configuration for linear solvers + +# Constructors + + NonlinearVerbosity(preset::AbstractVerbosityPreset) + +Create a `NonlinearVerbosity` using a preset configuration: +- `SciMLLogging.None()`: All messages disabled +- `SciMLLogging.Minimal()`: Only critical errors and fatal issues +- `SciMLLogging.Standard()`: Balanced verbosity (default) +- `SciMLLogging.Detailed()`: Comprehensive debugging information +- `SciMLLogging.All()`: Maximum verbosity + + NonlinearVerbosity(; error_control=nothing, performance=nothing, numerical=nothing, linear_verbosity=nothing, kwargs...) + +Create a `NonlinearVerbosity` with group-level or individual field control. + +# Examples + +```julia +# Use a preset +verbose = NonlinearVerbosity(SciMLLogging.Standard()) + +# Set entire groups +verbose = NonlinearVerbosity( + error_control = SciMLLogging.WarnLevel(), + numerical = SciMLLogging.InfoLevel() +) + +# Set individual fields +verbose = NonlinearVerbosity( + alias_u0_immutable = SciMLLogging.WarnLevel(), + threshold_state = SciMLLogging.InfoLevel() +) + +# Mix group and individual settings +verbose = NonlinearVerbosity( + numerical = SciMLLogging.InfoLevel(), # Set all numerical to InfoLevel + threshold_state = SciMLLogging.ErrorLevel() # Override specific field +) +``` +""" +@concrete struct NonlinearVerbosity <: AbstractVerbositySpecifier + # Linear verbosity + linear_verbosity + # Error control + non_enclosing_interval + alias_u0_immutable + linsolve_failed_noncurrent + termination_condition + # Numerical + threshold_state +end + +# Group classifications +const error_control_options = ( + :non_enclosing_interval, :alias_u0_immutable, :linsolve_failed_noncurrent, + :termination_condition +) +const performance_options = () +const numerical_options = (:threshold_state,) + +function option_group(option::Symbol) + if option in error_control_options + return :error_control + elseif option in performance_options + return :performance + elseif option in numerical_options + return :numerical + else + error("Unknown verbosity option: $option") + end +end + +# Get all options in a group +function group_options(verbosity::NonlinearVerbosity, group::Symbol) + if group === :error_control + return NamedTuple{error_control_options}(getproperty(verbosity, opt) + for opt in error_control_options) + elseif group === :performance + return NamedTuple{performance_options}(getproperty(verbosity, opt) + for opt in performance_options) + elseif group === :numerical + return NamedTuple{numerical_options}(getproperty(verbosity, opt) + for opt in numerical_options) + else + error("Unknown group: $group") + end +end + +function NonlinearVerbosity(; + error_control = nothing, performance = nothing, numerical = nothing, + linear_verbosity = nothing, kwargs...) + # Validate group arguments + if error_control !== nothing && !(error_control isa AbstractMessageLevel) + throw(ArgumentError("error_control must be a SciMLLogging.AbstractMessageLevel, got $(typeof(error_control))")) + end + if performance !== nothing && !(performance isa AbstractMessageLevel) + throw(ArgumentError("performance must be a SciMLLogging.AbstractMessageLevel, got $(typeof(performance))")) + end + if numerical !== nothing && !(numerical isa AbstractMessageLevel) + throw(ArgumentError("numerical must be a SciMLLogging.AbstractMessageLevel, got $(typeof(numerical))")) + end + + # Validate individual kwargs + for (key, value) in kwargs + if !(key in error_control_options || key in performance_options || + key in numerical_options) + throw(ArgumentError("Unknown verbosity option: $key. Valid options are: $(tuple(error_control_options..., performance_options..., numerical_options...))")) + end + if !(value isa AbstractMessageLevel) + throw(ArgumentError("$key must be a SciMLLogging.AbstractMessageLevel, got $(typeof(value))")) + end + end + + # Build arguments using NamedTuple for type stability + default_args = ( + linear_verbosity = linear_verbosity === nothing ? Minimal() : linear_verbosity, + non_enclosing_interval = WarnLevel(), + alias_u0_immutable = WarnLevel(), + linsolve_failed_noncurrent = WarnLevel(), + termination_condition = WarnLevel(), + threshold_state = WarnLevel() + ) + + # Apply group-level settings + final_args = if error_control !== nothing || performance !== nothing || + numerical !== nothing + NamedTuple{keys(default_args)}( + _resolve_arg_value( + key, default_args[key], error_control, performance, numerical) + for key in keys(default_args) + ) + else + default_args + end + + # Apply individual overrides + if !isempty(kwargs) + final_args = merge(final_args, NamedTuple(kwargs)) + end + + NonlinearVerbosity(values(final_args)...) +end + +# Constructor for verbosity presets following the hierarchical levels: +# None < Minimal < Standard < Detailed < All +# Each level includes all messages from levels below it plus additional ones +function NonlinearVerbosity(verbose::AbstractVerbosityPreset) + if verbose isa Minimal + # Minimal: Only fatal errors and critical warnings + NonlinearVerbosity( + linear_verbosity = Minimal(), + non_enclosing_interval = WarnLevel(), + alias_u0_immutable = Silent(), + linsolve_failed_noncurrent = WarnLevel(), + termination_condition = Silent(), + threshold_state = Silent() + ) + elseif verbose isa Standard + # Standard: Everything from Minimal + non-fatal warnings + NonlinearVerbosity() + elseif verbose isa Detailed + # Detailed: Everything from Standard + debugging/solver behavior + NonlinearVerbosity( + linear_verbosity = Detailed(), + non_enclosing_interval = WarnLevel(), + alias_u0_immutable = WarnLevel(), + linsolve_failed_noncurrent = WarnLevel(), + termination_condition = WarnLevel(), + threshold_state = WarnLevel() + ) + elseif verbose isa All + # All: Maximum verbosity - every possible logging message at InfoLevel + NonlinearVerbosity( + linear_verbosity = Detailed(), + non_enclosing_interval = WarnLevel(), + alias_u0_immutable = WarnLevel(), + linsolve_failed_noncurrent = WarnLevel(), + termination_condition = WarnLevel(), + threshold_state = InfoLevel() + ) + end +end + +@inline function NonlinearVerbosity(verbose::None) + NonlinearVerbosity( + None(), + Silent(), + Silent(), + Silent(), + Silent(), + Silent() + ) +end + +# Helper function to resolve argument values based on group membership +@inline function _resolve_arg_value( + key::Symbol, default_val, error_control, performance, numerical) + if key === :linear_verbosity + return default_val + elseif key in error_control_options && error_control !== nothing + return error_control + elseif key in performance_options && performance !== nothing + return performance + elseif key in numerical_options && numerical !== nothing + return numerical + else + return default_val + end +end \ No newline at end of file diff --git a/lib/NonlinearSolveFirstOrder/Project.toml b/lib/NonlinearSolveFirstOrder/Project.toml index 6013a0360..41c99f87b 100644 --- a/lib/NonlinearSolveFirstOrder/Project.toml +++ b/lib/NonlinearSolveFirstOrder/Project.toml @@ -47,7 +47,7 @@ LinearAlgebra = "1.10" LinearSolve = "2.36.1, 3" MaybeInplace = "0.1.4" NonlinearProblemLibrary = "0.1.2" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" Pkg = "1.10" PrecompileTools = "1.2" Random = "1.10" diff --git a/lib/NonlinearSolveFirstOrder/src/NonlinearSolveFirstOrder.jl b/lib/NonlinearSolveFirstOrder/src/NonlinearSolveFirstOrder.jl index dba01edfe..72ce1ae1e 100644 --- a/lib/NonlinearSolveFirstOrder/src/NonlinearSolveFirstOrder.jl +++ b/lib/NonlinearSolveFirstOrder/src/NonlinearSolveFirstOrder.jl @@ -21,7 +21,8 @@ using NonlinearSolveBase: NonlinearSolveBase, AbstractNonlinearSolveAlgorithm, Utils, InternalAPI, get_timer_output, @static_timeit, update_trace!, L2_NORM, NonlinearSolvePolyAlgorithm, NewtonDescent, DampedNewtonDescent, GeodesicAcceleration, - Dogleg, NonlinearSolveForwardDiffCache, reused_jacobian + Dogleg, NonlinearSolveForwardDiffCache, NonlinearVerbosity, + @SciMLMessage, None, reused_jacobian, AbstractVerbosityPreset using SciMLBase: SciMLBase, AbstractNonlinearProblem, NLStats, ReturnCode, NonlinearFunction, NonlinearLeastSquaresProblem, NonlinearProblem, NoSpecialize diff --git a/lib/NonlinearSolveFirstOrder/src/solve.jl b/lib/NonlinearSolveFirstOrder/src/solve.jl index 874f5dbc4..e577da907 100644 --- a/lib/NonlinearSolveFirstOrder/src/solve.jl +++ b/lib/NonlinearSolveFirstOrder/src/solve.jl @@ -87,6 +87,8 @@ end kwargs initializealg + + verbose end function SciMLBase.get_du(cache::GeneralizedFirstOrderAlgorithmCache) @@ -129,7 +131,7 @@ function SciMLBase.__init( prob::AbstractNonlinearProblem, alg::GeneralizedFirstOrderAlgorithm, args...; stats = NLStats(0, 0, 0, 0, 0), alias_u0 = false, maxiters = 1000, abstol = nothing, reltol = nothing, maxtime = nothing, - termination_condition = nothing, internalnorm::IN = L2_NORM, + termination_condition = nothing, internalnorm::IN = L2_NORM, verbose = NonlinearVerbosity(), linsolve_kwargs = (;), initializealg = NonlinearSolveBase.NonlinearSolveDefaultInit(), kwargs... ) where {IN} @set! alg.autodiff = NonlinearSolveBase.select_jacobian_autodiff(prob, alg.autodiff) @@ -152,6 +154,16 @@ function SciMLBase.__init( NonlinearSolveBase.select_reverse_mode_autodiff(prob, alg.vjp_autodiff) end + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + timer = get_timer_output() @static_timeit timer "cache construction" begin u = Utils.maybe_unaliased(prob.u0, alias_u0) @@ -164,7 +176,7 @@ function SciMLBase.__init( termination_cache = NonlinearSolveBase.init_termination_cache( prob, abstol, reltol, fu, u, termination_condition, Val(:regular) ) - linsolve_kwargs = merge((; abstol, reltol), linsolve_kwargs) + linsolve_kwargs = merge((; verbose = verbose.linear_verbosity, abstol, reltol), linsolve_kwargs) jac_cache = NonlinearSolveBase.construct_jacobian_cache( prob, alg, prob.f, fu, u, prob.p; @@ -221,7 +233,7 @@ function SciMLBase.__init( jac_cache, descent_cache, linesearch_cache, trustregion_cache, stats, 0, maxiters, maxtime, alg.max_shrink_times, timer, 0.0, true, termination_cache, trace, ReturnCode.Default, false, kwargs, - initializealg + initializealg, verbose ) NonlinearSolveBase.run_initialization!(cache) end @@ -265,10 +277,8 @@ function InternalAPI.step!( return else # Jacobian Information is not current and linear solve failed, recompute it - if !haskey(cache.kwargs, :verbose) || cache.kwargs[:verbose] - @warn "Linear Solve Failed but Jacobian Information is not current. \ - Retrying with updated Jacobian." - end + @SciMLMessage("Linear Solve Failed but Jacobian information is not current. Retrying with updated Jacobian. \ + Retrying with updated Jacobian.", cache.verbose, :linsolve_failed_noncurrent) # In the 2nd call the `new_jacobian` is guaranteed to be `true`. cache.make_new_jacobian = true InternalAPI.step!(cache; recompute_jacobian = true, cache.kwargs...) diff --git a/lib/NonlinearSolveHomotopyContinuation/Project.toml b/lib/NonlinearSolveHomotopyContinuation/Project.toml index 41a4ea3c4..4239bbe89 100644 --- a/lib/NonlinearSolveHomotopyContinuation/Project.toml +++ b/lib/NonlinearSolveHomotopyContinuation/Project.toml @@ -34,7 +34,7 @@ HomotopyContinuation = "2.12.0" LinearAlgebra = "1.10" NaNMath = "1.1" NonlinearSolve = "4.10" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" SciMLBase = "2.116" SymbolicIndexingInterface = "0.3.43" TaylorDiff = "0.3.1" diff --git a/lib/NonlinearSolveQuasiNewton/Project.toml b/lib/NonlinearSolveQuasiNewton/Project.toml index 40d0be717..c5c5661cd 100644 --- a/lib/NonlinearSolveQuasiNewton/Project.toml +++ b/lib/NonlinearSolveQuasiNewton/Project.toml @@ -45,7 +45,7 @@ LinearAlgebra = "1.10" LinearSolve = "2.36.1, 3" MaybeInplace = "0.1.4" NonlinearProblemLibrary = "0.1.2" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" Pkg = "1.10" PrecompileTools = "1.2" ReTestItems = "1.24" diff --git a/lib/NonlinearSolveQuasiNewton/src/NonlinearSolveQuasiNewton.jl b/lib/NonlinearSolveQuasiNewton/src/NonlinearSolveQuasiNewton.jl index 6bc55c48e..592648367 100644 --- a/lib/NonlinearSolveQuasiNewton/src/NonlinearSolveQuasiNewton.jl +++ b/lib/NonlinearSolveQuasiNewton/src/NonlinearSolveQuasiNewton.jl @@ -18,7 +18,8 @@ using NonlinearSolveBase: NonlinearSolveBase, AbstractNonlinearSolveAlgorithm, AbstractApproximateJacobianUpdateRule, AbstractDescentDirection, AbstractApproximateJacobianUpdateRuleCache, Utils, InternalAPI, get_timer_output, @static_timeit, - update_trace!, L2_NORM, NewtonDescent, reused_jacobian + update_trace!, L2_NORM, NewtonDescent, NonlinearVerbosity, + @SciMLMessage, None, AbstractVerbosityPreset, reused_jacobian using SciMLBase: SciMLBase, AbstractNonlinearProblem, NLStats, ReturnCode, NonlinearProblem, NonlinearFunction, NoSpecialize using SciMLOperators: AbstractSciMLOperator diff --git a/lib/NonlinearSolveQuasiNewton/src/initialization.jl b/lib/NonlinearSolveQuasiNewton/src/initialization.jl index 413cd0896..e52a5aaa8 100644 --- a/lib/NonlinearSolveQuasiNewton/src/initialization.jl +++ b/lib/NonlinearSolveQuasiNewton/src/initialization.jl @@ -153,7 +153,7 @@ NonlinearSolveBase.jacobian_initialized_preinverted(::BroydenLowRankInitializati function InternalAPI.init( prob::AbstractNonlinearProblem, alg::BroydenLowRankInitialization, solver, f::F, fu, u, p; - internalnorm::IN = L2_NORM, maxiters = 1000, kwargs... + internalnorm::IN = L2_NORM, maxiters = 1000, verbose = NonlinearVerbosity(), kwargs... ) where {F, IN} if u isa Number # Use the standard broyden return InternalAPI.init( @@ -161,6 +161,17 @@ function InternalAPI.init( solver, f, fu, u, p; internalnorm, maxiters, kwargs... ) end + + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + # Pay to cost of slightly more allocations to prevent type-instability for StaticArrays α = inv(Utils.initial_jacobian_scaling_alpha(alg.alpha, u, fu, internalnorm)) if u isa StaticArray @@ -168,8 +179,8 @@ function InternalAPI.init( else threshold = min(Utils.unwrap_val(alg.threshold), maxiters) if threshold > length(u) - @warn "`threshold` is larger than the size of the state, which may cause \ - numerical instability. Consider reducing `threshold`." + @SciMLMessage("`threshold` is larger than the size of the state, which may cause \ + numerical instability. Consider reducing `threshold`.", verbose, :threshold_state) end J = BroydenLowRankJacobian(fu, u; threshold, alpha = α) end diff --git a/lib/NonlinearSolveQuasiNewton/src/solve.jl b/lib/NonlinearSolveQuasiNewton/src/solve.jl index e24c6f45e..7585f537e 100644 --- a/lib/NonlinearSolveQuasiNewton/src/solve.jl +++ b/lib/NonlinearSolveQuasiNewton/src/solve.jl @@ -95,6 +95,8 @@ end # Initialization initializealg + + verbose end function SciMLBase.get_du(cache::QuasiNewtonCache) @@ -149,10 +151,22 @@ function SciMLBase.__init( maxiters = 1000, abstol = nothing, reltol = nothing, linsolve_kwargs = (;), termination_condition = nothing, internalnorm::F = L2_NORM, initializealg = NonlinearSolveBase.NonlinearSolveDefaultInit(), + verbose = NonlinearVerbosity(), kwargs... ) where {F} timer = get_timer_output() @static_timeit timer "cache construction" begin + + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + u = Utils.maybe_unaliased(prob.u0, alias_u0) fu = Utils.evaluate_f(prob, u) @bb u_cache = copy(u) @@ -170,7 +184,7 @@ function SciMLBase.__init( termination_cache = NonlinearSolveBase.init_termination_cache( prob, abstol, reltol, fu, u, termination_condition, Val(:regular) ) - linsolve_kwargs = merge((; abstol, reltol), linsolve_kwargs) + linsolve_kwargs = merge((;verbose = verbose.linear_verbosity, abstol, reltol), linsolve_kwargs) J = initialization_cache(nothing) @@ -231,7 +245,7 @@ function SciMLBase.__init( trustregion_cache, update_rule_cache, reinit_rule_cache, inv_workspace, stats, 0, 0, alg.max_resets, maxiters, maxtime, alg.max_shrink_times, 0, timer, 0.0, termination_cache, trace, - ReturnCode.Default, false, false, kwargs, initializealg + ReturnCode.Default, false, false, kwargs, initializealg, verbose ) NonlinearSolveBase.run_initialization!(cache) end @@ -333,10 +347,10 @@ function InternalAPI.step!( return else # Force a reinit because the problem is currently un-solvable - if !haskey(cache.kwargs, :verbose) || cache.kwargs[:verbose] - @warn "Linear Solve Failed but Jacobian Information is not current. \ - Retrying with reinitialized Approximate Jacobian." - end + + @SciMLMessage("Linear Solve Failed but Jacobian information is not current. Retrying with updated Jacobian. \ + Retrying with updated Jacobian.", cache.verbose, :linsolve_failed_noncurrent) + cache.force_reinit = true InternalAPI.step!(cache; recompute_jacobian = true) return diff --git a/lib/NonlinearSolveSciPy/Project.toml b/lib/NonlinearSolveSciPy/Project.toml index 9c7bc2880..ad5b05a50 100644 --- a/lib/NonlinearSolveSciPy/Project.toml +++ b/lib/NonlinearSolveSciPy/Project.toml @@ -18,7 +18,7 @@ path = "../NonlinearSolveBase" ConcreteStructs = "0.2.3" Hwloc = "3" InteractiveUtils = "<0.0.1, 1" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" PrecompileTools = "1.2" PythonCall = "0.9" ReTestItems = "1.24" diff --git a/lib/NonlinearSolveSpectralMethods/Project.toml b/lib/NonlinearSolveSpectralMethods/Project.toml index 267ea51f8..b19b18de0 100644 --- a/lib/NonlinearSolveSpectralMethods/Project.toml +++ b/lib/NonlinearSolveSpectralMethods/Project.toml @@ -34,7 +34,7 @@ InteractiveUtils = "<0.0.1, 1" LineSearch = "0.1.4" MaybeInplace = "0.1.4" NonlinearProblemLibrary = "0.1.2" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" Pkg = "1.10" PrecompileTools = "1.2" ReTestItems = "1.24" diff --git a/lib/NonlinearSolveSpectralMethods/src/NonlinearSolveSpectralMethods.jl b/lib/NonlinearSolveSpectralMethods/src/NonlinearSolveSpectralMethods.jl index 2706d5670..ae897d58c 100644 --- a/lib/NonlinearSolveSpectralMethods/src/NonlinearSolveSpectralMethods.jl +++ b/lib/NonlinearSolveSpectralMethods/src/NonlinearSolveSpectralMethods.jl @@ -9,7 +9,8 @@ using LineSearch: RobustNonMonotoneLineSearch using MaybeInplace: @bb using NonlinearSolveBase: NonlinearSolveBase, AbstractNonlinearSolveAlgorithm, AbstractNonlinearSolveCache, Utils, InternalAPI, get_timer_output, - @static_timeit, update_trace! + @static_timeit, update_trace!, NonlinearVerbosity, @SciMLMessage, None, + AbstractVerbosityPreset using SciMLBase: SciMLBase, AbstractNonlinearProblem, NLStats, ReturnCode, NonlinearProblem, NonlinearFunction, NoSpecialize diff --git a/lib/NonlinearSolveSpectralMethods/src/solve.jl b/lib/NonlinearSolveSpectralMethods/src/solve.jl index 9bfd5709c..04dc2d661 100644 --- a/lib/NonlinearSolveSpectralMethods/src/solve.jl +++ b/lib/NonlinearSolveSpectralMethods/src/solve.jl @@ -70,6 +70,8 @@ end kwargs initializealg + + verbose end function SciMLBase.get_du(cache::GeneralizedDFSaneCache) @@ -124,7 +126,8 @@ function SciMLBase.__init( prob::AbstractNonlinearProblem, alg::GeneralizedDFSane, args...; stats = NLStats(0, 0, 0, 0, 0), alias_u0 = false, maxiters = 1000, abstol = nothing, reltol = nothing, termination_condition = nothing, - maxtime = nothing, initializealg = NonlinearSolveBase.NonlinearSolveDefaultInit(), kwargs... + maxtime = nothing, verbose = NonlinearVerbosity(), + initializealg = NonlinearSolveBase.NonlinearSolveDefaultInit(), kwargs... ) timer = get_timer_output() @@ -158,11 +161,21 @@ function SciMLBase.__init( σ_n = T(alg.σ_1) end + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + cache = GeneralizedDFSaneCache( fu, fu_cache, u, u_cache, prob.p, du, alg, prob, σ_n, T(alg.σ_min), T(alg.σ_max), linesearch_cache, stats, 0, maxiters, maxtime, timer, 0.0, - tc_cache, trace, ReturnCode.Default, false, kwargs, initializealg + tc_cache, trace, ReturnCode.Default, false, kwargs, initializealg, verbose ) NonlinearSolveBase.run_initialization!(cache) end @@ -176,7 +189,7 @@ function InternalAPI.step!( ) if recompute_jacobian !== nothing @warn "GeneralizedDFSane is a Jacobian-Free Algorithm. Ignoring \ - `recompute_jacobian`" maxlog=1 + `recompute_jacobian`" end @static_timeit cache.timer "descent" begin diff --git a/lib/SCCNonlinearSolve/Project.toml b/lib/SCCNonlinearSolve/Project.toml index bd547fa43..f44d9e9b8 100644 --- a/lib/SCCNonlinearSolve/Project.toml +++ b/lib/SCCNonlinearSolve/Project.toml @@ -20,7 +20,7 @@ LinearAlgebra = "1.10" InteractiveUtils = "<0.0.1, 1" NonlinearProblemLibrary = "0.1.2" NonlinearSolve = "4.8" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" NonlinearSolveFirstOrder = "1" Pkg = "1.10" PrecompileTools = "1.2" diff --git a/lib/SimpleNonlinearSolve/Project.toml b/lib/SimpleNonlinearSolve/Project.toml index a4b487f35..a7154cfeb 100644 --- a/lib/SimpleNonlinearSolve/Project.toml +++ b/lib/SimpleNonlinearSolve/Project.toml @@ -58,7 +58,7 @@ LineSearch = "0.1.3" LinearAlgebra = "1.10" MaybeInplace = "0.1.4" NonlinearProblemLibrary = "0.1.2" -NonlinearSolveBase = "2" +NonlinearSolveBase = "2.1" Pkg = "1.10" PolyesterForwardDiff = "0.1.3" PrecompileTools = "1.2" diff --git a/lib/SimpleNonlinearSolve/src/SimpleNonlinearSolve.jl b/lib/SimpleNonlinearSolve/src/SimpleNonlinearSolve.jl index 782de6468..0cc71cdb6 100644 --- a/lib/SimpleNonlinearSolve/src/SimpleNonlinearSolve.jl +++ b/lib/SimpleNonlinearSolve/src/SimpleNonlinearSolve.jl @@ -12,7 +12,8 @@ using LineSearch: LiFukushimaLineSearch using MaybeInplace: @bb using NonlinearSolveBase: NonlinearSolveBase, ImmutableNonlinearProblem, L2_NORM, nonlinearsolve_forwarddiff_solve, nonlinearsolve_dual_solution, - AbstractNonlinearSolveAlgorithm + AbstractNonlinearSolveAlgorithm, NonlinearVerbosity, @SciMLMessage, + AbstractVerbosityPreset using SciMLBase: SciMLBase, NonlinearFunction, NonlinearProblem, NonlinearLeastSquaresProblem, ReturnCode, remake diff --git a/lib/SimpleNonlinearSolve/src/lbroyden.jl b/lib/SimpleNonlinearSolve/src/lbroyden.jl index 979b244f8..85a884ede 100644 --- a/lib/SimpleNonlinearSolve/src/lbroyden.jl +++ b/lib/SimpleNonlinearSolve/src/lbroyden.jl @@ -36,7 +36,7 @@ end function SciMLBase.__solve( prob::ImmutableNonlinearProblem, alg::SimpleLimitedMemoryBroyden, - args...; termination_condition = nothing, kwargs...) + args...; termination_condition = nothing, verbose = NonlinearVerbosity(), kwargs...) if prob.u0 isa SArray if termination_condition === nothing || termination_condition isa NonlinearSolveBase.AbsNormTerminationMode @@ -44,10 +44,21 @@ function SciMLBase.__solve( prob, alg, args...; termination_condition, kwargs... ) end - @warn "Specifying `termination_condition = $(termination_condition)` for \ + + if verbose isa Bool + if verbose + verbose = NonlinearVerbosity() + else + verbose = NonlinearVerbosity(None()) + end + elseif verbose isa AbstractVerbosityPreset + verbose = NonlinearVerbosity(verbose) + end + + @SciMLMessage("Specifying `termination_condition = $(termination_condition)` for \ `SimpleLimitedMemoryBroyden` with `SArray` is not non-allocating. Use \ either `termination_condition = AbsNormTerminationMode(Base.Fix2(norm, Inf))` \ - or `termination_condition = nothing`." maxlog=1 + or `termination_condition = nothing`.", verbose, :termination_condition) end return internal_generic_solve(prob, alg, args...; termination_condition, kwargs...) end diff --git a/src/NonlinearSolve.jl b/src/NonlinearSolve.jl index d11f99749..dc6a20507 100644 --- a/src/NonlinearSolve.jl +++ b/src/NonlinearSolve.jl @@ -11,7 +11,7 @@ using CommonSolve: CommonSolve, init, solve, solve! using LinearAlgebra: LinearAlgebra using LineSearch: BackTracking using NonlinearSolveBase: NonlinearSolveBase, AbstractNonlinearSolveAlgorithm, - NonlinearSolvePolyAlgorithm, pickchunksize + NonlinearSolvePolyAlgorithm, pickchunksize, NonlinearVerbosity using SciMLBase: SciMLBase, ReturnCode, AbstractNonlinearProblem, NonlinearFunction, @@ -85,18 +85,17 @@ include("forward_diff.jl") push!(nlls_problems, NonlinearLeastSquaresProblem(fn, u0, 2.0)) end + nlp_algs = [NewtonRaphson(), TrustRegion(), LevenbergMarquardt()] + nlls_algs = [GaussNewton(), TrustRegion(), LevenbergMarquardt()] + @compile_workload begin @sync begin - for prob in nonlinear_problems - Threads.@spawn CommonSolve.solve( - prob, nothing; abstol = 1e-2, verbose = false - ) + for prob in nonlinear_problems, alg in nlp_algs + Threads.@spawn CommonSolve.solve(prob, alg; abstol = 1e-2, verbose = NonlinearVerbosity()) end - for prob in nlls_problems - Threads.@spawn CommonSolve.solve( - prob, nothing; abstol = 1e-2, verbose = false - ) + for prob in nlls_problems, alg in nlls_algs + Threads.@spawn CommonSolve.solve(prob, alg; abstol = 1e-2, verbose = NonlinearVerbosity()) end end end diff --git a/test/core_tests.jl b/test/core_tests.jl index 76a3a9187..c1f3a3b58 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -216,7 +216,8 @@ end function objective_function!(resid, u0, p) odeprob = ODEProblem{true}(ode_func!, u0, (0.0, 100.0), p) - sol = solve(odeprob, Tsit5(), abstol = 1e-9, reltol = 1e-9, verbose = false) + sol = solve( + odeprob, Tsit5(), abstol = 1e-9, reltol = 1e-9, verbose = true) resid[1] = sol(0.0)[1] resid[2] = sol(100.0)[1] - 1.0 return nothing diff --git a/test/verbosity_tests.jl b/test/verbosity_tests.jl new file mode 100644 index 000000000..d984b40e3 --- /dev/null +++ b/test/verbosity_tests.jl @@ -0,0 +1,200 @@ +@testitem "Nonlinear Verbosity" tags=[:verbosity] begin + using NonlinearSolve + using BracketingNonlinearSolve + using NonlinearSolve: NonlinearVerbosity + using LinearSolve: LinearVerbosity + using SciMLLogging: SciMLLogging + using Test + + @testset "NonlinearVerbosity preset constructors" begin + v_none = NonlinearVerbosity(SciMLLogging.None()) + v_all = NonlinearVerbosity(SciMLLogging.All()) + v_minimal = NonlinearVerbosity(SciMLLogging.Minimal()) + v_standard = NonlinearVerbosity(SciMLLogging.Standard()) + v_detailed = NonlinearVerbosity(SciMLLogging.Detailed()) + + @test v_none.non_enclosing_interval isa SciMLLogging.Silent + @test v_none.threshold_state isa SciMLLogging.Silent + @test v_none.alias_u0_immutable isa SciMLLogging.Silent + + @test v_minimal.non_enclosing_interval isa SciMLLogging.WarnLevel + @test v_minimal.alias_u0_immutable isa SciMLLogging.Silent + @test v_minimal.termination_condition isa SciMLLogging.Silent + + @test v_standard.non_enclosing_interval isa SciMLLogging.WarnLevel + @test v_standard.threshold_state isa SciMLLogging.WarnLevel + + @test v_detailed.alias_u0_immutable isa SciMLLogging.WarnLevel + @test v_detailed.termination_condition isa SciMLLogging.WarnLevel + + @test v_all.linsolve_failed_noncurrent isa SciMLLogging.WarnLevel + @test v_all.threshold_state isa SciMLLogging.InfoLevel + end + + @testset "Group-level keyword constructors" begin + v_error = NonlinearVerbosity(error_control = SciMLLogging.ErrorLevel()) + @test v_error.alias_u0_immutable isa SciMLLogging.ErrorLevel + @test v_error.non_enclosing_interval isa SciMLLogging.ErrorLevel + @test v_error.termination_condition isa SciMLLogging.ErrorLevel + @test v_error.linsolve_failed_noncurrent isa SciMLLogging.ErrorLevel + + v_numerical = NonlinearVerbosity(numerical = SciMLLogging.Silent()) + @test v_numerical.threshold_state isa SciMLLogging.Silent + end + + @testset "Mixed group and individual settings" begin + v_mixed = NonlinearVerbosity( + numerical = SciMLLogging.Silent(), + threshold_state = SciMLLogging.WarnLevel(), + error_control = SciMLLogging.InfoLevel() + ) + # Individual override should take precedence + @test v_mixed.threshold_state isa SciMLLogging.WarnLevel + # Error control group setting should apply + @test v_mixed.alias_u0_immutable isa SciMLLogging.InfoLevel + @test v_mixed.linsolve_failed_noncurrent isa SciMLLogging.InfoLevel + end + + @testset "Individual keyword arguments" begin + v_individual = NonlinearVerbosity( + alias_u0_immutable = SciMLLogging.ErrorLevel(), + threshold_state = SciMLLogging.InfoLevel(), + termination_condition = SciMLLogging.Silent() + ) + @test v_individual.alias_u0_immutable isa SciMLLogging.ErrorLevel + @test v_individual.threshold_state isa SciMLLogging.InfoLevel + @test v_individual.termination_condition isa SciMLLogging.Silent + # Unspecified options should use defaults + @test v_individual.non_enclosing_interval isa SciMLLogging.WarnLevel + @test v_individual.linsolve_failed_noncurrent isa SciMLLogging.WarnLevel + end + + g(u, p) = u^2 - 4 + + int_prob = IntervalNonlinearProblem(g, (3.0, 5.0)) + + @test_logs (:info, + "The interval is not an enclosing interval, opposite signs at the boundaries are required.") solve( + int_prob, + ITP(), verbose = NonlinearVerbosity(non_enclosing_interval = SciMLLogging.InfoLevel())) + + @test_logs (:error, + "The interval is not an enclosing interval, opposite signs at the boundaries are required.") @test_throws ErrorException solve( + int_prob, + ITP(), verbose = NonlinearVerbosity(non_enclosing_interval = SciMLLogging.ErrorLevel())) + + # Test that the linear verbosity is passed to the linear solve + f(u, p) = [u[1]^2 - 2u[1] + 1, sum(u)] + prob = NonlinearProblem(f, [1.0, 1.0]) + + @test_logs (:warn, + "LU factorization failed, falling back to QR factorization. `A` is potentially rank-deficient.") match_mode=:any solve( + prob, + verbose = NonlinearVerbosity(linear_verbosity = SciMLLogging.Detailed())) + + @test_logs (:info, + "LU factorization failed, falling back to QR factorization. `A` is potentially rank-deficient.") match_mode=:any solve( + prob, + verbose = NonlinearVerbosity(linear_verbosity = LinearVerbosity(default_lu_fallback = SciMLLogging.InfoLevel()))) + + @test_logs min_level=0 solve( + prob, + verbose = NonlinearVerbosity(linear_verbosity = SciMLLogging.Standard())) + + @test_logs (:warn, + "LU factorization failed, falling back to QR factorization. `A` is potentially rank-deficient.") match_mode=:any solve( + prob, + verbose = NonlinearVerbosity(linear_verbosity = SciMLLogging.Detailed()) + ) + + @test_logs min_level=0 solve(prob, + verbose = NonlinearVerbosity(SciMLLogging.None())) + + @test_logs min_level=0 solve(prob, + verbose = false) + + # Test that caches get correct verbosities + cache = init( + prob, verbose = NonlinearVerbosity(threshold_state = SciMLLogging.InfoLevel())) + + @test cache.verbose.threshold_state == SciMLLogging.InfoLevel() + + f(u, p) = u .* u .- 2 + prob = NonlinearProblem(f, [1.0, 1.0]) + + @testset "solve with Bool verbose" begin + # Test verbose = true works (default verbosity) + sol1 = solve(prob, verbose = true) + @test sol1.retcode == ReturnCode.Success + + # Test verbose = false silences all output + @test_logs min_level=0 sol2=solve(prob, verbose = false) + end + + @testset "solve with Preset verbose" begin + # Test verbose = SciMLLogging.Standard() works + sol1 = solve(prob, verbose = SciMLLogging.Standard()) + @test sol1.retcode == ReturnCode.Success + + # Test verbose = SciMLLogging.None() silences output + @test_logs min_level=0 sol2=solve(prob, verbose = SciMLLogging.None()) + + # Test verbose = SciMLLogging.Detailed() works + sol3 = solve(prob, verbose = SciMLLogging.Detailed()) + @test sol3.retcode == ReturnCode.Success + + # Test verbose = SciMLLogging.All() works + sol4 = solve(prob, verbose = SciMLLogging.All()) + @test sol4.retcode == ReturnCode.Success + + # Test verbose = SciMLLogging.Minimal() works + sol5 = solve(prob, verbose = SciMLLogging.Minimal()) + @test sol5.retcode == ReturnCode.Success + end + + @testset "init with Bool verbose" begin + # Test verbose = true converts to NonlinearVerbosity() + cache1 = init(prob, verbose = true) + @test cache1.verbose isa NonlinearVerbosity + @test cache1.verbose.threshold_state == SciMLLogging.WarnLevel() + + # Test verbose = false converts to NonlinearVerbosity(None()) + cache2 = init(prob, verbose = false) + @test cache2.verbose isa NonlinearVerbosity + @test cache2.verbose.threshold_state isa SciMLLogging.Silent + @test cache2.verbose.non_enclosing_interval isa SciMLLogging.Silent + end + + @testset "init with Preset verbose" begin + # Test verbose = SciMLLogging.Standard() converts to NonlinearVerbosity(Standard()) + cache1 = init(prob, verbose = SciMLLogging.Standard()) + @test cache1.verbose isa NonlinearVerbosity + @test cache1.verbose.threshold_state == SciMLLogging.WarnLevel() + + # Test verbose = SciMLLogging.None() converts to NonlinearVerbosity(None()) + cache2 = init(prob, verbose = SciMLLogging.None()) + @test cache2.verbose isa NonlinearVerbosity + @test cache2.verbose.threshold_state isa SciMLLogging.Silent + + # Test verbose = SciMLLogging.Detailed() + cache3 = init(prob, verbose = SciMLLogging.Detailed()) + @test cache3.verbose isa NonlinearVerbosity + @test cache3.verbose.linear_verbosity isa SciMLLogging.Detailed + + # Test verbose = SciMLLogging.All() + cache4 = init(prob, verbose = SciMLLogging.All()) + @test cache4.verbose isa NonlinearVerbosity + @test cache4.verbose.threshold_state == SciMLLogging.InfoLevel() + + # Test verbose = SciMLLogging.Minimal() + cache5 = init(prob, verbose = SciMLLogging.Minimal()) + @test cache5.verbose isa NonlinearVerbosity + @test cache5.verbose.alias_u0_immutable isa SciMLLogging.Silent + end + + @testset "init then solve with converted verbose" begin + # Ensure the converted verbose works through the full solve pipeline + cache = init(prob, verbose = false) + @test_logs min_level=0 sol=solve!(cache) + end +end \ No newline at end of file