Skip to content

Commit

Permalink
Typed instance-free allocation (#192)
Browse files Browse the repository at this point in the history
* Typed instance-free allocation

* specialize for ProductManifold

* specialize for nested PowerManifold

* add default_type

* add a test

* rename allocate_as to allocate_on

* improved quaternion allocation

* fix CI?

* fix CI on 1.6 too

* update Julia versions on CI

* 1.10 == 1.1

* quarto

* update date
  • Loading branch information
mateuszbaran authored Jul 28, 2024
1 parent 5fb1add commit 2ac6a52
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
julia-version: ["1.6", "1.9", "~1.10.0-0"]
julia-version: ["1.6", "1.10", "~1.11.0-0"]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/documenter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
- uses: actions/checkout@v4
- uses: quarto-dev/quarto-actions/setup@v2
with:
version: 1.3.353
version: "1.5.55"
- uses: julia-actions/setup-julia@latest
with:
version: 1.9
version: "1.10"
- name: Julia Cache
uses: julia-actions/cache@v2
- name: Cache Quarto
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: 1.9
version: "1.10"
- name: Install JuliaFormatter and format
run: |
julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))'
Expand Down
12 changes: 12 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.15.11] 28/07/2024

### Added

* Function `allocate_on` to generically allocate point and tangent vectors on a manifold without a pre-existing instance but of a particular type.
* Function `default_type` to get the default type of points and tangent vectors for a manifold.
* Package extension for the `Quaternions.jl` package that handles allocation.

### Changed

* Default allocation method was made more robust to custom promotion functions.

## [0.15.10] 19/05/2024

### Added
Expand Down
8 changes: 6 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ManifoldsBase"
uuid = "3362f125-f0bb-47a3-aa74-596ffd7ef2fb"
authors = ["Seth Axen <[email protected]>", "Mateusz Baran <[email protected]>", "Ronny Bergmann <[email protected]>", "Antoine Levitt <[email protected]>"]
version = "0.15.10"
version = "0.15.11"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand All @@ -12,11 +12,13 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df"

[weakdeps]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[extensions]
ManifoldsBasePlotsExt = "Plots"
ManifoldsBaseQuaternionsExt = "Quaternions"
ManifoldsBaseRecursiveArrayToolsExt = "RecursiveArrayTools"
ManifoldsBaseStatisticsExt = "Statistics"

Expand All @@ -35,6 +37,7 @@ Requires = "1"
ReverseDiff = "1"
StaticArrays = "1"
Statistics = "1.6"
Quaternions = "0.7"
Test = "1.6"
julia = "1.6"

Expand All @@ -44,11 +47,12 @@ DoubleFloats = "497a8b3b-efae-58df-a0af-a86822472b78"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Aqua", "DoubleFloats", "ForwardDiff", "OrdinaryDiffEq", "Plots", "ReverseDiff", "StaticArrays", "Statistics", "RecursiveArrayTools"]
test = ["Test", "Aqua", "DoubleFloats", "ForwardDiff", "OrdinaryDiffEq", "Plots", "Quaternions", "ReverseDiff", "StaticArrays", "Statistics", "RecursiveArrayTools"]
23 changes: 23 additions & 0 deletions ext/ManifoldsBaseQuaternionsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module ManifoldsBaseQuaternionsExt

if isdefined(Base, :get_extension)
using ManifoldsBase
using ManifoldsBase:
using Quaternions
else
# imports need to be relative for Requires.jl-based workflows:
# https://github.com/JuliaArrays/ArrayInterface.jl/pull/387
using ..ManifoldsBase
using ..ManifoldsBase:
using ..Quaternions
end

@inline function ManifoldsBase.allocate_result_type(
::AbstractManifold{ℍ},
f::TF,
args::Tuple{},
) where {TF}
return QuaternionF64
end

end
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ if isdefined(Base, :get_extension)

import ManifoldsBase:
allocate,
allocate_on,
allocate_result,
default_inverse_retraction_method,
default_retraction_method,
Expand Down Expand Up @@ -40,6 +41,7 @@ else

import ..ManifoldsBase:
allocate,
allocate_on,
allocate_result,
default_inverse_retraction_method,
default_retraction_method,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ function allocate(x::ArrayPartition, T::Type)
return ArrayPartition(map(t -> allocate(t, T), submanifold_components(x))...)
end

