diff --git a/src/DynareSolvers.jl b/src/DynareSolvers.jl index 031e5390873b9cee538ee4e3954ff75c8700283f..be2c3f5955d811fd1fe26954fa30103a2b8c7f10 100644 --- a/src/DynareSolvers.jl +++ b/src/DynareSolvers.jl @@ -21,25 +21,46 @@ module DynareSolvers export trustregion +const p1 = .1 +const p5 = .5 +const p001 = .001 +const p0001 = .0001 +const macheps = eps(Float64) + +type TrustRegionWA + x::Vector{Float64} # Vector of unknowns + xx::Vector{Float64} # Vector of unknowns + fval0::Vector{Float64} # residuals of the non linear equations + fval1::Vector{Float64} # residuals of the non linear equations (next) + fjac::Matrix{Float64} # jacobian matrix of the non linear equations + fjaccnorm::Vector{Float64} # norms of the columns of the Jacobian matrix + fjaccnorm__::Vector{Float64} # copy of fjaccnorm + w::Vector{Float64} # working array + p::Vector{Float64} # direction + s::Vector{Float64} +end + +function TrustRegionWA(n::Int) + TrustRegionWA(Vector{Float64}(n), Vector{Float64}(n), Vector{Float64}(n), Vector{Float64}(n), Matrix{Float64}(n,n), + Vector{Float64}(n), Vector{Float64}(n), Vector{Float64}(n), Vector{Float64}(n), Vector{Float64}(n)) +end + + """ dogleg!(x::Vector{Float64}, r::Matrix{Float64}, b::Vector{Float64}, d::Vector{Float64}, δ::Float64) -Given an `n` by `n` matrix `r`, an `n` by 1 vector `d` with non zero entries, an `n` by `1` +Given an `n` by `n` matrix `r`, an `n` by 1 vector `d` with non zero entries, an `n` by `1` vector `b`, and a positive number δ, the problem is to determine the convex combination `x` of the gauss-newton and scaled gradient directions that minimizes (r*x - b) in the least squares sense, subject to the restriction that the euclidean norm of d*x be at most delta. """ -function dogleg!(x::Vector{Float64}, r::Matrix{Float64}, b::Vector{Float64}, d::Vector{Float64}, δ::Float64) +function dogleg!(x::Vector{Float64}, r::Matrix{Float64}, b::Vector{Float64}, d::Vector{Float64}, δ::Float64, s::Vector{Float64}) n = length(x) - @assert length(d)==n - @assert length(b)==n - @assert size(r,1)==n - @assert size(r,2)==n # Compute the Gauss-Newton direction. x .= r\b # Compute norm of scaled x. qnorm = zero(Float64) - @inbounds for i = 1:n + @inbounds for i=1:n qnorm += (d[i]*x[i])^2 end qnorm = sqrt(qnorm) @@ -47,17 +68,35 @@ function dogleg!(x::Vector{Float64}, r::Matrix{Float64}, b::Vector{Float64}, d:: # Gauss-Newton direction is acceptable. There is nothing to do here. else # Gauss-Newton direction is not acceptable… - # Compute the scale gradient direction. - s = (r'*b)./d + # Compute the scale gradient direction and its norm + gnorm = zero(Float64) + @inbounds for i=1:n + s[i] = zero(Float64) + @inbounds for j=1:n + s[i] += r[j,i]*b[j] + end + s[i] /= d[i] + gnorm += s[i]*s[i] + end + gnorm = sqrt(gnorm) # Compute the norm of the scaled gradient direction. - gnorm = norm(s) + # gnorm = norm(s) if gnorm>0 # Normalize and rescale → gradient direction. + temp0 = zero(Float64) @inbounds for i = 1:n - s[i] = s[i]/(gnorm*d[i]) + s[i] /= gnorm*d[i] end - temp = norm(r*s) - sgnorm = gnorm/(temp*temp) + temp0 = zero(Float64) + @inbounds for i=1:n + temp1 = zero(Float64) + @inbounds for j=1:n + temp1 += r[i,j]*s[j] + end + temp0 += temp1*temp1 + end + temp0 = sqrt(temp0) + sgnorm = gnorm/(temp0*temp0) if sgnorm<=δ # The scaled gradient direction is not acceptable… # Compute the point along the dogleg at which the @@ -79,40 +118,44 @@ function dogleg!(x::Vector{Float64}, r::Matrix{Float64}, b::Vector{Float64}, d:: end # Form the appropriate convex combination of the Gauss-Newton direction and the # scaled gradient direction. - x .= α*x + (one(Float64)-α)*min(sgnorm, δ)*s + temp1 = (one(Float64)-α)*min(sgnorm, δ) + @inbounds for i = 1:n + x[i] = α*x[i] + temp1*s[i] + end end end """ - trustregion(fj::Function, y0::Vector{Float64}, gstep::Float64, tolf::Float64, tolx::Float64, maxiter::Int) + trustregion(f!::Function, j!::Function, y0::Vector{Float64}, tolf::Float64, tolx::Float64, maxiter::Int) Solves a system of nonlinear equations of several variables using a trust region method. """ function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Float64, tolf::Float64, maxiter::Int) - macheps = eps(Float64) + wa = TrustRegionWA(length(x0)) + info = trustregion(f!, j!, x0, tolx, tolf, maxiter, wa) + return wa.x, info +end + +""" + trustregion(f!::Function, j!::Function, y0::Vector{Float64}, tolf::Float64, tolx::Float64, maxiter::Int, wa::TrustRegionWA) + +Solves a system of nonlinear equations of several variables using a trust region method. This version requires a last argument of type `TrustRegionWA` +which holds various working arrays. This version of the solver does not instantiate any array. Results of the solver are available in `wa.x` if `info=1`. +""" +function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Float64, tolf::Float64, maxiter::Int, wa::TrustRegionWA) n, iter, info = length(x0), 1, 0 - p1, p5, p001, p0001 = .1, .5, .001, .0001 - t1, t2, t3, t4 = .1, .5, .001, 1.0e-4 fnorm, fnorm1 = one(Float64), one(Float64) - x = copy(x0) - xx = Vector{Float64}(n) - fval0 = Vector{Float64}(n) # residuals of the non linear equations - fval1 = Vector{Float64}(n) # residuals of the non linear equations (next) - fjac = Matrix{Float64}(n,n) # jacobian matrix of the non linear equations - fjaccnorm = Vector{Float64}(n) # norms of the columns of the Jacobian matrix - fjaccnorm__ = Vector{Float64}(n) # copy of fjaccnorm - w = Vector{Float64}(n) - p = Vector{Float64}(n) + wa.x .= copy(x0) # Initial evaluation of the residuals (and compute the norm of the residuals) try - f!(fval0, x) - fnorm = norm(fval0) + f!(wa.fval0, wa.x) + fnorm = norm(wa.fval0) catch error("The system of nonlinear equations cannot be evaluated on the initial guess!") end # Initial evaluation of the Jacobian try - j!(fjac, x) + j!(wa.fjac, wa.x) catch error("The Jacobian of the system of nonlinear equations cannot be evaluated on the initial guess!") end @@ -124,41 +167,43 @@ function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Floa while iter<=maxiter && info==0 # Compute columns norm for the Jacobian matrix. @inbounds for i=1:n - fjaccnorm[i] = zero(Float64) + wa.fjaccnorm[i] = zero(Float64) @inbounds for j = 1:n - fjaccnorm[i] += fjac[j,i]*fjac[j,i] + wa.fjaccnorm[i] += wa.fjac[j,i]*wa.fjac[j,i] end - fjaccnorm[i] = sqrt(fjaccnorm[i]) + wa.fjaccnorm[i] = sqrt(wa.fjaccnorm[i]) end if iter==1 # On the first iteration, calculate the norm of the scaled vector of unknwonws x # and initialize the step bound δ. Scaling is done according to the norms of the # columns of the initial jacobian. @inbounds for i = 1:n - fjaccnorm__[i] = fjaccnorm[i] + wa.fjaccnorm__[i] = wa.fjaccnorm[i] end - fjaccnorm__[find(abs.(fjaccnorm__).<macheps)] = one(Float64) + wa.fjaccnorm__[find(abs.(wa.fjaccnorm__).<macheps)] = one(Float64) xnorm = zero(Float64) @inbounds for i=1:n - xnorm += (fjaccnorm__[i]*x[i])^2 + xnorm += (wa.fjaccnorm__[i]*wa.x[i])^2 + end + δ = sqrt(xnorm) + if δ<macheps + δ = one(Float64) end - xnorm = sqrt(xnorm) - δ = max(xnorm, one(Float64)) else - fjaccnorm__ = max.(.1*fjaccnorm__, fjaccnorm) + wa.fjaccnorm__ .= max.(.1*wa.fjaccnorm__, wa.fjaccnorm) end # Determine the direction p (with trust region model defined in dogleg routine). - dogleg!(p, fjac, fval0, fjaccnorm__, δ) + dogleg!(wa.p, wa.fjac, wa.fval0, wa.fjaccnorm__, δ, wa.s) @inbounds for i=1:n - w[i] = fval0[i] + wa.w[i] = wa.fval0[i] @inbounds for j = 1:n - w[i] -= fjac[i,j]*p[j] + wa.w[i] -= wa.fjac[i,j]*wa.p[j] end end # Compute the norm of p. pnorm = zero(Float64) @inbounds for i=1:n - pnorm += (fjaccnorm__[i]*p[i])^2 + pnorm += (wa.fjaccnorm__[i]*wa.p[i])^2 end pnorm = sqrt(pnorm) # On first iteration adjust the initial step bound. @@ -167,10 +212,10 @@ function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Floa end # Evaluate the function at xx = x+p and calculate its norm. @inbounds for i = 1:n - xx[i] = x[i]-p[i] + wa.xx[i] = wa.x[i]-wa.p[i] end - f!(fval1, xx) - fnorm1 = norm(fval1) + f!(wa.fval1, wa.xx) + fnorm1 = norm(wa.fval1) # Compute the scaled actual reduction. if fnorm1<fnorm actualreduction = one(Float64)-(fnorm1/fnorm)^2 @@ -179,7 +224,7 @@ function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Floa end # Compute the scaled predicted reduction and the ratio of the actual to the # predicted reduction. - t = norm(w) + t = norm(wa.w) ratio = zero(Float64) if t<fnorm predictedreduction = one(Float64) - (t/fnorm)^2 @@ -203,9 +248,9 @@ function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Floa # Succesfull iteration. Update x, xnorm, fval0, fnorm and fjac. xnorm = zero(Float64) @inbounds for i=1:n - x[i] = xx[i] - xnorm += (fjaccnorm__[i]*x[i])^2 - fval0[i] = fval1[i] + wa.x[i] = wa.xx[i] + xnorm += (wa.fjaccnorm__[i]*wa.x[i])^2 + wa.fval0[i] = wa.fval1[i] end xnorm = sqrt(xnorm) fnorm = fnorm1 @@ -235,14 +280,14 @@ function trustregion(f!::Function, j!::Function, x0::Vector{Float64}, tolx::Floa continue end # Compute the jacobian for next the iteration. - j!(fjac, x) + j!(wa.fjac, wa.x) iter += 1 end if info==0 && iter>maxiter info = 2 - fill!(x, Inf) + fill!(wa.x, Inf) end - return x, info + return info end end