"""
`module  JAC.Basics`
    ... a submodel of JAC that contains many basic types/struct that are specific to the JAC module; this module also defines
        a number of basic functions/methods that are later extended. We here provide proper docstrings for all abstract and
        conrete types (struct) in order to allow the user to easy understand and access the individual fields of each type/struct
        definition.
"""
module Basics

    using Printf

    export  add, analyze, AngularJ, AngularJ64, AngularM, AngularM64, Auger,
            compute, diagonalize,
            estimate, EmMultipole, E1, M1, E2, M2, E3, M3, E4, M4, EmProperty, ExpStokes, EmStokes,
            generate,
            ##x HalfInt, HalfInteger,
            interpolate, integrate,
            LevelSymmetry, LevelKey,
            modify,
            oplus,
            Parity, perform, provide,
            RadialMesh, Radiative,
            SolidAngle, Shell, Subshell, subshell_2j,
            tabulate, tools, TensorComp,
            UseCoulomb, UseBabushkin

    ##x include("inc-halfintegers.jl")

    abstract type  AngularJ  end

    """
    `struct  AngularJ64 <: AngularJ`  ... defines a type for angular momenta j.

        + num  ::Int64              ... numerator
        + den  ::Int64              ... denominator, must be 1 or 2
    """
    struct  AngularJ64 <: AngularJ
        num      ::Int64
        den      ::Int64
    end

    """
    `Basics.AngularJ64(m::Integer)`  ... constructor for a given integer (numerator).
    """
    function AngularJ64(j::Integer)
        j < 0   &&   error("j must be positive.")
        AngularJ64(j, 1)
    end

    """
    `Basics.AngularJ64(rational::Rational{Int64})`  ... constructor for a given  Rational{Int64}.
    """
    function AngularJ64(rational::Rational{Int64})
        !(rational.den in [1,2])   &&   error("Denominator must be 1 or 2.")
        rational.num < 0           &&   error("j must be positive.")
        AngularJ64(rational.num, rational.den)
    end

    # `Base.show(io::IO, j::AngularJ64)`  ... prepares a proper printout of the variable j::AngularJ64.
    function Base.show(io::IO, j::AngularJ64)
        if      j.den == 1    print(io, j.num)
        elseif  j.den == 2    print(io, j.num, "/2")
        else    error("stop a")
        end
    end

    """
    `Basics.oplus(ja::AngularJ64, jb::AngularJ64)`
        ... adds the angular momenta ja `oplus` jb and returns a list::Array{AngularJ64,1} of j-valuescin the interval
            |ja - jb| <= j <= ja + jb.
    """
    function  oplus(ja::AngularJ64, jb::AngularJ64)
        if  ja.den == 1   ja2 = 2ja.num   else   ja2 = ja.num   end
        if  jb.den == 1   jb2 = 2jb.num   else   jb2 = jb.num   end
        jList = AngularJ64[]
        for  j = abs(ja2 - jb2):2:ja2+jb2    push!(jList, AngularJ64(j//2) )    end
        return( jList )
    end

    ##x oplus(ja, jb) = oplus(HalfInt(ja), HalfInt(jb))
    ##x oplus(ja::Union{HalfInt,Integer}, jb::Union{HalfInt,Integer}) = abs(ja-jb):ja+jb

    ##x const ⊕ = oplus

    """
    `Basics.projections(ja::AngularJ64)`
        ... returns all allowed projections of a given angular momenta ja as a list::Array{AngularM64,1} of m-values, i.e. -ja, -ja+1, ..., j.
    """
    function  projections(ja::AngularJ64)
        if  ja.den == 1   ja2 = 2ja.num   else   ja2 = ja.num   end
        mList = AngularM64[]
        for  m = -ja2:2:ja2    push!(mList, AngularM64(m//2) )    end
        return( mList )
    end

    ##x projections(j) = projections(HalfInt(j))
    ##x projections(j::Union{HalfInt,Integer}) = j≥0 ? (-j:j) : throw(DomainError(j, "angular momentum j must be non-negative."))

    abstract type  AngularM  end

    """
    `struct  Basics.AngularM64 <: AngularM`  ... defines a type for angular momentum projections m.

        + num  ::Int64              ... numerator
        + den  ::Int64              ... denominator, must be 1 or 2
    """
    struct  AngularM64 <: AngularM
        num      ::Int64
        den      ::Int64
    end

    """
    `Basics.AngularM64(m::Integer)`  ... constructor for a given integer (numerator).
    """
    function AngularM64(m::Integer)
        AngularM64(m, 1)
    end

    """
    `Basics.AngularM64(m::Integer, j::AngularJ64)`
        ... constructor for a given integer (numerator) that is consistent with a given j-value.
    """
    function AngularM64(m::Integer, j::AngularJ64)
        !(j.den == 1)      &&  error("m must be integer for j = $(j).")
        j.num < abs(m)   &&  error("abs(m) must be <= j = $(j).")
        AngularM64(m, 1)
    end

    """
    `Basics.AngularM64(rational::Rational{Int64})`  ... constructor for a given  Rational{Int64}.
    """
    function AngularM64(rational::Rational{Int64})
        !(rational.den in [1,2])  &&  error("Denominator must be 1 or 2.")
        AngularM64(rational.num, rational.den)
    end

    """
    `Basics.AngularM64(rational::Rational{Int64}, j::AngularJ64)`
        ... constructor for a given  Rational{Int64} that is consistent with a given m-value.
    """
    function AngularM64(rational::Rational{Int64}, j::AngularJ64)
        !(rational.den in [1,2])      &&   error("Denominator must be 1 or 2.")
        !(j.den == rational.den)      &&   error("j,m must be both integer or half-integer.")
        j.num < abs(rational.num)   &&   error("abs(m) must be <= j = $(j).")
        AngularM64(rational.num, rational.den)
    end

    """
    `Basics.AngularM64(j::AngularJ64)`  ... constructor for a given j::AngularJ64 to define m = j.
    """
    function AngularM64(j::AngularJ64)
        AngularM64(j.num, j.den)
    end

    # `Base.show(io::IO, m::AngularM64)`  ... prepares a proper printout of the variable m::AngularM64.
    function Base.show(io::IO, m::AngularM64)
        if      m.den == 1    print(io, m.num)
        elseif  m.den == 2    print(io, m.num, "/2")
        else    error("stop a")
        end
    end

    """
    `Basics.add(ma::AngularM64, mb::AngularM64)`
        ... adds the projections of the angular momenta ma + mb and returns a mc::AngularM64.
    """
    function  add(ma::AngularM64, mb::AngularM64)
        if  ma.den == 1   ma2 = 2ma.num   else   ma2 = ma.num   end
        if  mb.den == 1   mb2 = 2mb.num   else   mb2 = mb.num   end
        return( AngularM64( (ma2+mb2)//2 ) )
    end

    # Conversion between HalfInt and AngularJ64, AngularM64

    twice(x::Union{AngularJ64,AngularM64}) = ifelse(isone(x.den), twice(x.num), x.num)
    twice(x) = x + x

    ##x AngularJ64(x::HalfInt) = isinteger(x) ? AngularJ64(Integer(x)) : AngularJ64(twice(x), 2)
    ##x AngularM64(x::HalfInt) = isinteger(x) ? AngularM64(Integer(x)) : AngularM64(twice(x), 2)

    """
    `struct  Basics.Eigen`
        ... defines a simple struct to communicate eigenvalues and eigenvectors if different diagonalization procedures are used.

        + values   ::Array{Float64,1}            ... List of eigenvalues.
        + vectors  ::Array{Vector{Float64},1}    ... List of eigenvectors.
    """
    struct  Eigen
        values     ::Array{Float64,1}
        vectors    ::Array{Vector{Float64},1}
    end

    """
    `Basics.isEqual(a::Eigen, b::Eigen, accuracy::Float64)`
        ... determines whether a == b with the requested accuracy; a value::Bool is returned. The test is made element-wise on
            abs(a.number - b.number) > accuracy.
    """
    function isEqual(a::Eigen, b::Eigen, accuracy::Float64)
        # Check the dimensions
        if  accuracy <= 0.                          error("Requires accuracy > 0.")                                    end
        if  size(a.values) != size(b.values)   ||   size(a.vectors) != size(b.vectors)                return( false )  end
        # Check the eigenvalues and vectors
        for  i = 1:length(a.values)
            if  abs(a.values[i] - b.values[i]) > accuracy                                             return( false )  end
            for  j = 1:length(a.vectors[i])   if  abs(a.vectors[i][j] - b.vectors[i][j]) > accuracy   return( false )  end    end
        end
        return( true )
    end

    """
    `struct  Basics.SolidAngle`  ... defines a type for a solid angle Omega = (theta, phi).

        + theta  ::Float64       ... polar angle theta with regard to the z-axis; 0 <= theta <= pi.
        + phi    ::Float64       ... azimuthal angle phi (with regard to the x-z plane in mathematical positive direction); 0 <= phi <= 2 pi.
    """
    struct  SolidAngle
        theta    ::Float64
        phi      ::Float64
    end

    # `Base.show(io::IO, Omega::SolidAngle)`  ... prepares a proper printout of the variable Omega::SolidAngle.
    function Base.show(io::IO, Omega::SolidAngle)
        sa = "Omega == (" * string(Omega.theta) * ", " * string(Omega.phi) * ")"
        print(io, sa)
    end

    """
    `@enum   Parity`  ... defines a enumeration for the allowed values of parity.

        + plus, minus               ... with obvious meaning
    """
    @enum   Parity    plus   minus

    """
    `Basics.Parity(sa::String)`  ... constructor for a given String.
    """
    function Parity(sa::String)
        if       sa == "+"    wa = plus
        elseif   sa == "-"    wa = minus
        else     error("stop a")
        end

        return( wa )
    end

    """
    `Basics.invertParity(p::Parity)`  ... inverts the given parity plus <--> minus.
    """
    function invertParity(p::Parity)
        if       p == plus    return( minus )
        elseif   p == minus   return( plus )
        else     error("stop a")
        end
    end

    #============================================
    # `Base.show(io::IO, p::Parity)`  ... prepares a proper printout of the variable p::Parity.
    function Base.show(io::IO, p::Parity)
        print(io, string(p) )
    end  ==================================#

    # `Base.string(p::Parity)`  ... provides a proper printout of the variable p::Parity.
    function Base.string(p::Parity)
        if      p == plus     return( "+" )
        elseif  p == minus    return( "-" )
        else    error("stop a")
        end
    end

    """
    `struct  Basics.LevelSymmetry`  ... defines a struct for defining the overall J^P symmetry of a level.

        + J          ::AngularJ64  ... total angular momentum of a level
        + parity     ::Parity      ... total parity of the level
    """
    struct  LevelSymmetry
        J            ::AngularJ64
        parity       ::Parity
    end

    """
    `Basics.LevelSymmetry(rational::Rational{Int64}, parity::Parity)`  ... constructor for a given (Rational,Parity).
    """
    function  LevelSymmetry(rational::Rational{Int64}, parity::Parity)
        !(rational.den in [1,2])      &&   error("Denominator must be 1 or 2.")
        LevelSymmetry( AngularJ64(rational.num, rational.den), parity )
    end

    """
    `Basics.LevelSymmetry(i::Int64, parity::Parity)`  ... constructor for a given (Int64,Parity).
    """
    function  LevelSymmetry(i::Int64, parity::Parity)
        LevelSymmetry( AngularJ64(i), parity )
    end

    """
    `Basics.LevelSymmetry(rational::Rational{Int64}, sa::String)`  ... constructor for a given (Rational,String).
    """
    function  LevelSymmetry(rational::Rational{Int64},sa::String)
        !(rational.den in [1,2])      &&   error("Denominator must be 1 or 2.")
        LevelSymmetry( AngularJ64(rational.num, rational.den), Parity(sa) )
    end

    """
    `Basics.LevelSymmetry(i::Int64, sa::String)`  ... constructor for a given (Int64,String).
    """
    function  LevelSymmetry(i::Int64,sa::String)
        LevelSymmetry( AngularJ64(i), Parity(sa) )
    end

    # `Base.show(io::IO, sym::LevelSymmetry)`  ... prepares a proper printout of the variable sym::LevelSymmetry.
    function Base.show(io::IO, sym::LevelSymmetry)
        print(io, string(sym) )
    end

    # `Base.string(sym::LevelSymmetry)`  ... provides a proper printout of the variable sym::LevelSymmetry.
    function Base.string(sym::LevelSymmetry)
        if      sym.parity == plus     return( "$(sym.J) +" )
        elseif  sym.parity == minus    return( "$(sym.J) -" )
        else    error("stop a")
        end
    end

    """
    `struct  Basics.LevelKey`  ... defines a struct for identifying a level by its symmetry, energy, etc.

        + sym          ::LevelSymmetry    ... Level symmetry.
        + index        ::Int64            ... Index of this level in its original multiplet, or 0.
        + energy       ::Float64          ... Total energy.
        + relativeOcc  ::Float64          ... Relative occupation of this level, if part of some multiplet/cascade.
    """
    struct  LevelKey
        sym            ::LevelSymmetry
        index          ::Int64
        energy         ::Float64
        relativeOcc    ::Float64
    end

    """
    `Basics.LevelKey()`  ... constructor for an empty instance of a LevelKey.
    """
    function  LevelKey()
        LevelKey( LevelSymmetry(0,Basics.plus), 0, 0., 0.)
    end

    # `Base.show(io::IO, key::LevelKey)`  ... prepares a proper printout of the variable key::LevelKey.
    function Base.show(io::IO, key::LevelKey)
        print(io, string(key) )
    end

    # `Base.string(key::LevelKey)`  ... provides a proper printout of the variable key::LevelKey.
    function Base.string(key::LevelKey)
        return( "LevelKey[$(key.sym) ($(key.index)), en=$(key.energy), occ=$(key.relativeOcc)]")
    end

    """
    `struct  Basics.LineKey`
        ... defines a struct for identifying a line by the keys of the initial and final level, its transition energy,
            decay strength, etc.

        + iLevelKey    ::LevelKey         ... Key of the initial level.
        + fLevelKey    ::LevelKey         ... Key of the final level.
        + energy       ::Float64          ... Total energy.
        + strength     ::Float64          ... Strength of the given line.
    """
    struct  LineKey
        iLevelKey      ::LevelKey
        fLevelKey      ::LevelKey
        energy         ::Float64
        strength       ::Float64
    end

    """
    `Basics.LineKey()`  ... constructor for an empty instance of a LineKey.
    """
    function  LineKey()
        LineKey( LevelKey(), LevelKey(), 0., 0.)
    end

    # `Base.show(io::IO, key::LineKey)`  ... prepares a proper printout of the variable key::LineKey.
    function Base.show(io::IO, key::LineKey)
        print(io, string(key) )
    end

    # `Base.string(key::LineKey)`  ... provides a proper printout of the variable key::LevelKey.
    function Base.string(key::LineKey)
        return( "LineKey[i=$(key.iLevelKey) --> f=$(key.fLevelKey), en=$(key.energy), strength=$(key.strength)]")
    end

    """
    `struct  Basics.Shell`  ... defines a enumeration for the allowed values of a non-relativistic shell.

        + n     ::Int64  ... principal quantum number
        + l     ::Int64  ... orbital angular quantum number
    """
    struct  Shell
        n       ::Int64
        l       ::Int64
    end

    """
    `Basics.Shell(sa::Union{String,SubString{String}})`  ... constructor for a given String, such as 1s, 2s, 2p, ... .
    """
    function Shell(sa::Union{String,SubString{String}})
        wa = strip( Base.convert(String, sa) )
        n  = parse(Int64, wa[1:end-1]);    l = shellNotation( string(wa[end]) )
        Shell(n, l)
    end

    # `Base.show(io::IO, sh::Shell)`  ... prepares a proper printout of the variable sh::Shell.
    function Base.show(io::IO, sh::Shell)
        print(io, string(sh) )
    end

    # `Base.string(sh::Shell)`  ... provides a proper printout of the variable sh::Shell.
    function Base.string(sh::Shell)
        return( string(sh.n) * shellNotation(sh.l) )
    end

    """
    `Basics.shellNotation(l::Int64)`  ... returns the corresponding spectroscopy letter for a given orbital angular quantum number l.
    """
    function shellNotation(l::Int64)
        !(0 <= l <= 25)      &&   error("Orbital QN 0 <= l <= 25; l = $l")
        wa = [ "s", "p", "d", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "q", "r", "t", "u", "v", "w", "x", "y", "z"]
        return( wa[l+1] )
    end

    """
    `Basics.shellNotation(sa::String)`  ... returns for a given spectroscopy letter the orbital angular quantum number l.
    """
    function shellNotation(sa::String)
        wa = [ "s", "p", "d", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "q", "r", "t", "u", "v", "w", "x", "y", "z"]
        wb = findall(in([sa]), wa)
        !(length(wb)==1)      &&   error("Unrecognized orbital letter = $sa")
        return( wb[1]-1)
    end

    """
    `Basics.shellSplitIntoSubshells(sh::Shell)'  ... to convert a non-relativistic shell into a list of Subshell[..]
    """
    function shellSplitIntoSubshells(sh::Shell)
        subshells = Subshell[]
        if     sh.l == 0   push!(subshells, Subshell(sh.n, -1) )
        else   j2 = 2sh.l - 1;    kappa = Int64(  (j2+1)/2 );   push!(subshells, Subshell(sh.n, kappa) )
            j2 = 2sh.l + 1;    kappa = Int64( -(j2+1)/2 );   push!(subshells, Subshell(sh.n, kappa) )
        end
        return( subshells )
    end

    """
    `Basics.shellSplitOccupation(sh::Shell, occ::Int64)'
        ... to split the occupation of a shell into proper subshell occupations; an  Array{Dict{Basics.Subshell,Int64},1} is returned.
    """
    function shellSplitOccupation(sh::Shell, occ::Int64)

        subshells = shellSplitIntoSubshells(sh)
        wa        = Dict{Subshell,Int64}[]

        for  k1 in 0:occ
            for  k2 in 0:occ
                if  k1 + k2 == occ
                    if       length( subshells ) == 1
                        !(0 <= occ <= Basics.subshell_2j( subshells[1] ) + 1)   &&    error("Wrong pair of subshell = $(subshells[1]) and occupation = $occ")
                        wb = Dict( subshells[1] => occ )
                        push!(wa, wb)
                        return( wa )

                        elseif  length( subshells ) == 2
                            j2_1 = Basics.subshell_2j(subshells[1])
                        j2_2 = Basics.subshell_2j(subshells[2])
                        if  k1 <= j2_1 + 1   &&    k2 <= j2_2 + 1
                        wb = Dict( subshells[1] => k1, subshells[2] => k2 )
                        # Delete contributions with zero occupation
                        ## if  k1 == 0    delete!(wb, subshells[1])   end
                        ## if  k2 == 0    delete!(wb, subshells[2])   end
                        push!(wa, wb)
                        end
                    else    error("stop a")
                    end
                end
            end
        end

        return( wa )
    end

    """
    `struct  Basics.Subshell`  ... defines a type for the allowed values of a relativistic subhell.

        + n        ::Int64  ... principal quantum number
        + kappa    ::Int64  ... relativistic angular quantum number
    """
    struct  Subshell
        n          ::Int64
        kappa      ::Int64
    end

    """
    `Basics.Subshell(sa::String)`  ... constructor for a given String, such as 1s_1/2, 2s_1/2, 2p_3/2, ... .
    """
    function Subshell(sa::String)
        wa = strip( sa );
        wb = findnext("_", wa, 1);    if  wb == nothing   error("No underscore in sa = $sa")           else   wb = wb[1]   end
        wc = findnext("/", wa, wb);   if  wc == nothing   error("No / in wa[] = $(wa[wb[1]+1:end])")   else   wc = wc[1]   end
        wd = string( wa[wb+1:wc-1] )
        n  = parse(Int64, wa[1:wb-2]);                  l  = shellNotation( string(wa[ wb-1 ]) )
        j2 = parse(Int64, wd );                         !(string(wa[end]) == "2")    &&  error("Unrecognized subshell string sa = $sa")
        if  l - j2/2 > 0     kappa = Int64( (j2+1)/2 )    else    kappa = Int64( -(j2+1)/2 )   end
        Subshell(n, kappa)
    end

    # `Base.show(io::IO, sh::Subshell)`  ... prepares a proper printout of the variable sh::Subshell.
    function Base.show(io::IO, sh::Subshell)
        print(io, string(sh) )
    end

    # `Base.string(sh::Subshell)`  ... provides a proper printout of the variable sh::Subshell.
    function Base.string(sh::Subshell)
        sa = string(sh.n) * shellNotation( Basics.subshell_l(sh) ) * "_" * string( Basics.subshell_j(sh) )
        return( sa )
    end

    """
    `Basics.subshellGrasp(sa::Stringl)`  ... returns a sh::Subshell if sa denotes a (relativistic) Subshell in Grasp-notation.
    """
    function subshellGrasp(sa::String)
            if   sa == "1s"   return( Subshell("1s_1/2")  )
        elseif   sa == "2s"   return( Subshell("2s_1/2")  )   elseif   sa == "2p-"   return( Subshell("2p_1/2") )
        elseif   sa == "2p"   return( Subshell("2p_3/2")  )
        elseif   sa == "3s"   return( Subshell("3s_1/2")  )   elseif   sa == "3p-"   return( Subshell("3p_1/2") )
        elseif   sa == "3p"   return( Subshell("3p_3/2")  )   elseif   sa == "3d-"   return( Subshell("3d_3/2") )
        elseif   sa == "3d"   return( Subshell("3d_5/2")  )
        elseif   sa == "4s"   return( Subshell("4s_1/2")  )   elseif   sa == "4p-"   return( Subshell("4p_1/2") )
        elseif   sa == "4p"   return( Subshell("4p_3/2")  )   elseif   sa == "4d-"   return( Subshell("4d_3/2") )
        elseif   sa == "4d"   return( Subshell("4d_5/2")  )   elseif   sa == "4f-"   return( Subshell("4f_5/2") )
        elseif   sa == "4f"   return( Subshell("4f_7/2")  )
        elseif   sa == "5s"   return( Subshell("5s_1/2")  )   elseif   sa == "5p-"   return( Subshell("5p_1/2") )
        elseif   sa == "5p"   return( Subshell("5p_3/2")  )   elseif   sa == "5d-"   return( Subshell("5d_3/2") )
        elseif   sa == "5d"   return( Subshell("5d_5/2")  )   elseif   sa == "5f-"   return( Subshell("5f_5/2") )
        elseif   sa == "5f"   return( Subshell("5f_7/2")  )   elseif   sa == "5g-"   return( Subshell("5g_7/2") )
        elseif   sa == "5g"   return( Subshell("5g_9/2")  )
        elseif   sa == "6s"   return( Subshell("6s_1/2")  )   elseif   sa == "6p-"   return( Subshell("6p_1/2") )
        elseif   sa == "6p"   return( Subshell("6p_3/2")  )   elseif   sa == "6d-"   return( Subshell("6d_3/2") )
        elseif   sa == "6d"   return( Subshell("6d_5/2")  )   elseif   sa == "6f-"   return( Subshell("6f_5/2") )
        elseif   sa == "6f"   return( Subshell("6f_7/2")  )   elseif   sa == "6g-"   return( Subshell("6g_7/2") )
        elseif   sa == "6g"   return( Subshell("6g_9/2")  )
        elseif   sa == "7s"   return( Subshell("7s_1/2")  )   elseif   sa == "7p-"   return( Subshell("7p_1/2") )
        elseif   sa == "7p"   return( Subshell("7p_3/2")  )   elseif   sa == "7d-"   return( Subshell("7d_3/2") )
        else     error("Grasp notation ... not yet implemented; sa = $sa")
        end
    end

    """
    `Basics.subshell_l(sh::Subshell)`  ... returns the orbital angular quantum number l for a given sh::Subshell; an Int64 is returned
    """
    function subshell_l(sh::Subshell)
        if   sh.kappa < 0   l = abs(sh.kappa) -1   else   l = sh.kappa   end
        return( l )
    end

    """
    `Basics.subshell_j(sh::Subshell)`  ... returns the total angular quantum number j for a given sh::Subshell; an AngularJ64 is returned.
    """
    function subshell_j(sh::Subshell)
        return( AngularJ64(abs(sh.kappa) - 1//2) )
    end

    """
    `Basics.subshell_2j(sh::Subshell)`  ... returns the total angular quantum number j for a given sh::Subshell; an AngularJ64 is returned.
    """
    function subshell_2j(sh::Subshell)
        return( 2* abs(sh.kappa) - 1 )
    end

    """
    `Basics.subshellsFromClosedShellConfiguration("[Ne]")`
        ... to provide a list of (relativistic) subshells for the given closed-shell configuration.
    """
    function subshellsFromClosedShellConfiguration(sa::String)
        if       sa == "[He]"    wa = [ Subshell("1s_1/2")]
        elseif   sa == "[Ne]"    wa = [ Subshell("1s_1/2"), Subshell("2s_1/2"), Subshell("2p_1/2"), Subshell("2p_3/2")]
        elseif   sa == "[Mg]"    wa = [ Subshell("1s_1/2"), Subshell("2s_1/2"), Subshell("2p_1/2"), Subshell("2p_3/2"), Subshell("3s_1/2")]
        elseif   sa == "[Ar]"    wa = [ Subshell("1s_1/2"), Subshell("2s_1/2"), Subshell("2p_1/2"), Subshell("2p_3/2"),
                                                            Subshell("3s_1/2"), Subshell("3p_1/2"), Subshell("3p_3/2")]
        elseif   sa == "[Kr]"    wa = [ Subshell("1s_1/2"), Subshell("2s_1/2"), Subshell("2p_1/2"), Subshell("2p_3/2"),
                                                            Subshell("3s_1/2"), Subshell("3p_1/2"), Subshell("3p_3/2"),
                                                            Subshell("3d_3/2"), Subshell("3d_5/2"),
                                        Subshell("4s_1/2"), Subshell("4p_1/2"), Subshell("4p_3/2")]
        elseif   sa == "[Xe]"    wa = [ Subshell("1s_1/2"), Subshell("2s_1/2"), Subshell("2p_1/2"), Subshell("2p_3/2"),
                                                            Subshell("3s_1/2"), Subshell("3p_1/2"), Subshell("3p_3/2"),
                                                            Subshell("3d_3/2"), Subshell("3d_5/2"),
                                        Subshell("4s_1/2"), Subshell("4p_1/2"), Subshell("4p_3/2"),
                                                            Subshell("4d_3/2"), Subshell("4d_5/2"),
                                        Subshell("5s_1/2"), Subshell("5p_1/2"), Subshell("5p_3/2")]
        else
            error("Unsupported keystring = $sa ")
        end

        return( wa )
    end

    """
    `struct   Basics.SubshellStateR`
        ... defines a struct for the relativistic antisymmetric subshell states in the seniority scheme; this struct can be accessed
            only internally and, therefore, the only the standard constructor is supported.

        + subshell     ::Subshell        ... to refer to a particular subshell.
        + occ          ::Int64           ... occupation of the subshell
        + nu           ::Int64           ... seniority of the subshell state, often called nu
        + Jsub2        ::Int64           ... 2*Jsub
    """
    struct  SubshellStateR
        subshell       ::Subshell
        occ            ::Int64
        nu             ::Int64
        Jsub2          ::Int64
    end

    # `Base.show(io::IO, state::SubshellStateR)`  ... prepares a proper printout of the variable state::SubshellStateR.
    function Base.show(io::IO, state::SubshellStateR)
        print(io, string(state) )
    end

    # `Base.string(state::SubshellStateR)`  ... provides a proper printout of the variable state::SubshellStateR.
    function Base.string(state::SubshellStateR)
        sa = "SubshellState: [" * string(state.subshell) * "^$(state.occ), nu=$(state.nu), 2*J_sub=$(state.Jsub2)]"
    end

    """
    `Basics.subshellStateString(subshell::String, occ::Int64, seniority::Int64, Jsub::AngularJ64, X::AngularJ64)`
        ... to provide a string of a given subshell state in the form '[2p_1/2^occ]_(seniority, J_sub), X=Xo' ... .
    """
    function subshellStateString(subshell::String, occ::Int64, seniority::Int64, Jsub::AngularJ64, X::AngularJ64)
        sa = "[" * subshell * "^$occ]_($seniority, " * string(Jsub) * ") X=" * string(X)
        return( sa )
    end

    """
    `@enum   Basics.AtomicProcess`
        ... defines a enumeration for the atomic processes that are supported in the JAC program in the computation of transition
            arrays or whole excitation/decay cascades.

        + Auger         ... Auger transitions, i.e. single autoionization or the emission of a single free electron into the continuum.
        + AugerInPlasma ... Auger transitions but calculated for a specified plasma model.
        + Compton       ... Rayleigh-Compton scattering cross sections.
        + Coulex        ... Coulomb-excitation of target or projeticle electrons by fast, heavy ions.
        + Coulion       ... Coulomb-ionization of target or projeticle electrons by fast, heavy ions.
        + Dierec        ... di-electronic recombination, i.e. the dielectronic capture of a free electron and the subsequent emission of a photon.
        + MultiPhotonDE ... multi-photon excitation and decay rates, including 2-photon, etc. processes.
        + MultiPI       ... multi-photon (single-electron) ionization.
        + MultiPDI      ... multi-photon (single-electron) double ionization.
        + Photo         ... Photoionization processes, i.e. the emission of a single free electron into the continuum due to an external light field.
        + PhotoExc      ... Photoexcitation rates.
        + PhotoExcFluor ... photoexcitation fluorescence rates and cross sections.
        + PhotoExcAuto  ... photoexcitation autoionization cross sections and collision strengths.
        + PhotoInPlasma ... Photoionization processes but calculated for a specified plasma model.
        + PhotoIonFluor ... photoionization fluorescence rates and cross sections.
        + PhotoIonAuto  ... photoionization autoionization cross sections and collision strengths.
        + Radiative    ... Radiative (multipole) transitions between bound-state levels of the same charge state.
        + Rec           ... radiative electron capture, i.e. the capture of a free electron with the simultaneous emission of a photon.
        + Eimex         ... electron-impact excitation cross sections and collision strengths.
        + RAuger        ... Radiative Auger rates.
    """
    @enum   AtomicProcess  NoProcess  Auger  AugerInPlasma  Compton  Coulex  Dierec  Eimex  ImpactExcAuto  InternalConv  MultiPhotonDE  MultiPI  MultiPDI=
                    20  Photo  PhotoExc  PhotoExcAuto  PhotoExcFluor  PhotoInPlasma  PhotoIonAuto  PhotoIonFluor   Radiative  RAuger=
                    40  Rec  PairA1P  Coulion
    export  AtomicProcess, NoProcess, Auger, AugerInPlasma, Compton, Coulex, Dierec, Eimex, ImpactExcAuto, InternalConv, MultiPhotonDE, MultiPI, MultiPDI,
                        Photo, PhotoExc, PhotoExcAuto, PhotoExcFluor, PhotoInPlasma, PhotoIonAuto, PhotoIonFluor,  Radiative, RAuger,
                        Rec, PairA1P, Coulion

    """
    `Basics.AtomicProcess(sa::String)`  ... constructor for a given String.
    """
    function AtomicProcess(sa::String)
        if       sa in [ "none", "NoProcess"]                       wa = NoProcess
        elseif   sa in [ "Auger"]                                   wa = Auger
        elseif   sa in [ "Auger in plasma"]                         wa = AugerInPlasma
        elseif   sa in [ "Compton", "Rayleigh"]                     wa = Compton
        elseif   sa in [ "Coulex", "Coulomb excitation"]            wa = Coulex
        elseif   sa in [ "Coulion", "Coulomb ionization"]           wa = Coulion
        elseif   sa in [ "dierec", "Dierec"]                        wa = Dierec
        elseif   sa in [ "eimex", "Eimex"]                          wa = Eimex
        elseif   sa in [ "internal conversion"]                     wa = InternalConv
        elseif   sa in [ "multi-DE"]                                wa = MultiPhotonDE
        elseif   sa in [ "multi-PI"]                                wa = MultiPI
        elseif   sa in [ "multi-PDI"]                               wa = MultiPDI
        elseif   sa in [ "photo", "Photo"]                          wa = Photo
        elseif   sa in [ "photo in plasma"]                         wa = PhotoInPlasma
        elseif   sa in [ "PhotoExc", "photoexcitation"]             wa = PhotoExc
        elseif   sa in [ "photo-EA"]                                wa = PhotoExcAuto
        elseif   sa in [ "photo-IA"]                                wa = PhotoIonAuto
        elseif   sa in [ "photo-IF"]                                wa = PhotoIonFluor
        elseif   sa in [ "photo-EF"]                                wa = PhotoExcFluor
        elseif   sa in [ "radiative", "Radiative"]                  wa = Radiative
        elseif   sa in [ "radiativeAuger", "RAuger"]                wa = RAuger
        elseif   sa in [ "rec", "Rec"]                              wa = Rec
        elseif   sa in [ "pair-annihilation-1-photon", "PA1P"]      wa = PairA1P
        elseif   sa in [ "impact-EA"]                               wa = ImpactExcAuto
        elseif   sa in [ "rec", "Rec"]                              wa = Rec
        else     error("stop a")
        end

        AtomicProcess(wa)
    end

    #===========================
    # `Base.show(io::IO, process::AtomicProcess)`  ... prepares a proper printout of the variable process::AtomicProcess.
    function Base.show(io::IO, process::AtomicProcess)
        print(io, string(process) )
    end   =======================#

    # `Base.string(process::AtomicProcess)`  ... provides a proper printout of the variable process::AtomicProcess.
    function Base.string(process::AtomicProcess)
        if      process == NoProcess       return( "no process" )
        elseif  process == Auger           return( "Auger" )
        elseif  process == AugerInPlasma   return( "Auger in plasma" )
        elseif  process == Compton         return( "Rayleigh-Compton" )
        elseif  process == Dierec          return( "Dielectronic recombination" )
        elseif  process == Eimex           return( "Eimex" )
        elseif  process == MultiPhotonDE   return( "multi-photon excitation & decay" )
        elseif  process == Photo           return( "Photo" )
        elseif  process == PhotoExcFluor   return( "Photo-Excitation-Fluoresence" )
        elseif  process == PhotoExcAuto    return( "Photo-Excitation-Autoionization" )
        elseif  process == PhotoInPlasma   return( "Photo in plasma" )
        elseif  process == PhotoIonFluor   return( "Photo-Ionization-Fluoresence" )
        elseif  process == PhotoIonAuto    return( "Photo-Ionization-Autoionization" )
        elseif  process == PhotoExc        return( "Photo-Excitation" )
        elseif  process == Radiative       return( "Radiative" )
        elseif  process == RAuger          return( "Radiative Auger" )
        elseif  process == Rec             return( "Rec" )
        else    error("stop a")
        end
    end

    """
    `struct  Basics.EmMultipole`  ... defines a struct for the em multipoles, E1, M1, E2, ...

        + L           ::Int64    ... multipolarity
        + electric    ::Bool     ... True if the multipole is an electric one, and false otherwise.
    """
    struct  EmMultipole
        L             ::Int64
        electric      ::Bool
    end

    const  E1 = EmMultipole(1, true);     const  M1 = EmMultipole(1, false)
    const  E2 = EmMultipole(2, true);     const  M2 = EmMultipole(2, false)
    const  E3 = EmMultipole(3, true);     const  M3 = EmMultipole(3, false)
    const  E4 = EmMultipole(4, true);     const  M4 = EmMultipole(4, false)

    """
    `Basics.EmMultipole(sa::String)`  ... constructor for a given String.
    """
    function EmMultipole(sa::String)
        if       sa == "E1"    L = 1;   electric = true
        elseif   sa == "M1"    L = 1;   electric = false
        elseif   sa == "E2"    L = 2;   electric = true
        elseif   sa == "M2"    L = 2;   electric = false
        elseif   sa == "E3"    L = 3;   electric = true
        elseif   sa == "M3"    L = 3;   electric = false
        elseif   sa == "E4"    L = 4;   electric = true
        elseif   sa == "M4"    L = 4;   electric = false
        else     error("stop a")
        end

        EmMultipole(L, electric)
    end

    # `Base.show(io::IO, mp::EmMultipole)`  ... prepares a proper printout of the variable mp::EmMultipole.
    function Base.show(io::IO, mp::EmMultipole)
        if  mp.electric   sa = "E"*string(mp.L)    else    sa = "M"*string(mp.L)   end
        print(io, string(mp) )
    end

    # `Base.string(mp::EmMultipole)`  ... provides a proper printout of the variable mp::EmMultipole.
    function Base.string(mp::EmMultipole)
        mp.L < 1    &&    error("Improper multipolarity L = $(mp.L)")
        if  mp.electric   sa = "E"*string(mp.L)    else    sa = "M"*string(mp.L)   end
        return( sa )
    end

    """
    `Basics.multipole_p(mp::EmMultipole)`  ... returns the kind integer p=0 (magnetic) or p=1 (electric) of the given  mp::EmMultipole.
    """
    function multipole_p(mp::EmMultipole)
        if  mp.electric   return(1)    else    return(0)   end
    end

    """
    `@enum   Basics.EmGauge`
        ... defines a enumeration for the (allowed) gauges to deal with the radiation field; this differs from the UseGauge which can
            be selected for explicit computations.

            + NoGauge      ... No gauge define (yet).
            + Coulomb      ... Coulomb gauge (= velocity gauge in the non-relativistic limit; Grant, 1974).
            + Babushkin    ... Babushkin gauge (= length gauge in the non-relativistic limit; Grant, 1974).
            + Magnetic     ... Magnetic gauge (transverse velocity gauge).
            + Velocity     ... Transverse velocity gauge (Johnson, 2007).
            + Length       ... Length gauge (Johnson, 2007).
    """
    @enum   EmGauge    NoGauge    Coulomb   Babushkin   Magnetic   Velocity   Length
    export  EmGauge,   NoGauge,   Coulomb,  Babushkin,  Magnetic,  Velocity,  Length

    """
    `Basics.EmGauge(sa::String)`  ... constructor for a given String.
    """
    function EmGauge(sa::String)
        if       sa in ["none", "NoGauge"]         wa = NoGauge
        elseif   sa == "Coulomb"                   wa = Coulomb
        elseif   sa == "Babushkin"                 wa = Babushkin
        elseif   sa in ["Magnetic", "magnetic"]    wa = Magnetic
        else     error("stop a")
        end

        return( wa )
    end

    #===========================
    # `Base.show(io::IO, gauge::EmGauge)`  ... prepares a proper printout of the variable gauge::EmGauge.
    function Base.show(io::IO, gauge::EmGauge)
        print(io, string(gauge) )
    end  ====================#

    # `Base.string(gauge::EmGauge)`  ... provides a proper printout of the variable gauge::EmGauge.
    function Base.string(gauge::EmGauge)
        if      gauge == NoGauge     return( "no gauge" )
        elseif  gauge == Coulomb     return( "Coulomb" )
        elseif  gauge == Babushkin   return( "Babushkin" )
        elseif  gauge == Magnetic    return( "Magnetic" )
        else    error("stop a")
        end
    end

    """
    `@enum   Basics.UseGauge`  ... defines a enumeration for the (allowed) gauges to be selected in explicit computations
    """
    @enum   UseGauge    UseCoulomb   UseBabushkin
    export  UseGauge,   UseCoulomb,  UseBabushkin

    """
    `Basics.UseGauge(sa::String)`  ... constructor for a given String.
    """
    function UseGauge(sa::String)
        if       sa == "Coulomb"                   wa = UseCoulomb
        elseif   sa == "Babushkin"                 wa = UseBabushkin
        else     error("stop a")
        end

        UseGauge(wa)
    end

    #===========================
    # `Base.show(io::IO, gauge::UseGauge)`  ... prepares a proper printout of the variable gauge::UseGauge.
    function Base.show(io::IO, gauge::UseGauge)
        print(io, string(gauge) )
    end    ==========================#

    # `Base.string(gauge::UseGauge)`  ... provides a proper printout of the variable gauge::EmGauge.
    function Base.string(gauge::UseGauge)
        if      gauge == UseCoulomb     return( "Coulomb" )
        elseif  gauge == UseBabushkin   return( "Babushkin" )
        else    error("stop a")
        end
    end

    """
    `struct  EmProperty`  ... defines a type to maintain two gauge forms of a computed result that depends on the radiation field.

        + Coulomb         ::Float64    ... Value for the Coulomb gauge of the radiation field.
        + Babushkin       ::Float64    ... Value for the Coulomb gauge of the radiation field.
    """
    struct  EmProperty
        Coulomb           ::Float64
        Babushkin         ::Float64
    end

    """
    `Basics.EmProperty(wa::Float64)`  ... constructor for an `constant` instance of EmProperty.
    """
    function EmProperty(wa::Float64)
        EmProperty(wa, wa)
    end

    Base.:+(a::EmProperty, b::EmProperty) = EmProperty(a.Coulomb + b.Coulomb, a.Babushkin + b.Babushkin)
    Base.:+(a::EmProperty, b) = EmProperty(a.Coulomb + b, a.Babushkin + b)
    Base.:+(a, b::EmProperty) = b + a
    Base.:/(a::EmProperty, b::EmProperty) = EmProperty(a.Coulomb / b.Coulomb, a.Babushkin / b.Babushkin)
    Base.:/(a, b::EmProperty) = EmProperty(a / b.Coulomb, a / b.Babushkin)

    # `Base.show(io::IO, property::EmProperty)`  ... prepares a proper printout of the variable property::EmProperty.
    function Base.show(io::IO, property::EmProperty)
        sa = Base.string(property);                print(io, sa)
    end

    # `Base.string(property::EmProperty)`  ... provides a String notation for the variable property::EmProperty.
    function Base.string(property::EmProperty)
        sa = "$(property.Coulomb) [Coulomb],  $(property.Babushkin) [Babushkin]"
        return( sa )
    end

    """
    `struct  Basics.ExpStokes`
        ... defines a type to maintain the (experimentally) given Stokes parameter for the polarization of incoming
            photons or electrons.

        + P1              ::Float64    ... Stokes P1 parameter.
        + P2              ::Float64    ... Stokes P2 parameter.
        + P3              ::Float64    ... Stokes P3 parameter.
    """
    struct  ExpStokes
        P1                ::Float64
        P2                ::Float64
        P3                ::Float64
    end

    """
    `Basics.ExpStokes()`  ... constructor for an (empty or unpolarized) Stokes vector.
    """
    function ExpStokes()
        ExpStokes(0., 0., 0.)
    end

    # `Base.show(io::IO, ps::ExpStokes)`  ... prepares a proper printout of the variable ps::ExpStokes.
    function Base.show(io::IO, ps::ExpStokes)
        sa = Base.string(ps);                print(io, sa)
    end

    # `Base.string(ps::ExpStokes)`  ... provides a String notation for the variable ps::ExpStokes.
    function Base.string(ps::ExpStokes)
        sa = "exp. Stokes parameters P1 = $(ps.P1),  P2 = $(ps.P2),  P3 = $(ps.P3)"
        return( sa )
    end

    """
    `struct  Basics.EmStokes`
        ... defines a type to maintain the (computed) given Stokes parameter for the polarization of emitted photons or electrons.

        + P1              ::EmProperty    ... Stokes P1 parameter in Coulomb and Babushkin gauge
        + P2              ::EmProperty    ... Stokes P2 parameter.
        + P3              ::EmProperty    ... Stokes P3 parameter.
    """
    struct  EmStokes
        P1                ::EmProperty
        P2                ::EmProperty
        P3                ::EmProperty
    end

    # `Base.show(io::IO, ps::EmStokes)`  ... prepares a proper printout of the variable ps::EmStokes.
    function Base.show(io::IO, ps::EmStokes)
        sa = Base.string(ps);                print(io, sa)
    end

    # `Base.string(ps::EmStokes)`  ... provides a String notation for the variable ps::EmStokes.
    function Base.string(ps::EmStokes)
        sa = "Stokes parameter P1 = $(ps.P1),  P2 = $(ps.P2),  P3 = $(ps.P3)  "
        return( sa )
    end

    """
    `struct  Basics.TensorComp`  ... defines a type for a component of the statistical tensor as associated with an atomic or ionic level.

        + k               ::Int64          ... rank of the tensor component
        + q               ::Int64          ... q-component
        + comp            ::ComplexF64     ... value of the tensor component
    """
    struct  TensorComp
        k                 ::Int64
        q                 ::Int64
        comp              ::ComplexF64
    end

    # `Base.show(io::IO, st::TensorComp)`  ... prepares a proper printout of the variable st::TensorComp.
    function Base.show(io::IO, st::TensorComp)
        sa = Base.string(st);                print(io, sa)
    end

    # `Base.string(st::TensorComp)`  ... provides a String notation for the variable st::TensorComp.
    function Base.string(st::TensorComp)
        sa = "Statistical tensor component rho_($(st.k),$(st.q)) = $(st.comp)  "
        return( sa )
    end

    """
    `Basics.tensorComp(k::Int64, q::Int64, tcomps::Array{TensorComp,1};  withZeros::Bool = false)`
        ... returns the value of the statistical tensor component rho_kq if it is contained in tcomps.
    """
    function tensorComp(k::Int64, q::Int64, tcomps::Array{TensorComp,1};  withZeros::Bool = false)
        for  tc in  tcomps
            if   k == tc.k    &&    q == tc.q    return( tc.comp )    end
        end

        if   withZeros    return( ComplexF64(0.) )    else    error("Statistical tensor component not found for k = $k and q = $q ")    end
    end

    """
    `@enum   Basics.AtomicLevelProperty`  ... defines a enumeration for the atomic level properties that are supported in the JAC program.

        + NoProperty      ... No level property defined.
        + Einstein        ... Einstein A, B coefficients and oscillator strength; although not a 'level property', `Einstein` is treated within
                            a single basis and does not support to include relaxation effects, etc. It can be used for a quick overwiew to
                            transition probabilities or in cascade computations, however,
        + HFS             ... Hyperfine A and B parameters.
        + Isotope         ... Isotope shift M and F parameters.
        + LandeJ          ... Lande g_J factors.
        + LandeF          ... Lande g_F factors.
        + Polarizibility  ... static and dynamic polarizibilities.
        + Plasma          ... CI computations including interactions from various plasma models.
        + Zeeman          ... Zeeman splitting of fine-structure levels.
    """
    @enum   AtomicLevelProperty   NoProperty   AlphaX   EinsteinX   FormF   Green  HFS   Isotope   LandeJ   LandeF  Plasma=
                            20   Polarity   Yields  Zeeman
    export  AtomicLevelProperty,  NoProperty,  AlphaX,  EinsteinX,  FormF,  Green, HFS,  Isotope,  LandeJ,  LandeF, Plasma,
                                Polarity,  Yields,  Zeeman

    """
    `Basics.AtomicLevelProperty(sa::Union{String,SubString{String}})`  ... constructor for a given String.
    """
    function AtomicLevelProperty(sa::Union{String,SubString{String}})
        sa = strip( Base.convert(String, sa) )
        if       sa in ["none", "NoProperty"]                wa = NoProperty
        elseif   sa in ["alpha"]                             wa = AlphaX
        elseif   sa in ["Einstein"]                          wa = EinsteinX
        elseif   sa in ["form factor"]                       wa = FormF
        elseif   sa in ["Einstein"]                          wa = EinsteinX
        elseif   sa in ["Green", "Greens function"]          wa = Green
        ##x elseif   sa in ["IJF_expansion", "IJF"]              wa = IJF_expansion
        elseif   sa == ["isotope shift", "IS"]               wa = Isotope
        elseif   sa == ["LandeJ", "g_J"]                     wa = LandeJ
        elseif   sa == ["LandeF", "g_F"]                     wa = LandeF
        elseif   sa == ["Plasma", "plasma"]                  wa = Plasma
        elseif   sa == ["polarity", "polarizibility"]        wa = Polarity
        elseif   sa == ["yields", "fluorescence yield"]      wa = Yields
        elseif   sa == ["Zeeman"]                            wa = Zeeman
        else     error("stop a")
        end

        AtomicLevelProperty(wa)
    end

    #===========================
    # `Base.show(io::IO, property::AtomicLevelProperty)`  ... prepares a proper printout of the variable property::AtomicLevelProperty.
    function Base.show(io::IO, property::AtomicLevelProperty)
        print(io, string(property) )
    end   ======================#

    # `Base.string(property::AtomicLevelProperty)`  ... provides a proper printout of the variable property::AtomicLevelProperty.
    function Base.string(property::AtomicLevelProperty)
        if      property == NoProperty    return( "no property" )
        elseif  property == AlphaX        return( "alpha variation" )
        elseif  property == EinsteinX     return( "Einstein A, B coefficients" )
        elseif  property == FormF         return( "form & scattering factors" )
        elseif  property == Green         return( "approximate Greens function" )
        elseif  property == HFS           return( "HFS A, B parameters" )
        elseif  property == Isotope       return( "isotope shift M and F parameters" )
        elseif  property == LandeJ        return( "Lande g_J factors" )
        elseif  property == LandeF        return( "Lande g_F factors" )
        elseif  property == Plasma        return( "CI computations with plasma-type interactions" )
        elseif  property == Polarity      return( "multipole polarizibility" )
        elseif  property == Yields        return( "fluorescence & Auger yields" )
        elseif  property == Zeeman        return( "Zeeman splitting in an external magnetic field" )
        else    error("stop a")
        end
    end

    """
    `@enum   Basics.RadialMesh`  ... defines a enumeration for dealing with the radial mesh points and integration scheme.

        + MeshGrasp       ... use the exponential or exponential-linearized grid from Grasp92.
        + MeshGL          ... use a Gauss-Legendre grid with grid points due to the orderGL.
    """
    @enum   RadialMesh    MeshGrasp    MeshGL
    export  RadialMesh,   MeshGrasp,   MeshGL

    #==
    """
    `@enum   Basics.QedModel`  ... defines a enumeration for dealing with the radiative (QED) contribution to the Hamiltonian matrix.

        + QedSydney       ... to use the local QED potentials by Flambaum & Ginges PRA 72, 052115 (2005).
        + QedPetersburg   ... to use the local QED potential similar as suggested by Shabaev et al., PRA 88, 012513 (2013).
    """
    @enum   QedModel    QedSydney    QedPetersburg
    export  QedModel,   QedSydney,   QedPetersburg   ==#

    """
    `@enum   Basics.ContinuumNormalization`  ... defines a enumeration for dealing with the normalization of continuum orbitals.

        + PureSine       ... normalize with regard to an (asymtotic) pure sine funtion, sin(kr).
        + CoulombSine    ... normalize with regard to an (asymtotic) Coulombic-sine funtion, sin(kr + ...).
        + OngRussek      ... normalize by following Ong & Russek (1973).
    """
    @enum   ContinuumNormalization    PureSine    CoulombSine    OngRussek
    export  ContinuumNormalization,   PureSine,   CoulombSine,   OngRussek

    """
    `@enum   Basics.ContinuumSolutions`  ... defines a enumeration for solving the continuum orbitals in a given potential.

        + ContBessel              ... generate a pure Bessel function for the large component together with kinetic balance.
        + ContSine                ... generate a pure Sine function for the large component together with kinetic balance.
        + NonrelativisticCoulomb  ... generate a non-relativistic Coulomb function for the large component together with kinetic balance.
        + AsymptoticCoulomb       ... generate a pure (asymptotic) Coulombic function for both components.
        + BsplineGalerkin         ... generate a continuum orbital with the Galerkin method.
    """
    @enum   ContinuumSolutions    ContBessel    ContSine     AsymptoticCoulomb    NonrelativisticCoulomb    BsplineGalerkin
    export  ContinuumSolutions,   ContBessel,   ContSine,    AsymptoticCoulomb,   NonrelativisticCoulomb,   BsplineGalerkin

    """
    `@enum   Basics.Warnings`      ... defines a enumeration for dealing with warnings that are made during a run or REPL session.
                                Cf. Defaults.warn().

        + AddWarning        ... add a Warning to a warningList.
        + PrintWarnings     ... print all warnings into a jac-warn.report file.
        + ResetWarnings     ... reset (empty) the warningList, usually at the beginning of a new run.
    """
    @enum   Warnings    AddWarning    PrintWarnings    ResetWarnings
    export  Warnings,   AddWarning,   PrintWarnings,   ResetWarnings

    """
    `@enum   Basics.Guint`      ... defines a enumeration for dealing with graphical user interfaces (GUI).

        + Gui            ... use a graphical (interactive) user interface.
        + GuiSettings    ... use a graphical (interactive) user interface to define some settings.
        + Cascade        ... use a graphical (interactive) user interface to define some settings.
    """
    @enum   Guint    Gui    GuiSettings   GuiCascade
    export  Guint,   Gui,   GuiSettings,  GuiCascade

    """
    `abstract type Basics.AbstractExcitationScheme`
        ... defines an abstract and a number of singleton types to distinguish between different schemes for
            generating configuration lists as they frequently occur in Green function and cascade computations.

      + struct NoExcitationScheme
        ... dummy scheme for (unsupported) initialization of this abstract tpye.

      + struct DeExciteSingleElectron
        ... generates all excitations and de-excitations of a single electron from a given list of bound
            electron configurations. The number of electrons of the generated configurations is the same as
            for the given bound configurations.

      + struct DeExciteTwoElectrons
        ... generates all excitations and de-excitations of one or two electrons from a given list of bound
            electron configurations. The number of electrons of the generated configurations is the same as
            for the given bound configurations.

      + struct AddSingleElectron
        ... generates configurations by just adding a single electrons to a given list of bound
            electron configurations. The number of electrons of the generated configurations is N+1.
    """
    abstract type  AbstractExcitationScheme                               end
    struct         NoExcitationScheme      <:  AbstractExcitationScheme   end
    struct         DeExciteSingleElectron  <:  AbstractExcitationScheme   end
    struct         DeExciteTwoElectrons    <:  AbstractExcitationScheme   end
    struct         AddSingleElectron       <:  AbstractExcitationScheme   end

    # Functions/methods that are later added to the module Basics
    function add                                            end
    function addZerosToCsfR                                 end
    function analyze                                        end
    function analyzeConvergence                             end
    function compute                                        end
    function computeDiracEnergy                             end
    function computeMeanSubshellOccupation                  end
    function computePotentialCoreHartree                    end
    function computePotentialHartree                        end
    function computePotentialHartreeSlater                  end
    function computePotentialKohnSham                       end
    function computePotentialDFS                            end
    function computePotentialExtendedHartree                end
    function determineEnergySharings                        end
    function determineHoleShells                            end
    function determineParity                                end
    function determineSelectedLines                         end
    function determineSelectedPathways                      end
    function diagonalize                                    end
    function display                                        end
    function excludeDoubles                                 end
    function extractNoOpenShells                            end
    function extractNonrelativisticShellList                end
    function extractNonrelativisticConfigurations           end
    function extractNonrelativisticConfigurationFromCsfR    end
    function extractOpenShellQNfromCsfNR                    end
    function extractOpenShellQNfromCsfR                     end
    function extractShellOccupationFromCsfR                 end
    function generate()                                     end
    function generateConfigurationsForExcitationScheme      end
    function generateLevelWithExtraElectron                 end
    function generateLevelWithExtraSubshell                 end
    function generateLevelWithSymmetryReducedBasis          end
    function generateShellList                              end
    function integrate                                      end
    function integrateOnGridNewtonCotes                     end
    function integrateOnGridSimpsonRule                     end
    function integrateOnGridTrapezRule                      end
    function interpolateOnGridGrasp92                       end
    function interpolateOnGridTrapezRule                    end
    function isSimilar                                      end
    function isStandardSubshellList                         end
    function merge                                          end
    function modifyLevelEnergies                            end
    function perform                                        end
    function plot                                           end
    function read                                           end
    function readCslFileGrasp92                             end
    function readOrbitalFileGrasp92                         end
    function readMixFileRelci                               end
    function readMixingFileGrasp92                          end
    function recast                                         end
    function sortByEnergy                                   end
    function tabulate                                       end
    function tabulateKappaSymmetryEnergiesDirac             end
    function tools                                          end
    function yesno                                          end

end # module


using DataVisualization: log_scaler, logistic_scaler, standardizer
using DataVisualization: min_max_scaler, max_abs_scaler
using DataVisualization: binner, quantile_binner, uniform_binner

@testset "Scaler test" begin
    isf = logistic_scaler([1.0])
    @test 15 |> isf |> inv(isf) ≈ 15
    isf = log_scaler([1.0])
    @test 15 |> isf |> inv(isf) ≈ 15
    isf = standardizer([1, 10])
    @test 15 |> isf |> inv(isf) ≈ 15
    isf = min_max_scaler([1, 10])
    @test 15 |> isf |> inv(isf) ≈ 15
    isf = max_abs_scaler([1, 10])
    @test 15 |> isf |> inv(isf) ≈ 15
end

@testset "Binning test" begin
    column = [0, 11, 23, 34, 50, 75, 98]
    bins = 0:20:100
    result = [1, 1, 2, 2, 3, 4, 5]
    @test binner(column, bins) ≈ result
    result = [1, 1, 2, 2, 3, 4, 6]
    @test uniform_binner(column, 6) ≈ result
    result = [1, 1, 2, 3, 4, 5, 6]
    @test quantile_binner(column, 6) ≈ result
end


using Plots
gr(xlims=[0,2*pi],ylims=[-1.2,1.2],border=:none)

Np_F = 4
h = 2*pi/Np_F

fourier(x) = @. cos(pi*x)
r_plot = 0:4/200:2
plot(r_plot*pi,fourier(r_plot),lw=3,legend=nothing)
plot!(r_plot*pi,fourier(r_plot,2),lw=3,legend=nothing)
plot!(r_plot*pi,fourier(r_plot,4),lw=3,legend=nothing)
# plot!(r_plot*pi,fourier(r_plot,8),lw=3,legend=nothing)

# y = ones(size(r_plot))
# l = 0.3
# u = 0.3
# plot!([-pi,0],[2,2],fill=(0,:grey),fillalpha=0.3)
# plot!([-pi,0],-[2,2],fill=(0,:grey),fillalpha=0.3)
# plot!([2*pi,3*pi],[2,2],fill=(0,:grey),fillalpha=0.3)
# plot!([2*pi,3*pi],-[2,2],fill=(0,:grey),fillalpha=0.3)

savefig("fourier_basis.png")


#-----------------------------------------------------------------------
#       BEGIN   KerrSchild.jl
#-----------------------------------------------------------------------

#-----------------------------------------------------------------------
#   KERR-SCHILD FUNCTIONS
#-----------------------------------------------------------------------

#-----------------------------------------------------------------------
"""
    rsq( X::RealVec , a::Real )
The `rsq` function takes a point `X` in Cartesian Kerr-Schild
coordinates and calculates the value of ``r^2`` at that point in a
Kerr spacetime with rotation parameter `a`
"""
function rsq( X::RealVec , a::Real )
    tpfl=typeof(X[1])
    x = X[2]
    y = X[3]
    z = X[4]

    return ( -a^2 + x^2 + y^2 + z^2 + sqrt(4*(a^2)*(z^2)
             + (-a^2 + x^2 + y^2 + z^2)^2))/2
end     #---------------------------------------------------------------

#-----------------------------------------------------------------------
"""
    fks( X::RealVec , a::Real , GM::Real=1 )
The `fks` function takes a point `X` in Cartesian Kerr-Schild
coordinates and calculates the value of function ``f`` at that point in
a Kerr spacetime with rotation parameter `a` and with the product of
gravitational constant and mass `GM`
"""
function fks( X::RealVec , a::Real , GM::Real=1 )
    tpfl=typeof(X[1])
    x = X[2]
    y = X[3]
    z = X[4]

    rs = rsq(X,a)
    r = sqrt(rs)
    return 2*GM*rs*r/(rs^2+(a^2)*(z^2))
end     #---------------------------------------------------------------

#-----------------------------------------------------------------------
"""
    kks( X::RealVec , a::Real )
The `kks` function takes a point `X` in Cartesian Kerr-Schild
coordinates and calculates the tensor product ``k_\\mu k_\\nu`` at that
point in a Kerr spacetime with rotation parameter `a`
"""
function kks( X::RealVec , a::Real )
    tpfl=typeof(X[1])
    k = zeros(tpfl,4)
    kk = zeros(tpfl,4,4)
    x = X[2]
    y = X[3]
    z = X[4]

    rs = rsq(X,a)
    r = sqrt(rs)

    k[1] = one(tpfl)
    k[2] = (r*x+a*y)/(rs+a^2)
    k[3] = (r*y-a*x)/(rs+a^2)
    k[4] = z/r

    for i=1:4
        for j=1:4
            kk[i,j] = k[i]*k[j]
        end
    end
    return kk
end     #---------------------------------------------------------------

#-----------------------------------------------------------------------
#   KERR-SCHILD METRIC COMPONENTS
#-----------------------------------------------------------------------

#-----------------------------------------------------------------------
"""
    gks( X::RealVec , a::Real=0 , GM::Real=1 )
The `gks` function takes a point `X` in Cartesian Kerr-Schild
coordinates and calculates the components of the Kerr-Schild metric at
that point in a Kerr spacetime with rotation parameter `a` and with the
product of gravitational constant and mass `GM`
"""
function gks( X::RealVec , a::Real=0 , GM::Real=1 )
    tpfl=typeof(X[1])

    return ημν(tpfl) + fks(X,a,GM)*kks(X,a)
end     #---------------------------------------------------------------

#-----------------------------------------------------------------------
"""
    ge( X::RealVec )
The `ge` function takes a point `X` in Cartesian Kerr-Schild
coordinates and calculates the components of the Kerr-Schild metric at
that point in a Kerr spacetime with the product of gravitational
constant and mass ``GM=1`` and with rotation parameter ``a=738``
(roughly corresponding to the Earth's angular momentum)
"""
function ge( X::RealVec )
    tpfl=typeof(X[1])

    a = tpfl(738)   #   This is the roughly the rotation parameter
                    #   for Earth in units where M_Earth = 1
                    #   For reference, the avg radius of Earth is
                    #   1.437e9

    return gks(X,a,1)
end     #---------------------------------------------------------------

#-----------------------------------------------------------------------
#       END     KerrSchild.jl
#-----------------------------------------------------------------------


include("gausslegendre.jl")
include("getdigit_n_xc.jl")
include("myprintf.jl")
using Printf
using .GetDigit_n_xc
using .GaussLegendre
using .MyPrintf

const DIGIT = 17
const MAXN = 1000

function gauss_gl(xc::BigFloat, n::Int64)
    res = GaussLegendre.gauleg(x::BigFloat -> exp(- x * x), BigFloat(0), xc, n)

    return BigFloat(4) * res * res
end

function main()
    xc = GetDigit_n_xc.getxc(gauss_gl, MAXN, DIGIT, true)
    n = GetDigit_n_xc.getn_linear(gauss_gl, xc, MAXN, DIGIT, true)
    precision = GetDigit_n_xc.getdigit(gauss_gl, xc, n, DIGIT, true)
    precision = GetDigit_n_xc.getdigit_2(gauss_gl, xc, n, DIGIT, precision)

    setprecision(precision)
    MyPrintf.myprintf("π（計算値） = ", DIGIT, gauss_gl(xc, n))
    MyPrintf.myprintf("π（厳密値） = ", DIGIT, BigFloat(π))
    @printf("xc = %.14f, n = %d, 計算%d桁で、%d桁求まりました\n", xc, n, precision, DIGIT)
end

main()


### Log-likehood & log-prior

export negLogLikelihood_gauss, negLogPost, negLogPrior_gauss, negLogPrior_L1smooth

function negLogLikelihood_gauss(
	d::Data, m::Model, σ2_dat::Float32,
	F::FunctionMod2Dat;
	compute_grad::Bool = false)

	# Compute functional
	Fm = F.eval(m; store_extracomp = compute_grad)
	Δd = Fm-d
	f = 1f0/(2f0*σ2_dat)*norm(Δd).^2f0

	!compute_grad ? (return f) : (return f, F.jac_adj(m, Δd)/σ2_dat)

end

function negLogPrior_gauss(
	m::Model, σ2_mod::Float32;
	b::Union{Nothing, Model} = nothing, m0::Model = Model(ModelGeom(0, 0f0), zeros(Float32, 1)), D::LinFunctionMod2Mod = LinFunctionMod2Mod(m->m, m->m),
	compute_grad::Bool = false)

	# Compute functional
	(b == nothing) ? (r = D.eval(m-m0)) : (r = D.eval(m-m0)-b)
	f = 1f0/(2f0*σ2_mod)*norm(r).^2f0

	if !compute_grad
		return f

	# Compute gradient
	else
		return f, D.eval_adj(r)/σ2_mod
	end

end

function negLogPrior_L1smooth(
	m::Model, σ_mod::Float32, ϵ2::Float32;
	m0::Model = Model(ModelGeom(0, 0f0), zeros(Float32, 1)), D::LinFunctionMod2Mod = LinFunctionMod2Mod(m->m, m->m),
	compute_grad::Bool = false)

	# Compute functional
	Dm = D.eval(m-m0)
	w = sqrt.(abs.(Dm.m).^2f0.+ϵ2)
	f = 1f0/σ_mod*vec(sum(w; dims = 1))

	if !compute_grad
		return f

	# Compute gradient
	else
		return f, D.eval_adj(Dm/w)/σ_mod
	end

end

function negLogPost(
	m::Model,
	negLogLikelihood_fun::Function, negLogPrior_fun::Function;
	compute_grad::Bool = false)

	outlike = negLogLikelihood_fun(m; compute_grad = compute_grad)
	outpr   = negLogPrior_fun(m; compute_grad = compute_grad)
	if !compute_grad
		return outlike.+outpr
	else
		return outlike[1].+outpr[1], outlike[2]+outpr[2]
	end

end


using Distributions
using LinearAlgebra
using Random

# Define the DGP
function simulate_sample(N0, N1)
    dy1 = Weibull(2, 1);
    dy2 = Weibull(2, 1.3);
    dy = MixtureModel([Weibull(2, 0.5), Weibull(2, 1.5)], [0.5, 0.5])
    X0 = [ones(N0) rand([0, 1], N0, 4) rand(N0)]
    X1 = kron([ones(2, 4) [1, 0] [0, 0]], ones(N1))
    λ0 = @. exp(-1.0 + 1.0 * X0[:, end - 1] + 1.0 * X0[:, end])
    y0 = λ0 .* rand(dy, N0)
    y1 = LinRange(0, 6, N1) |> collect |> x -> repeat(x, 2)
    c0 = rand(Exponential(10), N0)
    z0 = deepcopy(y0)
    return dy1, dy2, z0, c0, y0, X0, y1, X1
end

# Not run
Random.seed!(1);
N0, N1 = 500, 0;
dy1, dy2, z0, c0, y0, X0, y1, X1 = simulate_sample(N0, N1);
println(mean(z0 .> c0))
println(maximum(z0))
println(mean(z0))


# Module taken from:  https://github.com/alexyarosh/hyperbolic

using Plots
using Eirene
using Ripserer
using Statistics

# compute the persistent betti numbers of the Vietoris-Rips complex given by the distance matrix `matr`
# if mintime, maxtime and numofsteps are specified -- returns a `numofsteps x maxdim` array
# if either of the keyword arguments is not specified or is set to Inf, returns `maxdim` arrays for method=:eirene, or error for :ripser

# function bettis(matr, maxdim; mintime=-Inf, maxtime=Inf, numofsteps=Inf, method=:ripser)
#     if (method == :ripser) || (method == :Ripser)
#         if VERSION < v"0.7.0"
#             error("Ripser requires at least Julia v 0.7.0")
#         end
#         return bettis_ripser(matr, maxdim, mintime=mintime, maxtime=maxtime, numofsteps=numofsteps)
#     elseif (method == :eirene) || (method == :Eirene)
#         return bettis_eirene(matr, maxdim, mintime=mintime, maxtime=maxtime, numofsteps=numofsteps)
#     else
#         error("Method $(method) is not supported. Supported methods are: method=:eirene, method=:ripser")
#     end
# end

function bettis_ripser(matr, maxdim; mintime=-Inf, maxtime=Inf, numofsteps=Inf)
    if (mintime == -Inf) || (maxtime == -Inf) || (numofsteps == -Inf)
        error("To use Ripser, specify parameters mintime, maxtime, numofsteps")
    end
    r = ripser(matr,  dim_max = maxdim, threshold = maxtime)

    int_length = maxtime-mintime
    step_length= int_length/numofsteps
    betts = zeros(numofsteps, maxdim)

    for dim=1:maxdim
        ints = r[dim+1]
        for intl in ints
            st = Int(ceil((intl[1]-mintime)/step_length))
            if intl[2] == Inf
                fin = numofsteps
            else
                fin = Int(ceil((intl[2]-mintime)/step_length))
            end
            betts[st:fin, dim] = map(x->x+1, betts[st:fin, dim])
         end
    end
    return betts
end
#
# # Original function returns 2 different types of betti curves. If no default
# # value parameters is given, it returns vector of matrices. If num of steps is
# # given, then it return matrix maxdim x numsteps.
# function bettis_eirene(matr, maxdim; mintime=-Inf, maxtime=Inf, numofsteps=Inf)
#     c = eirene(matr, minrad = mintime, maxrad= maxtime, numrad= numofsteps, maxdim=maxdim)
#
#     int_length = maxtime-mintime
#     step_length= int_length/numofsteps
#
#     if (mintime == -Inf) || (maxtime == Inf) || (numofsteps == Inf)
#         # return [betticurve(c, dim=maxdim) for d=1:maxdim]
#         return hcat([betticurve(c, dim=d)[:,2] for d=1:maxdim]...)
#     end
#
#     betts = zeros(numofsteps, maxdim)
#     # For every dimension compute betti curve
#     for dim=1:maxdim
#         bet = betticurve(c, dim=dim)
#
#         #for every element in betti curve return betti value if index is positive
#         for i=1:size(bet,1)
#             b = bet[i,:]
#             ind = Int(ceil((b[1]-mintime)/step_length))
#             if ind > 0
#                 betts[ind,dim]=b[2]
#             else
#                 betts[1,dim]=b[2]
#             end
#         end
#     end
#     return betts
# end

# average betti numbers over arrs
# assuming arrs is an array of arrays, where each arrs[j] is the same size

function average_bettis(arrs; maxdim=-1)
    if size(arrs,2) > 1
        return arrs
    end
    md = maxdim
    if maxdim == -1
        md = size(arrs[1],2)
    end
    numofints = size(arrs[1],1)
    av_bet = zeros(numofints,md)

    for i=1:numofints
        for d=1:md
            av_bet[i,d] = mean([arrs[j][i,d] for j=1:length(arrs)])
        end
    end
    return av_bet
end

# compute standard deviation of betti numbers over arrays in arrs
# assuming arrs is an array of arrays, where each arrs[j] is the same size

function std_bettis(arrs; maxdim=-1)
    md = maxdim
    if maxdim == -1
        md = size(arrs[1],2)
    end
    numofints = size(arrs[1],1)
    std_bet = zeros(numofints,md)

    if size(arrs,2) > 1
        return std_bet
    end

    for i=1:numofints
        for d=1:md
            std_bet[i,d] = std([arrs[j][i,d] for j=1:length(arrs)])
        end
    end
    return std_bet
end

# plot average curves at values `xval`, with averages given by `means` and standard deviations given by `std`

function plot_averages(xvals, means, stds; ribbon=true, label="", linestyle=:solid, color=:auto)
    if ribbon
         return plot(xvals, means, ribbon=stds,fillalpha=.3, labels=label, linestyle=linestyle, color=color)
    else
        return plot(xvals, means, labels=label, linestyle=linestyle, c=color)
    end
end

function plot_averages!(xvals, means, stds; ribbon=true, label="", linestyle=:solid, color=:auto)
    if ribbon
         return plot!(xvals, means, ribbon=stds,fillalpha=.3, labels=label, linestyle=linestyle, color=color)
    else
        return plot!(xvals, means, labels=label, linestyle=linestyle, c=color)
    end
end

function load_bettis(filename)
    dict = load(filename)
    for (matr_name, matr) in dict
        return matr
    end
end

# plot average curves at values `xval`, given that the bettis numbers are saved in `file`

function plot_averages(xvals, file::String; dim=1, ribbon=true, label="", linestyle=:solid, color=:auto)
    matr = load_bettis(file)

    av = average_bettis(matr)[:,dim]
    if ribbon
         st = std_bettis(matr)[:,dim]
         return plot(xvals, av, ribbon=st,fillalpha=.3, labels=label, linestyle=linestyle, c=color)
    else
        return plot(xvals, av, labels=label, linestyle=linestyle, c=color)
    end
end

function plot_averages!(xvals, file::String; dim=1, ribbon=true, label="", linestyle=:solid, color=:auto)
    matr = load_bettis(file)

    av = average_bettis(matr)[:,dim]
    if ribbon
         st = std_bettis(matr)[:,dim]
         return plot!(xvals, av, ribbon=st,fillalpha=.3, labels=label, linestyle=linestyle, c=color)
    else
        return plot!(xvals, av, labels=label, linestyle=linestyle, c=color)
    end
end


### four vectors
################################################################################

function four_vectors_in_binary_decay(cosθ, ϕ;
        m1sq::Real=error("give mass 1 squared"),
        m2sq::Real=error("give mass 2 squared"),
        m0sq::Real=error("give decay mass squared"))
    E1 = (m0sq+m1sq-m2sq)/(2sqrt(m0sq));
    E2 = (m0sq-m1sq+m2sq)/(2sqrt(m0sq));
    √m0sq < √m1sq + √m2sq && error("!!! -√m0sq + √m1sq + √m2sq = $(-√m0sq + √m1sq + √m2sq)")
    p = sqrt(Kallen(m0sq,m1sq,m2sq))/(2sqrt(m0sq));
    #
    sinθ = sqrt(1-cosθ^2)
    p1 = MVector( p*sinθ*cos(ϕ),  p*sinθ*sin(ϕ),  p*cosθ, E1)
    p2 = MVector(-p*sinθ*cos(ϕ), -p*sinθ*sin(ϕ), -p*cosθ, E2)
    return (p1, p2)
end

function four_vectors_in_binary_decay(p0,cosθ,ϕ;
        m1sq::Real=error("give mass 1 squared"),
        m2sq::Real=error("give mass 2 squared"))
    psq = sum(abs2,p0[1:3])
    m0sq = p0[4]^2-psq
    #
    γ = p0[4]/sqrt(m0sq)
    cosθ0 = psq≈0.0 ? 1.0 : p0[3]/sqrt(psq)
    ϕ0 = atan(p0[2],p0[1])
    #
    p1, p2 = four_vectors_in_binary_decay(cosθ,ϕ; m1sq=m1sq, m2sq=m2sq, m0sq=m0sq)
    boostz!(p1,γ); roty_cos!(p1,cosθ0); rotz!(p1, ϕ0)
    boostz!(p2,γ); roty_cos!(p2,cosθ0); rotz!(p2, ϕ0)
    #
    return (p1, p2)
end

invmasssq(p) = p[4]^2-sum(abs2, p[1:3])

function rotz!(p,θ)
    c, s = cos(θ), sin(θ)
    p[1], p[2] = [c -s; s c]*[p[1], p[2]]
    return
end
function roty!(p,θ)
    c, s = cos(θ), sin(θ)
    p[1], p[3] = [c s; -s c]*[p[1], p[3]]
    return
end
function roty_cos!(p,cosθ)
    c, s = cosθ, sqrt(1-cosθ^2)
    p[1], p[3] = [c s; -s c]*[p[1], p[3]]
    return
end
function roty_cos_inv!(p,cosθ)
    c, s = cosθ, sqrt(1-cosθ^2)
    p[1], p[3] = [c -s; s c]*[p[1], p[3]]
    return
end
function boostz!(p,γ)
    γ, βγ = γ*sign(γ), sqrt(γ^2-1)*sign(γ)
    p[3], p[4] = [γ βγ; βγ γ]*[p[3], p[4]]
    return
end


# The file contains example from readme

# -----------------------------------------------------------------------------

import Pkg;
Pkg.add(url="https://github.com/sumiya11/RationalFunctionFields.jl")

# -----------------------------------------------------------------------------

using Nemo
using RationalFunctionFields

# -----------------------------------------------------------------------------

R, (x, y) = PolynomialRing(QQ, ["x", "y"])

generators = [
            (x^2 + y^2) // R(1),
            1 // (x^2)
]

# -----------------------------------------------------------------------------

rff = RationalFunctionField(generators)

print( contains(rff, y^2, p=0.999) )

print( contains(rff, y^2, p=1) )


function uhlig01()
#
#  test polynomial used by F. Uhlig
#
    a = 0.01;
    p1 = [1;0;0;0;-a^4]
    p2 = reverse(poly([a;a;a;a]).a)
    p = conv(p1,p2)
    z = [-0.01000000000000 - 0.00000000000000im
         -0.00000000000000 + 0.01000000000000im
         -0.00000000000000 - 0.01000000000000im
         0.01000000000000 - 0.00000000000000im];
    z = [z [1;1;1;5]]
    p, PolyZeros(z)
end


using StrainRecon
using Base.Test

if StrainRecon.hasGurobi
	@testset "getMAPConvex" begin

	M  = [1 1 1; 1 1 0; 1 0 1; 1 0 0; 0 1 1; 0 1 0; 0 0 1;]
	(m,n) = size(M)
	R     = StrainRecon.sdiag(ones(m)*100);
	atrue = [.6; 0.3; 0.1]
	Data  = M*atrue

	param = getStrainReconParam(Data,n,R=R)

	Mmap,amap = solveConvexMAP(param)
	@test norm(amap - atrue)/norm(atrue) < 1e-8
	@test norm(Mmap*amap-Data) < 1e-7
	end
end


export λmap
export compressedλmap

"""
    λmap(s)

Map from S³ into ℝ³ using stereographic projection with the given point `s`.
"""
λmap(q::Quaternion) = ℝ³(vec(q)[1:3] ./ (1 - vec(q)[4]))
compressedλmap(q::Quaternion) = begin
    #sigmoid(x::ℝ³) = exp.(vec(x)) ./ (1 .+ exp.(vec(x)))
    #ℝ³(2 .* (sigmoid(λmap(q)) .- 0.5))
    p = λmap(q)
    magnitude = norm(p)
    normalize(p) * tanh(magnitude)
end
λmap(s::S³) = λmap(Quaternion(s))
compressedλmap(s::S³) = λmap(Quaternion(s))


struct Sphere <: Hitable
	center::Point3
	radius::Float64
	material::Material
end

export Sphere, Hit

function hit(s::Sphere, t, ray)
	p = at(ray, t)
	outward_normal = (p - s.center) / s.radius
	ff = dot(ray.direction, outward_normal)
	norm = ff < 0 ? outward_normal : -outward_normal
	Hit(p, norm, t, ff < 0)
end

function trace(sphere::Sphere, ray::Ray, t_min::Float64, t_max::Float64)::Float64
	oc = ray.origin - sphere.center
	a = magnitudesq(ray.direction)
	half_b = dot(oc, ray.direction)
	c = magnitudesq(oc) - sphere.radius^2
	discriminant = half_b^2 - a*c
	if discriminant < 0
		return -1.0
	end

	sqrtd = sqrt(discriminant)
	root = (-half_b - sqrtd) / a
	if t_max < root || root < t_min
		root = (-half_b + sqrtd) / a
		if t_max < root || root < t_min
			return -1.0
		end
	end

	root
end


@testset "Homotopies" begin
    @testset "StraightLineHomotopy" begin
        F = SPSystem(equations(katsura(5)))
        G = SPSystem(equations(cyclic(6)))
        H = StraightLineHomotopy(F, G)
        @test H isa AbstractHomotopy
        @test size(H) == (6, 6)
        @test HC.gamma(H) isa ComplexF64
        HomotopyContinuation.homotopy_interface_test(H)
    end

    @testset "HomotopyWithCache" begin
        x = rand(Complex{Float64}, 6)
        t = rand()
        F = SPSystem(equations(katsura(5)))
        G = SPSystem(equations(cyclic(6)))
        H = HomotopyWithCache(StraightLineHomotopy(F, G), x, t)
        @test H isa AbstractHomotopy
        @test size(H) == (6, 6)

        m, n = size(H)
        u = zeros(Complex{Float64}, m)
        U = zeros(Complex{Float64}, m, n)

        evaluate!(u, H, x, t)
        @test evaluate(H, x, t) ≈ u

        dt!(u, H, x, t)
        @test dt(H, x, t) ≈ u

        jacobian!(U, H, x, t)
        @test jacobian(H, x, t) ≈ U

        evaluate_and_jacobian!(u, U, H, x, t)
        @test all(evaluate_and_jacobian(H, x, t) .≈ (u, U))
        @test all((u, U) .≈ (evaluate(H, x, t), jacobian(H, x, t)))

        jacobian_and_dt!(U, u, H, x, t)
        @test all(jacobian_and_dt(H, x, t) .≈ (U, u))
        @test all((U, u) .≈ (jacobian(H, x, t), dt(H, x, t)))
    end

    @testset "ParameterHomotopy" begin
        F = equations(katsura(5))
        vars = variables(F)
        H = ParameterHomotopy(F, vars[5:6], p₁ = rand(2), p₀ = rand(2))
        @test H isa AbstractHomotopy
        @test size(H) == (6, 4)
        @test size(H, 1) == 6
        @test size(H, 2) == 4
        @test length(H) == 6
        @test HC.nvariables(H) == 4

        HomotopyContinuation.homotopy_interface_test(H)

        H = ParameterHomotopy(
            F,
            vars[5:6],
            p₁ = rand(2),
            p₀ = rand(2),
            γ₁ = randn(ComplexF64),
            γ₀ = randn(ComplexF64),
        )
        @test H isa ParameterHomotopy
        HomotopyContinuation.homotopy_interface_test(H)
    end

    @testset "FixedPointHomotopy" begin
        F = SPSystem(equations(katsura(5)))
        H = FixedPointHomotopy(F, rand(ComplexF64, 6))
        @test H isa AbstractHomotopy
        @test size(H) == (6, 6)

        HomotopyContinuation.homotopy_interface_test(H)
    end

    @testset "PatchedHomotopy" begin
        F = SPSystem(equations(katsura(5)))
        G = SPSystem(equations(cyclic(6)))
        x = ProjectiveVectors.embed(rand(Complex{Float64}, 5))
        H = PatchedHomotopy(StraightLineHomotopy(F, G), OrthogonalPatch(), x)
        @test H isa AbstractHomotopy
        @test size(H) == (7, 6)

        HomotopyContinuation.homotopy_interface_test(H, x)
    end

    @testset "Coefficient Homotopy" begin
        E = [[2 1 0; 0 0 0], [1 0; 1 0]]
        start = [[1.0 + 0im, -3.0, 2.0], [2.0 + 0im, -2.0]]
        target = [[2.0 + 0im, -2.0, 5.0], [3.0 + 0im, -1.0]]
        H = CoefficientHomotopy(E, start, target)
        x = randn(ComplexF64, 2)
        HomotopyContinuation.homotopy_interface_test(H, x)
    end

    @testset "ConstantHomotopy" begin
        F = SPSystem(equations(katsura(5)))
        H = ConstantHomotopy(F)
        @test H isa AbstractHomotopy
        @test size(H) == (6, 6)

        HomotopyContinuation.homotopy_interface_test(H)
    end
end


# utilities for pipe diameters

"compute equivalent diameter from serial pipe segments"
function serialmerge(diams::Vector{Diameter}, lengths::Vector{Float64})
    @assert length(diams) == length(lengths)
    @assert sum(lengths) ≈ 1.0
    y = sum(l / d.value^5 for (d,l) in zip(diams, lengths))
    y^(-1/5)
end

"compute lengths of two segments for equivalent diameter"
function pipesplit(diams::Vector, equiv::Float64)
    ds = [d.value for d in diams]
    dmin, dmax = extrema(ds)
    @assert isapproxin(equiv, dmin, dmax, atol=1e-8)
    y, ys = equiv^(-5), ds.^(-5)
    @assert issorted(ys, rev=true) # decreasing

    l = fill(0.0, length(diams))

    right = findfirst(yi -> y ≥ yi, ys) # decreasing
    if right == 1 # on left boundary
        l[1] = 1.0
        return l
    end

    left = right - 1
    λ = (y - ys[left])/(ys[right] - ys[left])
    l[left] = 1 - λ
    l[right] = λ
    l
end


f(x) = cos(x)
g(x) = -sin(x)
h(x) = -cos(x)

x = 2.0

x = optimize_newton(x, f, g, h)

@test x ≈ π   rtol = 1e-4


using CircGeometry, Test

center = Point(-0.9,0.0)
p1 = Point(-1.0,-1.0)
p2 = Point(1.0,1.0)

fo = CircGeometry.FillingCircle(0.1,center,10.0)
olist = [fo]
outline = CircGeometry.OutlineRectangle(p1,p2)

Fx,Fy = CircGeometry.compute_wall_repulsion(1,olist,outline.wlist)
bool1 = CircGeometry.is_intersecting_walls(outline,fo)
bool2 = CircGeometry.is_inside_outline(fo,outline)


# %%
using Tmatrix
using Profile

rx, rz = 1e-7, 1.3e-7
n_max = 8
k1 = Complex(6.2e6)
k2 = 7e6 + 1e3 * im
rotationally_symmetric = true

@profile calculate_Tmatrix_for_spheroid(rx, rz, n_max, k1, k2; rotationally_symmetric=rotationally_symmetric)
Profile.print()

"""
using VectorSphericalWaves
n_θ_points, n_ϕ_points = 10000,20000
θ_array, ϕ_array = Tmatrix.meshgrid_θ_ϕ(n_θ_points, n_ϕ_points; min_θ=1e-16, min_ϕ=1e-16, rotationally_symmetric=rotationally_symmetric)
r_array, n̂_array = Tmatrix.ellipsoid(rx, rz, θ_array);
VectorSphericalWaves.N_mn_wave_SVector.(0,1,k2*r_array, θ_array,ϕ_array; kind= "regular")
VectorSphericalWaves.N_mn_wave_SVector(0,1,k2*r_array[1], θ_array[1],ϕ_array[1]; kind= "regular")
@time for i = 1:1e3; VectorSphericalWaves.N_mn_wave_SVector.(0,1,k2*r_array, θ_array,ϕ_array; kind= "regular"); end
@time for i = 1:1e3; VectorSphericalWaves.N_mn_wave.(0,1,k2*r_array, θ_array,ϕ_array; kind= "regular"); end
"""


using TomoForward
using XfromProjections

img = zeros(128, 128)
img[40:60, 40:60] .= 1.0
H, W = size(img)

nangles = 90
detcount = Int(floor(size(img,1)*1.4))
proj_geom = ProjGeom(1.0, detcount, LinRange(0,pi,nangles+1)[1:nangles])

A = fp_op_parallel2d_strip(proj_geom, size(img, 1), size(img, 2))
p = reshape(A * vec(img), nangles, detcount)

u = zeros(size(img))
niter=100
@time recon2d_sirt!(u, A, p, niter)

# using PyPlot
# imshow(u, cmap="gray")
# title("reconstructed image")
# show()


module SimpleQuantum
using LinearAlgebra, SparseArrays, SimpleRandom

import Base: show, ==, length, kron

export measure!, (⊗), lg

RR = Float64            # shortcut for real
CC = Complex{RR}        # shortcut for complex

"""
`A⊗B` is the Kronecker (tensor) product of `A` and `B`.
"""
⊗(A) = A
⊗(A, B) = kron(A, B)
⊗(A, B, C...) = ⊗(A, ⊗(B, C...))

"""
`lg(n)` is a fast, base-2 log of `n` assuming `n` is a power of 2.
"""
function lg(n::Int)::Int
    count = 0
    while n>1
        n = n>>1
        count += 1
    end
    return count
end

include("quantum_state.jl")
include("qubit.jl")
include("register.jl")
include("gate.jl")

end  # end of module


#   Copyright (c) 2020-2022 Velexi Corporation
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""
Blade_operators_mgs.jl defines versions of operations on the Blade type based
on modified Gram-Schmidt orthogonalization (as opposed to Householder
triangularization).

Notes
-----
* Compared with algorithms based on Householder triangularization, algorithms
  based on modified Gram-Schmidt orthogonalization typically have higher
  computational performance at the cost of lower accuracy and higher memory
  usage.
"""

# --- Exports

export wedge_mgs
export dual_mgs

# --- Method definitions

using LinearAlgebra: norm

"""
    wedge_mgs(B::Blade, C::Blade)::AbstractBlade

Return the outer product of `B` and `C`.

Implementation
==============
* The extension of the basis of `B` to a basis for span(B, C) is computed
  using modified Gram-Schmidt orthogonalization.
"""
function wedge_mgs(B::Blade, C::Blade)
    # Compute the rejections of the basis of `C` from the subspace represented
    # by `B`
    rejections = rejection(basis(C), B)

    # Orthogonalize the rejections using the modified Gram-Schmidt algorithm
    for j in 1:size(rejections, 2)
        norm = LinearAlgebra.norm(rejections[:, j])
        if norm > 0
            rejections[:, j] /= norm
        else
            return zero(B)
        end

        column = rejections[:, j]
        for j_inner in (j + 1):size(rejections, 2)
            rejections[:, j_inner] -=
                (rejections[:, j_inner] ⋅ column) * column
        end
    end

    # Construct the Blade representing the outer product
    B_wedge_C_basis = hcat(basis(B), rejections)
    Blade{typeof(B.volume)}(dim(B), grade(B) + grade(C),
                            B_wedge_C_basis, volume(B) * volume(C),
                            copy_basis=false)

#    rejections = rejection(basis(C), B)
#    Blade(hcat(basis(B), rejections), volume=volume(B) * volume(C))
end

"""
    dual_mgs(B::AbstractBlade, C::AbstractBlade)

Return the dual `B` relative to `C`.

Notes
=====

* `dual(B, C)` is only defined if (1) `B` and `C` are extended from real
  vector spaces of the same dimension and (2) the subspace represented by `B`
  is contained in subspace represented by `C`.

* The volume of `C` is ignored.

Implementation
==============

* The extension of `basis(B)` to `basis(C)` is computed using modified
  Gram-Schmidt orthogonalization.
"""
function dual_mgs(B::Blade, C::Blade)
    # --- Handle edge cases

    # Check that B and C are extended from the real vector spaces of the same
    # dimension
    if dim(B) != dim(C)
        throw(DimensionMismatch("`dim(B)` not equal to `dim(C)`"))
    end

    # Check that B is contained in C
    projection_coefficients = transpose(basis(C)) * basis(B)
    if LinearAlgebra.norm(projection_coefficients)^2 ≉ grade(B)
        throw(DimensionMismatch("`B` not contained in `C`"))
    end

    # Subspaces represented by B and C are the same
    if grade(B) == grade(C)
        return Scalar(volume(B))
    end

    # --- Compute dual using the basis vectors of `C` with the smallest
    #     projections (i.e., largest rejections) onto the basis of `B`.

    permutation = sortperm(sum(abs.(projection_coefficients), dims=2)[:, 1])
    dual_basis = rejection(basis(C)[:, permutation[1:grade(C) - grade(B)]], B)
    Blade(dual_basis, volume=volume(B))
end


"Hermite expansion coefficients for Gaussian product"
function E(a::Float64, b::Float64, Rx::Float64, i::Int, j::Int, t::Int=0)
    if i < 0 || j < 0 || t < 0 || t > (i + j)
        # out of bounds for i, j, or t
        return 0.0
    end

    p = a + b

    if i == j == t == 0 # base case
        return exp(-a*b/p*Rx*Rx)
    end

    if j == 0 # decrement index i
        return (0.5/p)*E(a,b,Rx,i-1,0,t-1) -
               (b/p*Rx)*E(a,b,Rx,i-1,0,t)  +
               (t+1)*E(a,b,Rx,i-1,0,t+1)
    else # decrement index j
        return (0.5/p)*E(a,b,Rx,i,j-1,t-1) +
               (a/p*Rx)*E(a,b,Rx,i,j-1,t)  +
               (t+1)*E(a,b,Rx,i,j-1,t+1)
    end
end

function ∇E(a::Float64, b::Float64, Rx::Float64, i::Int, j::Int, t::Int=0)
    if Rx == 0.0
        return 0.0
    end

    return 2*a*E(a,b,Rx,i+1,j,t) - i*E(a,b,Rx,i-1,j,t)
end

function ∇∇E(a::Float64, b::Float64, Rx::Float64, i::Int, j::Int, t::Int=0)
    return -i*j*E(a,b,Rx,i-1,j-1,t)   +
            2*i*b*E(a,b,Rx,i-1,j+1,t) +
            2*a*j*E(a,b,Rx,i+1,j-1,t) -
            4*a*b*E(a,b,Rx,i+1,j+1,t)
end

function int1e_ovlp(
        a::Float64,
        LA::Vec3{Int},
        b::Float64,
        LB::Vec3{Int},
        RAB::Vec3{Float64},
    )
    p = a + b
    Sx = E(a, b, RAB.x, LA.x, LB.x, 0)
    Sy = E(a, b, RAB.y, LA.y, LB.y, 0)
    Sz = E(a, b, RAB.z, LA.z, LB.z, 0)
    return (π / p)^(1.5) * Sx * Sy * Sz
end

function int1e_kin(
        a::Float64,
        LA::Vec3{Int},
        b::Float64,
        LB::Vec3{Int},
        RAB::Vec3{Float64},
    )
    p = a + b
    Sx = E(a, b, RAB.x, LA.x, LB.x)
    Sy = E(a, b, RAB.y, LA.y, LB.y)
    Sz = E(a, b, RAB.z, LA.z, LB.z)
    ∇∇Sx = ∇∇E(a, b, RAB.x, LA.x, LB.x)
    ∇∇Sy = ∇∇E(a, b, RAB.y, LA.y, LB.y)
    ∇∇Sz = ∇∇E(a, b, RAB.z, LA.z, LB.z)
    return -0.5 * (π / p)^(1.5) *  (∇∇Sx * Sy * Sz + Sx * ∇∇Sy * Sz + Sx * Sy * ∇∇Sz)
end

"Auxiliary Hermite integrals"
function R(t::Int, u::Int, v::Int, n::Int, p::Float64, RPC::Vec3{Float64})
    if t == u == v == 0 # base case
        return (-2 * p)^n * boys(n, p * (RPC[1]*RPC[1] + RPC[2]*RPC[2] + RPC[3]*RPC[3]))
    elseif u == v == 0 # decrement index t
        if t == 1
            return RPC[1] * R(0, 0, 0, n+1, p, RPC)
        end

        return (t-1) * R(t-2, 0, 0, n+1, p, RPC) +
               RPC[1] * R(t-1, 0, 0, n+1, p, RPC)
    elseif v == 0 # decrement index u
        if u == 1
            return RPC[2] * R(t, 0, 0, n+1, p ,RPC)
        end

        return (u-1) * R(t, u-2, 0, n+1, p, RPC) +
               RPC[2] * R(t, u-1, 0, n+1, p, RPC)
    else # decrement index v
        if v == 1
            return RPC[3] * R(t, u, 0, n+1, p, RPC)
        end

        return (v-1) * R(t, u, v-2, n+1, p, RPC) +
               RPC[3] * R(t, u, v-1, n+1, p, RPC)
    end
end

function int1e_rinv(
        a::Float64,
        LA::Vec3{Int},
        b::Float64,
        LB::Vec3{Int},
        RAB::Vec3{Float64},
        RPC::Vec3{Float64},
    )
    p = a + b
    ret = 0.0
    for t = 0:LA.x + LB.x, u = 0:LA.y + LB.y, v = 0:LA.z + LB.z
        ret += E(a, b, RAB.x, LA.x, LB.x, t) *
               E(a, b, RAB.y, LA.y, LB.y, u) *
               E(a, b, RAB.z, LA.z, LB.z, v) *
               R(t, u, v, 0, p, RPC)
    end
    return 2 * π / p * ret
end

function int2e(
        a::Float64,
        LA::Vec3{Int},
        b::Float64,
        LB::Vec3{Int},
        c::Float64,
        LC::Vec3{Int},
        d::Float64,
        LD::Vec3{Int},
        RAB::Vec3{Float64},
        RCD::Vec3{Float64},
        RPQ::Vec3{Float64},
    )
    p = a + b
    q = c + d
    ω = p * q / (p + q)
    ret = 0.0
    for t = 0:LA.x + LB.x, u = 0:LA.y + LB.y, v = 0:LA.z + LB.z
        for τ = 0:LC.x + LD.x, μ = 0:LC.y + LD.y, υ = 0:LC.z + LD.z
            ret += E(a, b, RAB.x, LA.x, LB.x, t) *
                   E(a, b, RAB.y, LA.y, LB.y, u) *
                   E(a, b, RAB.z, LA.z, LB.z, v) *
                   E(c, d, RCD.x, LC.x, LD.x, τ) *
                   E(c, d, RCD.y, LC.y, LD.y, μ) *
                   E(c, d, RCD.z, LC.z, LD.z, υ) *
                   R(t+τ, u+μ, v+υ, 0, ω, RPQ) *
                   (-1)^(τ+μ+υ)
        end
    end
    ret *= 2 * π^2.5 / (p * q * sqrt(p + q))
    return ret
end


function tmp(posterior, t, numsamples)

    μ, Σ = mean(posterior), cov(posterior)

    a_idx, b_idx = [1;2], [3;4]

    μ_a = μ[a_idx]
    μ_b = μ[b_idx]

    Σ_aa = Σ[a_idx, a_idx]

    Σ_ab = Σ[a_idx, b_idx]

    Σ_ba = Σ[b_idx, a_idx]

    Σ_bb = Σ[b_idx, b_idx]

    samples = [rand(posterior) for i in 1:numsamples]

    aux(x) = x[a_idx]*t + x[b_idx]

    # mean(map(aux, samples)), μ_a*t + μ_b

    Σ_a_given_b = Σ_aa - Σ_ab*(Σ_bb\Σ_ba)

    K = t*Σ_ab/Σ_bb + I

    cov(map(aux, samples)), t*Σ_a_given_b*t + K*Σ_bb*K'

end


# ridge regression as example of Bayesian estimation
using Plots, LinearAlgebra
function main()
n = 100
a = 0.95 # controls collinearity: close to 0 indep close to 1 highly collinear
bs = zeros(1000,6)
truebeta = ones(3,1)
for i = 1:1000
	# generate regressors that are collinear
	x1 = rand(n,1)
	x2 = a*x1 + (1-a)*randn(n,1)
	x = [ones(n,1) x1 x2]
	# true params and dep var
	e = randn(n,1)
	y = x*truebeta+e;
	# OLS
	bols = x\y
	# ridge regression
	k = 1.0
	yy = [y; k*zeros(3,1)]
	xx = [x; k*Matrix{Float64}(I, 3, 3)]
	bridge = xx\yy
	# store results
	bs[i,:] = [bols' bridge']

end
# histograms to compare
bs = bs .- [truebeta' truebeta'] # take away true, so centered around 0
p1 = histogram(bs[:,2], title="OLS")
p2 = histogram(bs[:,5],title="Ridge")
plot(p1,p2,layout=(1,2), legend=false)
#savefig("RidgeExample.svg")
gui()
return
end
main()


using BenchmarkTools, Symbolics

using LinearAlgebra

SUITE = BenchmarkGroup()

vars = @variables a,b,c,d,e,f,g,h,i

X = [0 b c;
     d e f;
     g h i]

z = det(X) - (-b * (d*i-f*g) + c * (d*h - e*g))

F = lu(X)
FbyX = F \ X

x = (f + ((((g*(c^2)*(e^2)) / d - e*h*(c^2)) / b + (-c*e*f*g) / d + c*e*i) /
          (i + ((c*e*g) / d - c*h) / b + (-f*g) / d) - c*e) / b +
     ((g*(f^2)) / d + ((-c*e*f*g) / d + c*f*h) / b - f*i) /
     (i + ((c*e*g) / d - c*h) / b + (-f*g) / d)) / d

o = (d + (e*((c*(g + (-d*g) / d)) / (i + (-c*(h + (-e*g) / d)) / b + (-f*g) / d))) / b + (-f*(g + (-d*g) / d)) / (i + (-c*(h + (-e*g) / d)) / b + (-f*g) / d)) / d

@show iszero(x)
@show isone(o)
SUITE["iszero/1"] = @benchmarkable iszero($((b*(h + (-e*g) / d)) / b + (e*g) / d - h))
SUITE["isone/1"] = @benchmarkable FbyX == I

SUITE["iszero/2"] = @benchmarkable iszero(x)
SUITE["isone/2"] = @benchmarkable isone(o)
SUITE["lu"] = @benchmarkable lu(X)

SUITE["_solve"] = @benchmarkable Symbolics._solve(X, X, true)


using JuMP, Ipopt, Plots

function blockMoving(numCollocationPoints)
    model = Model(Ipopt.Optimizer)

    # Timestep
    hk = 30/(numCollocationPoints-1) # for now set as constant, later add variable timestep
    time = 0:hk:30

    # Variables and start values
    @variable(model, x[i=1:numCollocationPoints])
    set_start_value.(x,time)
    @variable(model, v[1:numCollocationPoints], start = 1)
    @variable(model, u[1:numCollocationPoints], start = 0)

    # Boundary constraints
    fix(x[1], 0)
    fix(x[end], 30)
    fix(v[1], 0)
    fix(v[end], 0)

    # System dynamics
    @NLexpression(model, xDot[i=1:numCollocationPoints], v[i])
    @NLexpression(model, vDot[i=1:numCollocationPoints], u[i])

    # Objective function
    @NLexpression(model, objective[i=1:numCollocationPoints], u[i]^2)
    for i in 1:numCollocationPoints - 1
        # Collocation points
        @NLconstraint(model, x[i+1] - x[i] - 0.5 * hk * (xDot[i+1] + xDot[i]) == 0)
        @NLconstraint(model, v[i+1] - v[i] - 0.5 * hk * (vDot[i+1] + vDot[i]) == 0)
    end

    # Objective function interpolation
    @NLexpression(model, objectiveInterp, sum(0.5 * hk * (objective[i] + objective[i+1]) for i in 1:numCollocationPoints - 1))
    @NLobjective(model, Min, objectiveInterp)
    optimize!(model)
    return value.(x), value.(v), value.(u), time
end

x,v,u,times = blockMoving(3000)

# Plotting
plotly()
plot(times, x, xlabel="time (s)", ylabel="x (m)", title="Location plot")
plot(times, v, xlabel="time (s)", ylabel="velocity (m/s)", title="Velocity plot")
plot(times, u, xlabel="time (s)", ylabel="Force (N)", title="Force plot")


export GaussianKernel, IMQKernel, RQKernel

abstract type AbstractKernel end

function kernelsum(k::AbstractKernel, x::AbstractMatrix, y::AbstractMatrix, dist::MetricOrFun)
    sum(k(dist(x,y))) / (size(x,2) * size(y,2))
end

function kernelsum(k::AbstractKernel, x::AbstractMatrix{T}, dist::MetricOrFun) where T
    l = size(x,2)
    (sum(k(dist(x,x))) - l*k(T(0)))/(l^2 - l)
end

kernelsum(k::AbstractKernel, x::AbstractVector, dist::MetricOrFun) = zero(eltype(x))

"""
	GaussianKernel(γ)

	implements the standard Gaussian kernel ``exp(-γ * x)
"""
struct GaussianKernel{T} <: AbstractKernel
	γ::T
end

(m::GaussianKernel)(x::Number) = exp(-m.γ * x)
(m::GaussianKernel)(x::AbstractArray) = exp.(-m.γ .* x)

"""
	ImqKernel(c)

Inverse polynomial kernel ``\\frac{C}{C + x}`` used in Tolstikhin, Ilya, et al.
"Wasserstein Auto-Encoders." arXiv preprint arXiv:1711.01558 (2017)

`c` being equal to 2d σ2z, which is expected squared distance between two
samples drawn from p(x).
"""
struct IMQKernel{T} <: AbstractKernel
	c::T
end

(m::IMQKernel)(x::Number) = m.c/ (m.c + x)
(m::IMQKernel)(x::AbstractArray) = m.c ./ (m.c .+ x)

"""
	RQ kernel ``\\frac{C}{C + \\|X - Y\\|}`` used
	in Tolstikhin, Ilya, et al. "Wasserstein Auto-Encoders." arXiv preprint arXiv:1711.01558 (2017)

	`c` being equal to 2d σ2z, which is expected squared distance between two samples drawn from p(x).

"""
struct RQKernel{T} <: AbstractKernel
	α::T
end

(m::RQKernel)(x::Number) = (1 + (x + eps(x))/2m.α)^-m.α
(m::RQKernel)(x::AbstractArray) = (1 .+(x .+ eps(eltype(x))) ./ (2m.α) ).^-m.α


using LinearAlgebra
using Distances

function kernel_BTG(r, theta)
    @assert length(theta) == 2
    return theta[1]^(r^(theta[2]))
end

# function kernel_gauss(d, x, y, θ)
#     @assert d == size(x, 1) "input vector x should in dimension d"
#     @assert d == size(y, 1) "input vector y should in dimension d"
#     @assert d == size(θ, 1) "length scale vector should in dimension d"
#     return exp(-dot(θ, (x-y).^2))
# end

function KernelMat(x0, x, kernel, param)
    n0 = size(x0, 1)
    n  = size(x, 1)
    dim = size(x, 2)
    Phi = zeros(n0, n)
    for i in 1:n0, j in 1:n
        if dim == 1
            Phi[i,j] = kernel(norm(x0[i] - x[j]), param)
        else
            Phi[i,j] = kernel(norm(x0[i,:] - x[j,:]), param)
        end
    end
    return Phi
end


struct PDBijector <: Bijector{2} end

# This function has custom adjoints defined for Tracker, Zygote and ReverseDiff.
# I couldn't find a mutation-free implementation that maintains TrackedArrays in Tracker
# and ReverseDiff, hence the need for custom adjoints.
function replace_diag(f, X)
    g(i, j) = ifelse(i == j, f(X[i, i]), X[i, j])
    return g.(1:size(X, 1), (1:size(X, 2))')
end
function (b::PDBijector)(X::AbstractMatrix{<:Real})
    Y = cholesky(X; check = true).L
    return replace_diag(log, Y)
end
function (ib::Inverse{<:PDBijector})(Y::AbstractMatrix{<:Real})
    X = replace_diag(exp, Y)
    return LowerTriangular(X) * LowerTriangular(X)'
end