LsqFit.curve_fit not working

What am I doing wrong here?

using LsqFit
polynom(x,p) = p[1].+p[2].*x
curve_fit(polynom, 1:100,polynom(1:100,[10 10]),[0.5 0.5])

And I get this error

ERROR: MethodError: no method matching levenberg_marquardt(::NLSolversBase.OnceDifferentiable{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Matrix{Float64}, Matrix{Float64}}, ::Matrix{Float64})

You might have used a 2d row vector where a 1d column vector was required.
Note the difference between 1d column vector [1,2,3] and 2d row vector [1 2 3].
You can convert to a column vector with the vec() function.
Closest candidates are:
  levenberg_marquardt(::NLSolversBase.OnceDifferentiable, ::AbstractVector{T}; x_tol, g_tol, maxIter, lambda, tau, lambda_increase, lambda_decrease, min_step_quality, good_step_quality, show_trace, lower, upper, avv!) where T at C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\levenberg_marquardt.jl:34
Stacktrace:
 [1] lmfit(R::NLSolversBase.OnceDifferentiable{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Matrix{Float64}, Matrix{Float64}}, p0::Matrix{Float64}, wt::Vector{Int64}; autodiff::Symbol, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ LsqFit C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\curve_fit.jl:68
 [2] lmfit(R::NLSolversBase.OnceDifferentiable{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, Matrix{Float64}, Matrix{Float64}}, p0::Matrix{Float64}, wt::Vector{Int64})
   @ LsqFit C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\curve_fit.jl:68
 [3] lmfit(f::LsqFit.var"#18#20"{typeof(polynom), UnitRange{Int64}, StepRangeLen{Int64, Int64, Int64, Int64}}, p0::Matrix{Float64}, wt::Vector{Int64}; autodiff::Symbol, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ LsqFit C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\curve_fit.jl:64
 [4] lmfit(f::Function, p0::Matrix{Float64}, wt::Vector{Int64})
   @ LsqFit C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\curve_fit.jl:61
 [5] curve_fit(model::typeof(polynom), xdata::UnitRange{Int64}, ydata::StepRangeLen{Int64, Int64, Int64, Int64}, p0::Matrix{Float64}; inplace::Bool, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ LsqFit C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\curve_fit.jl:115
 [6] curve_fit(model::Function, xdata::UnitRange{Int64}, ydata::StepRangeLen{Int64, Int64, Int64, Int64}, p0::Matrix{Float64})
   @ LsqFit C:\Users\torfi\.julia\packages\LsqFit\hgZQe\src\curve_fit.jl:106
 [7] top-level scope
   @ REPL[3]:1

Shouldn’t this just work? To use a function to fit to the output of that function? I am so confused.

The method levenberg_marquardt wants a column Vector for its input. You’re supplying a row vector in the form of a Matrix, a 2D-Array.

Observe the difference below:

julia> [10 10]
1Ă—2 Matrix{Int64}:
 10  10

julia> [10, 10]
2-element Vector{Int64}:
 10
 10

Try this

using LsqFit
polynom(x,p) = p[1].+p[2].*x
result = curve_fit(polynom, collect(1:100) ,polynom(1:100,[10, 10]),[0.5, 0.5]);
result.param

julia> result.param
2-element Vector{Float64}:
 10.000000000000025
 10.0

I agree though. This should be more flexible and user friendly.

Thank you sososo much! When I send both my x and y data through collect first it works!
I feel like I need to make a bug report though. Because it seems to me that this problem is literally impossible to debug in my case.
You see, when I do a typeof for the data I was trying to send to curve_fit I get Vector{Int64} (alias for Array{Int64, 1}) and when I wrap it in collect() I also get Vector{Int64} (alias for Array{Int64, 1}) . The two are identical as far as I can tell. What’s changing? Since one works and one doesn’t.

What else can I look out for in the future?

julia> typeof(1:100)
UnitRange{Int64}

julia> typeof(collect(1:100))
Vector{Int64} (alias for Array{Int64, 1})

The type changes here.

Note that I changed two things:

  1. I added commas in the array literals them a Vector instead of a Matrix.
  2. I added collect to make the UnitRange a Vector.

This is almost certainly a bug in LsqFit.jl, so I’ll encourage you to file it (referencing this thread).

curve_fit is written to accept any AbstractArray type for its second argument, which are things that behave like arrays, include ranges like 1:100 as in your case. But then, internally, it seems like it tries to assign to (a copy of) that argument - you can’t assign to ranges, hence your error.

@mkitti 's suggestion to use collect changes 1:100 into [1, 2, 3, 4, ... 98, 99, 100] i.e. it changes the Range into a Vector type, which can be assigned to, so curve_fit no longer errors. However, this shouldn’t be necessary since it should work for any AbstractArray subtype, so should be considered a bug in curve_fit.

collect takes anything that can be iterated upon, and converts it into a Vector by allocating memory for all elements of the iterator. Generally, using collect is a bad habit that newcomers in Julia sometimes pick up, just because it produces nicer, easier-to-understand output in the REPL compared to many iterators. However, in many cases you can use the Range or other iterator directly in your code, pass it as arguments to other functions, etc. It’s unfortunate that you encountered this bug that requires a collect to solve, but keep in mind that in general, using collect unnecessarily can allocate a lot of memory and slow down your code.

1 Like