text:
using Images, FixedPointNumbers, Color
using Base.Test

urlbase = "http://www.imagemagick.org/Usage/images/"

const savedir = joinpath(tempdir(), "Images")
const writedir = joinpath(savedir, "write")

function getfile(name)
    file = joinpath(savedir, name)
    if !isfile(file)
        file = download(urlbase*name, file)
    end
    file
end

if !isdir(savedir)
    mkdir(savedir)
end
if !isdir(writedir)
    mkdir(writedir)
end

# Gray
file = getfile("jigsaw_tmpl.png")
img = imread(file)
@test colorspace(img) == "Gray"
@test ndims(img) == 2
@test colordim(img) == 0
@test eltype(img) == Gray{Ufixed8}
outname = joinpath(writedir, "jigsaw_tmpl.png")
imwrite(img, outname)
imgc = imread(outname)
@test img.data == imgc.data
@test reinterpret(Uint32, data(map(mapinfo(RGB24, img), img))) ==
    map(x->x&0x00ffffff, reinterpret(Uint32, data(map(mapinfo(ARGB32, img), img))))
@test mapinfo(Uint32, img) == mapinfo(RGB24, img)
@test data(convert(Image{Gray{Float32}}, img)) == float32(data(img))
mapi = mapinfo(RGB{Ufixed8}, img)
imgrgb8 = map(mapi, img)
@test imgrgb8[1,1].r == img[1].val
open(outname, "w") do file
    writemime(file, "image/png", img)
end

# Gray with alpha channel
file = getfile("wmark_image.png")
img = imread(file)
@test colorspace(img) == "GrayAlpha"
@test ndims(img) == 2
@test colordim(img) == 0
@test eltype(img) == Images.ColorTypes.GrayAlpha{Ufixed8}
@osx? nothing : begin
    outname = joinpath(writedir, "wmark_image.png")
    imwrite(img, outname)
    sleep(0.2)
    imgc = imread(outname)
    @test img.data == imgc.data
    open(outname, "w") do file
        writemime(file, "image/png", img)
    end
end
@test reinterpret(Uint32, data(map(mapinfo(RGB24, img), img))) ==
    map(x->x&0x00ffffff, reinterpret(Uint32, data(map(mapinfo(ARGB32, img), img))))
@test mapinfo(Uint32, img) == mapinfo(ARGB32, img)

# RGB
file = getfile("rose.png")
img = imread(file)
# Mac reader reports RGB4, imagemagick reports RGB
@osx? begin
            @test colorspace(img) == "RGB4"
      end : @test colorspace(img) == "RGB"
@test ndims(img) == 2
@test colordim(img) == 0
@osx? begin
            @test eltype(img) == RGB4{Ufixed8}
      end : @test eltype(img) == RGB{Ufixed8}
outname = joinpath(writedir, "rose.ppm")
imwrite(img, outname)
imgc = imread(outname)
T = eltype(imgc)
lim = limits(imgc)
@test (typeof(lim[1]) == typeof(lim[2]) == T)  # issue #62
# Why does this one fail on OSX??
@osx? nothing : @test img.data == imgc.data
@test reinterpret(Uint32, data(map(mapinfo(RGB24, img), img))) ==
    map(x->x&0x00ffffff, reinterpret(Uint32, data(map(mapinfo(ARGB32, img), img))))
@test mapinfo(Uint32, img) == mapinfo(RGB24, img)
mapi = mapinfo(RGB{Ufixed8}, img)
imgrgb8 = map(mapi, img)
@test data(imgrgb8) == data(img)
convert(Array{Gray{Ufixed8}}, img)
convert(Image{Gray{Ufixed8}}, img)
convert(Array{Gray}, img)
convert(Image{Gray}, img)
imgs = separate(img)
@test permutedims(convert(Image{Gray}, imgs), [2,1]) == convert(Image{Gray}, img)
# Make sure that all the operations in README will work:
buf = Array(Uint32, size(img))
uint32color(img)
uint32color!(buf, img)
imA = convert(Array, img)
uint32color(imA)
uint32color!(buf, imA)
uint32color(imgs)
uint32color!(buf, imgs)
imr = reinterpret(Ufixed8, img)
uint32color(imr)
uint32color!(buf, imr)
@osx? nothing : begin
    imhsv = convert(Image{HSV}, float32(img))
    uint32color(imhsv)
    uint32color!(buf, imhsv)
    @test pixelspacing(restrict(img)) == [2.0,2.0]
end
outname = joinpath(writedir, "rose.png")
open(outname, "w") do file
    writemime(file, "image/png", img)
end

# RGBA with 16 bit depth
file = getfile("autumn_leaves.png")
img = imread(file)
@osx? begin
            @test colorspace(img) == "RGBA"
      end : @test colorspace(img) == "BGRA"
@test ndims(img) == 2
@test colordim(img) == 0
@osx? begin
            @test eltype(img) == Images.ColorTypes.RGBA{Ufixed16}
      end : @test eltype(img) == Images.ColorTypes.BGRA{Ufixed16}
outname = joinpath(writedir, "autumn_leaves.png")
@osx? nothing : begin
    imwrite(img, outname)
    sleep(0.2)
    imgc = imread(outname)
    @test img.data == imgc.data
    @test reinterpret(Uint32, data(map(mapinfo(RGB24, img), img))) ==
        map(x->x&0x00ffffff, reinterpret(Uint32, data(map(mapinfo(ARGB32, img), img))))
    @test mapinfo(Uint32, img) == mapinfo(ARGB32, img)
end
open(outname, "w") do file
    writemime(file, "image/png", img)
end

# Indexed
file = getfile("present.gif")
img = imread(file)
@test nimages(img) == 1
@test reinterpret(Uint32, data(map(mapinfo(RGB24, img), img))) ==
    map(x->x&0x00ffffff, reinterpret(Uint32, data(map(mapinfo(ARGB32, img), img))))
@test mapinfo(Uint32, img) == mapinfo(RGB24, img)
outname = joinpath(writedir, "present.png")
open(outname, "w") do file
    writemime(file, "image/png", img)
end

# Images with a temporal dimension
fname = "swirl_video.gif"
#fname = "bunny_anim.gif"  # this one has transparency but LibMagick gets confused about its size
file = getfile(fname)  # this also has transparency
img = imread(file)
@test timedim(img) == 3
@test nimages(img) == 26
outname = joinpath(writedir, fname)
imwrite(img, outname)
imgc = imread(outname)
# Something weird happens after the 2nd image (compression?), and one starts getting subtle differences.
# So don't compare the values.
# Also take the opportunity to test some things with temporal images
@test storageorder(img) == ["x", "y", "t"]
@test haskey(img, "timedim") == true
@test timedim(img) == 3
s = getindexim(img, 1:5, 1:5, 3)
@test timedim(s) == 0
s = sliceim(img, :, :, 5)
@test timedim(s) == 0
imgt = sliceim(img,"t",1)
@test reinterpret(Uint32, data(map(mapinfo(RGB24, imgt), imgt))) ==
    map(x->x&0x00ffffff, reinterpret(Uint32, data(map(mapinfo(ARGB32, imgt), imgt))))


text:
module HelloWorldController
using ..HelloWorld
using JSON

export call

function call(message)
  yourname = message["your_name"]

  Dict(:message => HelloWorld.message(yourname)) |> JSON.json
end

end

ext: jl


text:
struct UniformPolicy <: Network
    net::Network
end

initial_inference(n::UniformPolicy, game::MuZeroGame) = initial_inference(n, game.game)
# @todo just have initial_inference(x::UniformPolicy, args...)
initial_inference(x::UniformPolicy, game::AbstractEnv) = initial_inference(x.net, game)
initial_inference(x::UniformPolicy, games::Vector{<:AbstractEnv}) = initial_inference(x.net, games)
initial_inference(x::UniformPolicy, hidden_state::AbstractArray{Float32, 4}) = initial_inference(x.net, hidden_state)

function recurrent_inference(x::UniformPolicy, hidden_state::AbstractArray{Float32, 3}, action::Integer)
    recurrent_inference(x.net, hidden_state, action)
end

function recurrent_inference(x::UniformPolicy, hidden_state::Array{Float32, 4}, action::Array{Float32, 4})
    recurrent_inference(x.net, hidden_state, action)
end

function prediction(x::UniformPolicy, hidden_state::AbstractArray{Float32})
    policy_logits, state_values = prediction(x.net, hidden_state)
    zeros(Float32, size(policy_logits)...), state_values
end


text:
" Helper function: One-indexed version of rem(x,y) "
function rem1(x,y)
    return mod(x-1,y)+1
end

"""
    struct LocalMatrix{T} <: AbstractMatrix{T}
Block diagonal structure for local matrix. `A[:,:,iS,iK]` is a block matrix for
state iS and element iK
"""
struct LocalMatrix{T} <: AbstractMatrix{T}
    data::Array{T,4}
    factorizations::Array{Any,2}

    function LocalMatrix(data::Array{T,4}) where {T}
        new{T}(data,Array{Any}(undef, size(data,3), size(data,4)))
    end
end

"""
    size(A::LocalMatrix)
Returns a tuple containing the dimensions of actual operator A
"""
function Base.size(A::LocalMatrix)
    (m,n,ns,ne) = size(A.data)
    return (m*ns*ne, n*ns*ne)
end

"""
    getindex(A::LocalMatrix, i::Integer)
Linear scalar indexing into LocalMatrix
"""
@inline function Base.getindex(A::LocalMatrix, i::Integer)
    (m,n,ns,ne) = size(A.data)
    j = rem1(i, m*ns*ne); i = div(i, m*ns*ne, RoundUp)
    return getindex(A,i,j)
end

"""
    getindex(A::LocalMatrix{T}, i::Integer, j::Integer)
2-dimensional scalar indexing into LocalMatrix
"""
@inline function Base.getindex(A::LocalMatrix{T}, i::Integer, j::Integer) where {T}
    @boundscheck checkbounds(Bool, A,i,j)
    (m,n,ns,ne) = size(A.data)
    im = rem1(i ,m ); i2 = div(i ,m , RoundUp)
    is = rem1(i2,ns); i3 = div(i2,ns, RoundUp)
    ie = rem1(i3,ne)
    jn = rem1(j ,n ); j2 = div(j ,n , RoundUp)
    js = rem1(j2,ns); j3 = div(j2,ns, RoundUp)
    je = rem1(j3,ne)
    if is==js && ie==je && checkbounds(Bool,A.data, im,jn,is,ie)
        return @inbounds A.data[im,jn,is,ie]
    else
        return zero(T)
    end
end

"""
    setindex!(A::LocalMatrix{T}, v, i::Integer)
Linear scalar indexed assignment
"""
@inline function Base.setindex!(A::LocalMatrix, v, i::Integer)
    @boundscheck checkbounds(A.data, i)
    setindex!(A.data, v, i)
end

"""
    setindex!(A::LocalMatrix{T}, v, i::Integer, j::Integer)
2-dimensional scalar indexed assignment
"""
@inline function Base.setindex!(A::LocalMatrix, v, i::Integer, j::Integer)
    @boundscheck checkbounds(A,i,j)
    (m,n,ns,ne) = size(A.data)
    im = rem1(i ,m ); i2 = div(i ,m , RoundUp)
    is = rem1(i2,ns); i3 = div(i2,ns, RoundUp)
    ie = rem1(i3,ne)
    jn = rem1(j ,n ); j2 = div(j ,n , RoundUp)
    js = rem1(j2,ns); j3 = div(j2,ns, RoundUp)
    je = rem1(j3,ne)
    if is==js && ie==je && checkbounds(Bool,A.data, im,jn,is,ie)
        @inbounds setindex!(A.data, v, im,jn,is,ie)
    elseif !iszero(v)
        throw(ArgumentError("Cannot set off-diagonal entry ($i,$j) to non-zero value $v"))
    end
    return v
end

"""
    issymmetric(A::LocalMatrix)
Tests whether a LocalMatrix is symmetric
"""
function LinearAlgebra.issymmetric(A::LocalMatrix)
    all(LinearAlgebra.issymmetric, [@view A.data[:,:,iS,iK] for iS=1:size(A.data,3), iK=1:size(A.data,4)])
end

"""
    isdiag(A::LocalMatrix)
Tests whether a LocalMatrix is diagonal
"""
function LinearAlgebra.isdiag(A::LocalMatrix)
    all(LinearAlgebra.isdiag, [@view A.data[:,:,iS,iK] for iS=1:size(A.data,3), iK=1:size(A.data,4)])
end

