Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Manifest.toml
.DS_Store
docs/build/
9 changes: 8 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ uuid = "3630a16b-0f2f-4d88-afbf-c7d59eccf553"
version = "0.1.0"

[deps]
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Manifolds = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e"
ManifoldsBase = "3362f125-f0bb-47a3-aa74-596ffd7ef2fb"
Expand All @@ -12,13 +13,19 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
TensorOperations = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
JuliaFormatter = "1.0.33"
Manifolds = "0.11.20"
ManifoldsBase = "2.3.5"
Manopt = "0.5.37"
ProgressMeter = "1.11.0"
RecursiveArrayTools = "4.3"
TensorOperations = "5.6"
julia = "1.10"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
58 changes: 55 additions & 3 deletions src/api.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# User-facing convenience API: tucker, save_result, load_result
# User-facing convenience API: save_result, load_result
export save_result, load_result


Expand All @@ -8,21 +8,73 @@ include("api/nncpd.jl")
include("api/btd.jl")
include("api/tucker.jl")


"""
save_result(path::AbstractString, result)

Save a result to a file.
Save a TensorKitchen result object to `path` for later use.

* This uses Julia's built-in `Serialization` format, so the file is a Julia-native binary file.
* It is intended for saving results during local experiments, benchmarks, and development workflows.
* Common file extensions are `.jls`, `.julia`, `.bin`, or `.tkresult`; the extension is only a convention.

# Examples

```julia
A = randn(20, 15, 10)
res = cpd(A, 35)

save_result("cpd_rank35.jls", res)
```

* You can also save richer records by serializing a `NamedTuple` of the result and additional metadata.

```julia
record = (
method = :cpd,
rank = 35,
input_size = size(A),
result = res,
)
save_result("experiment_cpd_rank35.jls", record)
```
"""
function save_result(path::AbstractString, result)
open(path, "w") do io
Base.Serialization.serialize(io, result)
end
return path
end

"""
load_result(path::AbstractString)

Load a result from a file
Load a previously saved TensorKitchen result or experiment record from `path` for later use.

* The file must be written with `save_result`, or otherwise created using Julia's `Serialization.serialize`.

# Examples

```julia
res = load_result("cpd_rank35.jls")

weights(res)
factors(res)
reconstruct(res)
```
If the saved object was an experiment record, you can access the stored fields:

```julia
record = load_result("experiment_cpd_rank35.jls")

record.method
record.rank
record.input_size
record.result
record.result.weights
record.result.factors
reconstruct(record.result)
```
"""
function load_result(path::AbstractString)
open(path, "r") do io
Expand Down
2 changes: 1 addition & 1 deletion src/api/approx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ For the generic join path:

##Notes##
* `: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.
* `warm_steps` and `warm_init` are not part of the generic `approx(...)` path. Generic joins start from the selected initializer and then use manifold solvers for refinement.
* `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.
* For generic mixed joins, use manifold solvers such as `:rgd`, `:rcg`, or `:lbfgs`.
"""
function approx(
Expand Down
10 changes: 5 additions & 5 deletions src/manifolds/join.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ join_product(base, r) constructs the product-domain manifold used to
parameterize sums of r structured components. It does not construct the
image join/secant variety itself.
"""

function _segre_flat_factors(M::Manifolds.Segre)
dims = factor_dims(M)
return (Euclidean(1), (Sphere(n - 1) for n in dims)...)
end

"""
join_product(base, r) constructs a ProductManifold
join_product(base, r)

Construct rank-`r` join parameter manifold as a plain `ProductManifold`.
For `Manifolds.Segre`, uses flattened `(Euclidean(1), Sphere, ..., Sphere)` factors
per component to preserve current CP parameter layout.
Decides how to expand base into r components:
* Manifolds.Segre → flattened (Euclidean(1), Sphere, ...) × r (one λ + spheres per rank-1).
* Manifolds.Tucker / ProductManifold → repeat each factor r times.
* Generic manifold → ProductManifold(base, base, ..., base).
"""
function join_product(base::T, r::Int) where {T<:AbstractManifold}
r >= 2 || throw(ArgumentError("Join rank must be at least 2, got r=$r."))
Expand Down
16 changes: 10 additions & 6 deletions src/manifolds/secant.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# manifolds/secant.jl — ProductManifold join constructors for Segre and Tucker families.
#
# Pipeline: Manopt uses manifold M for retraction (exp map) and project (egrad→Riemannian grad).
# - ProductManifold(Euclidean(1), Sphere×...) is the placeholder for Segre (CPD rank-1).
# - CanonicalCP: Euclidean(r) × Π_m ProductManifold(Sphere(n_m-1), ..., Sphere(n_m-1)) for rank-r CPD.
# - `join_product(Manifolds.Segre(...), r)` gives flattened CP layout.
Expand All @@ -13,8 +12,9 @@ export CPJoin, TuckerJoin, SegreProduct, CanonicalCP
"""
SegreProduct(dims, r)

Product manifold `Manifolds.Segre(dims) × ... × Manifolds.Segre(dims)` (r factors).
Each component point uses the `Manifolds.Segre` layout `[[λ], x₁, …, x_d]`.
* Product manifold `Manifolds.Segre(dims) × ... × Manifolds.Segre(dims)` (r factors).
* Each component point uses the `Manifolds.Segre` layout `[[λ], x₁, …, x_d]`.
* `SegreProduct` is used to build a join model for CPD.
"""
function SegreProduct(dims::NTuple{N,Int}, r::Int) where {N}
r >= 2 || throw(
Expand All @@ -25,15 +25,19 @@ end

SegreProduct(dims::Vector{T}, r::Int) where {T<:Int} = SegreProduct(Tuple(dims), r)

# Canonical rank-r CP: ℝ^r × ∏_m (S^(n_m-1))^r

"""
CanonicalCP(dims, r)

* CanonicalCP: Euclidean(r) × Π_m ProductManifold(Sphere(n_m-1), ..., Sphere(n_m-1))
* Used for rank-r CPD.
"""
function CanonicalCP(dims::NTuple{N,Int}, r::Int) where {N}
r >= 2 || throw(ArgumentError("Canonical rank-r CP manifold needs r>=2, got r=$r"))
mode_factors = ntuple(m -> ProductManifold(ntuple(_ -> Sphere(dims[m] - 1), r)...), N)
return ProductManifold(Euclidean(r), mode_factors...)
end

CanonicalCP(dims::Vector{T}, r::Int) where {T<:Int} = CanonicalCP(Tuple(dims), r)

CPJoin(M::Manifolds.Segre, r::Int) = join_product(M, r)
CPJoin(dims::NTuple{N,T}, r::Int) where {N,T<:Int} = CPJoin(Manifolds.Segre(dims), r)
CPJoin(dims::Vector{T}, r::Int) where {T<:Int} = CPJoin(Tuple(dims), r)
Expand Down
Loading