From 650dcb964d2ec66ec3be6552d5c3174f9b62bd93 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 19 Feb 2026 09:37:59 +0530 Subject: [PATCH 1/8] refactor: migrate Curvefit from Optim.jl to CurveFit.jl --- ...xt.jl => DataInterpolationsCurveFitExt.jl} | 27 ++++--- ext/DataInterpolationsCurveFitext.jl | 78 +++++++++++++++++++ src/DataInterpolations.jl | 4 +- 3 files changed, 93 insertions(+), 16 deletions(-) rename ext/{DataInterpolationsOptimExt.jl => DataInterpolationsCurveFitExt.jl} (72%) create mode 100644 ext/DataInterpolationsCurveFitext.jl diff --git a/ext/DataInterpolationsOptimExt.jl b/ext/DataInterpolationsCurveFitExt.jl similarity index 72% rename from ext/DataInterpolationsOptimExt.jl rename to ext/DataInterpolationsCurveFitExt.jl index 8b38e4f8..df377648 100644 --- a/ext/DataInterpolationsOptimExt.jl +++ b/ext/DataInterpolationsCurveFitExt.jl @@ -1,4 +1,4 @@ -module DataInterpolationsOptimExt +module DataInterpolationsCurveFitExt using DataInterpolations import DataInterpolations: munge_data, @@ -6,7 +6,7 @@ import DataInterpolations: munge_data, ExtrapolationError, integral, IntegralNotFoundError, DerivativeNotFoundError -using Optim, ForwardDiff +using CurveFit, ForwardDiff ### Curvefit function Curvefit( @@ -14,25 +14,24 @@ function Curvefit( t, model, p0, - alg, + alg = nothing, box = false, lb = nothing, ub = nothing; extrapolate = false ) u, t = munge_data(u, t) - errfun(t, u, p) = sum(abs2.(u .- model(t, p))) - if box == false - mfit = optimize(p -> errfun(t, u, p), p0, alg) + if box && (lb === nothing || ub === nothing) + error("lower or upper bound should not be nothing") + end + prob = CurveFit.NonlinearCurveFitProblem((p, x) -> model(x, p), p0, t, u) + sol = if isnothing(alg) + CurveFit.solve(prob) else - if lb === nothing || ub === nothing - error("lower or upper bound should not be nothing") - end - od = OnceDifferentiable(p -> errfun(t, u, p), p0, autodiff = :finite) - mfit = optimize(od, lb, ub, p0, Fminbox(alg)) + CurveFit.solve(prob, alg) end - pmin = Optim.minimizer(mfit) - return CurvefitCache(u, t, model, p0, ub, lb, alg, pmin, extrapolate) + pmin = sol.u + CurvefitCache(u, t, model, p0, ub, lb, alg, pmin, extrapolate) end # Curvefit @@ -65,7 +64,7 @@ end function get_show(A::CurvefitCache) return "Curvefit" * - " with $(length(A.t)) points, using $(nameof(typeof(A.alg)))\n" + " with $(length(A.t)) points.\n" end function integral(A::CurvefitCache{<:AbstractVector{<:Number}}, t::Number) diff --git a/ext/DataInterpolationsCurveFitext.jl b/ext/DataInterpolationsCurveFitext.jl new file mode 100644 index 00000000..df377648 --- /dev/null +++ b/ext/DataInterpolationsCurveFitext.jl @@ -0,0 +1,78 @@ +module DataInterpolationsCurveFitExt + +using DataInterpolations +import DataInterpolations: munge_data, + Curvefit, CurvefitCache, _interpolate, get_show, derivative, + ExtrapolationError, + integral, IntegralNotFoundError, DerivativeNotFoundError + +using CurveFit, ForwardDiff + +### Curvefit +function Curvefit( + u, + t, + model, + p0, + alg = nothing, + box = false, + lb = nothing, + ub = nothing; + extrapolate = false + ) + u, t = munge_data(u, t) + if box && (lb === nothing || ub === nothing) + error("lower or upper bound should not be nothing") + end + prob = CurveFit.NonlinearCurveFitProblem((p, x) -> model(x, p), p0, t, u) + sol = if isnothing(alg) + CurveFit.solve(prob) + else + CurveFit.solve(prob, alg) + end + pmin = sol.u + CurvefitCache(u, t, model, p0, ub, lb, alg, pmin, extrapolate) +end + +# Curvefit +function _interpolate( + A::CurvefitCache{<:AbstractVector{<:Number}}, + t::Union{AbstractVector{<:Number}, Number} + ) + ((t < A.t[1] || t > A.t[end]) && !A.extrapolate) && + throw(ExtrapolationError()) + return A.m(t, A.pmin) +end + +function _interpolate( + A::CurvefitCache{<:AbstractVector{<:Number}}, + t::Union{AbstractVector{<:Number}, Number}, + i + ) + return _interpolate(A, t), i +end + +function derivative( + A::CurvefitCache{<:AbstractVector{<:Number}}, + t::Union{AbstractVector{<:Number}, Number}, order = 1 + ) + ((t < A.t[1] || t > A.t[end]) && !A.extrapolate) && throw(ExtrapolationError()) + order > 2 && throw(DerivativeNotFoundError()) + order == 1 && return ForwardDiff.derivative(x -> A.m(x, A.pmin), t) + return ForwardDiff.derivative(t -> ForwardDiff.derivative(x -> A.m(x, A.pmin), t), t) +end + +function get_show(A::CurvefitCache) + return "Curvefit" * + " with $(length(A.t)) points.\n" +end + +function integral(A::CurvefitCache{<:AbstractVector{<:Number}}, t::Number) + throw(IntegralNotFoundError()) +end + +function integral(A::CurvefitCache{<:AbstractVector{<:Number}}, t1::Number, t2::Number) + throw(IntegralNotFoundError()) +end + +end # module diff --git a/src/DataInterpolations.jl b/src/DataInterpolations.jl index 89196195..15d48bb0 100644 --- a/src/DataInterpolations.jl +++ b/src/DataInterpolations.jl @@ -215,9 +215,9 @@ struct CurvefitCache{ end end -# Define an empty function, so that it can be extended via `DataInterpolationsOptimExt` +# Define an empty function, so that it can be extended via `DataInterpolationsCurveFitExt` function Curvefit() - error("CurveFit requires loading Optim and ForwardDiff, e.g. `using Optim, ForwardDiff`") + error("CurveFit requires loading CurveFit, e.g. `using CurveFit`") end export Curvefit From 372cac0feee5f6fd6c879d57ec8daf691d5fa669 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 19 Feb 2026 09:38:20 +0530 Subject: [PATCH 2/8] build: add CurveFit, remove Optim --- Project.toml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Project.toml b/Project.toml index 78a89653..c5ddfca9 100644 --- a/Project.toml +++ b/Project.toml @@ -12,10 +12,10 @@ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" [weakdeps] +CurveFit = "5a033b19-8c74-5913-a970-47c3779ef25c" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" -Optim = "429524aa-4258-5aef-a3af-852621145aeb" RegularizationTools = "29dad682-9a27-4bc3-9c72-016788665182" SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" @@ -23,7 +23,7 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [extensions] DataInterpolationsChainRulesCoreExt = "ChainRulesCore" -DataInterpolationsOptimExt = "Optim" +DataInterpolationsCurveFitExt = "CurveFit" DataInterpolationsRegularizationToolsExt = "RegularizationTools" DataInterpolationsSparseConnectivityTracerExt = ["SparseConnectivityTracer", "FillArrays"] DataInterpolationsSymbolicsExt = "Symbolics" @@ -34,6 +34,7 @@ AllocCheck = "0.2" Aqua = "0.8" BenchmarkTools = "1" ChainRulesCore = "1.25" +CurveFit = "1.5" EnumX = "1.0.4" FillArrays = "1.13" FindFirstFunctions = "1.3" @@ -41,7 +42,7 @@ FiniteDifferences = "0.12.31" ForwardDiff = "0.10.37, 1" LinearAlgebra = "1.10" Makie = "0.22, 0.23, 0.24" -Optim = "1.6, 2" +NonlinearSolve = "4.16.0" PrettyTables = "2.4, 3" QuadGK = "2.9.1" RecipesBase = "1.3" @@ -62,10 +63,11 @@ AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +CurveFit = "5a033b19-8c74-5913-a970-47c3779ef25c" FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" -Optim = "429524aa-4258-5aef-a3af-852621145aeb" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" RegularizationTools = "29dad682-9a27-4bc3-9c72-016788665182" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" @@ -78,4 +80,4 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [targets] -test = ["Aqua", "AllocCheck", "BenchmarkTools", "SafeTestsets", "ChainRulesCore", "Optim", "RegularizationTools", "Test", "StableRNGs", "FiniteDifferences", "QuadGK", "ForwardDiff", "StaticArrays", "Symbolics", "Unitful", "Zygote", "SparseConnectivityTracer"] +test = ["Aqua", "AllocCheck", "BenchmarkTools", "SafeTestsets", "ChainRulesCore", "CurveFit", "NonlinearSolve", "RegularizationTools", "Test", "StableRNGs", "FiniteDifferences", "QuadGK", "ForwardDiff", "StaticArrays", "Symbolics", "Unitful", "Zygote", "SparseConnectivityTracer"] From 0a55f983f67d02a0f47304833d0426c495359ed6 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 19 Feb 2026 09:38:35 +0530 Subject: [PATCH 3/8] test: update tests --- test/derivative_tests.jl | 4 ++-- test/integral_tests.jl | 4 ++-- test/interpolation_tests.jl | 8 ++++---- test/show.jl | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/derivative_tests.jl b/test/derivative_tests.jl index 6abab98f..bdcfb473 100644 --- a/test/derivative_tests.jl +++ b/test/derivative_tests.jl @@ -5,7 +5,7 @@ using DataInterpolations: derivative, get_transition_ts using Symbolics using StableRNGs using RegularizationTools -using Optim +using CurveFit import ForwardDiff using LinearAlgebra @@ -397,7 +397,7 @@ end t = range(-10, stop = 10, length = 40) u = model(t, [1.0, 2.0]) + 0.01 * randn(rng, length(t)) p0 = [0.5, 0.5] - test_derivatives(Curvefit; args = [u, t, model, p0, LBFGS()], name = "Curvefit") + test_derivatives(Curvefit; args = [u, t, model, p0], name = "Curvefit") end @testset "Symbolic derivatives" begin diff --git a/test/integral_tests.jl b/test/integral_tests.jl index 7f388df4..0ef5a618 100644 --- a/test/integral_tests.jl +++ b/test/integral_tests.jl @@ -1,7 +1,7 @@ using DataInterpolations, Test using QuadGK using DataInterpolations: integral -using Optim, ForwardDiff +using CurveFit, ForwardDiff using RegularizationTools using StableRNGs using Unitful @@ -247,7 +247,7 @@ end t = range(-10, stop = 10, length = 40) u = model(t, [1.0, 2.0]) + 0.01 * randn(rng, length(t)) p0 = [0.5, 0.5] - A = Curvefit(u, t, model, p0, LBFGS()) + A = Curvefit(u, t, model, p0) @test_throws DataInterpolations.IntegralNotFoundError integral(A, 0.0, 1.0) @test_throws DataInterpolations.IntegralNotFoundError integral(A, 5.0) end diff --git a/test/interpolation_tests.jl b/test/interpolation_tests.jl index f3e9509d..aea0ec54 100644 --- a/test/interpolation_tests.jl +++ b/test/interpolation_tests.jl @@ -1,7 +1,7 @@ using DataInterpolations using FindFirstFunctions: searchsortedfirstcorrelated using StableRNGs -using Optim, ForwardDiff +using CurveFit, NonlinearSolve, ForwardDiff using BenchmarkTools using Unitful using LinearAlgebra @@ -1225,7 +1225,7 @@ end u = model(t, [1.0, 2.0]) + 0.01 * randn(rng, length(t)) p0 = [0.5, 0.5] - A = Curvefit(u, t, model, p0, LBFGS()) + A = Curvefit(u, t, model, p0, LevenbergMarquardt()) ts = [-7.0, -2.0, 0.0, 2.5, 5.0] vs = [ @@ -1241,9 +1241,9 @@ end @test @inferred(output_size(A)) == () # Test extrapolation - A = Curvefit(u, t, model, p0, LBFGS(); extrapolate = true) + A = Curvefit(u, t, model, p0, LevenbergMarquardt(); extrapolate = true) @test A(15.0) == model(15.0, A.pmin) - A = Curvefit(u, t, model, p0, LBFGS()) + A = Curvefit(u, t, model, p0, LevenbergMarquardt()) @test_throws DataInterpolations.ExtrapolationError A(15.0) end diff --git a/test/show.jl b/test/show.jl index aa188d48..fb6f7a19 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1,5 +1,5 @@ using DataInterpolations -using Optim, StableRNGs +using CurveFit, StableRNGs using RegularizationTools t = [1.0, 2.0, 3.0, 4.0, 5.0] @@ -67,10 +67,10 @@ end t = range(-10, stop = 10, length = 40) u = model(t, [1.0, 2.0]) + 0.01 * randn(rng, length(t)) p0 = [0.5, 0.5] - A = Curvefit(u, t, model, p0, LBFGS()) + A = Curvefit(u, t, model, p0) @test startswith( sprint(io -> show(io, MIME"text/plain"(), A)), - "Curvefit with 40 points, using LBFGS\n" + "Curvefit with 40 points.\n" ) end From 58b6f6664349c94fc0cfe1c2ad6bf0a745b14a89 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 19 Feb 2026 07:25:10 +0000 Subject: [PATCH 4/8] refactor: remove unnecessary file --- ext/DataInterpolationsCurveFitext.jl | 78 ---------------------------- 1 file changed, 78 deletions(-) delete mode 100644 ext/DataInterpolationsCurveFitext.jl diff --git a/ext/DataInterpolationsCurveFitext.jl b/ext/DataInterpolationsCurveFitext.jl deleted file mode 100644 index df377648..00000000 --- a/ext/DataInterpolationsCurveFitext.jl +++ /dev/null @@ -1,78 +0,0 @@ -module DataInterpolationsCurveFitExt - -using DataInterpolations -import DataInterpolations: munge_data, - Curvefit, CurvefitCache, _interpolate, get_show, derivative, - ExtrapolationError, - integral, IntegralNotFoundError, DerivativeNotFoundError - -using CurveFit, ForwardDiff - -### Curvefit -function Curvefit( - u, - t, - model, - p0, - alg = nothing, - box = false, - lb = nothing, - ub = nothing; - extrapolate = false - ) - u, t = munge_data(u, t) - if box && (lb === nothing || ub === nothing) - error("lower or upper bound should not be nothing") - end - prob = CurveFit.NonlinearCurveFitProblem((p, x) -> model(x, p), p0, t, u) - sol = if isnothing(alg) - CurveFit.solve(prob) - else - CurveFit.solve(prob, alg) - end - pmin = sol.u - CurvefitCache(u, t, model, p0, ub, lb, alg, pmin, extrapolate) -end - -# Curvefit -function _interpolate( - A::CurvefitCache{<:AbstractVector{<:Number}}, - t::Union{AbstractVector{<:Number}, Number} - ) - ((t < A.t[1] || t > A.t[end]) && !A.extrapolate) && - throw(ExtrapolationError()) - return A.m(t, A.pmin) -end - -function _interpolate( - A::CurvefitCache{<:AbstractVector{<:Number}}, - t::Union{AbstractVector{<:Number}, Number}, - i - ) - return _interpolate(A, t), i -end - -function derivative( - A::CurvefitCache{<:AbstractVector{<:Number}}, - t::Union{AbstractVector{<:Number}, Number}, order = 1 - ) - ((t < A.t[1] || t > A.t[end]) && !A.extrapolate) && throw(ExtrapolationError()) - order > 2 && throw(DerivativeNotFoundError()) - order == 1 && return ForwardDiff.derivative(x -> A.m(x, A.pmin), t) - return ForwardDiff.derivative(t -> ForwardDiff.derivative(x -> A.m(x, A.pmin), t), t) -end - -function get_show(A::CurvefitCache) - return "Curvefit" * - " with $(length(A.t)) points.\n" -end - -function integral(A::CurvefitCache{<:AbstractVector{<:Number}}, t::Number) - throw(IntegralNotFoundError()) -end - -function integral(A::CurvefitCache{<:AbstractVector{<:Number}}, t1::Number, t2::Number) - throw(IntegralNotFoundError()) -end - -end # module From 63140c48a6a4ae9cdaa3c709ce1c30fa5df1ef7d Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 19 Feb 2026 15:05:22 +0530 Subject: [PATCH 5/8] docs: update Curvefit --- docs/Project.toml | 5 +++-- docs/src/methods.md | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 8dcac1fd..9ae7c326 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,8 +1,9 @@ [deps] +CurveFit = "5a033b19-8c74-5913-a970-47c3779ef25c" +DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" -Optim = "429524aa-4258-5aef-a3af-852621145aeb" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" RegularizationTools = "29dad682-9a27-4bc3-9c72-016788665182" @@ -10,10 +11,10 @@ StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] +CurveFit = "1.5" Documenter = "1" ModelingToolkit = "9, 10.1, 11.1" ModelingToolkitStandardLibrary = "2" -Optim = "1, 2.0" OrdinaryDiffEq = "6" Plots = "1" RegularizationTools = "0.6" diff --git a/docs/src/methods.md b/docs/src/methods.md index 6a9a8432..2f55e1a4 100644 --- a/docs/src/methods.md +++ b/docs/src/methods.md @@ -263,8 +263,8 @@ match our data. Let's start with the guess of every `p` being zero, that is `p=ones(4)`. Then we would fit this curve using: ```@example tutorial -using Optim -A = Curvefit(u, t, m, ones(4), LBFGS()) +using CurveFit +A = Curvefit(u, t, m, ones(4)) plot(A) ``` @@ -280,7 +280,7 @@ parameters. For example, with `p=zeros(4)` as the initial parameters, the fit is not good: ```@example tutorial -A = Curvefit(u, t, m, zeros(4), LBFGS()) +A = Curvefit(u, t, m, zeros(4)) plot(A) ``` From ef84155afff3192bb11f6b4757f3795630b9565e Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 19 Feb 2026 15:05:32 +0530 Subject: [PATCH 6/8] chore: format --- ext/DataInterpolationsCurveFitExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/DataInterpolationsCurveFitExt.jl b/ext/DataInterpolationsCurveFitExt.jl index df377648..e4a72b41 100644 --- a/ext/DataInterpolationsCurveFitExt.jl +++ b/ext/DataInterpolationsCurveFitExt.jl @@ -31,7 +31,7 @@ function Curvefit( CurveFit.solve(prob, alg) end pmin = sol.u - CurvefitCache(u, t, model, p0, ub, lb, alg, pmin, extrapolate) + return CurvefitCache(u, t, model, p0, ub, lb, alg, pmin, extrapolate) end # Curvefit From dc94a70b52e85572a32ea3e984fa71fecbae6d30 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Wed, 4 Mar 2026 09:17:18 +0100 Subject: [PATCH 7/8] refactor: add lb, ub to CurveFitProblem --- ext/DataInterpolationsCurveFitExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/DataInterpolationsCurveFitExt.jl b/ext/DataInterpolationsCurveFitExt.jl index e4a72b41..d1a45494 100644 --- a/ext/DataInterpolationsCurveFitExt.jl +++ b/ext/DataInterpolationsCurveFitExt.jl @@ -24,7 +24,7 @@ function Curvefit( if box && (lb === nothing || ub === nothing) error("lower or upper bound should not be nothing") end - prob = CurveFit.NonlinearCurveFitProblem((p, x) -> model(x, p), p0, t, u) + prob = CurveFit.NonlinearCurveFitProblem((p, x) -> model(x, p), p0, t, u; lb = lb, ub = ub) sol = if isnothing(alg) CurveFit.solve(prob) else From e6b6f2046f21c90da9df40c88a41b486b671f0bf Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Wed, 4 Mar 2026 09:17:32 +0100 Subject: [PATCH 8/8] test: add test with bounds --- test/interpolation_tests.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/interpolation_tests.jl b/test/interpolation_tests.jl index aea0ec54..01ae786c 100644 --- a/test/interpolation_tests.jl +++ b/test/interpolation_tests.jl @@ -1245,6 +1245,10 @@ end @test A(15.0) == model(15.0, A.pmin) A = Curvefit(u, t, model, p0, LevenbergMarquardt()) @test_throws DataInterpolations.ExtrapolationError A(15.0) + + # With lb, ub + A = Curvefit(u, t, model, p0, LevenbergMarquardt(), false, [0.0, 0.0], [1.0, 1.0]) + @test all(0.0 .<= A.pmin .<= 1.0) end @testset "Type of vector returned" begin