(==)(A::LocalMatrix, B::LocalMatrix) = A.data == B.data
(-)(A::LocalMatrix) = LocalMatrix(-A.data)
(+)(A::LocalMatrix, B::LocalMatrix) = LocalMatrix(A.data+B.data)
(-)(A::LocalMatrix, B::LocalMatrix) = LocalMatrix(A.data-B.data)
(*)(c::Number, A::LocalMatrix) = LocalMatrix(c*A.data)
(*)(A::LocalMatrix, c::Number) = LocalMatrix(c*A.data)
(/)(A::LocalMatrix, c::Number) = LocalMatrix(A.data / c)
function (*)(A::LocalMatrix{T1}, B::LocalMatrix{T2}) where {T1,T2}
    (ma,na,nsa,nea) = size(A.data)
    (mb,nb,nsb,neb) = size(B.data)
    @assert ma == mb && na == nb && nea == neb
    data = Array{typeof(zero(T1)*zero(T2)),4}(undef, ma,na,max(nsa,nsb),nea)
    if nsa == nsb
        for iK = 1:nea
            for iS = 1:nsa
                @inbounds @views data[:,:,iS,iK] = A.data[:,:,iS,iK]*B.data[:,:,iS,iK]
            end
        end
    elseif nsa == 1
        for iK = 1:nea
            for iS = 1:nsb
                @inbounds @views data[:,:,iS,iK] = A.data[:,:,1,iK]*B.data[:,:,iS,iK]
            end
        end
    elseif nsb == 1
        for iK = 1:nea
            for iS = 1:nsa
                @inbounds @views data[:,:,iS,iK] = A.data[:,:,iS,iK]*B.data[:,:,1,iK]
            end
        end
    else
        throw(DimensionMismatch("Local matrices A $(size(A.data)) and B $(size(B.data))"))
    end
    return LocalMatrix(data)
end

"""
    mul!(C::LocalMatrix, A::LocalMatrix, B::LocalMatrix, α::Number, β::Number)
Return A B α + C β by overwriting C
"""
function LinearAlgebra.mul!(C::LocalMatrix{T3}, A::LocalMatrix{T1}, B::LocalMatrix{T2}, α::Number, β::Number) where {T1,T2,T3}
    @assert T3 == typeof(zero(T1)*zero(T2))
    (ma,na,nsa,nea) = size(A.data)
    (mb,nb,nsb,neb) = size(B.data)
    (mc,nc,nsc,nec) = size(C.data)
    @assert ma == mb == mc && na == nb == nc && nea == neb == nec
    if nsa == nsb == nsc
        C.data .*= β
        for iK = 1:nea
            for iS = 1:nsa
                @inbounds @views C.data[:,:,iS,iK] += α*A.data[:,:,iS,iK]*B.data[:,:,iS,iK]
            end
        end
    elseif nsa == 1 && nsb == nsc
        C.data .*= β
        for iK = 1:nea
            for iS = 1:nsb
                @inbounds @views C.data[:,:,iS,iK] += α*A.data[:,:,1,iK]*B.data[:,:,iS,iK]
            end
        end
    elseif nsb == 1 && nsa == nsc
        C.data .*= β
        for iK = 1:nea
            for iS = 1:nsa
                @inbounds @views C.data[:,:,iS,iK] += α*A.data[:,:,iS,iK]*B.data[:,:,1,iK]
            end
        end
    else
        throw(DimensionMismatch("Local matrices A $(size(A.data)), B $(size(B.data)), and C $(size(C.data))"))
    end
    return C
end

function (*)(A::LocalMatrix{T1}, x::AbstractVector{T2}) where {T1, T2}
    (m,n,ns,ne) = size(A.data)
    b = Vector{typeof(zero(T1)*zero(T2))}(undef, m*ns*ne)
    for iK = 1:ne
        for iS = 1:ns
            offset = ns*(iS-1)*(iK-1)
            @views b[offset+m*(iS-1)+1:offset+m*iS] = A.data[:,:,iS,iK]*x[offset+n*(iS-1)+1:offset+n*iS]
        end
    end
    return b
end

"""
    mul!(y::SolutionVector, A::LocalMatrix, x::SolutionVector, α::Number, β::Number)
Return A x α + y β by overwriting y
"""
function LinearAlgebra.mul!(y::SolutionVector, A::LocalMatrix, x::SolutionVector, α::Number, β::Number)
    (ma,na,nsa,nea) = size(A.data)
    (nx,nsx,nex) = size(x.data)
    (ny,nsy,ney) = size(y.data)
    @assert ma == ny && na == nx && nea == nex == ney
    if nsa == nsx == nsy
        y.data .*= β
        for iK = 1:nea
            for iS = 1:nsa
                @inbounds @views y.data[:,iS,iK] += α*A.data[:,:,iS,iK]*x.data[:,iS,iK]
            end
        end
    elseif nsa == 1 && nsx == nsy
        y.data .*= β
        for iK = 1:nea
            for iS = 1:nsx
                @inbounds @views y.data[:,iS,iK] += α*A.data[:,:,1,iK]*x.data[:,iS,iK]
            end
        end
    else
        throw(DimensionMismatch("Local matrix A $(size(A.data)), SolutionVector x $(size(x.data)) and y $(size(y.data))"))
    end
    return y
end

function (*)(A::LocalMatrix{T1}, x::SolutionVector{T2}) where {T1, T2}
    (m,n,ns,ne) = size(A.data)
    (nx,nsx,nex) = size(x.data)
    @assert n == nx && ne == nex
    b = Array{typeof(zero(T1)*zero(T2)),3}(undef, m,nsx,ne)
    if ns == nsx
        for iK = 1:ne
            for iS = 1:nsx
                @inbounds @views b[:,iS,iK] = A.data[:,:,iS,iK]*x.data[:,iS,iK]
            end
        end
    elseif ns == 1
        for iK = 1:ne
            for iS = 1:nsx
                @inbounds @views b[:,iS,iK] = A.data[:,:,1,iK]*x.data[:,iS,iK]
            end
        end
    else
        throw(DimensionMismatch("Local matrix A $(size(A.data)) cannot multiply x $(size(x.data))"))
    end
    return SolutionVector(b)
end

"""
    factorize(A::LocalMatrix)
Explicitly compute the block-diagonal full LU factorizations of A
"""
function factorize!(A::LocalMatrix)
    (m,n,ns,ne) = size(A.data)
    @assert m == n "Block diagonals of LocalMatrix must be square!"
    for iK = 1:ne
        for iS = 1:ns
            A.factorizations[iS,iK] = LinearAlgebra.lu(A.data[:,:,iS,iK])
        end
    end
end

"""
    isfactorized(A::LocalMatrix)
Return whether this LocalMatrix has been factorized yet
"""
isfactorized(A::LocalMatrix) = isassigned(A.factorizations,1)

"""
    ldiv!(A::LocalMatrix, x::SolutionVector)
In-place linear solve A\\x using block-diagonal LU factorizations. Compute this
block-diagonal factorization if not yet computed.
"""
function LinearAlgebra.ldiv!(A::LocalMatrix, x::SolutionVector)
    println("my ldiv!")
    (m,n,ns,ne) = size(A.data)
    (nx,nsx,nex) = size(x.data)
    @assert n == nx && ne == nex && m == n
    if !isfactorized(A)
        factorize!(A)
    end
    if ns == nsx
        for iK = 1:ne
            for iS = 1:nsx
                @views LinearAlgebra.ldiv!(A.factorizations[iS,iK], x.data[:,iS,iK])
            end
        end
    elseif ns == 1
        for iK = 1:ne
            for iS = 1:nsx
                @views LinearAlgebra.ldiv!(A.factorizations[1,iK], x.data[:,iS,iK])
            end
        end
    else
        throw(DimensionMismatch("Local matrix A $(size(A.data)) cannot \\ x $(size(x.data))"))
    end
    x
end

"""
    A::LocalMatrix \\ x::SolutionVector
Linear solve A\\x using block-diagonal LU factorizations. Compute this
block-diagonal factorization if not yet computed.
"""
function \(A::LocalMatrix, x::SolutionVector)
    println("my \\")
    (m,n,ns,ne) = size(A.data)
    (nx,nsx,nex) = size(x.data)
    @assert n == nx && ne == nex && m == n
    b = deepcopy(x)
    LinearAlgebra.ldiv!(A, b)
end

"""
    L2_error(u::SolutionVector, u_true::SolutionVector, M::LocalMatrix)
Compute the L2 error ||u-u_true||_{L_2} = (u-u_true)'*M*(u-u_true) of a
SolutionVector (see: L2_norm). Reuses the storage in u_true to compute this error.
"""
function L2_error!(u::SolutionVector, u_true::SolutionVector, M::LocalMatrix)
    u_true -= u
    return L2_norm(u_true, M)
end

"""
    L2_norm(u::SolutionVector, M::LocalMatrix)
Compute the L2 norm ||u||_{L_2} = u'*M*u of a SolutionVector. If u has multiple
states, then return array of L2 norms for each state.
"""
function L2_norm(u::SolutionVector, M::LocalMatrix)
    Mu = M*u
    (n,ns,ne) = true_size(Mu)
    l2_norms = zeros(ns)
    for iK = 1:ne
        for iS = 1:ns
            @views l2_norms[iS] += u.data[:,iS,iK]'*Mu.data[:,iS,iK]
        end
    end
    return l2_norms
end

max_stars_repo_name: NoseKnowsAll/DGToolkit

max_issues_repo_name: NoseKnowsAll/DGToolkit

max_forks_repo_name: NoseKnowsAll/DGToolkit


text:
using Manifolds
using Base.Test

M = Sphere()
x = randn(3)
g = randn(3)
@test norm(retract!(M, x)) ≈ 1
@test abs(dot(project_tangent(M,g,x),x)) < 1e-14


text:
using Blink
using Interact

model = deepcopy(mdp.driver_models[2])
ui = @manipulate for step in 1:n_steps(hist)+1
    s = state_hist(hist)[step]
    # b = hist.belief_hist[step]
    b = nothing
    acc = s.ego_info.acc
    if step < n_steps(hist)+1
        a = collect(action_hist(hist))[step]
        act = action_map(mdp, acc, a).a
    else
        a = -1
        act = NaN
    end
    if step == 1
        reset_hidden_state!(model)
        # @show model.a
    end
    if b!= nothing
        super_render(mdp, s, b)
    else
        super_render(mdp, s)
    end
    # model.other_acc = s.ego_info.acc
    # AutoViz.render(s.scene, mdp.env.roadway,
    #       SceneOverlay[
    #                    IDOverlay(color=colorant"white",font_size=20),
    #                    MergingNeighborsOverlay(target_id=EGO_ID, env=mdp.env),
    #                    DistToMergeOverlay(target_id=EGO_ID, env=mdp.env),
    #                    DistToMergeOverlay(target_id=2, env=mdp.env),
    #                 #    DistToMergeOverlay(target_id=3, env=mdp.env),
    #                 #    DistToMergeOverlay(target_id=6, env=mdp.env),
    #                    CooperativeIDMOverlay(targetid=2, model=model, textparams=TextParams(size = 20, x = 100, y_start=300)),
    #                    MaskingOverlay(mdp=mdp, acc=s.ego_info.acc),
    #                    TextOverlay(text=[@sprintf("Acc %2.2f m/s^2", act),
    #                                      @sprintf("Action %d", a),
    #                                      @sprintf("Time %2.1f", mdp.dt*step)],
    #                                pos=VecE2(300, 20), font_size=20),
    #                 #    CooperativeIDMOverlay(targetid=3, model=model, textparams=TextParams(size = 20, x = 700, y_start=250))
    #                    #    NeighborsOverlay(EGO_ID),
    #                 #    CarFollowingStatsOverlay(EGO_ID),
    #                     ],
    #     #   cam=SceneFollowCamera(10.0),
    #     # cam = CarFollowCamera(1, 5.0),
    #     # cam = FitToContentCamera(0.0),
    #     cam = StaticCamera(VecE2(-25.0, -10.0), 6.0),
    #     #   car_colors = Dict{Int64, Colorant}(1 => COLOR_CAR_EGO)
    #     car_colors=get_car_type_colors(s0.scene, mdp.driver_models)
    #     )
end

w = Window()
body!(w, ui);


text:
using PyPlot
using PyCall
#mpl = pyimport("tikzplotlib")
using JLD2

T = 200.0
NT = 200
t = LinRange(0,T,NT+1)

tid = 3
nntype = length(ARGS)>=1 ? ARGS[1] : "piecewise"

@load "Data/domain$tid.jld2" domain
@load "Data/$(nntype)/domain_te$(tid).jld2" domain_te
#domain_te = domain

# close("all")
# u1 = hcat(domain.history["state"]...)
# u2 = hcat(domain_te.history["state"]...)
# u = abs.(u1 - u2)
# for i = 1:5
#     plot(t, u[i,:], label="$i")
# end
# xlabel("\$t\$")
# ylabel("\$||u_{ref}-u_{exact}||\$")
# legend()
# #mpl.save("truss1d_disp_diff$tid.tex")
# savefig("truss1d_disp_diff.pdf")

close("all")
strain = hcat(domain.history["strain"]...)
stress = hcat(domain.history["stress"]...)
for i = 1:2:8
plot(strain[i,:], stress[i,:], "--", label="Reference$(i)")
end

strain = hcat(domain_te.history["strain"]...)
stress = hcat(domain_te.history["stress"]...)

