I am writing code that would benefit from generic generator arithmetic over a vector type. In general, Vector{PrimeGenerator}
suffices, but it introduces duplication of irrelevant data. Thus, I am attempting to subtype AbstractVector
. The MWE of that is as follows:
# Vector elements are generators for modular arithmetic
struct PrimeGenerator
val::BigInt
mod::BigInt
end
value(x::PrimeGenerator) = x.val
modulus(x::PrimeGenerator) = x.mod
import Base.*
function *(x::PrimeGenerator, y::PrimeGenerator)
@assert x.mod == y.mod "Groups must be equal"
mod = modulus(x)
val = mod(value(x) * value(y), mod)
PrimeGenerator(val, mod)
end
import Base.^
^(g::PrimeGenerator, n::Integer) = PrimeGenerator(powermod(value(g), n, modulus(g)), modulus(g))
# Definition of the vector
struct GVector{G} <: AbstractVector{G}
x::Vector{T} where T
g::G
end
Base.IndexStyle(::Type{<:GVector}) = IndexLinear()
Base.setindex!(𝐠::GVector, val::PrimeGenerator, i::Int) = 𝐠.x[i] = value(val)
Base.getindex(𝐠::GVector, i::Int) = PrimeGenerator(𝐠.x[i], modulus(𝐠.g))
Base.length(g::GVector) = length(g.x)
Base.size(g::GVector) = size(g.x)
With this implementation, when I run broadcasting like:
a = PrimeGenerator(3, 23)
gv = GVector([a, a^2, a^3], a)
s = gv .^ 2
The resulting type of s
is Vector{PrimeGenerator}
where instead I wish it to be a GVector
. Vaguely I want broadcasting to work like:
mybroadcast(f::Function, 𝐠::GVector, x::Integer) = GVector([value(f(i, x)) for i in 𝐠], 𝐠.g)
# mybroadcast(^, gv, 2)
function mybroadcast(::typeof(*), 𝐠::GVector{T}, 𝐡::GVector{T}) where T
@assert 𝐠.g == 𝐡.g "The groups must be equal"
p = modulus(𝐠.g)
v = GVector([mod(i*j, p) for (i, j) in zip(𝐠.x, 𝐡.x)], 𝐠.g)
return v
end
# mybroadcast(*, gv, gv)
To get the desired behaviour, I attempted to modify the ArrayAndChar
example from documentation and came up with:
Base.BroadcastStyle(::Type{<:GVector}) = Broadcast.ArrayStyle{GVector}()
function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{GVector}}, ::Type{ElType}) where ElType
# Scan the inputs for the ArrayAndChar:
A = find_aac(bc)
# Use the char field of A to create the output
GVector(similar(Array{ElType}, axes(bc)), A.g)
end
"`A = find_aac(As)` returns the first ArrayAndChar among the arguments."
find_aac(bc::Base.Broadcast.Broadcasted) = find_aac(bc.args)
find_aac(args::Tuple) = find_aac(find_aac(args[1]), Base.tail(args))
find_aac(x) = x
find_aac(::Tuple{}) = nothing
find_aac(a::GVector, rest) = a
find_aac(::Any, rest) = find_aac(rest)
However it fails attempting to initialize GVector(::Vector{PrimeGenerator}, ::PrimeGenerator)
which is not desired. Surelly I could strip up irrelevant data by defining
GVector(x::Vector{PrimeGenerator}, g::PrimeGenerator) = GVector(value.(x), g)
But that is instead an ill fix, and I would be better off using Vector{PrimeGenerator}
type in calculations instead.