allocate_on(M::ProductManifold) = ArrayPartition(map(N -> allocate_on(N), M.manifolds)...)
function allocate_on(M::ProductManifold, ::Type{ArrayPartition{T,U}}) where {T,U}
return ArrayPartition(map((N, V) -> allocate_on(N, V), M.manifolds, U.parameters)...)
end
function allocate_on(M::ProductManifold, ft::TangentSpaceType)
return ArrayPartition(map(N -> allocate_on(N, ft), M.manifolds)...)
end
function allocate_on(
M::ProductManifold,
ft::TangentSpaceType,
::Type{ArrayPartition{T,U}},
) where {T,U}
return ArrayPartition(
map((N, V) -> allocate_on(N, ft, V), M.manifolds, U.parameters)...,
)
end

@inline function allocate_result(M::ProductManifold, f)
return ArrayPartition(map(N -> allocate_result(N, f), M.manifolds))
end
Expand Down
88 changes: 82 additions & 6 deletions src/ManifoldsBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Type `T` is the new number element type [`number_eltype`](@ref), if it is not gi
the element type of `a` is retained. The `dims` argument can be given for non-nested
allocation and is forwarded to the function `similar`.
It's behavior can be overriden by a specific manifold, for example power manifold with
It's behavior can be overridden by a specific manifold, for example power manifold with
nested replacing representation can decide that `allocate` for `Array{<:SArray}` returns
another `Array{<:SArray}` instead of `Array{<:MArray}`, as would be done by default.
"""
Expand All @@ -86,6 +86,62 @@ function allocate(::AbstractManifold, a, T::Type, dim1::Integer, dims::Integer..
end
allocate(::AbstractManifold, a, T::Type, dims::Tuple) = allocate(a, T, dims)

"""
allocate_on(M::AbstractManifold, [T:::Type])
allocate_on(M::AbstractManifold, F::FiberType, [T:::Type])
Allocate a new point on manifold `M` with optional type given by `T`. Note that `T` is not
number element type as in [`allocate`](@ref) but rather the type of the entire point to be
returned.
If `F` is provided, then an element of the corresponding fiber is allocated, assuming it is
independent of the base point.
To allocate a tangent vector, use ``
# Example
```julia-repl
julia> using ManifoldsBase
julia> M = ManifoldsBase.DefaultManifold(4)
DefaultManifold(4; field = ℝ)
julia> allocate_on(M)
4-element Vector{Float64}:
0.0
0.0
0.0
0.0
julia> allocate_on(M, Array{Float64})
4-element Vector{Float64}:
0.0
0.0
0.0
0.0
julia> allocate_on(M, TangentSpaceType())
4-element Vector{Float64}:
0.0
0.0
0.0
0.0
julia> allocate_on(M, TangentSpaceType(), Array{Float64})
4-element Vector{Float64}:
0.0
0.0
0.0
0.0
```
"""
allocate_on(M::AbstractManifold) = similar(Array{Float64}, representation_size(M))
function allocate_on(M::AbstractManifold, T::Type{<:AbstractArray})
return similar(T, representation_size(M))
end

"""
_pick_basic_allocation_argument(::AbstractManifold, f, x...)
Expand Down Expand Up @@ -130,8 +186,8 @@ Return type of element of the array that will represent the result of function `
@inline eti_to_one(eti) = one(number_eltype(eti))
return typeof(sum(map(eti_to_one, args)))
end
@inline function allocate_result_type(::AbstractManifold, f::TF, args::Tuple{}) where {TF}
return Float64
@inline function allocate_result_type(M::AbstractManifold, f::TF, args::Tuple{}) where {TF}
return allocation_promotion_function(M, f, ())(Float64)
end

"""
Expand Down Expand Up @@ -351,6 +407,21 @@ function at the level, where also information from `p` and `M` can be accessed.
"""
copyto!(::AbstractManifold, Y, p, X) = copyto!(Y, X)

"""
default_type(M::AbstractManifold)
Get the default type of points on manifold `M`.
"""
default_type(M::AbstractManifold) = typeof(allocate_on(M))
"""
default_type(M::AbstractManifold, ft::FiberType)
Get the default type of points from the fiber `ft` of the fiber bundle based on manifold `M`.
For example, call `default_type(MyManifold(), TangentSpaceType())` to get the default type
of a tangent vector.
"""
default_type(M::AbstractManifold, ft::FiberType) = typeof(allocate_on(M, ft))

@doc raw"""
distance(M::AbstractManifold, p, q)
Expand Down Expand Up @@ -477,7 +548,7 @@ embed!(M::AbstractManifold, Y, p, X) = copyto!(M, Y, p, X)
Embed `p` from manifold `M` an project it back to `M`. For points from `M` this is identity
but in case embedding is defined for points outside of `M`, this can serve as a way
to for example remove numerical innacuracies caused by some algorithms.
to for example remove numerical inaccuracies caused by some algorithms.
"""
function embed_project(M::AbstractManifold, p)
return project(M, embed(M, p))
Expand All @@ -487,8 +558,8 @@ end
Embed vector `X` tangent at `p` from manifold `M` an project it back to tangent space
at `p`. For points from that tangent space this is identity but in case embedding is
defined for tagent vectors from outside of it, this can serve as a way to for example remove
numerical innacuracies caused by some algorithms.
defined for tangent vectors from outside of it, this can serve as a way to for example remove
numerical inaccuracies caused by some algorithms.
"""
function embed_project(M::AbstractManifold, p, X)
return project(M, p, embed(M, p, X))
Expand Down Expand Up @@ -1115,6 +1186,9 @@ function __init__()
@require Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" begin
include("../ext/ManifoldsBasePlotsExt.jl")
end
@require Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" begin
include("../ext/ManifoldsBaseQuaternionsExt.jl")
end
@require Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" begin
include("../ext/ManifoldsBaseStatisticsExt.jl")
end
Expand Down Expand Up @@ -1219,6 +1293,7 @@ export ×,
ℝ,
ℂ,
allocate,
allocate_on,
angle,
base_manifold,
base_point,
Expand All @@ -1236,6 +1311,7 @@ export ×,
default_approximation_method,
default_inverse_retraction_method,
default_retraction_method,
default_type,
default_vector_transport_method,
distance,
exp,
Expand Down
48 changes: 47 additions & 1 deletion src/PowerManifold.jl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,52 @@ function Base.:^(
return PowerManifold(M, size...)
end

function allocate_on(
M::PowerManifold{
𝔽,
TM,
TSize,
<:Union{NestedPowerRepresentation,NestedReplacingPowerRepresentation},
},
) where {𝔽,TM<:AbstractManifold{𝔽},TSize}
return [allocate_on(M.manifold) for _ in get_iterator(M)]
end
function allocate_on(
M::PowerManifold{
𝔽,
TM,
TSize,
<:Union{NestedPowerRepresentation,NestedReplacingPowerRepresentation},
},
::Type{<:Array{U}},
) where {𝔽,TM<:AbstractManifold{𝔽},TSize,U}
return [allocate_on(M.manifold, U) for _ in get_iterator(M)]
end

function allocate_on(
M::PowerManifold{
𝔽,
TM,
TSize,
<:Union{NestedPowerRepresentation,NestedReplacingPowerRepresentation},
},
ft::TangentSpaceType,
) where {𝔽,TM<:AbstractManifold{𝔽},TSize}
return [allocate_on(M.manifold, ft) for _ in get_iterator(M)]
end
function allocate_on(
M::PowerManifold{
𝔽,
TM,
TSize,
<:Union{NestedPowerRepresentation,NestedReplacingPowerRepresentation},
},
ft::TangentSpaceType,
::Type{<:Array{U}},
) where {𝔽,TM<:AbstractManifold{𝔽},TSize,U}
return [allocate_on(M.manifold, ft, U) for _ in get_iterator(M)]
end

"""
_allocate_access_nested(M::PowerManifoldNested, y, i)
Expand All @@ -194,7 +240,7 @@ function allocate_result(M::PowerManifoldNested, f, x...)
]
end
end
# avoid ambituities - though usually not used
# avoid ambiguities - though usually not used
function allocate_result(
M::PowerManifoldNested,
f::typeof(get_coordinates),
Expand Down
7 changes: 7 additions & 0 deletions src/bases.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,13 @@ const all_uncached_bases{T} = Union{
DefaultOrthonormalBasis{<:Any,T},
}

function allocate_on(M::AbstractManifold, ::TangentSpaceType)
return similar(Array{Float64}, representation_size(M))
end
function allocate_on(M::AbstractManifold, ::TangentSpaceType, T::Type{<:AbstractArray})
return similar(T, representation_size(M))
end

"""
allocate_coordinates(M::AbstractManifold, p, T, n::Int)
Expand Down
Loading

2 comments on commit 2ac6a52

@mateuszbaran
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

Added

  • Function allocate_on to generically allocate point and tangent vectors on a manifold without a pre-existing instance but of a particular type.
  • Function default_type to get the default type of points and tangent vectors for a manifold.
  • Package extension for the Quaternions.jl package that handles allocation.

Changed

  • Default allocation method was made more robust to custom promotion functions.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/111933

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.15.11 -m "<description of version>" 2ac6a527d55d76378f4bb29496c12456fbc0d0d7
git push origin v0.15.11

Please sign in to comment.