for i = 1:2:8
plot(strain[i,:], stress[i,:], ".", label="Estimated")
end

xlabel("Strain")
ylabel("Stress")
legend()
#mpl.save("truss1d_stress$tid.tex")
savefig("nn_$(nntype)_truss1d_stress$tid.pdf")


text:
let
    actions = Vector{TestActionType}(undef,0)
    push!(actions,TestActionType(STAY,1))
    push!(actions, TestActionType(0.5,2))
    cmssp = TestCMSSPType(actions, [PRE,CURR,POST])

    tsv = TestStateType(CURR, TestContState(0.4,false))
    @test n_actions(cmssp) == 2
    @test discount(cmssp) == 1.0
    @test convert_s(Vector{Float64},tsv,cmssp) == [2.0,0.4,0.0]
    @test get_modeswitch_actions(cmssp.actions) == [STAY]
    @test get_control_actions(cmssp.actions) == [0.5]
    olv = OpenLoopVertex{MODE,TestContState,MODESWITCH}(tsv,STAY)
end

# TODO Tests
# More complicated ordering of actions (in case alternating AD and AC)


text:
#   This file is part of UnitSystems.jl
#   It is licensed under the MIT license
#   UnitSystems Copyright (C) 2020 Michael Reed
#       _           _                         _
#      | |         | |                       | |
#   ___| |__   __ _| | ___ __ __ ___   ____ _| | __ _
#  / __| '_ \ / _` | |/ / '__/ _` \ \ / / _` | |/ _` |
# | (__| | | | (_| |   <| | | (_| |\ V / (_| | | (_| |
#  \___|_| |_|\__,_|_|\_\_|  \__,_| \_/ \__,_|_|\__,_|
#
#   https://github.com/chakravala
#   https://crucialflow.com

@pure charge(U::UnitSystem,S::UnitSystem) = unit(sqrt((turn(S)/turn(U))*(planckreduced(S)*vacuumpermeability(U)*lightspeed(U)*rationalization(U)*lorentz(U)^2)/(planckreduced(U)*vacuumpermeability(S)*lightspeed(S)*rationalization(S)*lorentz(S)^2)))

@pure current(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)/time(U,S))

@pure electricpotential(U::UnitSystem,S::UnitSystem) = unit(energy(U,S)/charge(U,S))
const voltage = electricpotential
@pure capacitance(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)/electricpotential(U,S))
@pure resistance(U::UnitSystem,S::UnitSystem) = unit(electricpotential(U,S)/current(U,S))
@pure conductance(U::UnitSystem,S::UnitSystem) = unit(current(U,S)/electricpotential(U,S))
@pure magneticflux(U::UnitSystem,S::UnitSystem) = unit(energy(U,S)/lorentz(U,S)/current(U,S))
@pure magneticfluxdensity(U::UnitSystem,S::UnitSystem) = unit(magneticflux(U,S)/area(U,S))
@pure inductance(U::UnitSystem,S::UnitSystem) = unit(magneticflux(U,S)/current(U,S)*lorentz(U,S))

# electromagnetics

@pure electricfluxdensity(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)*rationalization(U,S)/area(U,S))
@pure chargedensity(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)/volume(U,S))
@pure currentdensity(U::UnitSystem,S::UnitSystem) = unit(current(U,S)/area(U,S))
@pure conductivity(U::UnitSystem,S::UnitSystem) = unit(conductance(U,S)/length(U,S))
@pure permittivity(U::UnitSystem,S::UnitSystem) = unit(capacitance(U,S)*rationalization(U,S)/length(U,S))
@pure electricfield(U::UnitSystem,S::UnitSystem) = unit(electricpotential(U,S)/length(U,S))
@pure magneticfield(U::UnitSystem,S::UnitSystem) = unit(current(U,S)*rationalization(U,S)*lorentz(U,S)/length(U,S))
@pure exposure(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)/mass(U,S))
@pure resistivity(U::UnitSystem,S::UnitSystem) = unit(resistance(U,S)*length(U,S))
@pure linearchargedensity(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)/length(U,S))
@pure magneticdipolemoment(U::UnitSystem,S::UnitSystem) = unit(current(U,S)*lorentz(U,S)*area(U,S)/gravity(U,S)/angle(U,S))
@pure mobility(U::UnitSystem,S::UnitSystem) = unit(length(U,S)*speed(U,S)*electricpotential(U,S))
@pure reluctance(U::UnitSystem,S::UnitSystem) = unit(rationalization(U,S)*lorentz(U,S)^2/inductance(U,S))
@pure vectorpotential(U::UnitSystem,S::UnitSystem) = unit(magneticflux(U,S)/length(U,S))
@pure magneticmoment(U::UnitSystem,S::UnitSystem) = unit(magneticflux(U,S)*length(U,S))
#@pure rigidity(U::UnitSystem,S::UnitSystem) = unit(magneticfluxdensity(U,S)*length(U,S))
@pure susceptibility(U::UnitSystem,S::UnitSystem) = unit(rationalization(S,U))

# WARNING unchecked: rigidity, magneticmoment, vectorpotential linearchargedensity

# CGS extra: polarizability, permeance, magnetic-current? + density, magneticresistance

@pure electricflux(U::UnitSystem,S::UnitSystem) = unit(electricpotential(U,S)*length(U,S))
@pure electricdipolemoment(U::UnitSystem,S::UnitSystem) = unit(charge(U,S)*length(U,S))
@pure magneticpotential(U::UnitSystem,S::UnitSystem) = unit(magneticflux(U,S)*reluctance(U,S))
@pure polestrength(U::UnitSystem,S::UnitSystem) = unit(magneticdipolemoment(U,S)/length(U,S))
@pure permeance(U::UnitSystem,S::UnitSystem) = reluctance(S,U)
@pure specificsusceptibility(U::UnitSystem,S::UnitSystem) = unit(magneticdipolemoment(U,S)/magneticfield(U,S)/mass(U,S))
#@pure magnetizability(U::UnitSystem,S::UnitSystem) = unit(magneticmoment(U,S)/magneticfluxdensity(U,S))
@pure electricpolarizability(U::UnitSystem,S::UnitSystem) = unit(electricdipolemoment(U,S)/electricfield(U,S))
@pure magneticpolarizability(U::UnitSystem,S::UnitSystem) = unit(magneticdipolemoment(U,S)/magneticfield(U,S))
#@pure magnetization(U::UnitSystem,S::UnitSystem) = unit(magneticmoment(U,S)/volume(U,S))

# specificmagnetization, mass magnetization = 1?
# magneticfluxdensity(Metric,EMU)/density(Metric,EMU)
@pure specificmagnetization(U::UnitSystem,S::UnitSystem) = unit(magneticmoment(S,U)/mass(S,U))
@pure demagnetizingfactor(U::UnitSystem,S::UnitSystem) = unit(rationalization(U,S))

# Gyrator-capacitor model alternative:
#@pure magneticfluxrate(U::UnitSystem,S::UnitSystem) = unit(magneticflux(U,S)/time(U,S))
#@pure magneticfluxratedensity(U::UnitSystem,S::UnitSystem) = unit(magneticfluxrate(U,S)/area(U,S))
#@pure magneticresistance(U::UnitSystem,S::UnitSystem) = unit(magneticpotential(U,S)/magneticfluxrate(U,S)) # not reluctance

include("electromagneticdocs.jl")


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct DeploymentExtendedFilter <: SwaggerModel
    provisioningState::Any # spec type: Union{ Nothing, String } # spec name: provisioningState

    function DeploymentExtendedFilter(;provisioningState=nothing)
        o = new()
        validate_property(DeploymentExtendedFilter, Symbol("provisioningState"), provisioningState)
        setfield!(o, Symbol("provisioningState"), provisioningState)
        o
    end
end # type DeploymentExtendedFilter

const _property_map_DeploymentExtendedFilter = Dict{Symbol,Symbol}(Symbol("provisioningState")=>Symbol("provisioningState"))
const _property_types_DeploymentExtendedFilter = Dict{Symbol,String}(Symbol("provisioningState")=>"String")
Base.propertynames(::Type{ DeploymentExtendedFilter }) = collect(keys(_property_map_DeploymentExtendedFilter))
Swagger.property_type(::Type{ DeploymentExtendedFilter }, name::Symbol) = Union{Nothing,eval(Meta.parse(_property_types_DeploymentExtendedFilter[name]))}
Swagger.field_name(::Type{ DeploymentExtendedFilter }, property_name::Symbol) =  _property_map_DeploymentExtendedFilter[property_name]

function check_required(o::DeploymentExtendedFilter)
    true
end

function validate_property(::Type{ DeploymentExtendedFilter }, name::Symbol, val)
end


text:
import Base: copy, copyto!, ==
import Random: rand, seed!
import RandomNumbers: gen_seed, seed_type

@inline rand(r::MT19937, ::Type{UInt32}) = mt_get(r)

seed!(r::MT19937, seed::Integer) = mt_set!(r, seed % UInt32)
seed!(r::MT19937, seed::NTuple{N, UInt32}=gen_seed(UInt32, N)) = mt_set!(r, seed)

seed_type(::Type{MT19937}) = NTuple{N, UInt32}

function copyto!(dest::MT19937, src::MT19937)
    copyto!(dest.mt, src.mt)
    dest.mti = src.mti
    dest
end

copy(src::MT19937) = MT19937(copy(src.mt), src.mti)

==(r1::MT19937, r2::MT19937) = r1.mt == r2.mt && r1.mti == r2.mti


text:
abstract type CompositeKernel <: Kernel end

components(k::CompositeKernel) = k.kernels

@deprecate subkernels components

#################
# CompositeData #
#################

struct CompositeData <: KernelData
    datadict::Dict{String, KernelData}
    keys::Vector{String}
end

function KernelData(compkern::CompositeKernel, X1::AbstractMatrix, X2::AbstractMatrix)
    datadict = Dict{String, KernelData}()
    datakeys = String[]
    for k in components(compkern)
        data_type = kernel_data_key(k, X1, X2)
        if !haskey(datadict, data_type)
            datadict[data_type] = KernelData(k, X1, X2)
        end
        push!(datakeys, data_type)
    end
    CompositeData(datadict, datakeys)
end

function kernel_data_key(compkern::CompositeKernel, X1::AbstractMatrix, X2::AbstractMatrix)
    join(["CompositeData" ; sort(unique(kernel_data_key(k, X1, X2) for k in components(compkern)))])
end


text:
abstract type ModernHopfieldNetwork{T} end

struct ContinuousMHN{T} <: ModernHopfieldNetwork{T}

end

struct DiscreteMHN{T} <: ModernHopfieldNetwork{T}

end

function hopfieldlayers(x,ξ)
        return x ⋅ ξ
end
hopfieldlayers([1,2],[3,4])


text:
### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 69aafbda-f938-4358-97d9-87bf1cc3e82a
begin
    using ShortCodes
	using PlutoUI
end

# ╔═╡ 56531782-09f2-11ec-110b-551c2e199804
md"""
## Medicine is a Data-Intensive Discipline

I think one of the things that's really important for medical students to know is that all of the information that's generated around patients around patient care---getting lab tests, imaging tests, your notes when you see a patient---all of that ends up getting stored in some kind of data warehouse. And to have that information be useful to you in your next encounter, how that information is stored and how it gets extracted is really really important.

This is one of the pushes for data standards.  If i say a patient name is Joe Smith and you say a patient name is Joe Smith and someone else says a patient name is Joe Smith, if every one of our data systems stores first name and last name in a different way and it's the same Joe Smith, we're not going to be able to talk to each other about that patient very well. So I think medical students really need to understand that how data is stored is going to have an influence on their ability to extract that to take care of patients in the future.

So even asking questions about how does it work? why does it work? where does it go? what's the data structure? [is important].

[It is important to know] that it's a human that we're taking care of, __but as doctors we're generating data and patients are generating data__ and
that's really something that I think
medical students need to know. (Marta Heilbrun, MD, Vice Chair for Quality, Department of Radiology, Emory University)"""

# ╔═╡ b204a8a8-6125-4d9a-95bd-3db835edf818
md"""Below is the clip from Dr. Heilbrun, as well as other physicians about what every medical student should know about informatics."""

# ╔═╡ 76637834-8b1a-4ce0-ab61-4a3bbe16aa48
html"""

"""

# ╔═╡ 6af9a717-439d-4129-8014-f832bc289131
YouTube("652RmFCvV-w", 1, 120)

# ╔═╡ 7eaf4347-8e6b-46c0-aa64-5790d0fd77c1
html"""

"""

# ╔═╡ 027774d5-9034-4870-a5e4-aff73e381075
md"""
## But what is "data"?

- Data as singular vs plural and academic snobbery

We are going to follow what is known as the __The Diaphoric Definition of Data (DDD):__

> A datum is a putative fact regarding some difference or lack of uniformity within some context. ([Floridi, Luciano, "Semantic Conceptions of Information"](https://plato.stanford.edu/entries/information-semantic/))

In the David Nicholls case study there are a number of physical data that are collected.

For example, an ECG is a measurement of a __difference__ in __electrical voltage__ on different locations on the skin at different points of time.

### What are some other examples of physical data that are generated/colleced in this case?

#### Type your answers here
$(@bind physicaldata1 TextField((80,7)))

"""

