Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
36622d0
fix: apply JuliaFormatter output for cpd and rgd files
Copilot Jun 17, 2026
3e17731
make progress meter render correctly both phases
PBrdng Jun 17, 2026
a1cffff
export manopt helpers to a separate file
PBrdng Jun 17, 2026
b1c73cb
add helper documentation
PBrdng Jun 17, 2026
f76d069
clean code
PBrdng Jun 17, 2026
ec80afd
unify code
PBrdng Jun 17, 2026
861e9e9
make normalized_objective=true be the default
PBrdng Jun 17, 2026
abe7003
wire in relative norm natively
PBrdng Jun 17, 2026
6a5aa55
compactify progress meter
PBrdng Jun 17, 2026
7fb5557
print progress meter correctly
PBrdng Jun 17, 2026
9efdcde
run JuliaFormatter
PBrdng Jun 17, 2026
98c2d1e
format code with JuliaFormatter
sChoiKr Jun 17, 2026
a5a65cd
unify code
PBrdng Jun 17, 2026
c49f623
Merge branch 'default-convergence-gradient-tolerance' of https://gith…
PBrdng Jun 17, 2026
a9656a6
unify more code
PBrdng Jun 17, 2026
85a86d9
run JuliaFormatter
sChoiKr Jun 18, 2026
718dcbf
Format: apply JuliaFormatter
sChoiKr Jun 18, 2026
7973303
Merge pull request #24 from TensorKitchen/default-convergence-gradien…
sChoiKr Jun 18, 2026
ee58c0b
update Riemannian Conjugate Gradient tunable
Jun 18, 2026
ea4c9d2
Run JuliaFormatter
Jun 18, 2026
7baa4f9
Align format CI with project JuliaFormatter version
Jun 18, 2026
97958c8
Merge pull request #25 from TensorKitchen/rcg
sChoiKr Jun 18, 2026
eef8c4a
Add Riemannian Levenberg-Marquardt solver via Manopt.
Jun 19, 2026
6d47cff
LM solver accepts nested arrays for BTD, temporarily fix, eventually …
Jun 19, 2026
51e1a2d
refactor abstract joinModel for RLM
Jun 20, 2026
be39355
apply formatter
Jun 20, 2026
aebd1a3
Keep the RLM path for canonical CP (ALS)
Jun 20, 2026
55ebe67
add generic JoinModel fitness checks for RLM in basic_tests
Jun 20, 2026
caa638a
refactor JoinComponent Abstraction
Jun 20, 2026
58000f2
refactor JoinComponent Abstraction
Jun 20, 2026
4e1513a
relocate manopt helpers to manopt_helpers.jl
Jun 20, 2026
c2efa31
refine component abstraction with LM Jacobian matrix ordering test
Jun 20, 2026
3d50ab1
rename CPEmbedding to CPparam in AbstractCPParametrization
Jun 20, 2026
9918167
update Manopt version and fix the compatibility
Jul 2, 2026
410acb8
Implement LM updates before resolving conflicts
Jul 2, 2026
8d89c8b
Merge remote-tracking branch 'origin/main' into refactor/join-components
Jul 3, 2026
b532e42
juliaFormatter
Jul 3, 2026
cc9f9b5
fixed the version of JuliaFormatter for format_check
Jul 3, 2026
95ea4ec
rewrite the test for BTD LM to bypass
Jul 3, 2026
0e26ac8
Temporarily disable BTD LM frontend
Jul 3, 2026
a7286b5
Apply JuliaFormatter output
Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/format_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ jobs:
- uses: actions/checkout@v6
- name: Install JuliaFormatter and format
run: |
julia --project=. -e 'using Pkg; Pkg.instantiate()'
julia --project=. -e 'using JuliaFormatter; format(["./src", "./test"], verbose=true)'
julia -e 'using Pkg; Pkg.activate(temp=true); Pkg.add(PackageSpec(name="JuliaFormatter", version="2.10.1")); using JuliaFormatter; format(["./src", "./test"], verbose=true)'
- name: Format check
run: |
julia -e '
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2"

