Skip to content

Commit 1e029fc

Browse files
authored
Merge pull request #27 from TensorKitchen/lm
Add Riemannian Levenberg-Marquardt solver via Manopt.
2 parents adbc321 + caa638a commit 1e029fc

14 files changed

Lines changed: 1100 additions & 52 deletions

File tree

src/api/approx.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,12 @@ For the generic join path:
127127
- `rgd_fixed`: Riemannian gradient descent with fixed step size
128128
- `rcg`: Riemannian conjugate gradient
129129
- `lbfgs`: Limited-memory quasi-Newton
130+
- `lm`: Levenberg-Marquardt on residual/Jacobian least squares
130131
131132
##Notes##
132133
* `:als` is not a solver option for `approx(...)`. However, if `approx(...)` auto-routes to `cpd(...)` or `btd(...)`, then those specialized pipelines may support ALS separately.
133134
* `warm_steps` and `warm_init` are not part of the generic `approx(...)` path. Generic joins start from random initial point and then use manifold solvers for refinement.
134-
* For generic mixed joins, use manifold solvers such as `:rgd`, `:rcg`, or `:lbfgs`.
135+
* For generic mixed joins, use manifold solvers such as `:rgd`, `:rcg`, `:lbfgs`, or `:lm`.
135136
"""
136137
function _approx_manifold_collection(
137138
dispatch::AutoApproxDispatch,

src/api/btd.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ refines it. Returns a [`BTDResult`](@ref).
159159
- `:als`: Alternating least squares.
160160
- `:rcg`: Riemannian conjugate gradient.
161161
- `:lbfgs`: Limited-memory quasi-Newton refinement.
162+
- `:lm`: Levenberg-Marquardt refinement.
162163
163164
## Extended Options
164165

src/api/cpd.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -579,13 +579,13 @@ end
579579
function _validate_cpd_solver_supported(solver::AbstractSolver)
580580
throw(
581581
ArgumentError(
582-
"Unsupported CPD solver $(typeof(solver)). Use :als, :rgd, :rgd_fixed, :rcg, or :lbfgs.",
582+
"Unsupported CPD solver $(typeof(solver)). Use :als, :rgd, :rgd_fixed, :rcg, :lbfgs, or :lm.",
583583
),
584584
)
585585
end
586586

587587
_validate_cpd_solver_supported(
588-
::Union{ALSSolver,RGDSolver,RGDFixedSolver,RCGSolver,LBFGSSolver},
588+
::Union{ALSSolver,RGDSolver,RGDFixedSolver,RCGSolver,LBFGSSolver,LMSolver},
589589
) = nothing
590590

591591
function _validate_cpd_solver_options(
@@ -750,7 +750,7 @@ end
750750

751751
function _cpd_manifold_grad_tol(
752752
model::JoinModel{<:AbstractFloat,<:CPDBackend},
753-
solver::Union{RGDSolver,RGDFixedSolver,RCGSolver,LBFGSSolver},
753+
solver::Union{RGDSolver,RGDFixedSolver,RCGSolver,LBFGSSolver,LMSolver},
754754
tol::Real,
755755
)
756756
return tol
@@ -992,6 +992,7 @@ If `r` is omitted, uses the smallest tensor mode as a heuristic rank.
992992
- `rgd_fixed`: Riemannian gradient descent with fixed step size
993993
- `rcg`: Riemannian conjugate gradient
994994
- `lbfgs`: Limited-memory Riemannian quasi-Newton
995+
- `lm`: Levenberg-Marquardt using residual/Jacobian least squares
995996
- `als`: Alternating Least Squares
996997
997998
## Extended Options

src/backend.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ include("solvers/rgd.jl")
88
include("solvers/btd_tsd.jl")
99
include("solvers/rcg.jl")
1010
include("solvers/lbfgs.jl")
11+
include("solvers/lm.jl")
1112
include("results/reconstruct.jl")
1213
include("results/rel_error.jl")
1314
include("solvers/solve_dispatch.jl")

src/core/model.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# core/model.jl — Top-level decomposition model interface
2-
export AbstractDecompositionModel, rgrad, supports_rgrad, tensor, cost, post_step!
2+
export AbstractDecompositionModel,
3+
manifold, initial_point, egrad, rgrad, supports_rgrad, tensor, cost, post_step!
34
"""
45
AbstractDecompositionModel{T}
56

src/core/unpack_points.jl

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# core/unpack_points.jl — Point unpacking and legacy/vector interop
22
export pack_point_rank1,
3-
unpack_point_rank1, pack_point_rankr, unpack_point_rankr, unpack_point_rankr_components
3+
unpack_point_rank1,
4+
pack_point_rankr,
5+
unpack_point_rankr,
6+
unpack_point_rankr_components,
7+
canonical_to_joinpoint,
8+
joinpoint_to_canonical
49

510
function unpack_rankr_native(p, dims::NTuple{N,Int}, r::Int) where {N}
611
parts = normalize_rankr_native_point(p, dims, r)
@@ -72,6 +77,43 @@ function unpack_rankr_join(p, dims::NTuple{N,Int}, r::Int) where {N}
7277
return λ, U
7378
end
7479

80+
"""
81+
canonical_to_joinpoint(λ, U)
82+
canonical_to_joinpoint(p_canonical, dims, r)
83+
84+
Convert a CPD point from canonical factor-matrix storage to the native
85+
rank-`r` Segre join point layout used by generic `JoinModel((Segre, ...), A)`.
86+
87+
The conversion preserves the represented tensor but may renormalize component
88+
gauges the same way `pack_rankr_native` does.
89+
"""
90+
function canonical_to_joinpoint(
91+
λ::AbstractVector{T},
92+
U::Vector{<:AbstractMatrix{T}},
93+
) where {T<:AbstractFloat}
94+
r = length(λ)
95+
return pack_rankr_native(λ, U, r)
96+
end
97+
98+
function canonical_to_joinpoint(p, dims::NTuple{N,Int}, r::Int) where {N}
99+
λ, U = unpack_rankr_canonical(p, dims, r)
100+
return canonical_to_joinpoint(λ, U)
101+
end
102+
103+
"""
104+
joinpoint_to_canonical(p_join, dims, r)
105+
106+
Convert a native Segre join point layout back to the canonical CPD point
107+
layout `(λ, (u₁¹, …, uᵣ¹), …, (u₁ᴺ, …, uᵣᴺ))`.
108+
109+
The conversion preserves the represented tensor but may renormalize component
110+
gauges the same way `pack_rankr_canonical` does.
111+
"""
112+
function joinpoint_to_canonical(p, dims::NTuple{N,Int}, r::Int) where {N}
113+
λ, U = unpack_rankr_native(p, dims, r)
114+
return pack_rankr_canonical(λ, U, r)
115+
end
116+
75117
function pack_point_rank1::T, U::Vector{Vector{T}}) where {T<:AbstractFloat}
76118
parts = Vector{Vector{T}}(undef, length(U) + 1)
77119
parts[1] = T[λ]

0 commit comments

Comments
 (0)