# ╔═╡ f8e829ea-5ff9-4203-a7ae-9a9e7a6646c0
let answer1 = md"""

- Blood pressure: Systolic and diastolic blood pressures are differences in pressure at different points of time.
- Respiration:
"""

HTML("My Answers$(html(answer1))")
    end

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
ShortCodes = "f62ebe17-55c5-4640-972f-b59c0dd11ccf"

[compat]
PlutoUI = "~0.7.9"
ShortCodes = "~0.3.2"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "44e3b40da000eab4ccb1aecdc4801c040026aeb5"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.13"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[JSON3]]
deps = ["Dates", "Mmap", "Parsers", "StructTypes", "UUIDs"]
git-tree-sha1 = "b3e5984da3c6c95bcf6931760387ff2e64f508f3"
uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
version = "1.9.1"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "0fb723cd8c45858c22169b2e42269e53271a6df7"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.7"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

[[Memoize]]
deps = ["MacroTools"]
git-tree-sha1 = "2b1dfcba103de714d31c033b5dacc2e4a12c7caa"
uuid = "c03570c3-d221-55d1-a50c-7939bbd78826"
version = "0.4.4"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "438d35d2d95ae2c5e8780b330592b6de8494e779"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.3"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[ShortCodes]]
deps = ["Base64", "CodecZlib", "HTTP", "JSON3", "Memoize", "UUIDs"]
git-tree-sha1 = "866962b3cc79ad3fee73f67408c649498bad1ac0"
uuid = "f62ebe17-55c5-4640-972f-b59c0dd11ccf"
version = "0.3.2"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[StructTypes]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "8445bf99a36d703a09c601f9a57e2f83000ef2ae"
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
version = "1.7.3"

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[TranscodingStreams]]
deps = ["Random", "Test"]
git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.9.6"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
"""

# ╔═╡ Cell order:
# ╟─56531782-09f2-11ec-110b-551c2e199804
# ╟─b204a8a8-6125-4d9a-95bd-3db835edf818
# ╟─76637834-8b1a-4ce0-ab61-4a3bbe16aa48
# ╠═6af9a717-439d-4129-8014-f832bc289131
# ╠═7eaf4347-8e6b-46c0-aa64-5790d0fd77c1
# ╠═027774d5-9034-4870-a5e4-aff73e381075
# ╠═f8e829ea-5ff9-4203-a7ae-9a9e7a6646c0
# ╠═69aafbda-f938-4358-97d9-87bf1cc3e82a
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


text:
"""
    TMatrix <: SMatrix{4, 4, T}

A custom `SMatrix` subtype that represents the `T` matrix from [Clemmow1954] that
is semi-sparse. The form of the matrix is:

```
┌                ┐
| T₁₁ T₁₂ 0  T₁₄ |
| 0   0   1  0   |
| T₃₁ T₃₂ 0  T₃₄ |
| T₄₁ T₄₂ 0  T₄₄ |
└                ┘
```

`TMatrix` implements efficient matrix-vector multiplication and other math based on its
special form.

See also: [`tmatrix`](@ref)

# References

[Clemmow1954]: P. C. Clemmow and J. Heading, “Coupled forms of the differential equations
    governing radio propagation in the ionosphere,” Mathematical Proceedings of the
    Cambridge Philosophical Society, vol. 50, no. 2, pp. 319–333, Apr. 1954.
"""
struct TMatrix{T} <: StaticMatrix{4, 4, T}
    data::SVector{9,T}

    @inline function TMatrix(data::SVector{9,T}) where T
        new{T}(data)
    end
end

#==
# Constructors
==#

@generated function TMatrix(a::StaticMatrix{4, 4, T}) where {T}
    I = _tmatrix_indices()
    expr = Vector{Expr}(undef, 9)
    i = 0
    for index = 1:16
        if I[index] > 0
            expr[i += 1] = :(a[$index])
        end
    end
    quote
        @_inline_meta
        @inbounds return TMatrix(SVector{9,T}(tuple($(expr...))))
    end
end

@generated function TMatrix(a::NTuple{N,Any}) where {N}
    if N != 9
        throw(DimensionMismatch("Tuple length must be 9. Length of input was $N."))
    end
    expr = [:(a[$i]) for i = 1:9]
    quote
        @_inline_meta
        @inbounds return TMatrix(SVector{9}(tuple($(expr...))))
    end
end

#==
# Characteristics
==#

Base.size(::Type{TMatrix}) = (4, 4)
Base.size(::Type{TMatrix}, d::Int) = d > 2 ? 1 : 4

@inline function _tmatrix_indices()
    # Returns tuple containing TMatrix.data index for dense matrix index of the
    # tuple. For example, for linear index `5` of 4×4 matrix, return `4` which
    # is the index of TMatrix.data.
    # For "empty" values, return `0`.
    return tuple(1, 0, 2, 3, 4, 0, 5, 6, 0, 0, 0, 0, 7, 0, 8, 9)
end

@propagate_inbounds function Base.getindex(a::TMatrix{T}, i::Int) where T
    I = _tmatrix_indices()

    # Written out this is much faster than `i in (2, 6, 9, 11, 12, 14)`
    # Could probably be fewer lines with a generated function
    if I[i] > 0
        return a.data[I[i]]
    elseif i == 2
        return zero(T)
    elseif i ==  6
        return zero(T)
    elseif i == 9
        return zero(T)
    elseif i == 10
        return one(T)
    elseif i == 11
        return zero(T)
    elseif i == 12
        return zero(T)
    elseif i == 14
        return zero(T)
    else
        throw(BoundsError("attempt to access $(typeof(a)) at index $i"))
    end
end

@propagate_inbounds function getindex(a::TMatrix, inds::Int...)
    @boundscheck checkbounds(a, inds...)
    _getindex_scalar(a, inds...)
end

@generated function _getindex_scalar(a::TMatrix, inds::Int...)
    if length(inds) == 0
        return quote
            @_propagate_inbounds_meta
            a[1]
        end
    end

    # This function will only ever be called for length(inds) == 2
    stride = 1
    ind_expr = :()
    for i in 1:length(inds)
        if i == 1
            ind_expr = :(inds[1])
        else
            ind_expr = :($ind_expr + $stride * (inds[$i] - 1))
        end
        stride *= 4
    end
    return quote
        @_propagate_inbounds_meta
        a[$ind_expr]
    end
end

@generated function Base.Tuple(v::TMatrix)
    exprs = [:(v[$i]) for i = 1:16]
    quote
        @_inline_meta
        tuple($(exprs...))
    end
end

#==
# Math
==#

LinearAlgebra.ishermitian(v::TMatrix) = false
LinearAlgebra.issymmetric(v::TMatrix) = false

@inline Base.:(==)(a::TMatrix, b::TMatrix) = a.data == b.data

@inline Base.sum(v::TMatrix) = sum(v.data) + 1

@generated function Base.:*(a::Number, b::TMatrix{T}) where T
    I = _tmatrix_indices()
    exprs = [:(($I[$i] > 0) ? a*b.data[$I[$i]] : ($i == 10 ? convert(newtype,a) : zero(newtype))) for i = 1:16]
    quote
        @_inline_meta
        newtype = promote_type(T, typeof(a))
        SMatrix{4,4,newtype}(tuple($(exprs...)))
    end
end
@generated function Base.:*(a::TMatrix{T}, b::Number) where T
    I = _tmatrix_indices()
    exprs = [:(($I[$i] > 0) ? b*a.data[$I[$i]] : ($i == 10 ? convert(newtype,b) : zero(newtype))) for i = 1:16]
    quote
        @_inline_meta
        newtype = promote_type(T, typeof(b))
        SMatrix{4,4,newtype}(tuple($(exprs...)))
    end
end

@inline Base.:*(A::TMatrix, B::AbstractVector) = _mul(A, B)
@inline Base.:*(A::TMatrix, B::StaticVector) = _mul(Size(B), A, B)
@inline Base.:*(A::TMatrix, B::StaticMatrix) = _mul(Size(B), A, B)

@generated function _mul(a::TMatrix{Ta}, b::AbstractVector{Tb}) where {Ta,Tb}
    sa = Size(4,4)

    exprs = [:(a[$(LinearIndices(sa)[1,1])]*b[1]+a[$(LinearIndices(sa)[1,2])]*b[2]+a[$(LinearIndices(sa)[1,4])]*b[4]),
             :(b[3]),
             :(a[$(LinearIndices(sa)[3,1])]*b[1]+a[$(LinearIndices(sa)[3,2])]*b[2]+a[$(LinearIndices(sa)[3,4])]*b[4]),
             :(a[$(LinearIndices(sa)[4,1])]*b[1]+a[$(LinearIndices(sa)[4,2])]*b[2]+a[$(LinearIndices(sa)[4,4])]*b[4])]

    return quote
        @_inline_meta
        if length(b) != $sa[2]
            throw(DimensionMismatch("Tried to multiply arrays of size $sa and $(size(b))"))
        end
        T = Base.promote_op(LinearAlgebra.matprod, Ta, Tb)
        @inbounds return similar_type(b, T, Size($sa[1]))(tuple($(exprs...)))
    end
end

@generated function _mul(::Size{sb}, a::TMatrix{Ta}, b::StaticVector{<:Any, Tb}) where {sb, Ta, Tb}
    sa = Size(4,4)
    if sb[1] != sa[2]
        throw(DimensionMismatch("Tried to multiply arrays of size $sa and $sb"))
    end

    exprs = [:(a[$(LinearIndices(sa)[1,1])]*b[1]+a[$(LinearIndices(sa)[1,2])]*b[2]+a[$(LinearIndices(sa)[1,4])]*b[4]),
             :(b[3]),
             :(a[$(LinearIndices(sa)[3,1])]*b[1]+a[$(LinearIndices(sa)[3,2])]*b[2]+a[$(LinearIndices(sa)[3,4])]*b[4]),
             :(a[$(LinearIndices(sa)[4,1])]*b[1]+a[$(LinearIndices(sa)[4,2])]*b[2]+a[$(LinearIndices(sa)[4,4])]*b[4])]

    return quote
        @_inline_meta
        T = Base.promote_op(LinearAlgebra.matprod, Ta, Tb)
        @inbounds return similar_type(b, T, Size($sa[1]))(tuple($(exprs...)))
    end
end

@generated function _mul(Sb::Size{sb}, a::TMatrix{Ta}, b::StaticMatrix{<:Any,<:Any,Tb}) where {sb, Ta, Tb}
    sa = Size(4,4)
    if sb[1] != sa[2]
        throw(DimensionMismatch("Tried to multiply arrays of size $sa and $sb"))
    end

    S = Size(sa[1], sb[2])

    tmp_exprs = Vector{Vector{Expr}}(undef, sb[2])
    for i = 1:sb[2]
        tmp_exprs[i] = [:(a[$(LinearIndices(sa)[1,1])]*b[$(LinearIndices(sb)[1,i])]+
                          a[$(LinearIndices(sa)[1,2])]*b[$(LinearIndices(sb)[2,i])]+
                          a[$(LinearIndices(sa)[1,4])]*b[$(LinearIndices(sb)[4,i])]),
                        :(b[$(LinearIndices(sb)[3,i])]),
                        :(a[$(LinearIndices(sa)[3,1])]*b[$(LinearIndices(sb)[1,i])]+
                          a[$(LinearIndices(sa)[3,2])]*b[$(LinearIndices(sb)[2,i])]+
                          a[$(LinearIndices(sa)[3,4])]*b[$(LinearIndices(sb)[4,i])]),
                        :(a[$(LinearIndices(sa)[4,1])]*b[$(LinearIndices(sb)[1,i])]+
                          a[$(LinearIndices(sa)[4,2])]*b[$(LinearIndices(sb)[2,i])]+
                          a[$(LinearIndices(sa)[4,4])]*b[$(LinearIndices(sb)[4,i])])]
    end
    exprs = vcat(tmp_exprs...)

    return quote
        @_inline_meta
        T = Base.promote_op(LinearAlgebra.matprod, Ta, Tb)
        @inbounds return similar_type(a, T, $S)(tuple($(exprs...)))
    end
end

#==
Computing the T matrix entries
==#

@doc raw"""
    tmatrix(ea::EigenAngle, M)

Compute the matrix `T` as a `TMatrix` for the differential equations of a wave propagating
at angle `ea` in an ionosphere with susceptibility `M`.

Clemmow and Heading derived the `T` matrix from Maxwell's equations for an electromagnetic
wave in the anisotropic, collisional cold plasma of the ionosphere in a coordinate frame
where ``z`` is upward, propagation is directed obliquely in the ``x``-``z`` plane and
invariance is assumed in ``y``. For the four characteristic wave components
``e = (Ex, -Ey, Z₀Hx, Z₀Hy)ᵀ``, the differential equations are ``de/dz = -ikTe``.

