# How to constraint a outer type of a variable in a struct to be the same, but allow the inner type to be different?

How to constraint a outer type of a variable in a struct to be the same, but allow the inner type to be different?
e.g.

``````struct Foo{A<:Union{<:AbstractMatrix, Cholesky}}
a::A
b::A
end
``````

In this example `a` & `b` can be either an `AbstractMatrix` or a `Cholesky`. Both have to be of the same type. However, in the case of Cholesky I would like allow, that `a` is a `Cholesky` with an inner type of a `Matrix` and that `b` is a `Cholesky` with an inner type of a `Diagonal` or vice versa.

So `a` and `b` can both be either be an `AbstractMatrix` or a `Cholesky`. In the case of `Cholesky` `a` and `b` can differ, in the sense that the inner type is either a `Diagonal` or a `Matrix`.

How would I do that?

Interpreting your request literally, I defined Foo and its constructor like this:

``````# Copyright © 2021: Neven Sajko
#

using LinearAlgebra

struct Foo{
S <: Number,
Ta <: Union{<:AbstractMatrix, <:Cholesky},
Tb <: Union{<:AbstractMatrix, <:Cholesky},
}
a :: Ta
b :: Tb

# NOTE: the inner constructor methods are not meant to be used directly,
# use the outer constructor methods instead!

function Foo{S, Ta, Tb}(
a :: AbstractMatrix{S},
b :: AbstractMatrix{S},
) where {
S <: Number,
Ta <: AbstractMatrix{S},
Tb <: AbstractMatrix{S},
}
typeof(a) === Ta || error("type parameter 1 disagrees with the argument type")
typeof(b) === Tb || error("type parameter 2 disagrees with the argument type")
new{S, Ta, Tb}(a, b)
end

function Foo{S, Ta, Tb}(
a :: Cholesky{S, A1},
b :: Cholesky{S, A2},
) where {
S <: Number,
A1 <: AbstractMatrix{S},
A2 <: AbstractMatrix{S},
Ta <: Cholesky{S, A1},
Tb <: Cholesky{S, A2},
}
typeof(a) === Ta || error("type parameter 1 disagrees with the argument type")
typeof(b) === Tb || error("type parameter 2 disagrees with the argument type")
new{S, Ta, Tb}(a, b)
end
end

function Foo(
a :: AbstractMatrix{S},
b :: AbstractMatrix{S},
) where {
S <: Number,
}
Foo{S, typeof(a), typeof(b)}(a, b)
end

function Foo(
a :: Cholesky{S, A1},
b :: Cholesky{S, A2},
) where {
S <: Number,
A1 <: AbstractMatrix{S},
A2 <: AbstractMatrix{S},
}
Foo{S, Cholesky{S, A1}, Cholesky{S, A2}}(a, b)
end
``````

Example REPL session:

``````               _
_       _ _(_)_     |  Documentation: https://docs.julialang.org
(_)     | (_) (_)    |
_ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` |  |
| | |_| | | | (_| |  |  Version 1.5.4 (2021-03-11)
_/ |\__'_|_|_|\__'_|  |
|__/                   |

julia> include("zsoerenm.jl")
Foo

julia> A = [4. 12. -16.; 12. 37. -43.; -16. -43. 98.]
3×3 Array{Float64,2}:
4.0   12.0  -16.0
12.0   37.0  -43.0
-16.0  -43.0   98.0

julia> Foo(A, A)
Foo{Float64,Array{Float64,2},Array{Float64,2}}([4.0 12.0 -16.0; 12.0 37.0 -43.0; -16.0 -43.0 98.0], [4.0 12.0 -16.0; 12.0 37.0 -43.0; -16.0 -43.0 98.0])

julia> D = Diagonal(A)
3×3 Diagonal{Float64,Array{Float64,1}}:
4.0    ⋅     ⋅
⋅   37.0    ⋅
⋅     ⋅   98.0

julia> Foo(D, D)
Foo{Float64,Diagonal{Float64,Array{Float64,1}},Diagonal{Float64,Array{Float64,1}}}([4.0 0.0 0.0; 0.0 37.0 0.0; 0.0 0.0 98.0], [4.0 0.0 0.0; 0.0 37.0 0.0; 0.0 0.0 98.0])

julia> CA = cholesky(A)
Cholesky{Float64,Array{Float64,2}}
U factor:
3×3 UpperTriangular{Float64,Array{Float64,2}}:
2.0  6.0  -8.0
⋅   1.0   5.0
⋅    ⋅    3.0

julia> CD = cholesky(D)
Cholesky{Float64,Diagonal{Float64,Array{Float64,1}}}
U factor:
3×3 Diagonal{Float64,Array{Float64,1}}:
2.0   ⋅        ⋅
⋅   6.08276   ⋅
⋅    ⋅       9.89949

julia> Foo(CA, CD)
Foo{Float64,Cholesky{Float64,Array{Float64,2}},Cholesky{Float64,Diagonal{Float64,Array{Float64,1}}}}(Cholesky{Float64,Array{Float64,2}}([2.0 6.0 -8.0; 12.0 1.0 5.0; -16.0 -43.0 3.0], 'U', 0), Cholesky{Float64,Diagonal{Float64,Array{Float64,1}}}([2.0 0.0 0.0; 0.0 6.082762530298219 0.0; 0.0 0.0 9.899494936611665], 'U', 0))
``````

Keep in mind that:

1. You may have asked an overly specific question. That is, while I think that I answered your question, your question may have been the wrong question to ask.

2. Someone who isn’t as new to Julia as I am may be able to improve on my code.

3. Your question was actually also somewhat ambiguous regarding the Cholesky types, so my interpretation of the question may not align completely with what you thought you wanted.

3 Likes

I am not sure I understand the specs fully, but generally you do validation in the inner constructor.

Eg

``````using ArgCheck
struct Foo{TA,TB}
a::TA
b::TB
function Foo(a::TA, b::TB) where {TA,TB}
@argcheck (TA <: Cholesky && TB <: Cholesky) || (TA == TB && TB <: AbstractMatrix)
new{TA,TB}(a, b)
end
end
``````
5 Likes