[compat]
JuliaFormatter = "2.8.5"
JuliaFormatter = "=2.10.1"
Manifolds = "0.11.28"
ManifoldsBase = "2.3.5"
Manopt = "0.5.37"
Manopt = "0.6"
ProgressMeter = "1.11.0"
RecursiveArrayTools = "4.3"
TensorOperations = "5.6"
Expand Down
15 changes: 14 additions & 1 deletion src/api/btd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ _btd_uses_warm_start(::AbstractSolver, ::BTDALSWarmStartInit) = true
_btd_should_polish(::ALSSolver, ::Integer) = false
_btd_should_polish(::AbstractSolver, polish_n::Integer) = polish_n > 0

function _reject_unsupported_btd_solver(solver_obj)
solver_obj isa LMSolver || return nothing
throw(
ArgumentError(
"BTD currently does not support LM refinement because the required Manopt operator path is not yet available for nested Tucker layouts. Use :rgd, :rcg, :lbfgs, :als, or :btd_tsd instead.",
),
)
end

function _btd_warm_start_result(
model::JoinModel{T,<:BTDBackend},
backend::BTDBackend,
Expand Down Expand Up @@ -159,7 +168,10 @@ refines it. Returns a [`BTDResult`](@ref).
- `:als`: Alternating least squares.
- `:rcg`: Riemannian conjugate gradient.
- `:lbfgs`: Limited-memory quasi-Newton refinement.
- `:lm`: Levenberg-Marquardt refinement.
- `:btd_tsd`: Blockwise tangent-subspace descent for BTD.

`solver = :lm` is currently not supported for BTD because the required Manopt
LM operator path is not yet available for nested Tucker layouts.

## Extended Options

Expand Down Expand Up @@ -234,6 +246,7 @@ function btd(
kwargs...,
) where {T<:AbstractFloat,N}
solver_obj = _solver_object(solver, stepsize; kwargs...)
_reject_unsupported_btd_solver(solver_obj)
solver_sym = _btd_solver_symbol(solver_obj)
init_resolved = _resolve_btd_init(init, solver_obj)
init_eff =
Expand Down
38 changes: 24 additions & 14 deletions src/btd/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,18 @@ Backend state for block-term decomposition as a sum of Tucker blocks. Stores
the target tensor, product manifold, reusable work buffers, and per-block
ambient reconstruction buffers used by cost, gradient, and ALS routines.
"""
struct BTDBackend{T,N,MT<:Tuple,A<:AbstractArray{T,N},V,MP<:ProductManifold,I,C} <:
AbstractJoinBackend
struct BTDBackend{
T,
N,
CT<:Tuple,
MT<:Tuple,
A<:AbstractArray{T,N},
V,
MP<:ProductManifold,
I,
C,
} <: AbstractJoinBackend
components::CT
manifolds::MT
r::Int
# Preserve the target array/backend so BTD shares the generic join storage behavior.
Expand Down Expand Up @@ -60,7 +70,7 @@ model_exact_join_basis_function(model::JoinModel{<:AbstractFloat,<:BTDBackend})
(M, p) -> begin
backend = model.backend
residual = _join_residual!(backend, p)
_join_basis_project(backend.manifolds, p, residual)
_join_basis_project(backend.components, p, residual)
end


Expand All @@ -71,22 +81,19 @@ function _btd_sequential_tucker_init(model::JoinModel{<:AbstractFloat,<:BTDBacke
residual = copy(backend.target)
parts = Vector{Manifolds.TuckerPoint{eltype(backend.target)}}(undef, backend.r)
for k = 1:backend.r
pk = _manifold_init(backend.manifolds[k], residual, init)
component = _backend_component(backend, k)
Mk = _backend_manifold(backend, k)
pk = _component_init(component, residual, init)
parts[k] = pk
_subtract_ambient_tensor!(
residual,
backend.manifolds[k],
pk,
backend.component_bufs[k],
)
_subtract_ambient_tensor!(residual, Mk, pk, backend.component_bufs[k])
end
return ArrayPartition(parts...)
end

function _btd_block_ranks_by_mode(backend::BTDBackend{T,N}) where {T,N}
ranks = Vector{NTuple{N,Int}}(undef, backend.r)
for b = 1:backend.r
M = backend.manifolds[b]
M = _backend_manifold(backend, b)
M isa Manifolds.Tucker || throw(
ArgumentError(
"BTD HOSVD multistart expects Tucker manifolds, got $(typeof(M)) at block $b.",
Expand Down Expand Up @@ -160,7 +167,7 @@ function _btd_hosvd_split_candidate(
parts[b] = pk
_subtract_ambient_tensor!(
residual,
backend.manifolds[b],
_backend_manifold(backend, b),
pk,
backend.component_bufs[b],
)
Expand Down Expand Up @@ -190,7 +197,7 @@ function initial_point(
init_sym = _builtin_initializer_symbol(init)
if init_sym == :random
parts = ntuple(
k -> _manifold_init(backend.manifolds[k], backend.target, init),
k -> _component_init(_backend_component(backend, k), backend.target, init),
backend.r,
)
return ArrayPartition(parts...)
Expand Down Expand Up @@ -282,6 +289,9 @@ function rgrad(model::JoinModel{<:AbstractFloat,<:BTDBackend}, p)
parts = point_parts(p)
_check_parts_len(parts, backend.r, "BTD rgrad")
eg = point_parts(_btd_egrad(backend, p))
vals = ntuple(k -> egrad_to_rgrad(backend.manifolds[k], parts[k], eg[k]), backend.r)
vals = ntuple(
k -> egrad_to_rgrad(_backend_manifold(backend, k), parts[k], eg[k]),
backend.r,
)
return wrap_like_point(p, vals)
end
59 changes: 58 additions & 1 deletion src/core/model.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# core/model.jl — Top-level decomposition model interface
export AbstractDecompositionModel,
manifold, initial_point, egrad, rgrad, supports_rgrad, tensor, cost, post_step!
manifold,
initial_point,
egrad,
rgrad,
supports_rgrad,
tensor,
cost,
post_step!,
residual,
differential_action,
differential_action!,
adjoint_action
"""
AbstractDecompositionModel{T}

Expand Down Expand Up @@ -113,3 +124,49 @@ end
function tensor(model::AbstractDecompositionModel)
error("tensor not implemented for $(typeof(model))")
end

function residual(model::AbstractDecompositionModel, p)
error("residual not implemented for $(typeof(model))")
end

function differential_action!(out::AbstractVector, model::AbstractDecompositionModel, p, X)
error("differential_action! not implemented for $(typeof(model))")
end

function differential_action(model::AbstractDecompositionModel, p, X)
T = eltype(tensor(model))
out = Vector{T}(undef, length(tensor(model)))
differential_action!(out, model, p, X)
return out
end

function adjoint_action(
model::AbstractDecompositionModel,
p,
a::AbstractVector;
basis = ManifoldsBase.DefaultOrthonormalBasis(),
)
M = manifold(model)
d = manifold_dimension(M)
length(a) == length(tensor(model)) || throw(
DimensionMismatch(
"adjoint_action expected ambient vector of length $(length(tensor(model))) for $(typeof(model)), got $(length(a)).",
),
)
T = _scalar_eltype(p)
coeff = zeros(T, d)
e_j = zeros(T, d)
col = Vector{T}(undef, length(a))
@inbounds for j = 1:d
fill!(e_j, zero(T))
e_j[j] = one(T)
Xj = ManifoldsBase.get_vector(M, p, e_j, basis)
differential_action!(col, model, p, Xj)
coeff[j] = dot(col, a)
end
return ManifoldsBase.get_vector(M, p, coeff, basis)
end

function adjoint_action(model::AbstractDecompositionModel, p, a::AbstractArray; kwargs...)
return adjoint_action(model, p, vec(a); kwargs...)
end
Loading
Loading