See also: [`susceptibility`](@ref), [`dtmatrix`](@ref)

# References

[Budden1955a]: K. G. Budden, “The numerical solution of differential equations governing
    reflexion of long radio waves from the ionosphere,” Proc. R. Soc. Lond. A, vol. 227,
    no. 1171, pp. 516–537, Feb. 1955.

[Clemmow1954]: P. C. Clemmow and J. Heading, “Coupled forms of the differential equations
    governing radio propagation in the ionosphere,” Mathematical Proceedings of the
    Cambridge Philosophical Society, vol. 50, no. 2, pp. 319–333, Apr. 1954.
"""
function tmatrix(ea::EigenAngle, M)
    S, C² = ea.sinθ, ea.cos²θ

    # Denominator of most of the entries of `T`
    den = inv(1 + M[3,3])

    M31den = M[3,1]*den
    M32den = M[3,2]*den

    T11 = -S*M31den
    T12 = S*M32den
    # T13 = 0
    T14 = (C² + M[3,3])*den
    # T21 = 0
    # T22 = 0
    # T23 = 1
    # T24 = 0
    T31 = M[2,3]*M31den - M[2,1]
    T32 = C² + M[2,2] - M[2,3]*M32den
    # T33 = 0
    T34 = S*M[2,3]*den
    T41 = 1 + M[1,1] - M[1,3]*M31den
    T42 = M[1,3]*M32den - M[1,2]
    # T43 = 0
    T44 = -S*M[1,3]*den

    # `TMatrix` is a special 4×4 matrix
    return TMatrix(T11, T31, T41,
                   T12, T32, T42,
                   T14, T34, T44)
end

"""
    dtmatrix(ea::EigenAngle, M)

Compute a dense `SMatrix` with the derivative of `T` with respect to `θ`.

See also: [`tmatrix`](@ref)
"""
function dtmatrix(ea::EigenAngle, M)
    S, C = ea.sinθ, ea.cosθ
    dC² = -2*S*C  # d/dθ (C²)

    den = inv(1 + M[3,3])

    dT11 = -C*M[3,1]*den
    dT12 = C*M[3,2]*den
    dT13 = 0
    dT14 = dC²*den
    dT21 = 0
    dT22 = 0
    dT23 = 0
    dT24 = 0
    dT31 = 0
    dT32 = dC²
    dT33 = 0
    dT34 = C*M[2,3]*den
    dT41 = 0
    dT42 = 0
    dT43 = 0
    dT44 = -C*M[1,3]*den

    return SMatrix{4,4}(dT11, dT21, dT31, dT41,
                        dT12, dT22, dT32, dT42,
                        dT13, dT23, dT33, dT43,
                        dT14, dT24, dT34, dT44)
end


text:
# This file is auto-generated by AWSMetadata.jl
using AWS
using AWS.AWSServices: cloudhsm_v2
using AWS.Compat
using AWS.UUIDs

"""
    copy_backup_to_region(backup_id, destination_region)
    copy_backup_to_region(backup_id, destination_region, params::Dict{String,<:Any})

Copy an AWS CloudHSM cluster backup to a different region.

# Arguments
- `backup_id`: The ID of the backup that will be copied to the destination region.
- `destination_region`: The AWS region that will contain your copied CloudHSM cluster
  backup.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"TagList"`: Tags to apply to the destination backup during creation. If you specify
  tags, only these tags will be applied to the destination backup. If you do not specify
  tags, the service copies tags from the source backup to the destination backup.
"""
copy_backup_to_region(BackupId, DestinationRegion; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("CopyBackupToRegion", Dict{String, Any}("BackupId"=>BackupId, "DestinationRegion"=>DestinationRegion); aws_config=aws_config)
copy_backup_to_region(BackupId, DestinationRegion, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("CopyBackupToRegion", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("BackupId"=>BackupId, "DestinationRegion"=>DestinationRegion), params)); aws_config=aws_config)

"""
    create_cluster(hsm_type, subnet_ids)
    create_cluster(hsm_type, subnet_ids, params::Dict{String,<:Any})

Creates a new AWS CloudHSM cluster.

# Arguments
- `hsm_type`: The type of HSM to use in the cluster. Currently the only allowed value is
  hsm1.medium.
- `subnet_ids`: The identifiers (IDs) of the subnets where you are creating the cluster.
  You must specify at least one subnet. If you specify multiple subnets, they must meet the
  following criteria:   All subnets must be in the same virtual private cloud (VPC).   You
  can specify only one subnet per Availability Zone.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"BackupRetentionPolicy"`: A policy that defines how the service retains backups.
- `"SourceBackupId"`: The identifier (ID) of the cluster backup to restore. Use this value
  to restore the cluster from a backup instead of creating a new cluster. To find the backup
  ID, use DescribeBackups.
- `"TagList"`: Tags to apply to the CloudHSM cluster during creation.
"""
create_cluster(HsmType, SubnetIds; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("CreateCluster", Dict{String, Any}("HsmType"=>HsmType, "SubnetIds"=>SubnetIds); aws_config=aws_config)
create_cluster(HsmType, SubnetIds, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("CreateCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("HsmType"=>HsmType, "SubnetIds"=>SubnetIds), params)); aws_config=aws_config)

"""
    create_hsm(availability_zone, cluster_id)
    create_hsm(availability_zone, cluster_id, params::Dict{String,<:Any})

Creates a new hardware security module (HSM) in the specified AWS CloudHSM cluster.

# Arguments
- `availability_zone`: The Availability Zone where you are creating the HSM. To find the
  cluster's Availability Zones, use DescribeClusters.
- `cluster_id`: The identifier (ID) of the HSM's cluster. To find the cluster ID, use
  DescribeClusters.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"IpAddress"`: The HSM's IP address. If you specify an IP address, use an available
  address from the subnet that maps to the Availability Zone where you are creating the HSM.
  If you don't specify an IP address, one is chosen for you from that subnet.
"""
create_hsm(AvailabilityZone, ClusterId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("CreateHsm", Dict{String, Any}("AvailabilityZone"=>AvailabilityZone, "ClusterId"=>ClusterId); aws_config=aws_config)
create_hsm(AvailabilityZone, ClusterId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("CreateHsm", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("AvailabilityZone"=>AvailabilityZone, "ClusterId"=>ClusterId), params)); aws_config=aws_config)

"""
    delete_backup(backup_id)
    delete_backup(backup_id, params::Dict{String,<:Any})

Deletes a specified AWS CloudHSM backup. A backup can be restored up to 7 days after the
DeleteBackup request is made. For more information on restoring a backup, see RestoreBackup.

# Arguments
- `backup_id`: The ID of the backup to be deleted. To find the ID of a backup, use the
  DescribeBackups operation.

"""
delete_backup(BackupId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DeleteBackup", Dict{String, Any}("BackupId"=>BackupId); aws_config=aws_config)
delete_backup(BackupId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DeleteBackup", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("BackupId"=>BackupId), params)); aws_config=aws_config)

"""
    delete_cluster(cluster_id)
    delete_cluster(cluster_id, params::Dict{String,<:Any})

Deletes the specified AWS CloudHSM cluster. Before you can delete a cluster, you must
delete all HSMs in the cluster. To see if the cluster contains any HSMs, use
DescribeClusters. To delete an HSM, use DeleteHsm.

# Arguments
- `cluster_id`: The identifier (ID) of the cluster that you are deleting. To find the
  cluster ID, use DescribeClusters.

"""
delete_cluster(ClusterId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DeleteCluster", Dict{String, Any}("ClusterId"=>ClusterId); aws_config=aws_config)
delete_cluster(ClusterId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DeleteCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId), params)); aws_config=aws_config)

"""
    delete_hsm(cluster_id)
    delete_hsm(cluster_id, params::Dict{String,<:Any})

Deletes the specified HSM. To specify an HSM, you can use its identifier (ID), the IP
address of the HSM's elastic network interface (ENI), or the ID of the HSM's ENI. You need
to specify only one of these values. To find these values, use DescribeClusters.

# Arguments
- `cluster_id`: The identifier (ID) of the cluster that contains the HSM that you are
  deleting.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"EniId"`: The identifier (ID) of the elastic network interface (ENI) of the HSM that you
  are deleting.
- `"EniIp"`: The IP address of the elastic network interface (ENI) of the HSM that you are
  deleting.
- `"HsmId"`: The identifier (ID) of the HSM that you are deleting.
"""
delete_hsm(ClusterId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DeleteHsm", Dict{String, Any}("ClusterId"=>ClusterId); aws_config=aws_config)
delete_hsm(ClusterId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DeleteHsm", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId), params)); aws_config=aws_config)

"""
    describe_backups()
    describe_backups(params::Dict{String,<:Any})

Gets information about backups of AWS CloudHSM clusters. This is a paginated operation,
which means that each response might contain only a subset of all the backups. When the
response contains only a subset of backups, it includes a NextToken value. Use this value
in a subsequent DescribeBackups request to get more backups. When you receive a response
with no NextToken (or an empty or null value), that means there are no more backups to get.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`: One or more filters to limit the items returned in the response. Use the
  backupIds filter to return only the specified backups. Specify backups by their backup
  identifier (ID). Use the sourceBackupIds filter to return only the backups created from a
  source backup. The sourceBackupID of a source backup is returned by the CopyBackupToRegion
  operation. Use the clusterIds filter to return only the backups for the specified clusters.
  Specify clusters by their cluster identifier (ID). Use the states filter to return only
  backups that match the specified state. Use the neverExpires filter to return backups
  filtered by the value in the neverExpires parameter. True returns all backups exempt from
  the backup retention policy. False returns all backups with a backup retention policy
  defined at the cluster.
- `"MaxResults"`: The maximum number of backups to return in the response. When there are
  more backups than the number you specify, the response contains a NextToken value.
- `"NextToken"`: The NextToken value that you received in the previous response. Use this
  value to get more backups.
- `"SortAscending"`: Designates whether or not to sort the return backups by ascending
  chronological order of generation.
"""
describe_backups(; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DescribeBackups"; aws_config=aws_config)
describe_backups(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DescribeBackups", params; aws_config=aws_config)

"""
    describe_clusters()
    describe_clusters(params::Dict{String,<:Any})

Gets information about AWS CloudHSM clusters. This is a paginated operation, which means
that each response might contain only a subset of all the clusters. When the response
contains only a subset of clusters, it includes a NextToken value. Use this value in a
subsequent DescribeClusters request to get more clusters. When you receive a response with
no NextToken (or an empty or null value), that means there are no more clusters to get.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`: One or more filters to limit the items returned in the response. Use the
  clusterIds filter to return only the specified clusters. Specify clusters by their cluster
  identifier (ID). Use the vpcIds filter to return only the clusters in the specified virtual
  private clouds (VPCs). Specify VPCs by their VPC identifier (ID). Use the states filter to
  return only clusters that match the specified state.
- `"MaxResults"`: The maximum number of clusters to return in the response. When there are
  more clusters than the number you specify, the response contains a NextToken value.
- `"NextToken"`: The NextToken value that you received in the previous response. Use this
  value to get more clusters.
"""
describe_clusters(; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DescribeClusters"; aws_config=aws_config)
describe_clusters(params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("DescribeClusters", params; aws_config=aws_config)

"""
    initialize_cluster(cluster_id, signed_cert, trust_anchor)
    initialize_cluster(cluster_id, signed_cert, trust_anchor, params::Dict{String,<:Any})

Claims an AWS CloudHSM cluster by submitting the cluster certificate issued by your issuing
certificate authority (CA) and the CA's root certificate. Before you can claim a cluster,
you must sign the cluster's certificate signing request (CSR) with your issuing CA. To get
the cluster's CSR, use DescribeClusters.

# Arguments
- `cluster_id`: The identifier (ID) of the cluster that you are claiming. To find the
  cluster ID, use DescribeClusters.
- `signed_cert`: The cluster certificate issued (signed) by your issuing certificate
  authority (CA). The certificate must be in PEM format and can contain a maximum of 5000
  characters.
- `trust_anchor`: The issuing certificate of the issuing certificate authority (CA) that
  issued (signed) the cluster certificate. You must use a self-signed certificate. The
  certificate used to sign the HSM CSR must be directly available, and thus must be the root
  certificate. The certificate must be in PEM format and can contain a maximum of 5000
  characters.

"""
initialize_cluster(ClusterId, SignedCert, TrustAnchor; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("InitializeCluster", Dict{String, Any}("ClusterId"=>ClusterId, "SignedCert"=>SignedCert, "TrustAnchor"=>TrustAnchor); aws_config=aws_config)
initialize_cluster(ClusterId, SignedCert, TrustAnchor, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("InitializeCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId, "SignedCert"=>SignedCert, "TrustAnchor"=>TrustAnchor), params)); aws_config=aws_config)

"""
    list_tags(resource_id)
    list_tags(resource_id, params::Dict{String,<:Any})

