diff --git a/src/HyperdimensionalComputing.jl b/src/HyperdimensionalComputing.jl index 681c28a..7ef4d7d 100644 --- a/src/HyperdimensionalComputing.jl +++ b/src/HyperdimensionalComputing.jl @@ -9,13 +9,15 @@ export AbstractHV, GradedBipolarHV, RealHV, GradedHV, - TernaryHV + TernaryHV, + FHRR include("representations.jl") include("operations.jl") export bundle, bind, + unbind, shift!, shift, ρ, diff --git a/src/encoding.jl b/src/encoding.jl index a7d4b26..bbcec69 100644 --- a/src/encoding.jl +++ b/src/encoding.jl @@ -493,25 +493,25 @@ end """ level(v::HV, n::Int) where {HV <: AbstractHV} - level(HV::Type{<:AbstractHV}, n::Int; dims::Int = 10_000) + level(HV::Type{<:AbstractHV}, m::Int; n::Int = 10_000) Creates a set of level correlated hypervectors, where the first and last hypervectors are quasi-orthogonal. # Arguments - `v::HV`: Base hypervector -- `n::Int`: Number of levels (alternatively, provide a vector to be encoded) +- `m::Int`: Number of levels (alternatively, provide a vector to be encoded) """ -function level(v::HV, n::Int) where {HV <: AbstractHV} +function level(v::HV, m::Int) where {HV <: AbstractHV} hvs = [v] - p = 2 / n - while length(hvs) < n + p = 2 / m + while length(hvs) < m u = last(hvs) push!(hvs, perturbate(u, p)) end return hvs end -level(HV::Type{<:AbstractHV}, n::Int; dims::Int = 10_000) = level(HV(dims), n) +level(HV::Type{<:AbstractHV}, m::Int; n::Int = 10_000) = level(HV(n), m) level(HVv, vals::AbstractVector) = level(HVv, length(vals)) level(HVv, vals::UnitRange) = level(HVv, length(vals)) @@ -591,12 +591,37 @@ end decodelevel(hvlevels::AbstractVector{<:AbstractHV}, a::Number, b::Number) = decodelevel(hvlevels, range(a, b, length(hvlevels))) -decodelevel(HV, numvalues; testbound = false) = decodelevel(level(HV, length(numvalues)), numvalues) +decodelevel(HV, numvalues; testbound = false) = decodelevel(level(HV, length(numvalues)), numvalues; testbound) """ convertlevel(hvlevels, numvals..., kwargs...) + convertlevel(HV::AbstractHV, numvals..., kwargs...) Creates the `encoder` and `decoder` for a level incoding in one step. See `encodelevel` and `decodelevel` for their respective documentations. """ convertlevel(hvlevels, numvals...; kwargs...) = encodelevel(hvlevels, numvals...; kwargs...), decodelevel(hvlevels, numvals..., kwargs...) + +convertlevel(hv::AbstractHV, numvals...; kwargs...) = encodelevel(hv, numvals...; kwargs...), decodelevel(hv, numvals..., kwargs...) + + +# levels using FHRR + +function level(v::FHRR, m::Int; β = 1 / m) + return [v^(x * β) for x in 1:m] +end + +function level(v::FHRR, vals::Union{AbstractVector{<:Number}, UnitRange}; β = 1 / (maximum(vals) - minimum(vals))) + return [v^(x * β) for x in vals] +end + +function encodelevel(v::FHRR, vals = (0, 1); β = 1 / (maximum(vals) - minimum(vals))) + return x -> v^(β * x) +end + +function decodelevel(v::FHRR, vals = (0, 1); β = 1 / (maximum(vals) - minimum(vals))) + return u -> @.(real(log(u.v) / log(v.v) / β)) |> mean +end + + +convertlevel(v::FHRR, vals = (0, 1); kwargs...) = encodelevel(v, vals; kwargs...), decodelevel(v, vals; kwargs...) diff --git a/src/inference.jl b/src/inference.jl index 2cd50d6..625a2b5 100644 --- a/src/inference.jl +++ b/src/inference.jl @@ -20,6 +20,7 @@ similarity(u::GradedBipolarHV, v::GradedBipolarHV) = sim_cos(u, v) similarity(u::RealHV, v::RealHV) = sim_cos(u, v) similarity(u::BinaryHV, v::BinaryHV) = sim_jacc(u, v) similarity(u::GradedHV, v::GradedHV) = sim_jacc(u, v) +similarity(u::FHRR, v::FHRR) = real(dot(u.v, v.v)) / length(u) """ similarity(u::AbstractVector, v::AbstractVector; method::Symbol) diff --git a/src/operations.jl b/src/operations.jl index 62b2b1d..747950f 100644 --- a/src/operations.jl +++ b/src/operations.jl @@ -89,8 +89,8 @@ end return r end -# AGGREGATION -# ----------- +# BUNDLE +# ------ # binary and bipolar: use majority function bundle(hvr::Union{BinaryHV, BipolarHV}, hdvs, r) @@ -143,6 +143,14 @@ function bundle(::GradedBipolarHV, hdvs, r) return GradedBipolarHV(r) end +function bundle(::FHRR, hdvs, r) + for hv in hdvs + r .+= hv.v + end + r ./= abs.(r) + return FHRR(r) +end + function bundle(hdvs; kwargs...) hv = first(hdvs) r = empty_vector(hv) @@ -153,17 +161,34 @@ Base.:+(hv1::HV, hv2::HV) where {HV <: AbstractHV} = bundle((hv1, hv2)) # BINDING # ------- - +Base.bind(hv1::HV, hv2::HV) where {HV <: AbstractHV} = HV(hv1.v .* hv2.v) # default Base.bind(hv1::BinaryHV, hv2::BinaryHV) = BinaryHV(hv1.v .⊻ hv2.v) Base.bind(hv1::BipolarHV, hv2::BipolarHV) = BipolarHV(hv1.v .⊻ hv2.v) Base.bind(hv1::TernaryHV, hv2::TernaryHV) = TernaryHV(hv1.v .* hv2.v) Base.bind(hv1::RealHV, hv2::RealHV) = RealHV(hv1.v .* hv2.v) Base.bind(hv1::GradedHV, hv2::GradedHV) = GradedHV(fuzzy_xor.(hv1.v, hv2.v)) Base.bind(hv1::GradedBipolarHV, hv2::GradedBipolarHV) = GradedBipolarHV(fuzzy_xor_bipol.(hv1.v, hv2.v)) +Base.bind(hv1::FHRR, hv2::FHRR) = FHRR(hv1.v .* hv2.v) Base.:*(hv1::HV, hv2::HV) where {HV <: AbstractHV} = bind(hv1, hv2) Base.bind(hvs::AbstractVector{HV}) where {HV <: AbstractHV} = prod(hvs) +""" + unbind(hv1::HV, hv2::HV) + +Unbinds `hv2` from `hv1`. For many types of hypervectors, the binding operator is +idempotent, i.e., `u * v * v == u`. + +Aliases with `\`. +""" +unbind(hv1::HV, hv2::HV) where {HV <: AbstractHV} = bind(hv1, hv2) + +unbind(hv1::RealHV, hv2::RealHV) where {HV <: AbstractHV} = RealHV(hv1.v ./ hv2.v) +unbind(hv1::FHRR, hv2::FHRR) where {HV <: AbstractHV} = FHRR(hv1.v ./ hv2.v) + +Base.:/(hv1::HV, hv2::HV) where {HV <: AbstractHV} = unbind(hv1, hv2) + + # SHIFTING # -------- @@ -286,3 +311,8 @@ end perturbate!(hv, args...) = perturbate!(vectype(hv), hv, args...) perturbate(hv::AbstractHV, args...; kwargs...) = perturbate!(copy(hv), args...; kwargs...) + +# OTHER +# ----- + +Base.:^(hv::FHRR, x::Number) = FHRR(hv.v .^ x) diff --git a/src/types.jl b/src/types.jl index 9944974..f864b2a 100644 --- a/src/types.jl +++ b/src/types.jl @@ -18,12 +18,10 @@ Every hypervector HV has the following basic functionality TODO: - [ ] SparseHV - [ ] support for different types -- [ ] complex HDC =# abstract type AbstractHV{T} <: AbstractVector{T} end -#Base.collect(hv::AbstractHV) = hv.v Base.sum(hv::AbstractHV) = sum(hv.v) Base.size(hv::AbstractHV) = size(hv.v) Base.getindex(hv::AbstractHV, i) = hv.v[i] @@ -106,7 +104,6 @@ end RealHV(n::Integer = 10_000, distr::Distribution = eldist(RealHV)) = RealHV(rand(distr, n)) - Base.similar(hv::RealHV) = RealHV(length(hv), eldist(RealHV)) function normalize!(hv::RealHV) @@ -167,6 +164,30 @@ eldist(::Type{<:GradedBipolarHV}) = 2eldist(GradedHV) - 1 Base.similar(hv::GradedBipolarHV) = GradedBipolarHV(length(hv)) LinearAlgebra.normalize!(hv::GradedBipolarHV) = clamp!(hv.v, -1, 1) +# Fourier Holographically Reduced Represenetations +# ------------------------------------------------ + +struct FHRR{T <: Complex} <: AbstractHV{T} + v::Vector{T} +end + +#Base.eltype(::FHRR{T}) where {T} = Complex{T} + +FHRR(n::Int = 10_000) = FHRR(exp.(2π * im .* rand(n))) +FHRR(T::Type, n::Int = 10_000) = FHRR(exp.(2π * im .* rand(T, n))) + +Base.similar(hv::FHRR{<:Complex{R}}) where {R} = FHRR(exp.(2π * im .* rand(R, length(hv)))) + +""" + LinearAlgebra.normalize!(hv::FHRR) + +A Fourier Holographically Reduced Represenetation is normalized by +setting the norm of each complex element to 1. +""" +function LinearAlgebra.normalize!(hv::FHRR) + hv.v ./= abs.(hv.v) + return hv +end # TRAITS # ------ diff --git a/test/encoding.jl b/test/encoding.jl index c4f76f1..7a2af2d 100644 --- a/test/encoding.jl +++ b/test/encoding.jl @@ -63,4 +63,22 @@ x = decoder(hv) @test 1 ≤ x ≤ 2 end + + @testset "FHRR numbers" begin + + v = FHRR() + + numvals = 0:0.1:10 + + encoder, decoder = convertlevel(v, numvals) + + x, y, z = 2, 5, 10 + + hx, hy, hz = encoder.((x, y, z)) + + @test hx isa FHRR + @test similarity(hx, hy) > similarity(hx, hz) + + @test decoder(hx) < decoder(hy) < decoder(hz) + end end diff --git a/test/operations.jl b/test/operations.jl index 8a54648..7a5dc06 100644 --- a/test/operations.jl +++ b/test/operations.jl @@ -69,4 +69,21 @@ using LinearAlgebra, Random end end end + @testset "FHRR" begin + hv1 = FHRR(n) + hv2 = FHRR(n) + + @test bundle([hv1, hv2]) isa FHRR + @test hv1 + hv2 isa FHRR + @test bind([hv1, hv2]) isa FHRR + @test norm(bind([hv1, hv2])) ≈ sqrt(n) + + @test shift(hv1, 2) isa FHRR + + @test similarity(hv1, hv2) < 0.5 + @test similarity(hv2, hv2) ≈ 1 + + @test norm(hv1^3) ≈ sqrt(n) + end + end diff --git a/test/types.jl b/test/types.jl index f5f493f..7fe095e 100644 --- a/test/types.jl +++ b/test/types.jl @@ -97,4 +97,15 @@ using Distributions, LinearAlgebra @test norm(hdv) ≈ norm(hdv.v) normalize!(hdv) end + + @testset "FHRR" begin + hdv = FHRR(n) + @test length(hdv) == n + @test eltype(hdv) <: Complex + @test hdv[2] isa Complex + + @test sum(hdv) ≈ sum(hdv.v) + @test norm(hdv) ≈ norm(hdv.v) + + end end