API
Index
The global API index is as follows:
NLS_Solver.AbstractNLS
NLS_Solver.Abstract_BC_QuadSolver_Result
NLS_Solver.Abstract_BC_Solver_Conf
NLS_Solver.Abstract_BC_Solver_Result
NLS_Solver.Abstract_Solver_Conf
NLS_Solver.Abstract_Solver_Result
NLS_Solver.BoundConstraint_Enum
NLS_Solver.BoundConstraints
NLS_Solver.LevenbergMarquardt_BC_Conf
NLS_Solver.LevenbergMarquardt_BC_Result
NLS_Solver.LevenbergMarquardt_Conf
NLS_Solver.LevenbergMarquardt_Result
NLS_Solver.NLS_ForwardDiff
NLS_Solver.check_first_order
NLS_Solver.clean_τ!
NLS_Solver.compute_δL_constrained
NLS_Solver.compute_δL_unconstrained
NLS_Solver.compute_δf
NLS_Solver.converged
NLS_Solver.converged
NLS_Solver.create_NLS_problem_using_ForwardDiff
NLS_Solver.eval_nls_fobj
NLS_Solver.eval_nls_∇fobj
NLS_Solver.eval_nls_∇∇fobj
NLS_Solver.eval_r
NLS_Solver.eval_r_J
NLS_Solver.initialize_x_Z
NLS_Solver.iteration_count
NLS_Solver.iteration_count
NLS_Solver.lower_bound
NLS_Solver.multiplier_τ
NLS_Solver.objective_value
NLS_Solver.objective_value
NLS_Solver.parameter_size
NLS_Solver.project!
NLS_Solver.quadratic_subproblem
NLS_Solver.residue_size
NLS_Solver.restrict_to_inactive!
NLS_Solver.set_max_iteration!
NLS_Solver.set_max_iteration!
NLS_Solver.set_ε_grad_Inf_norm!
NLS_Solver.set_ε_grad_Inf_norm!
NLS_Solver.set_ε_step_Inf_norm!
NLS_Solver.set_ε_step_Inf_norm!
NLS_Solver.solution
NLS_Solver.solution
NLS_Solver.solve
NLS_Solver.solve
NLS_Solver.update_Z!
NLS_Solver.update_x!
NLS_Solver.upper_bound
Documentation
Public
NLS_Solver.AbstractNLS
— Typeabstract type AbstractNLS end
Defines an abstract non-linear least squares problem (NLS). In our context such problem is essentially a differentiable function $\mathbf{r}$:
\[\mathbf{r}: \theta\in\mathbb{R}^{n_θ}\mapsto \mathbf{r}(\mathbf{\theta})\in\mathbb{R}^{n_S}\]
where:
- $\mathbf{r}(\mathbf{θ})∈\mathbb{R}^{n_S}$ is the residue vector,
- $\mathbf{θ}∈\mathbb{R}^{n_θ}$ is the parameter vector to be optimized
The objective function to minimize is:
\[f(θ)=\frac{1}{2}\| \mathbf{r}(θ) \|^2\]
The classical approach uses a linear approximation of $\mathbf{r}$:
\[\mathbf{r}(\mathbf{θ}+δ\mathbf{θ})\approx \mathbf{r}(\mathbf{θ}) + \mathbf{J}(\mathbf{θ})\cdot δ\mathbf{θ}\]
where $\mathbf{J}$ is the Jacobian:
\[\mathbf{J}_{i,j}=\partial_j r^i(\mathbf{θ}),\ i\in[1,n_S],\ j\in[1,n_θ]\]
This leads to
\[f(\mathbf{θ}+δ\mathbf{θ})\approx f(\mathbf{θ}) + \langle \nabla f, δ\mathbf{θ} \rangle + \frac{1}{2} \langle \nabla^2 f \cdot δ\mathbf{θ}, δ\mathbf{θ} \rangle\]
Where the gradient $\nabla f$ is $\mathbf{J}^t \mathbf{r}$ and the (approximate) Hessian $\nabla^2 f$ is $\mathbf{J}^t \mathbf{J}$.
To implement such model, you must define the following functions:
parameter_size
: returns $n_θ$residue_size
: returns $n_S$eval_r
: compute $\mathbf{r}$eval_r_J
: compute $(\mathbf{r}, \mathbf{J})$
NLS_Solver.Abstract_BC_Solver_Conf
— Typeabstract type Abstract_BC_Solver_Conf end
Abstract solver configuration. These are the solvers to be used to solve bound constrained nonlinear least squares:
\[\min\limits_{\theta_l \le \theta \le \theta_u } \frac{1}{2}\|r(\theta)\|^2\]
Implementations:
NLS_Solver.Abstract_BC_Solver_Result
— TypeThe structure returned by solve
when using the LevenbergMarquardt_BC_Conf
method.
NLS_Solver.Abstract_Solver_Conf
— Typeabstract type Abstract_Solver_Conf end
Abstract solver configuration. These are the solvers to be used to solve unconstrained nonlinear least squares:
\[\min\limits_\theta \frac{1}{2}\|r(\theta)\|^2\]
Implementations:
NLS_Solver.Abstract_Solver_Result
— Typeabstract type Abstract_Solver_Result end
This is the base type returned by the solve
method. It contains the information related to the founded solution.
Interface
Implementations
NLS_Solver.BoundConstraints
— TypeStore bound constraints $[l,u]$
Presence of NaN
component and the $l\le u$ condition is checked at construction time. Note however that some components can be infinite.
Constructors
The following constructors are available:
- Construct $[0.0,1.0]^n$
BoundConstraints(n)
- Construct $[T(0),T(1)]^n$ where components are of type
T
BoundConstraints(T,n)
- Construct $[l,u]$ where
l
andu
are lower and upper bound vectors
BoundConstraints(l,u)
Related functions
Base.eltype(bc::BoundConstraints{ELT}) where ELT
Base.axes(bc::BoundConstraints)
Base.length(bc::BoundConstraints)
Base.size(bc::BoundConstraints)
in(x::AbstractArray{<:Real,N},bc::BoundConstraints{<:Real,N}) where N
lower_bound(bc::BoundConstraints)
upper_bound(bc::BoundConstraints)
project!(x::AbstractArray{<:Real,N},bc::BoundConstraints{<:Real,N}) where N
Base.:-(bc::BoundConstraints,τ::AbstractArray)
Base.:+(bc::BoundConstraints,τ::AbstractArray)
NLS_Solver.LevenbergMarquardt_BC_Conf
— Typemutable struct LevenbergMarquardt_BC_Conf <: Abstract_Solver_Conf
...
end
Use this constructor
LevenbergMarquardt_BC_Conf()
to initialize the bound constrained Levenberg-Marquardt solver default configuration parameters.
To solve a problem with this method, you must then call solve(nls::AbstractNLS, θ_init::AbstractVector, bc::BoundConstraints, conf::Abstract_BC_Solver_Conf)
See:
NLS_Solver.LevenbergMarquardt_BC_Result
— Typestruct LevenbergMarquardt_BC_Result{T<:Real} <: Abstract_BC_Solver_Result
...
end
This structure subtypes Abstract_BC_Solver_Result
NLS_Solver.LevenbergMarquardt_Conf
— Typemutable struct LevenbergMarquardt_Conf <: Abstract_Solver_Conf
...
end
Use this constructor
LevenbergMarquardt_Conf()
to initialize the Levenberg-Marquardt solver default configuration parameters.
To solve a problem with this method, you must then call solve(nls::AbstractNLS, θ_init::AbstractVector, conf::Abstract_Solver_Conf)
See:
NLS_Solver.LevenbergMarquardt_Result
— Typestruct LevenbergMarquardt_Result{T<:Real} <: Abstract_Solver_Result
...
end
This structure subtypes Abstract_Solver_Result
NLS_Solver.NLS_ForwardDiff
— Typestruct NLS_ForwardDiff <: AbstractNLS
...
end
A specialization that uses the ForwardDiff
package to compute the Jacobian.
By comparison with AbstractNLS
you only have to define these functions:
parameter_size
: returns $n_θ$residue_size
: returns $n_S$eval_r
: computation of $\mathbf{r}$
NLS_Solver.converged
— MethodNLS_Solver.converged
— Methodconverged(::Abstract_BC_QuadSolver_Result)
Return true
if the solver converged
NLS_Solver.create_NLS_problem_using_ForwardDiff
— Methodcreate_NLS_problem_using_ForwardDiff(r::Function;domain_image_dim::Pair{Int,Int})
Creates an AbstractNLS
specialized instance where the eval_r_J
function is automatically defined using automatic differentiation.
r
is a function that maps a parameter vector θ to its residue. The Jacobian matrix is computed using theForwardDiff
package.domain_image_dim
is a pair of the formθ length => r length
that defines domain and codomain dimensions.
Usage example
An example defining the Rosenbrock function
\[\frac{1}{2}\|r(\theta)\|^2\text{ where }r = \sqrt{2} \left( \begin{array}{c} 1-\theta_1 \\ 10(\theta_2-\theta_1^2) \end{array} \right)\]
nls = create_NLS_problem_using_ForwardDiff(2 => 2) do θ
sqrt(2) sqrt(2)* [ 1-θ[1], 10*(θ[2]-θ[1]^2) ]* [ 1-θ[1], 10*(θ[2]-θ[1]^2) ]
end
NLS_Solver.eval_nls_fobj
— Methodeval_nls_fobj(r::AbstractVector{T}) -> f(θ)
Compute $f(θ)=\frac{1}{2}\| \mathbf{r}(\mathbf{θ}) \|^2$
NLS_Solver.eval_nls_∇fobj
— Methodeval_nls_∇fobj(r,J) -> ∇fobj
Compute the gradient: $\nabla f(\mathbf{θ}) = \mathbf{J}^t\mathbf{r}$
NLS_Solver.eval_nls_∇∇fobj
— Methodeval_nls_∇∇fobj(J) -> ∇∇fobj
Compute the (approximate) Hessian: $\nabla^2 f(\mathbf{θ}) = \mathbf{J}^t\mathbf{J}$
NLS_Solver.eval_r
— Methodeval_r(nls::AbstractNLS,
θ::AbstractVector) -> r
Compte the residual vector $\mathbf{r}$
NLS_Solver.eval_r_J
— Methodeval_r_J(nls::AbstractNLS,θ::AbstractVector) -> (r,J)
Compute the residual the vector $\mathbf{r}$ and its Jacobian $\mathbf{J}$
NLS_Solver.iteration_count
— Methoditeration_count(::Abstract_Solver_Result)
Return the number of consumed iteration
NLS_Solver.iteration_count
— Methoditeration_count(::Abstract_BC_QuadSolver_Result)
Return the number of consumed iteration
NLS_Solver.lower_bound
— MethodNLS_Solver.objective_value
— Methodobjective_value(::Abstract_Solver_Result)
Returns objective value at the point solution
.
NLS_Solver.objective_value
— Methodobjective_value(::Abstract_BC_QuadSolver_Result)
Returns objective value at the point solution
.
NLS_Solver.parameter_size
— Methodparameter_size(nls::AbstractNLS)
Return the dimension $n_θ$ of the parameter vector $θ$.
NLS_Solver.project!
— Methodproject!(x::AbstractArray{<:Real,N},bc::BoundConstraints{<:Real,N})
Project x
such that $x \in [l,u]$ is fullfiled.
See: BoundConstraints
NLS_Solver.residue_size
— Methodsample_size(nls::AbstractNLS)
Return the dimension $n_S$ of the residue vector $r$.
NLS_Solver.set_max_iteration!
— Methodset_max_iteration!(conf::LevenbergMarquardt_BC_Conf,
max_iter::Int)
Modify the maximum number of iterations
NLS_Solver.set_max_iteration!
— Methodset_max_iteration!(conf::LevenbergMarquardt_Conf,
max_iter::Int)
Modify the maximum number of iterations
NLS_Solver.set_ε_grad_Inf_norm!
— Methodset_ε_grad_Inf_norm!(conf::LevenbergMarquardt_BC_Conf,
ε_grad_Inf_norm::Float64)
Modify the stopping criterion $|\nabla f|_\infty\le\epsilon$
NLS_Solver.set_ε_grad_Inf_norm!
— Methodset_ε_grad_Inf_norm!(conf::LevenbergMarquardt_Conf,
ε_grad_Inf_norm::Float64)
Modify the stopping criterion $|\nabla f|_\infty\le\epsilon$
NLS_Solver.set_ε_step_Inf_norm!
— Methodset_ε_step_Inf_norm!(conf::LevenbergMarquardt_BC_Conf,
ε_step_Inf_norm::Float64)
Modify the stopping criterion $|\delta x|_\infty\le\epsilon$
NLS_Solver.set_ε_step_Inf_norm!
— Methodset_ε_step_Inf_norm!(conf::LevenbergMarquardt_Conf,
ε_step_Inf_norm::Float64)
Modify the stopping criterion $|\delta x|_\infty\le\epsilon$
NLS_Solver.solution
— MethodNLS_Solver.solution
— Methodsolution(::Abstract_BC_QuadSolver_Result)
Returns the founded solution
NLS_Solver.solve
— Methodsolve(nls::AbstractNLS,
θ_init::AbstractVector,
conf::Abstract_Solver_Conf)::Abstract_Solver_Result
Generic interface to solve an AbstractNLS
problem.
The used algorithm is defined through Abstract_Solver_Conf
specializations.
The method returns a Abstract_Solver_Result
specialization.
NLS_Solver.solve
— Methodsolve(nls::AbstractNLS,
θ_init::AbstractVector,
bc::BoundConstraints,
conf::Abstract_BC_Solver_Conf) -> Abstract_Solver_Result
Generic interface to solve a AbstractNLS
problem with bound constraints.
The used algorithm is defined through Abstract_BC_Solver_Conf
specializations.
The method returns a Abstract_BC_Solver_Result
specialization.
NLS_Solver.upper_bound
— MethodPrivate
NLS_Solver.Abstract_BC_QuadSolver_Result
— Typeabstract type Abstract_BC_QuadSolver_Result end
Quadratic solver result abstraction
NLS_Solver.BoundConstraint_Enum
— Type@enum(BoundConstraint_Enum,
ACTIVE_LB = -1,
INACTIVE_BC = 0,
ACTIVE_UB = +1)
An enum to store bound constraint state.
Base.:+
— MethodBase.:+(bc::BoundConstraints,τ::AbstractArray)
Translate bound constraints
\[[a+τ,b+τ] = [a,b]+τ\]
See: BoundConstraints
Base.:-
— MethodBase.:-(bc::BoundConstraints,τ::AbstractArray)
Translate bound constraints
\[[a-τ,b-τ] = [a,b]-τ\]
See: BoundConstraints
Base.axes
— MethodBase.eltype
— MethodBase.in
— MethodBase.length
— MethodBase.size
— MethodNLS_Solver.check_first_order
— Methodcheck_first_order(∇f::AbstractVector{<:Real},
xstar::AbstractVector{<:Real},
bc::BoundConstraints{<:Real,1})
check_first_order(Q::Symmetric{<:Real},
q::AbstractVector{<:Real},
xstar::AbstractVector{<:Real},
bc::BoundConstraints{<:Real,1})
Check First-Order Conditions (see Bound Constrained Optimization slides)
If $x^\star=\arg\min f(x), x\in[l,u]$ then:
\[\partial_i f(x^\star) = \left\{\begin{array}{ll} \ge 0, & \text{if } x^\star[i] = l[i] \\ = 0, & \text{if } l[i] \le x^\star[i] \le u[i] \\ \le 0, & \text{if } x^\star[i] = u[i] \\ \end{array} \right.\]
This is equivalent to:
\[x^\star = P_{[l,u]}(x^\star-\nabla f(x^\star))\]
According to the previous result, this function returns:
\[\max \mid x^\star - P_{[l,u]}(x^\star-(Q.x^\star+q)) \mid\]
For a local stationary point this quantity must be null
The second function is a wrapper that computes $∇f=Q.x^\star+q$
NLS_Solver.clean_τ!
— Methodclean_τ!(τ::AbstractArray{<:Real},
Z::AbstractArray{BoundConstraint_Enum})
By definition τ=-Qx-q. If the algorithm converged, then one must have τ[i]=0 when the constraint is inactive.
This function updates τ by overwriting τ[i]=0 when Z[i]=inactive.
NLS_Solver.compute_δL_constrained
— MethodSame idea than compute_δL_unconstrained
, however when bound constraints are present $h$ is such that:
\[(\nabla^2 f + \mu I)h + \nabla f + \tau = 0\]
it follows that:
\[δL = L(0)-L(h) = \frac{1}{2} \langle h, \mu h + \tau - \nabla f \rangle\]
NLS_Solver.compute_δL_unconstrained
— MethodCompute δL = L(0)-L(h) where L is the quadratic model
\[L(h)=f(\theta) + \langle \nabla f, h \rangle + \frac{1}{2}\langle \nabla^2 f h, h \rangle\]
with $f(\theta)=\frac{1}{2}\| r(θ) \|_2^2$, $\nabla f = J^t r$ and $\nabla^2 f = J^t J$
A direct computation gives:
\[δL = L(0)-L(h) = -\left( \langle J^tr, h \rangle + \frac{1}{2}\langle \nabla^2 f h, h \rangle \right)\]
However one can avoid the computation of $\nabla^2 f h$ if one uses the fact that $h$ is solution of:
\[(\nabla^2 f + \mu I)h + \nabla f = 0\]
With this hypothesis, one gets:
\[δL = L(0)-L(h) = \frac{1}{2} \langle h, \mu h - \nabla f \rangle\]
NLS_Solver.compute_δf
— MethodCompute true variation of the real model: $δf = \frac{1}{2}(r^t(θ)r(θ)-r^t(θ+h)r(θ+h))$
Contrary to $δL$ things are simpler. However a trick is to use an equivalent formulation:
\[δf = \frac{1}{2}(r^t(θ)r(θ)-r^t(θ+h)r(θ+h)) = \frac{1}{2}(r(θ)-r(θ+h))^t(r(θ)+r(θ+h))\]
that has a better numerical behavior.
NLS_Solver.initialize_x_Z
— Methodinitialize_x_Z(x_init::AbstractArray,
bc::BoundConstraints)
Create (x,Z) from initial guess x_init
and bound constraints bc
Z
is created by recording how x_init
fulfils the bound constraints bc
.
x
is the projection of x_init
on the bounded domain [l,b].
NLS_Solver.multiplier_τ
— Methodmultiplier_τ(::Abstract_BC_QuadSolver_Result)
Returns the multipliers stored in a compact form (see τ definition, TODO)
NLS_Solver.quadratic_subproblem
— MethodSolve
\[\min\limits_{h∈[θ^l-θ,θ^u-θ]}\frac{1}{2}h^t.(H+μI).h + ∇f^t.h\]
We use the quadratic model of $f$, the bound contraints are such that the step $h$ makes the update $x+h$ falls in the $[θ^l,θ^u]$ bound.
NLS_Solver.restrict_to_inactive!
— Methodrestrict_to_inactive!(Q::Symmetric,
q::AbstractVector,
Z::AbstractVector{BoundConstraint_Enum},
lb::AbstractVector{<:Real},
ub::AbstractVector{<:Real})
function restrict_to_inactive!(Q::Symmetric,
q::AbstractVector,
Z::AbstractVector{BoundConstraint_Enum},
bc::BoundConstraints{<:Real,1})
In-place modification of $(Q,q)$ that produces $(\tilde{Q},\tilde{q})$ such that the initial optimization problem:
\[\tilde{x} = \arg\min \frac{1}{2} x^t Q x + q^tx\]
under these constraints:
\[x[i] = \left\{\begin{array}{ll} l[i], & \text{if } Z[i] = -1 \\ u[i], & \text{if } Z[i] = +1 \end{array}\right.\]
is transformed into this linear system:
\[\tilde{Q}\tilde{x}+\tilde{q}=0\]
NLS_Solver.update_Z!
— Methodupdate_Z!(x::AbstractVector,
τ::AbstractVector,
Z::AbstractVector{BoundConstraint_Enum},
lb::AbstractVector,
ub::AbstractVector)
update_Z!(x::AbstractVector,
τ::AbstractVector,
Z::AbstractVector{BoundConstraint_Enum},
bc::BoundConstraints{<:Real,1})
This function updates Z
according to x
, τ
and bounds lb
, ub
values.
It also count how many changes have be done during this update.
No change means that the algorithm has converged.
Note: this function only modifies Z
and return the number of bad hypothesis.
NLS_Solver.update_x!
— Methodupdate_x!(x::AbstractArray,
Z::AbstractArray{BoundConstraint_Enum},
bc::BoundConstraints)
Update x value such that:
\[x[i] = \left\{\begin{array}{ll} l[i], & \text{if } Z[i] = -1 \\ u[i], & \text{if } Z[i] = +1 \end{array}\right.\]
When $Z[i]=0$ the $x[i]$ value is unaffected.