Gets a list of tags for the specified AWS CloudHSM cluster. This is a paginated operation,
which means that each response might contain only a subset of all the tags. When the
response contains only a subset of tags, it includes a NextToken value. Use this value in a
subsequent ListTags request to get more tags. When you receive a response with no NextToken
(or an empty or null value), that means there are no more tags to get.

# Arguments
- `resource_id`: The cluster identifier (ID) for the cluster whose tags you are getting. To
  find the cluster ID, use DescribeClusters.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of tags to return in the response. When there are more
  tags than the number you specify, the response contains a NextToken value.
- `"NextToken"`: The NextToken value that you received in the previous response. Use this
  value to get more tags.
"""
list_tags(ResourceId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("ListTags", Dict{String, Any}("ResourceId"=>ResourceId); aws_config=aws_config)
list_tags(ResourceId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("ListTags", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ResourceId"=>ResourceId), params)); aws_config=aws_config)

"""
    modify_backup_attributes(backup_id, never_expires)
    modify_backup_attributes(backup_id, never_expires, params::Dict{String,<:Any})

Modifies attributes for AWS CloudHSM backup.

# Arguments
- `backup_id`: The identifier (ID) of the backup to modify. To find the ID of a backup, use
  the DescribeBackups operation.
- `never_expires`: Specifies whether the service should exempt a backup from the retention
  policy for the cluster. True exempts a backup from the retention policy. False means the
  service applies the backup retention policy defined at the cluster.

"""
modify_backup_attributes(BackupId, NeverExpires; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("ModifyBackupAttributes", Dict{String, Any}("BackupId"=>BackupId, "NeverExpires"=>NeverExpires); aws_config=aws_config)
modify_backup_attributes(BackupId, NeverExpires, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("ModifyBackupAttributes", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("BackupId"=>BackupId, "NeverExpires"=>NeverExpires), params)); aws_config=aws_config)

"""
    modify_cluster(backup_retention_policy, cluster_id)
    modify_cluster(backup_retention_policy, cluster_id, params::Dict{String,<:Any})

Modifies AWS CloudHSM cluster.

# Arguments
- `backup_retention_policy`: A policy that defines how the service retains backups.
- `cluster_id`: The identifier (ID) of the cluster that you want to modify. To find the
  cluster ID, use DescribeClusters.

"""
modify_cluster(BackupRetentionPolicy, ClusterId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("ModifyCluster", Dict{String, Any}("BackupRetentionPolicy"=>BackupRetentionPolicy, "ClusterId"=>ClusterId); aws_config=aws_config)
modify_cluster(BackupRetentionPolicy, ClusterId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("ModifyCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("BackupRetentionPolicy"=>BackupRetentionPolicy, "ClusterId"=>ClusterId), params)); aws_config=aws_config)

"""
    restore_backup(backup_id)
    restore_backup(backup_id, params::Dict{String,<:Any})

Restores a specified AWS CloudHSM backup that is in the PENDING_DELETION state. For mor
information on deleting a backup, see DeleteBackup.

# Arguments
- `backup_id`: The ID of the backup to be restored. To find the ID of a backup, use the
  DescribeBackups operation.

"""
restore_backup(BackupId; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("RestoreBackup", Dict{String, Any}("BackupId"=>BackupId); aws_config=aws_config)
restore_backup(BackupId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("RestoreBackup", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("BackupId"=>BackupId), params)); aws_config=aws_config)

"""
    tag_resource(resource_id, tag_list)
    tag_resource(resource_id, tag_list, params::Dict{String,<:Any})

Adds or overwrites one or more tags for the specified AWS CloudHSM cluster.

# Arguments
- `resource_id`: The cluster identifier (ID) for the cluster that you are tagging. To find
  the cluster ID, use DescribeClusters.
- `tag_list`: A list of one or more tags.

"""
tag_resource(ResourceId, TagList; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("TagResource", Dict{String, Any}("ResourceId"=>ResourceId, "TagList"=>TagList); aws_config=aws_config)
tag_resource(ResourceId, TagList, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("TagResource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ResourceId"=>ResourceId, "TagList"=>TagList), params)); aws_config=aws_config)

"""
    untag_resource(resource_id, tag_key_list)
    untag_resource(resource_id, tag_key_list, params::Dict{String,<:Any})

Removes the specified tag or tags from the specified AWS CloudHSM cluster.

# Arguments
- `resource_id`: The cluster identifier (ID) for the cluster whose tags you are removing.
  To find the cluster ID, use DescribeClusters.
- `tag_key_list`: A list of one or more tag keys for the tags that you are removing.
  Specify only the tag keys, not the tag values.

"""
untag_resource(ResourceId, TagKeyList; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("UntagResource", Dict{String, Any}("ResourceId"=>ResourceId, "TagKeyList"=>TagKeyList); aws_config=aws_config)
untag_resource(ResourceId, TagKeyList, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()) = cloudhsm_v2("UntagResource", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ResourceId"=>ResourceId, "TagKeyList"=>TagKeyList), params)); aws_config=aws_config)


text:
module PBC
export PrivateKey, PublicKey, Signature, Hash, Identity, PrivateKeyPoly, PublicKeyPoly, SignatureShares

module Curve
    # load curve dynamically
    const CURVE = get(ENV, "RELIC_TOOLKIT_CURVE", "BLS381")
    eval(Meta.parse("using RelicToolkit.$CURVE"))
    eval(Meta.parse("using RelicToolkit.$CURVE: bn_read_bin!, bn_write_bin!, LIB, Limb"))
end

module Config
    import ..Curve
    include(joinpath(@__DIR__, "Config.jl"))
end

module Util
    import ..Config, ..Curve
    include(joinpath(@__DIR__, "Util.jl"))
end

module Shamir
    import ..Config, ..Curve, ..Util
    include(joinpath(@__DIR__, "LagrangeCoeffGenerator.jl"))
    include(joinpath(@__DIR__, "BarycentricWeightGenerator.jl"))
    include(joinpath(@__DIR__, "Shamir.jl"))
end

module Model
    import ..Config, ..Util, ..Curve, ..Shamir
    include(joinpath(@__DIR__, "Model.jl"))
end

module Spawn
    import ..Config, ..Model, ..Curve
    abstract type SpawnProcesses end
    abstract type SpawnThreads end
    const DefaultSpawn = SpawnThreads
    include(joinpath(@__DIR__, "Distributed.jl"))
    include(joinpath(@__DIR__, "Threads.jl"))
end

using .Model: PrivateKey, PublicKey, Signature, Hash, Identity, PrivateKeyPoly, PublicKeyPoly, SignatureShares

#sign(sk::PrivateKey, point::Util.Point) = sign(sk, Util.encode(point))
sign(sk::PrivateKey, hash::Hash) = Signature(sk, hash)
sign(sk::PrivateKey, bytes::Vector{UInt8}) = sign(sk, Hash(bytes))
sign(sk::PrivateKey, msg::String) = sign(sk, Hash(msg))

pair(ep::Curve.EP, ep2::Curve.EP2) = field_final_exp(curve_miller(ep, ep2))
pair(ep2::Curve.EP2, ep::Curve.EP) = field_final_exp(curve_miller(ep, ep2))

function create_share(skp::PrivateKeyPoly, id::Model.AbstractIdentity)
    return PrivateKey(Shamir.evalpoly(skp.coeffs, id.id))
end
create_share(skp::PrivateKeyPoly, pk::PublicKey) = create_share(skp, Model.Identity(pk))

function verify_share(pkp::PublicKeyPoly, id::Model.AbstractIdentity, sks::PrivateKey)
    return Shamir.evalpoly(pkp.coeffs, id.id) == Util.genpk(sks.sk)
end
verify_share(pkp::PublicKeyPoly, pk::PublicKey, sks::PrivateKey) = verify_share(pkp, Model.Identity(pk), sks)

Model.Signature(ss::SignatureShares) = Signature(Shamir.lagrange_interpolate_c0(ss.weights, ss.shares))
PublicKey(pkp::PublicKeyPoly) = PublicKey(pkp.coeffs[1])

Base.setindex!(ss::SignatureShares, sigshare::Signature, pk::PublicKey) = setindex!(ss, sigshare, Model.Identity(pk))
function Base.setindex!(ss::SignatureShares, sigshare::Signature, id::Model.AbstractIdentity)
    ss.shares[id.id] = sigshare.sig # register share
    push!(ss.weights, id.id) # register id
end

Base.length(poly::PrivateKeyPoly) = length(poly.coeffs)
Base.getindex(poly::PrivateKeyPoly, i::Int) = getindex(poly.coeffs, i)
Base.iterate(poly::PrivateKeyPoly) = iterate(poly.coeffs)
Base.iterate(poly::PrivateKeyPoly, state) = iterate(poly.coeffs, state)

Base.length(poly::PublicKeyPoly) = length(poly.coeffs)
Base.getindex(poly::PublicKeyPoly, i::Int) = getindex(poly.coeffs, i)
Base.iterate(poly::PublicKeyPoly) = iterate(poly.coeffs)
Base.iterate(poly::PublicKeyPoly, state) = iterate(poly.coeffs, state)

"""
verify(sig, pkhashpairs)

Cryptographically verify of the specified signature against one or more `PublicKey` and `Hash` pairs,
which are presented as an iterator with potentially millions of `Pair{PublicKey,Hash}` items.

# Complexity

Memory complexity is Θ(1) and time complexity is Θ(n) of this function.
It is possible to define the `PBC_THREADS=n` environment variable, which will
spawn extra processes to split up the computation (linearly) between multiple threads.

# Custom Iterators

You need to define a couple of methods for your `CustomPublicKeyAndHashIterator` to work:

```julia
Base.length(iter::CustomPublicKeyAndHashIterator)
Base.getindex(iter::CustomPublicKeyAndHashIterator, r::UnitRange{Int})
Base.getindex(iter::CustomPublicKeyAndHashIterator, i::Int)
Base.iterate(iter::CustomPublicKeyAndHashIterator)
Base.iterate(iter::CustomPublicKeyAndHashIterator, state)
```
"""
function verify(sig::Signature, pkhashpairs)
    isempty(pkhashpairs) && error("pkhashpairs is empty")
    return Spawn.verify(Spawn.DefaultSpawn, sig, pkhashpairs)
end
# mostly for and or simple use cases
verify(sig::Signature, pk::PublicKey, hash::Hash) = verify(sig, (pk=>hash,))
verify(sig::Signature, pk::PublicKey, bytes::Vector{UInt8}) = verify(sig, (pk=>Hash(bytes),))
verify(sig::Signature, pk::PublicKey, msg::String) = verify(sig, (pk=>Hash(msg),))

Base.Vector{UInt8}(sk::PrivateKey) = Curve.bn_write_bin!(Vector{UInt8}(undef, Config.PRIVATE_KEY_SIZE), sk.sk)
Base.Vector{UInt8}(pk::PublicKey) = Util.encode(pk.pk)
Base.Vector{UInt8}(sig::Signature) = Util.encode(sig.sig)
Base.Vector{UInt8}(hash::Hash) = Util.encode(hash.hash)

Base.iszero(sk::PrivateKey) = iszero(sk.sk)
Base.isinf(pk::PublicKey) = isinf(pk.pk)
Base.isinf(sig::Signature) = isinf(sig.sig)
Base.isinf(hash::Hash) = isinf(hash.hash)

# TODO: how can we trick the compiler to always perform both checks (constant time)?
Base.isvalid(sk::PrivateKey) = iszero(sk.sk.sign) && sk.sk < Config.ORDER
Base.isvalid(pk::PublicKey) = isvalid(pk.pk)
Base.isvalid(hash::Hash) = isvalid(hash.hash)
Base.isvalid(sig::Signature) = isvalid(sig.sig)

Base.:(+)(a::PrivateKey, b::PrivateKey) = PrivateKey(mod(a.sk + b.sk, Config.ORDER))
Base.:(+)(a::PublicKey, b::PublicKey) = PublicKey(a.pk + b.pk)
Base.:(+)(a::PublicKeyPoly, b::PublicKeyPoly) = PublicKeyPoly(map(+, a.coeffs, b.coeffs))
Base.:(+)(a::Signature, b::Signature) = Signature(a.sig + b.sig)
Base.:(-)(a::PrivateKey, b::PrivateKey) = PrivateKey(mod(a.sk - b.sk, Config.ORDER))
Base.:(-)(a::PublicKey, b::PublicKey) = PublicKey(a.pk - b.pk)
Base.:(-)(a::PublicKeyPoly, b::PublicKeyPoly) = PublicKeyPoly(map(-, a.coeffs, b.coeffs))
Base.:(-)(a::Signature, b::Signature) = Signature(a.sig - b.sig)

Base.rand(::Type{PrivateKey}) = PrivateKey(Util.gensk())

Base.:(==)(a::PrivateKey, b::PrivateKey) = a.sk == b.sk
Base.:(==)(a::Model.AbstractPublicKey, b::Model.AbstractPublicKey) = a.pk == b.pk
Base.:(==)(a::Signature, b::Signature) = a.sig == b.sig
Base.:(==)(a::Hash, b::Hash) = a.hash == b.hash

"""
tl;dr: Given 1_000_000_000 random Affline Points, there is a 5.42% chance of a conflict with this algo.

Assuming that x in each point is a uniformly distributed Prime Field element,
then the probability of conflict can be calculated with (for 32-bit systems):

P(conflict) = 1/k + 2/k + ... + (n-1)/k = 1/k * (n-1)^2/2; where k=2^63

Q: Why is k!=2^64?
A: Because some of the Lagrange Coeff and Barycentric Weight calculation involves signed arithmetic (e.g. i - k)
   which should not overflow

Example: n = 1_000_000_000 => P(conflict) = 1/2^63 * (1_000_000_000 - 1)^2/2 = 999_999_999^2 / 2^64 = 0.0542...
"""
Base.Int64(p::Curve.EP; i=1) = signed(Curve.Limb == UInt64 ? p.x[i] : p.x[2i] << 32 | p.x[2i-1])
Base.Int64(p::Curve.EP2; i=1) = signed(Curve.Limb == UInt64 ? p.x[1][i] : p.x[1][2i] << 32 | p.x[1][2i-1])
Base.Int128(p::Util.Point; i=1) = Int128(Int64(p, i=2i)) << 64 | Int128(Int64(p, i=2i-1))

function __init__()
    Spawn.add_processes(Config.NPROCS)
    @debug (
        CURVE = Curve.CURVE,
        SMALL_SIGNATURES = Config.SMALL_SIGNATURES,
        PRIVATE_KEY_SIZE = Config.PRIVATE_KEY_SIZE,
        PUBLIC_KEY_SIZE = Config.PUBLIC_KEY_SIZE,
        SIGNATURE_SIZE = Config.SIGNATURE_SIZE,
    )
end

end # module


text:
import MLJModelInterface
const MMI = MLJModelInterface

"""
    AbstractVectorizer <: Unsupervised

To be an AbstractVectorizer, a type needs to implement `vectorizer(model, diagrams)`. It
should return a callable object that transforms diagrams to vectors and has a method for
`output_size`.
"""
abstract type AbstractVectorizer <: MMI.Unsupervised end

function MMI.fit(model::AbstractVectorizer, ::Int, X)
    vectorizers = map(Tables.columnnames(X)) do col
        col => vectorizer(model, vec(Tables.getcolumn(X, col)))
    end
    return (vectorizers, nothing, NamedTuple())
end

function MMI.transform(model::AbstractVectorizer, vectorizers, X)
    matrix = zeros(MMI.nrows(X), sum(output_size(last(v)) for v in vectorizers))
    for (i, row) in enumerate(Tables.rows(X))
        matrix[i, :] .= mapreduce(vcat, vectorizers) do (k, v)
            vec(v(Tables.getcolumn(row, k)))
        end
    end

    names = Symbol[]
    for (k, v) in vectorizers
        append!(names, Symbol(k, :_, i) for i in 1:output_size(v))
    end
    return MMI.table(matrix; names=names)
end

MMI.input_scitype(::Type{<:AbstractVectorizer}) = MMI.Table(PersistenceDiagram)
MMI.output_scitype(::Type{<:AbstractVectorizer}) = MMI.Table(MMI.Continuous)

function _clean_parameter!(m, name, predicate, default, property)
    if predicate(getfield(m, name))
        return ""
    else
        setfield!(m, name, default)
        return "`$name` must be $property; using `$name=$default`. "
    end
end

"""
    PersistenceImageVectorizer(; kwargs...)

Converts persistence diagrams into persistence images. Each pixel in the image curve is
mapped to a column.

# Hyperparameters

* `distribution::Any = :default`: the distribution used to smear each point in the
  diagram. Can be a function, a callable object, or :default. When set to `:default`, a
  binormal distribution is used.

* `sigma::Float64 = -1`: the width of the gaussian distribution. Only applicable when
  `distribution=:default`. If set to -1, its value is learned from data.

* `weight::Any = :default`: the weighting function. Can be a function, a callable object, or
  :default. When set to `:default`, a piecewise linear function is used. To make this method
  work correctly, `weight(0.0, _) == 0.0` should always hold.

* `slope_end::Float64 = 1.0`: the (relative) position in the diagram where the default
  weight function stops decreasing. Only applicable when `weight=:default`.

* `width::Int = 10`, `height::Int = 10`: the size of the image. Note that all pixels of the
  image will be converted to columns. For example a 3×4 image will produce 12 columns per
  dimension.

# See also

* [`PersistenceImage`](@ref)

"""
mutable struct PersistenceImageVectorizer <: AbstractVectorizer
    distribution::Any
    sigma::Float64
    weight::Any
    slope_end::Float64
    width::Int
    height::Int
    margin::Float64
    zero_start::Bool
end

function PersistenceImageVectorizer(;
    distribution=:default,
    sigma=-1,
    weight=:default,
    slope_end=1.0,
    width=5,
    height=5,
    margin=0.1,
    zero_start=true,
)
    model = PersistenceImageVectorizer(
        distribution, sigma, weight, slope_end, width, height, margin, zero_start
    )
    warning = MMI.clean!(model)
    isempty(warning) || @warn warning
    return model
end

function _is_callable(fun)
    try
        x = fun(0.0, 0.0)
        return x isa Real
    catch e
        if e isa MethodError
            return false
        else
            rethrow()
        end
    end
end

function _clean_function_parameter!(model, fun, setting, default)
    if getfield(model, fun) ≠ :default
        if _is_callable(getfield(model, fun))
            if getfield(model, setting) ≠ default
                setfield!(model, setting, default)
                return "Both `$setting` and `$fun` were set; using `$setting=$default`. "
            end
        else
            setfield!(model, fun, :default)
            return "Invalid `$fun`; using `$fun=:default`. "
        end
    end
    return ""
end

function MMI.clean!(model::PersistenceImageVectorizer)
    warning = ""
    warning *= _clean_function_parameter!(model, :distribution, :sigma, -1.0)
    warning *= _clean_function_parameter!(model, :weight, :slope_end, 1.0)
    warning *= _clean_parameter!(model, :width, x -> x > 0, 10, "positive")
    warning *= _clean_parameter!(model, :height, x -> x > 0, 10, "positive")
    warning *= _clean_parameter!(model, :margin, x -> x ≥ 0, 0.1, "non-negative")
    warning *= _clean_parameter!(
        model, :sigma, x -> x > 0 || x == -1, -1.0, "positive or -1"
    )
    if !(0 < model.slope_end ≤ 1)
        warning *= "`0 < slope_end ≤ 1` does not hold; using `slope_end=1`. "
        model.slope_end = 1.0
    end
    return warning
end

function vectorizer(model::PersistenceImageVectorizer, diagrams)
    if model.distribution == :default
        distribution = nothing
        sigma = model.sigma == -1 ? nothing : model.sigma
    else
        distribution = model.distribution
        sigma = nothing
    end
    if model.weight == :default
        weight = nothing
        slope_end = model.slope_end
    else
        weight = model.weight
        slope_end = nothing
    end
    return PersistenceImage(
        diagrams;
        size=(model.height, model.width),
        distribution=distribution,
        sigma=sigma,
        weight=weight,
        slope_end=slope_end,
        margin=model.margin,
        zero_start=model.zero_start,
    )
end

"""
    PersistenceCurveVectorizer(; kwargs...)

Converts persistence diagrams into persistence curves. Each value of the curve is mapped to
a column.

# Hyperparameters

* `fun::Function = always_one`: The function used to construct the curve. See also
  [`PersistenceCurve`](@ref). Note: the `curve` argument must be `:custom` if this argument
  is set.

* `stat::Function = sum`: The statistic used to construct the curve. See also
  [`PersistenceCurve`](@ref). Note: the `curve` argument must be `:custom` if this argument
  is set.

* `curve::Symbol = :custom`: The type of curve used. Available options are `:custom`,
  `:betti`, `:silhuette`, `:life`, `:midlife`, `:life_entropy`, `:midlife_entropy`,
  and `:pd_thresholding`.

* `integrate::Bool = true`: If set to `true`, the curve is integrated. If set to `false`,
  the curve is simply sampled at specified points.

* `normalize::Bool = false`: Normalize the curve by dividing all values by
  `stat(fun.(diagram))`.

* `length::Int = 10`: The number of columns per dimension to output.

# See also

* [`PersistenceCurve`](@ref)
* [`BettiCurve`](@ref)
* [`Silhuette`](@ref)
* [`Life`](@ref)
* [`Midlife`](@ref)
* [`LifeEntropy`](@ref)
* [`MidlifeEntropy`](@ref)
* [`PDThresholding`](@ref)

"""
mutable struct PersistenceCurveVectorizer <: AbstractVectorizer
    fun::Function
    stat::Function
    curve::Symbol
    integrate::Bool
    normalize::Bool
    length::Int
end

function PersistenceCurveVectorizer(;
    fun=always_one, stat=sum, curve=:custom, integrate=true, normalize=false, length=10
)
    model = PersistenceCurveVectorizer(fun, stat, curve, integrate, normalize, length)
    warning = MMI.clean!(model)
    isempty(warning) || @warn warning
    return model
end

function vectorizer(model::PersistenceCurveVectorizer, diagrams)
    return PersistenceCurve(
        model.fun,
        model.stat,
        diagrams;
        length=model.length,
        integrate=model.integrate,
        normalize=model.normalize,
    )
end

function MMI.clean!(model::PersistenceCurveVectorizer)
    warning = ""
    if model.curve == :custom
        fun = model.fun
        stat = model.stat
    elseif model.curve == :betti
        fun = always_one
        stat = sum
    elseif model.curve == :silhuette
        fun = landscape
        stat = sum
    elseif model.curve == :life
        fun = life
        stat = sum
    elseif model.curve == :midlife
        fun = midlife
        stat = sum
    elseif model.curve == :life_entropy
        fun = life_entropy
        stat = sum
    elseif model.curve == :midlife_entropy
        fun = midlife_entropy
        stat = sum
    elseif model.curve == :pd_thresholding
        fun = thresholding
        stat = sum
    else
        warning *= "Unrecognized curve `$(model.curve)`; using `curve=:custom`. "
        model.curve = :custom
        fun = model.fun
        stat = model.stat
    end
    if model.curve ≠ :custom && model.fun ≢ always_one && model.fun ≢ fun
        warning *= "Both `curve` and `fun` were set; using `fun=$fun`. "
    end
    if model.curve ≠ :custom && model.stat ≢ always_one && model.stat ≢ stat
        warning *= "Both `curve` and `stat` were set; using `stat=$stat`. "
    end
    model.fun = fun
    model.stat = stat
    if model.normalize && model.curve == :silhuette
        warning *=
            "Normalizing `curve=:silhuette` is not supported; " *
            "using `normalize=false`. "
        model.normalize = false
    end
    if model.normalize && model.curve == :pd_thresholding
        warning *=
            "Normalizing `curve=:pd_thresholding` is not supported; " *
            "using `normalize=false`. "
        model.normalize = false
    end
    warning *= _clean_parameter!(model, :length, x -> x > 0, 10, "positive")
    return warning
end

"""
    PersistenceLandscapeVectorizer

Converts persistence diagrams into persistence landscapes. Each value of the curve is mapped
to a column.

# Hyperparameters

* `n_landscapes = 1`: use the top ``n`` landscapes.

* `length = 10`: the number of columns per dimension per landscape to output. For example,
  for `n_landscapes=3`, `length=10`, and two persistence diagrams, the vectorization will
  produce 30 columns.

# See also

* [`Landscapes`](@ref)
* [`Landscape`](@ref)

"""
mutable struct PersistenceLandscapeVectorizer <: AbstractVectorizer
    n_landscapes::Int
    length::Int
end

function PersistenceLandscapeVectorizer(; n_landscapes=1, length=10)
    model = PersistenceLandscapeVectorizer(n_landscapes, length)
    warning = MMI.clean!(model)
    isempty(warning) || @warn warning
    return model
end

function vectorizer(model::PersistenceLandscapeVectorizer, diagrams)
    return Landscapes(model.n_landscapes, diagrams; length=model.length)
end

function MMI.clean!(model::PersistenceLandscapeVectorizer)
    warning = ""
    warning *= _clean_parameter!(model, :length, x -> x > 0, 10, "positive")
    warning *= _clean_parameter!(model, :n_landscapes, x -> x > 0, 1, "positive")
    return warning
end

MMI.metadata_pkg.(
    (
        PersistenceImageVectorizer,
        PersistenceCurveVectorizer,
        PersistenceLandscapeVectorizer,
    ),
    name="PersistenceDiagrams",
    uuid="90b4794c-894b-4756-a0f8-5efeb5ddf7ae",
    url="https://github.com/mtsch/PersistenceDiagrams.jl",
    license="MIT",
    julia=true,
    is_wrapper=false,
)


text:
@testset "Mapping" begin

# Shapefile test
filename = joinpath(dirname(@__FILE__), "data", "SudQC_GCM.shp")
filenc = joinpath(dirname(@__FILE__), "data", "sresa1b_ncar_ccsm3-example.nc")

polyshp = read(filename,Shapefile.Handle)
P = shapefile_coords_poly(polyshp.shapes[1])
# P = [x y]
# P = P'
lat = NetCDF.ncread(filenc, "lat")
lon = NetCDF.ncread(filenc, "lon")
C = load(filenc, "tas", poly = P)
msk = inpolygrid(C.longrid, C.latgrid, P)
status, figh = mapclimgrid(C); @test status == true; PyPlot.close()
status, figh = mapclimgrid(C, mask = msk); @test status == true; PyPlot.close()
C = load(filenc, "tas")
status, figh = mapclimgrid(C); @test status == true; PyPlot.close()
@test size(P) == (2, 6)
@test isnan(P[1,1])
@test isnan(P[2,1])

# filename = joinpath(dirname(@__FILE__), "data", "zoneAgricoleQc15km.shp")
# polyshp = read(filename,Shapefile.Handle)
# x, y = shapefile_coords(polyshp.shapes[2])
# P = [x y]
# P = P'
# cities_lat = collect(45.5016889:0.1:46.8138783)
# cities_lon = collect(-73.56725599999999:0.1:-71.2079809) + 360
# @test sum(.!isnan.(inpolyvec(cities_lon, cities_lat, P))) == 336
# @test sum(.!isnan.(inpolyvec(cities_lon - 360, cities_lat, P))) == 336

# Mapping test
filename = joinpath(dirname(@__FILE__), "data", "sresa1b_ncar_ccsm3-example.nc")
C = load(filename, "tas")
status, figh = mapclimgrid(C);@test status == true;PyPlot.close()
status, figh = mapclimgrid(C, region = "World");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "WorldAz");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(C, region = "WorldEck4");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Canada");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Quebec");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(C, region = "QuebecNSP");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Americas");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "NorthAmerica");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(C, region = "SouthAmerica");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Greenwich");@test status == true; PyPlot.close()
status, figh = mapclimgrid(annualmax(C), region = "Europe");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(annualmin(C), region = "Arctic");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(annualmin(C), region = "Antarctic");@test status == true; PyPlot.close()

# precip
C = load(filename, "pr", data_units="mm") + 2.0
status, figh = mapclimgrid(C);@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, start_date=(2000, 05, 16, 12), end_date=(2000, 05, 16, 12));@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "World");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "WorldAz");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(C, region = "WorldEck4");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Canada");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Quebec");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(C, region = "QuebecNSP");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Americas");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "NorthAmerica");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "SouthAmerica");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Americas");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Arctic");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Antarctic");@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, region = "Greenwich");@test status == true; PyPlot.close()
status, figh = mapclimgrid(annualmax(C), region = "Europe");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(annualmin(C), region = "Arctic");@test status == true; PyPlot.close()
# status, figh = mapclimgrid(annualmin(C), region = "Antarctic");@test status == true; PyPlot.close()

# ua wind
filename = joinpath(dirname(@__FILE__), "data", "SudQC_GCM.shp")
filenc = joinpath(dirname(@__FILE__), "data", "sresa1b_ncar_ccsm3-example.nc")
polyshp = read(filename,Shapefile.Handle)
x, y = shapefile_coords(polyshp.shapes[1])
P = [x y]
P = P'
C = load(filenc, "ua")
status, figh = mapclimgrid(C, level = 3);@test status == true; PyPlot.close() # given level
status, figh = mapclimgrid(C);@test status == true; PyPlot.close() # feeding a 4D field
status, figh = mapclimgrid(C, poly = P);@test status == true; PyPlot.close() # feeding a 4D field with a polygon
status, figh = mapclimgrid(spatialsubset(C, P), mask = msk);@test status == true; PyPlot.close() # feeding a 4D field with a mask
C = load(filenc, "ua", poly = P)
status, figh = mapclimgrid(C);@test status == true; PyPlot.close() # feeding a 4D field

regions = ["World", "WorldAz", "Canada", "Quebec", "Quebec_agricole", "South_Quebec", "Americas", "NorthAmerica", "SouthAmerica", "Greenwich", "Outaouais", "Laurentides", "Estrie", "Arctic", "Antarctic", "Africa", "Europe", "Asia", "West-Asia"]

for iregion in regions
    status, figh = mapclimgrid(region=iregion);@test status == true; PyPlot.close()
end

# DUMMY MAPS
lon = collect(-180.0:180.0)
lat = collect(-90.0:90.0)
longrid, latgrid = ndgrid(lon, lat)
timeV = DateTime(2003,1,1):Day(1):DateTime(2003,01,31)
data = randn(361, 181, 31)
dimension_dict = Dict(["lon" => "lon", "lat" => "lat"])
varattribs = Dict(["standard_name" => "random noise"])
axisdata = AxisArray(data, Axis{:lon}(lon), Axis{:lat}(lat), Axis{:time}(timeV))
C = ClimateTools.ClimGrid(axisdata, variable = "psl", typeofvar="psl", longrid=longrid, latgrid=latgrid, dimension_dict=dimension_dict, varattribs=varattribs)

status, figh = mapclimgrid(C);@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, center_cs=true, caxis=[200, 250]);@test status == true; PyPlot.close()
status, figh = mapclimgrid(C, center_cs=true, surface=:pcolormesh);@test status == true; PyPlot.close()

lon = collect(-180.0:180.0)
lat = collect(-90.0:90.0)
longrid, latgrid = ndgrid(lon, lat)
timeV = Dates.year(DateTime(2003, 01, 01)):Dates.year(1):Dates.year(DateTime(2004, 01, 01))
data = randn(361, 181, 2)
dimension_dict = Dict(["lon" => "lon", "lat" => "lat"])
varattribs = Dict(["standard_name" => "random noise"])
axisdata = AxisArray(data, Axis{:lon}(lon), Axis{:lat}(lat), Axis{:time}(timeV))
C = ClimateTools.ClimGrid(axisdata, variable = "psl", typeofvar="psl", longrid=longrid, latgrid=latgrid, dimension_dict=dimension_dict, varattribs=varattribs)

status, figh = mapclimgrid(C);@test status == true; PyPlot.close()

lon = collect(-180.0:180.0)
lat = collect(-90.0:90.0)
longrid, latgrid = ndgrid(lon, lat)
timeV = DateTime(2003, 01, 01):Day(1):DateTime(2004, 01, 01)
data = randn(361, 181, 366)
dimension_dict = Dict(["lon" => "lon", "lat" => "lat"])
varattribs = Dict(["standard_name" => "random noise"])
axisdata = AxisArray(data, Axis{:lon}(lon), Axis{:lat}(lat), Axis{:time}(timeV))
C = ClimateTools.ClimGrid(axisdata, variable = "psl", typeofvar="psl", longrid=longrid, latgrid=latgrid, dimension_dict=dimension_dict, varattribs=varattribs)

status, figh = plot(C); @test status == true; PyPlot.close()
status, figh = plot(C, poly=P); @test status == true; PyPlot.close()
status, figh = plot(C, start_date=(2003, 01, 10), end_date=(2003, 05, 12)); @test status==true;PyPlot.close()
status, figh = plot(C, xlimit=[2 5]); @test status==true;PyPlot.close()
status, figh = plot(C, label = "dummy", titlestr="dummytest", gridfig=true); @test status == true; PyPlot.close()

status, figh = hist(C); @test status == true; PyPlot.close()
status, figh = hist(C, poly=P, ylimit=[2, 5]); @test status == true; PyPlot.close()
status, figh = hist(C, start_date=(2003, 01, 10), end_date=(2003, 05, 12)); @test status==true;PyPlot.close()
status, figh = hist(C, label = "dummy", titlestr="dummytest", gridfig=false); @test status == true; PyPlot.close()

end


text:
"""
        moora(decisionMat, weights, fns)

Apply MOORA (Multi-Objective Optimization By Ratio Analysis) method for a given matrix and weights.

# Arguments:
 - `decisionMat::DataFrame`: n × m matrix of objective values for n candidate (or strategy) and m criteria
 - `weights::Array{Float64, 1}`: m-vector of weights that sum up to 1.0. If the sum of weights is not 1.0, it is automatically normalized.
 - `fns::Array{Function, 1}`: m-vector of function that are either maximum or minimum.

# Description
moora() applies the MOORA method to rank n strategies subject to m criteria which are supposed to be either maximized or minimized.

# Output
- `::MooraResult`: MooraResult object that holds multiple outputs including scores and best index.

# Examples
```julia-repl
julia> w =  [0.110, 0.035, 0.379, 0.384, 0.002, 0.002, 0.010, 0.077];

julia> Amat = [
             100 92 10 2 80 70 95 80 ;
             80  70 8  4 100 80 80 90 ;
             90 85 5 0 75 95 70 70 ;
             70 88 20 18 60 90 95 85
           ];

julia> dmat = makeDecisionMatrix(Amat)
4×8 DataFrame
 Row │ Crt1     Crt2     Crt3     Crt4     Crt5     Crt6     Crt7     Crt8
     │ Float64  Float64  Float64  Float64  Float64  Float64  Float64  Float64
─────┼────────────────────────────────────────────────────────────────────────
   1 │   100.0     92.0     10.0      2.0     80.0     70.0     95.0     80.0
   2 │    80.0     70.0      8.0      4.0    100.0     80.0     80.0     90.0
   3 │    90.0     85.0      5.0      0.0     75.0     95.0     70.0     70.0
   4 │    70.0     88.0     20.0     18.0     60.0     90.0     95.0     85.0

julia> fns = makeminmax([maximum, maximum, maximum, maximum, maximum, maximum, maximum, maximum]);

julia> result = moora(dmat, w, fns)

julia> result.scores
4-element Array{Float64,1}:
 0.3315938731541169
 0.2901446390098523
 0.3730431072983815
 0.019265256092245782

julia> result.bestIndex
4
```

# References
Celikbilek Yakup, Cok Kriterli Karar Verme Yontemleri, Aciklamali ve Karsilastirmali
Saglik Bilimleri Uygulamalari ile. Editor: Muhlis Ozdemir, Nobel Kitabevi, Ankara, 2018

İşletmeciler, Mühendisler ve Yöneticiler için Operasyonel, Yönetsel ve Stratejik Problemlerin
Çözümünde Çok Kriterli Karar verme Yöntemleri, Editörler: Bahadır Fatih Yıldırım ve Emrah Önder,
Dora, 2. Basım, 2015, ISBN: 978-605-9929-44-8
"""
function moora(decisionMat::DataFrame, weights::Array{Float64,1}, fns::Array{Function,1})::MooraResult

    w = unitize(weights)

    nalternatives, ncriteria = size(decisionMat)

    normalizedMat = normalize(decisionMat)
    weightednormalizedMat = w * normalizedMat

    # cmaxs = colmaxs(weightednormalizedMat)
    cmaxs = apply_columns(fns, weightednormalizedMat)
    cmins = apply_columns(reverseminmax(fns), weightednormalizedMat)

    refmat = similar(weightednormalizedMat)

    for rowind in 1:nalternatives
        if fns[rowind] == maximum
            refmat[rowind, :] .= cmaxs - weightednormalizedMat[rowind, :]
        elseif fns[rowind] == minimum
            refmat[rowind, :] .= weightednormalizedMat[rowind, :] - cmins
        else
            @warn fns[rowind]
            error("Function must be either maximize or minimize")
        end
    end

    scores = rmaxs = rowmaxs(refmat)

    bestIndex = sortperm(rmaxs) |> first

    result = MooraResult(
       decisionMat,
       w,
       weightednormalizedMat,
       refmat,
       scores,
       bestIndex
   )

    return result
end

"""
        moora(setting)

Apply MOORA (Multi-Objective Optimization By Ratio Analysis) method for a given matrix and weights.

# Arguments:
 - `setting::MCDMSetting`: MCDMSetting object.

# Description
moora() applies the MOORA method to rank n strategies subject to m criteria which are supposed to be either maximized or minimized.

# Output
- `::MooraResult`: MooraResult object that holds multiple outputs including scores and best index.
"""
function moora(setting::MCDMSetting)::MooraResult
    moora(
        setting.df,
        setting.weights,
        setting.fns
    )
end

max_stars_repo_name: drvinceknight/JMcDM

max_issues_repo_name: drvinceknight/JMcDM

max_forks_repo_name: drvinceknight/JMcDM


text:
# Generate documentation with this command:
# (cd docs && julia --color=yes make.jl)

push!(LOAD_PATH, "..")

using BlockSparseArrays
using Documenter

makedocs(; sitename="BlockSparseArrays", format=Documenter.HTML(),
         modules=[BlockSparseArrays])

deploydocs(; repo="github.com/eschnett/BlockSparseArrays.jl.git",
           devbranch="main", push_preview=true)