    export compute

    """
    `Basics.compute("angular coefficients: e-e, Ratip2013", csfa::CsfR, csfb::CsfR)`
        ... to compute the angular coefficients in the decomposition of a (reduced) many-electron matrix element with a general
            rank-0 electron-electron interaction operator ``⟨csf_a ||V(e-e)|| csfb⟩ = \\sum_t  T(a_t, b_t, c_t, d_t) * R^k (a_t, b_t, c_t, d_t)``
            by a call to the Fortran procedure `anco_calculate_csf_pair` of the RATIP program; a Tuple{Array{AngularTcoeff,1},Array{AngularVcoeff,1}}`
            is returned.
    """
    function Basics.compute(sa::String, csfa::CsfR, csfb::CsfR)
        if sa == "angular coefficients: e-e, Ratip2013"
            if csfa == csfb
                JAC.AngularCoefficientsRatip2013.load_csl(csfa)
                t_coeffs, v_coeffs = JAC.AngularCoefficientsRatip2013.angular_coefficients_pair(1,1)
            else
                JAC.AngularCoefficientsRatip2013.load_csl(csfa, csfb)
                t_coeffs, v_coeffs = JAC.AngularCoefficientsRatip2013.angular_coefficients_pair(1,2)
            end
        else
            error("Unsupported keystring = ", sa)
        end
        return t_coeffs, v_coeffs
    end

    """
    `Basics.compute("angular coefficients: 1-p, Ratip2013", rank::Int64, csfa::CsfR, csfb::CsfR)`
        ... to compute the the angular coefficients in the decomposition of a (reduced) many-electron matrix element with a general
            single-particle operator of the given rank ``⟨csf_a ||O^rank|| csfb⟩ = \\sum_t  T(a_t, b_t) * R (a_t, b_t)``  by a call
            to the Fortran procedure `anco_calculate_csf_pair_1p` of the RATIP program; an `Array{AngularTcoeff,1}` is returned.
    """
    function Basics.compute(sa::String, rank, csfa::CsfR, csfb::CsfR)
        if      sa == "angular coefficients: 1-p, Ratip2013"
            if csfa == csfb
                JAC.AngularCoefficientsRatip2013.load_csl(csfa)
                t_coeffs = JAC.AngularCoefficientsRatip2013.angular_coefficients_pair_1p(rank,1,1)
            else
                JAC.AngularCoefficientsRatip2013.load_csl(csfa, csfb)
                t_coeffs = JAC.AngularCoefficientsRatip2013.angular_coefficients_pair_1p(rank,1,2)
            end
        else    error("Unsupported keystring = ", sa)
        end
        return t_coeffs
    end

    """
    `Basics.compute("angular coefficients: 1-p, Grasp92", parity, rank::Integer, csfa::CsfR, csfb::CsfR)`
        ... to compute the the angular coefficients in the decomposition of a (reduced) many-electron matrix element with a general
            single-particle operator of the given parity and rank by a call to the Fortran procedure `mct_generate_coefficients`
            of the RATIP program; an `Array{AngularTcoeff,1}` is returned.
    """
    function Basics.compute(sa::String, parity, rank::Integer, csfa::CsfR, csfb::CsfR)
        if      sa == "angular coefficients: 1-p, Grasp92"
            if csfa == csfb
                subshells  = JAC.AngularCoefficientsRatip2013.load_csl(csfa)
                mct_coeffs = JAC.AngularCoefficientsRatip2013.mct_generate_coefficients(1, 1, 1, 1, Int32(parity), rank)
            else
                # Add 'zeros' to the fields if the length of occupation does not agree
                if       ( nz = length(csfa.occupation) - length(csfb.occupation) ) <  0   csfa = Basics.addZerosToCsfR( -nz, csfa)
                elseif   ( nz = length(csfa.occupation) - length(csfb.occupation) ) >  0   csfb = Basics.addZerosToCsfR(  nz, csfb)    end
                subshells  = JAC.AngularCoefficientsRatip2013.load_csl(csfa, csfb)
                mct_coeffs = JAC.AngularCoefficientsRatip2013.mct_generate_coefficients(1, 1, 2, 2, Int32(parity), rank)
            end
        else    error("Unsupported keystring = ", sa)
        end
        return map(t -> JAC.AngularCoefficientsRatip2013.AngularTcoeff(t, subshells), mct_coeffs) # Convert Fmctcoefficient to AngularTcoeff
    end

    """
    `Basics.compute("matrix: CI, J^P symmetry", JP::LevelSymmetry, basis::Basis, nuclearModel::Nuclear.Model, grid::Radial.Grid,
                                               settings::AsfSettings; printout::Bool=true)`
        ... to compute the CI matrix for a given J^P symmetry block of basis and by making use of the nuclear model and the grid;
            a matrix::Array{Float64,2} is returned.
    """
    function Basics.compute(sa::String, JP::LevelSymmetry, basis::Basis, nuclearModel::Nuclear.Model, grid::Radial.Grid,
                            settings::AsfSettings; printout::Bool=true)
        !(sa == "matrix: CI, J^P symmetry")   &&   error("Not supported keystring")

        # Determine the dimension of the CI matrix and the indices of the CSF with J^P symmetry in the basis
        idx_csf = Int64[]
        for  idx = 1:length(basis.csfs)
            if  basis.csfs[idx].J == JP.J   &&   basis.csfs[idx].parity == JP.parity    push!(idx_csf, idx)    end
        end
        n = length(idx_csf)
        if printout    print("Compute CI matrix of dimension $n x $n for the symmetry $(string(JP.J))^$(string(JP.parity)) ...")    end

        # Generate an effective nuclear charge Z(r) on the given grid
        potential = Nuclear.nuclearPotential(nuclearModel, grid)
        if  settings.qedModel in [QedPetersburg(), QedSydney()]
            meanPot = potential
            ## meanPot = compute("radial potential: Dirac-Fock-Slater", grid, basis)
            ## meanPot = Basics.add(potential, meanPot)
        end

        matrix = zeros(Float64, n, n)
        keep = true
        InteractionStrength.XL_Coulomb_reset_storage(keep, printout=false)
        InteractionStrength.XL_Breit_reset_storage(keep)
        print(">> performCI() ... ")
        @time   for  r = 1:n
            for  s = 1:n
                wa = compute("angular coefficients: e-e, Ratip2013", basis.csfs[idx_csf[r]], basis.csfs[idx_csf[s]])
                me = 0.
                for  coeff in wa[1]
                    jj = Basics.subshell_2j(basis.orbitals[coeff.a].subshell)
                    me = me + coeff.T * sqrt( jj + 1) * RadialIntegrals.GrantIab(basis.orbitals[coeff.a], basis.orbitals[coeff.b], grid, potential)
                    if  settings.qedModel != NoneQed()
                        me = me + InteractionStrengthQED.qedLocal(basis.orbitals[coeff.a], basis.orbitals[coeff.b], nuclearModel,
                                                                  settings.qedModel, meanPot, grid)
                    end
                end

                for  coeff in wa[2]
                    if  settings.eeInteractionCI in [CoulombInteraction(), CoulombBreit()]
                        me = me + coeff.V * InteractionStrength.XL_Coulomb(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid, keep=keep)
                    elseif  false
                        xl1 = InteractionStrength.XL_Coulomb(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid, keep=false)
                        xl2 = InteractionStrength.XL_Coulomb_WO(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)
                        if abs(xl1 - xl2) > 1.0e-12  println("XL_Coulomb differ: $xl1   $xl2")      end
                    elseif  false
                        me = me + coeff.V * InteractionStrength.XL_Coulomb_WO(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)
                    end

                    if  settings.eeInteractionCI in [BreitInteraction(), CoulombBreit()]
                        me = me + coeff.V * InteractionStrength.XL_Breit(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid, keep=keep)
                    elseif  false
                        xl1 = InteractionStrength.XL_Breit(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid, keep=false)
                        xl2 = InteractionStrength.XL_Breit_WO(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)
                        if abs(xl1 - xl2) > 1.0e-12  println("XL_Breit differ: $xl1   $xl2")      end
                    elseif  false
                        me = me + coeff.V * InteractionStrength.XL_Breit_WO(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)
                    end
                end
                matrix[r,s] = me
            end
        end
        if printout    println("   ... done.")    end

        return( matrix )
    end

    """
    `Basics.compute("matrix: CI for plasma, J^P symmetry", JP::LevelSymmetry, basis::Basis, nuclearModel::Nuclear.Model, grid::Radial.Grid,
                                                          settings::AsfSettings, plasmaSettings::PlasmaShift.Settings; printout::Bool=true)`
        ... to compute the CI matrix for a given J^P symmetry block of basis and by making use of the nuclear model and the grid;
            a matrix::Array{Float64,2} is returned.
    """
    function Basics.compute(sa::String, JP::LevelSymmetry, basis::Basis, nuclearModel::Nuclear.Model, grid::Radial.Grid,
                                settings::AsfSettings, plasmaSettings::PlasmaShift.Settings; printout::Bool=true)
        !(sa == "matrix: CI for plasma, J^P symmetry")   &&   error("Not supported keystring")

        # Determine the dimension of the CI matrix and the indices of the CSF with J^P symmetry in the basis
        idx_csf = Int64[]
        for  idx = 1:length(basis.csfs)
            if  basis.csfs[idx].J == JP.J   &&   basis.csfs[idx].parity == JP.parity    push!(idx_csf, idx)    end
        end
        n = length(idx_csf)

        if  settings.eeInteractionCI in [BreitInteraction(), CoulombBreit()]
                                             error("No Breit interaction supported for plasma computations; use breitCI=false  in the asfSettings.")    end
        if  settings.qedModel != NoneQed()   error("No QED estimates supported for plasma computations; use qedModel=NoneQed()  in the asfSettings.")   end

        # Now distinguis the CI matrix for different plasma models
        if  plasmaSettings.plasmaModel == PlasmaShift.DebyeHueckel()
            if printout    print("Compute DebyeHueckel-CI matrix of dimension $n x $n for the symmetry $(string(JP.J))^$(string(JP.parity)) ...")    end

            # Generate an effective nuclear charge Z(r) for a screened Debye-Hueckel potential on the given grid
            potential = Nuclear.nuclearPotentialDH(nuclearModel, grid, plasmaSettings.lambdaDebye)

            matrix = zeros(Float64, n, n)
            for  r = 1:n
                for  s = 1:n
                    wa = compute("angular coefficients: e-e, Ratip2013", basis.csfs[idx_csf[r]], basis.csfs[idx_csf[s]])
                    me = 0.
                    for  coeff in wa[1]
                        jj = Basics.subshell_2j(basis.orbitals[coeff.a].subshell)
                        me = me + coeff.T * sqrt( jj + 1) * RadialIntegrals.GrantIab(basis.orbitals[coeff.a], basis.orbitals[coeff.b], grid, potential)
                    end

                    for  coeff in wa[2]
                        if  settings.eeInteractionCI in [CoulombInteraction(), CoulombBreit()]
                            me = me + coeff.V * InteractionStrength.XL_Coulomb_DH(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid,
                                                                                plasmaSettings.lambdaDebye)   end
                    end
                    matrix[r,s] = me
                end
            end
        else
            error("Unsupported plasma model = $(plasmaSettings.plasmaModel)")
        end

        if printout    println("   ... done.")    end

        return( matrix )
    end

    """
    `Basics.compute("radial orbital: NR, Bunge (1993)", subshell::Subshell, Z::Int64)`
        ... to compute a radial orbital::Orbital for the given subshell and nuclear charge by using the Roothan-Hartree-Fock data by
            Bunge et al., Atomic Data and Nuclear Data Tables 53 (1993) 113, as obtained for a non-relativistic RHF computation of the
            neutral atom. These functions are used a large component, and the small component is obtained from the kinetic balance
            condition. Radial orbitals can be obtained for the ground-state configuration for all elements with 2 <= Z <= 54.
    `Basics.compute("radial orbital: NR, McLean (1981)", subshell::Subshell, Z::Int64)`
        ... to compute a radial orbital::Orbital for the given subshell and nuclear charge by using the Roothan-Hartree-Fock data by
            McLean and McLean, Atomic Data and Nuclear Data Tables 26 (1981) 197., as obtained for a non-relativistic RHF computation of
            the neutral atom. These functions are used a large component, and the small component is obtained from the kinetic balance
            condition. Radial orbitals can be obtained for the ground-state configuration for all elements with 55 <= Z <= 92.
    """
    function Basics.compute(sa::String, subshell::Subshell, Z::Int64)
        if      sa == "radial orbital: NR, Bunge (1993)"          wa = Radial.OrbitalBunge1993(subshell,Z)
        elseif  sa == "radial orbital: NR, McLean (1981)"         wa = Radial.OrbitalMcLean1981(subshell,Z)
        elseif  sa == "radial orbital: hydrogenic"                wa = Radial.OrbitalHydrogenic(subshell,Z)
        elseif  sa == "radial orbital: Thomas-Fermi"              wa = Radial.OrbitalThomasFermi(subshell,Z)
        else    error("Unsupported keystring = $sa ")
        end

        return( wa )
    end

    """
    `Basics.compute("radial potential: core-Hartree", grid::Radial.Grid, level::Level)`
        ... to compute a (radial) core-Hartree potential from the given list of orbitals; cf. Basics.computePotentialCoreHartree.
            A potential::RadialPotential is returned.

    `Basics.compute("radial potential: Kohn-Sham", grid::Radial.Grid, level::Level)`
        ... to compute a (radial) Kohn-Sham potential from the given list of orbitals; cf. Basics.computePotentialKohnSham.
            A potential::RadialPotential is returned.

    `Basics.compute("radial potential: Dirac-Fock-Slater", grid::Radial.Grid, level::Level)`
        ... to compute a (radial) Dirac-Fock-Slater potential from the given list of orbitals; this potential is rather simple but
            includes some undesired self-interaction and exhibits an asymptotic behaviour. Cf. Basics.computePotentialDFS.
            A potential::RadialPotential is returned.

    `Basics.compute("radial potential: Dirac-Fock-Slater", grid::Radial.Grid, basis::Basis)`
        ... to compute a (radial) Dirac-Fock-Slater potential but for mean occupation of the given basis. Cf. Basics.computePotentialDFS.
            A potential::RadialPotential is returned.

    `Basics.compute("radial potential: extended Hartree", grid::Radial.Grid, level::Level)`
        ... to compute a (radial) extended Hartree potential from the given list of orbitals; it is a local potential that is based on
            direct and exchange coefficients as obtained from the configuration-averaged energy. Cf. Basics.computePotentialExtendedHartree.
            A potential::RadialPotential is returned.
    """
    function Basics.compute(sa::String, grid::Radial.Grid, level::Level)
        if      sa == "radial potential: core-Hartree"               wa = Basics.computePotentialCoreHartree(grid, level)
        elseif  sa == "radial potential: Hartree"                    wa = Basics.computePotentialHartree(grid, level)
        elseif  sa == "radial potential: Hartree-Slater"             wa = Basics.computePotentialHartreeSlater(grid, level)
        elseif  sa == "radial potential: Kohn-Sham"                  wa = Basics.computePotentialKohnSham(grid, level)
        elseif  sa == "radial potential: Dirac-Fock-Slater"          wa = Basics.computePotentialDFS(grid, level)
        elseif  sa == "radial potential: extended-Hartree"           wa = Basics.computePotentialExtendedHartree(grid, level)
        else    error("Unsupported keystring = $sa ")
        end

        return( wa )
    end

    function Basics.compute(sa::String, grid::Radial.Grid, basis::Basis)
        if      sa == "radial potential: Dirac-Fock-Slater"          wa = Basics.computePotentialDFS(grid, basis)
        else    error("Unsupported keystring = $sa ")
        end

        return( wa )
    end

    """
    `Basics.computeDiracEnergy(sh::Subshell, Z::Float64)`
        ... computes the Dirac energy for the hydrogenic subshell sh and for a point-like nucleus with nuclear charge Z;
            a energy::Float64 in atomic units and without the rest energy of the electron is returned. That is the binding
            energy of a 1s_1/2 electron for Z=1 is -0.50000665.
    """
    function Basics.computeDiracEnergy(sh::Subshell, Z::Float64)
        if  Z <= 0.1    error("Requires nuclear charge Z >= 0.1")    end
        # Compute the energy from the Dirac formula
        jPlusHalf = (Basics.subshell_2j(sh) + 1) / 2;   nr = sh.n - jPlusHalf;    alpha = Defaults.getDefaults("alpha")
        wa = sqrt(jPlusHalf^2 - Z^2 * alpha^2 )
        wa = sqrt(1.0 + Z^2 * alpha^2 / (nr + wa)^2)
        wa = Defaults.getDefaults("speed of light: c")^2 * (1/wa - 1.0)
        return( wa )
    end

    """
    `Basics.computeMeanSubshellOccupation(sh::Subshell, levels::Array{Level,1})`
        ... computes the mean subshell occupation for the subshell sh and for the given levels; a q::Float64 is returned.
    """
    function Basics.computeMeanSubshellOccupation(sh::Subshell, levels::Array{Level,1})
        q = 0.
        if  length(levels) < 1   error("stop a")    end
        for  level in levels
            subshells = level.basis.subshells;    nsh = 0
            for  i = 1:length(level.basis.subshells)    if  sh == subshells[i]   nsh = i;   break   end    end
            if   nsh == 0   error("Subshell not found in CSF basis.")   end
            #
            for  i = 1:length(level.basis.csfs)   q = q + abs(level.mc[i])^2 * level.basis.csfs[i].occupation[nsh]    end
        end
        return( q/length(levels) )
    end

    """
    `Basics.computeMeanSubshellOccupation(sh::Subshell, basis::Basis)`
        ... computes the mean subshell occupation for the subshell sh and for the given CSF in the basis; a q::Float64 is returned.
    """
    function Basics.computeMeanSubshellOccupation(sh::Subshell, basis::Basis)
        q = 0.
        for  csf in basis.csfs
            subshells = basis.subshells;    nsh = 0
            for  i = 1:length(basis.subshells)    if  sh == subshells[i]   nsh = i;   break   end    end
            if   nsh == 0   error("Subshell not found in CSF basis.")   end
            #
            for  i = 1:length(basis.csfs)   q = q + basis.csfs[i].occupation[nsh]    end
        end
        return( q/length(basis.csfs) )
    end

    """
    `Basics.computeMultipletForGreenApproach(approach::AtomicState.SingleCSFwithoutCI, basis::Basis, nModel::Nuclear.Model, grid::Radial.Grid,
                                             asfSettings::AsfSettings, greenSettings::GreenSettings; printout::Bool=false)`
        ... computes the (Green channel) multiplet from the given basis with the SingleCSFwithoutCI approach.
    """
    function Basics.computeMultipletForGreenApproach(approach::AtomicState.SingleCSFwithoutCI, basis::Basis, nModel::Nuclear.Model,
                                                     grid::Radial.Grid, asfSettings::AsfSettings, greenSettings::GreenSettings; printout::Bool=false)
        print("Compute a Green function multiplet in $approach approach ... ")
        # In the SingleCSFwithoutCI, only the diagonal ME are included into the Hamiltonian matrix
        # (1) Check that all CSF have same (level) symmetry; issue an error if not
        sym = LevelSymmetry(0, Basics.plus)
        for  (r, csf)  in enumerate(basis.csfs)
            if     r == 1   sym  = LevelSymmetry( csf.J, csf.parity )
            elseif          sym != LevelSymmetry( csf.J, csf.parity )  error("stop a")
            end
        end

        # (2) Compute Hamiltonian matrix with only diagonal ME with given settings; issue a message which flags are considered.
        if  asfSettings.qedModel != NoneQed()   println("   ++ No QED terms included for this Green function approach.")               end
        if  asfSettings.jjLS.makeIt             println("   ++ No jj-LS transformation included for this Green function approach.")    end
        potential = Nuclear.nuclearPotential(nModel, grid)
        ncsf      = length(basis.csfs);    matrix = zeros(Float64, ncsf, ncsf)
        for  (r, csf)  in enumerate(basis.csfs)
            wa = compute("angular coefficients: e-e, Ratip2013", basis.csfs[r], basis.csfs[r])
            me = 0.
            for  coeff in wa[1]
                jj = Basics.subshell_2j(basis.orbitals[coeff.a].subshell)
                me = me + coeff.T * sqrt( jj + 1) * RadialIntegrals.GrantIab(basis.orbitals[coeff.a], basis.orbitals[coeff.b], grid, potential)
            end

            for  coeff in wa[2]
                if  asfSettings.coulombCI
                    me = me + coeff.V * InteractionStrength.XL_Coulomb(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)   end
                if  asfSettings.breitCI
                    me = me + coeff.V * InteractionStrength.XL_Breit(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                               basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)     end
            end
            matrix[r,r] = me
        end

        # (3) Diagonalize matrix with Julia;   assign a multiplet
        eigen  = Basics.diagonalize("matrix: LinearAlgebra", matrix)
        levels = Level[]
        for  ev = 1:length(eigen.values)
            level = Level( sym.J, AngularM64(sym.J.num//sym.J.den), sym.parity, 0, eigen.values[ev], 0., true, basis, eigen.vectors[ev] )
            push!( levels, level)
        end
        println("done with $(length(levels)) levels")

        multiplet = Multiplet("SingleCSFwithoutCI multiplet for $sym", levels)
        return( multiplet )
    end

    """
    `Basics.computeMultipletForGreenApproach(approach::AtomicState.CoreSpaceCI, basis::Basis, nModel::Nuclear.Model, grid::Radial.Grid,
                                             asfSettings::AsfSettings, greenSettings::GreenSettings; printout::Bool=false)`
        ... computes the (Green channel) multiplet from the given basis with the CoreSpaceCI approach in which the electron-electron
            interaction is taken into account only between the bound-state orbitals.
    """
    function Basics.computeMultipletForGreenApproach(approach::AtomicState.CoreSpaceCI, basis::Basis, nModel::Nuclear.Model,
                                                     grid::Radial.Grid, asfSettings::AsfSettings, greenSettings::GreenSettings; printout::Bool=false)
        print("Compute a Green function multiplet in $approach approach ... ")
        # In the SingleCSFwithoutCI, only the diagonal ME are included into the Hamiltonian matrix
        # (1) Check that all CSF have same (level) symmetry; issue an error if not
        sym = LevelSymmetry(0, Basics.plus)
        for  (r, csf)  in enumerate(basis.csfs)
            if     r == 1   sym  = LevelSymmetry( csf.J, csf.parity )
            elseif          sym != LevelSymmetry( csf.J, csf.parity )  error("stop a")
            end
        end

        # (2) Compute Hamiltonian matrix with only diagonal ME with given settings; issue a message which flags are considered.
        if  asfSettings.qedModel != NoneQed()   println("   ++ No QED terms included for this Green function approach.")               end
        if  asfSettings.jjLS.makeIt             println("   ++ No jj-LS transformation included for this Green function approach.")    end
        potential = Nuclear.nuclearPotential(nModel, grid)
        ncsf      = length(basis.csfs);    matrix = zeros(Float64, ncsf, ncsf)
        for  (r, rcsf)  in enumerate(basis.csfs)
            for  (s, scsf)  in enumerate(basis.csfs)
                wa = compute("angular coefficients: e-e, Ratip2013", basis.csfs[r], basis.csfs[s])
                me = 0.
                for  coeff in wa[1]
                    jj = Basics.subshell_2j(basis.orbitals[coeff.a].subshell)
                    if  basis.orbitals[coeff.a].isBound   &&   basis.orbitals[coeff.b].isBound
                        me = me + coeff.T * sqrt( jj + 1) * RadialIntegrals.GrantIab(basis.orbitals[coeff.a], basis.orbitals[coeff.b], grid, potential)
                    end
                end

                for  coeff in wa[2]
                    if  basis.orbitals[coeff.a].isBound   &&   basis.orbitals[coeff.b].isBound   &&
                        basis.orbitals[coeff.c].isBound   &&   basis.orbitals[coeff.d].isBound
                        if  asfSettings.coulombCI
                            me = me + coeff.V * InteractionStrength.XL_Coulomb(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)   end
                        if  asfSettings.breitCI
                            me = me + coeff.V * InteractionStrength.XL_Breit(coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)     end
                    end
                end
                matrix[r,s] = me
            end
        end

        # (3) Diagonalize matrix with Julia;   assign a multiplet
        eigen  = Basics.diagonalize("matrix: LinearAlgebra", matrix)
        levels = Level[]
        for  ev = 1:length(eigen.values)
            level = Level( sym.J, AngularM64(sym.J.num//sym.J.den), sym.parity, 0, eigen.values[ev], 0., true, basis, eigen.vectors[ev] )
            push!( levels, level)
        end
        println("done with $(length(levels)) levels")

        multiplet = Multiplet("CoreSpaceCI multiplet for $sym", levels)
        return( multiplet )
    end

    """
    `Basics.computeMultipletForGreenApproach(approach::AtomicState.DampedSpaceCI, basis::Basis, nModel::Nuclear.Model, grid::Radial.Grid,
                                             asfSettings::AsfSettings, greenSettings::GreenSettings; printout::Bool=false)`
        ... computes the (Green channel) multiplet from the given basis with the DampedSpaceCI approach in which the electron-electron
            interaction strength are all damped by the factor exp(- dampingTau * r).
    """
    function Basics.computeMultipletForGreenApproach(approach::AtomicState.DampedSpaceCI, basis::Basis, nModel::Nuclear.Model,
                                                     grid::Radial.Grid, asfSettings::AsfSettings, greenSettings::GreenSettings;
                                                     printout::Bool=false)
        print("Compute a Green function multiplet in $approach approach ... ")
        # In the SingleCSFwithoutCI, only the diagonal ME are included into the Hamiltonian matrix
        # (1) Check that all CSF have same (level) symmetry; issue an error if not
        sym = LevelSymmetry(0, Basics.plus)
        for  (r, csf)  in enumerate(basis.csfs)
            if     r == 1   sym  = LevelSymmetry( csf.J, csf.parity )
            elseif          sym != LevelSymmetry( csf.J, csf.parity )  error("stop a")
            end
        end

        # (2) Compute Hamiltonian matrix with only diagonal ME with given settings; issue a message which flags are considered.
        if  asfSettings.qedModel != NoneQed()   println("   ++ No QED terms included for this Green function approach.")               end
        if  asfSettings.jjLS.makeIt             println("   ++ No jj-LS transformation included for this Green function approach.")    end
        potential = Nuclear.nuclearPotential(nModel, grid)
        ncsf      = length(basis.csfs);    matrix = zeros(Float64, ncsf, ncsf);    tau = greenSettings.dampingTau
        for  (r, rcsf)  in enumerate(basis.csfs)
            for  (s, scsf)  in enumerate(basis.csfs)
                wa = compute("angular coefficients: e-e, Ratip2013", basis.csfs[r], basis.csfs[s])
                me = 0.
                for  coeff in wa[1]
                    jj = Basics.subshell_2j(basis.orbitals[coeff.a].subshell)
                    me = me + coeff.T * sqrt( jj + 1) * RadialIntegrals.GrantIabDamped(tau, basis.orbitals[coeff.a], basis.orbitals[coeff.b], grid, potential)
                end

                for  coeff in wa[2]
                    if  asfSettings.eeInteraction in [CoulombInteraction(), CoulombBreit()]
                        me = me + coeff.V * InteractionStrength.XL_CoulombDamped(tau, coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)   end
                    if  asfSettings.eeInteraction in [BreitInteraction(), CoulombBreit()]
                        me = me + coeff.V * InteractionStrength.XL_BreitDamped(tau, coeff.nu, basis.orbitals[coeff.a], basis.orbitals[coeff.b],
                                                                                basis.orbitals[coeff.c], basis.orbitals[coeff.d], grid)     end
                end
                matrix[r,s] = me
            end
        end

        # (3) Diagonalize matrix with Julia;   assign a multiplet
        eigen  = Basics.diagonalize("matrix: LinearAlgebra", matrix)
        levels = Level[]
        for  ev = 1:length(eigen.values)
            level = Level( sym.J, AngularM64(sym.J.num//sym.J.den), sym.parity, 0, eigen.values[ev], 0., true, basis, eigen.vectors[ev] )
            push!( levels, level)
        end
        println("done with $(length(levels)) levels")

        multiplet = Multiplet("DampedSpaceCI multiplet for $sym", levels)
        return( multiplet )
    end

    """
    `Basics.computePotentialCoreHartree(grid::Radial.Grid, level::Level)`
        ... to compute a (radial) core-Hartree potential for the given level; a potential::RadialPotential is returned.
            The core-Hartree potential is defined by

                V_CH(r) = int_0^infty dr'  rho_c(r') / r_>

            with r_> = max(r,r')  and  rho_c(r) =  sum_a (Pa^2(r) + Qa^2(r)) the charge density of the core-electrons.
            An Radial.Potential with -r * V_CH(r)  is returned to be consistent with an effective charge Z(r).
    """
    function Basics.computePotentialCoreHartree(grid::Radial.Grid, level::Level)
        basis = level.basis;    npoints = grid.NoPoints
        println("coreSubshells = $(basis.coreSubshells);    subshells = $(basis.subshells)")
        rhoc = zeros( npoints );    wx = zeros( npoints );    wb = zeros( npoints )
        # Compute the charge density of the core orbitals for the given level
        for  sh in basis.coreSubshells
        ## for  sh in basis.subshells
            orb  = basis.orbitals[sh]
            occ  = Basics.computeMeanSubshellOccupation(sh, [level])    ## (Basics.subshell_2j(sh) + 1.0)  for closed-shells
            nrho = length(orb.P)
            for    i = 1:nrho   rhoc[i] = rhoc[i] + occ * (orb.P[i]^2 + orb.Q[i]^2)    end
        end
        # Define the integrant and take the integrals
        for i = 1:npoints
            for j = 1:npoints    rg = max( grid.r[i],  grid.r[j]);    wx[j] = rhoc[j]/rg   end
            wb[i] = RadialIntegrals.V0(wx, npoints, grid::Radial.Grid)
        end
        # Define the potential with regard to Z(r)
        for i = 1:npoints    wb[i] = - wb[i] * grid.r[i]   end
        #
        wc = Radial.Potential("CoreHartree", wb, grid)
        return( wc )
    end

    """
    `Basics.computePotentialHartree(grid::Radial.Grid, level::Level)`
        ... to compute a (radial) Hartree potential for the given level; a potential::RadialPotential is returned.
            Here, the Hartree potential is simply defined by

                V_H(r) = - Sum_a  q_a^bar  Y0_aa(r)

            An Radial.Potential with V_H(r)  is returned that is consistent with an effective charge Z(r).
    """
    function Basics.computePotentialHartree(grid::Radial.Grid, level::Level)
        basis = level.basis;    npoints = grid.NoPoints;    wx = zeros( npoints )
        # Sum_a ...
        for  a in basis.subshells
            occa = Basics.computeMeanSubshellOccupation(a, [level])
            orba = basis.orbitals[a];   nrho = length(orba.P);      rhoaa  = zeros(nrho)
            for  i = 1:nrho    rhoaa[i] = orba.P[i]^2 + orba.Q[i]^2    end
            for  i = 1:nrho    wx[i]    = wx[i] - occa * RadialIntegrals.Yk_ab(0, grid.r[i], rhoaa, nrho, grid)   end
            Yk = RadialIntegrals.Yk_ab(0, grid.r[nrho], rhoaa, nrho, grid)
            for  i = nrho+1:npoints       wx[i] = wx[i] - occa * Yk    end
        end

        wc = Radial.Potential("Hartree", wx, grid)
        return( wc )
    end

    """
    `Basics.computePotentialHartreeSlater(grid::Radial.Grid, level::Level)`
        ... to compute a (radial) Hartree-Slater potential for the given level; a potential::RadialPotential is returned.
            Here, the Hartree-Slater potential is defined by

                                                        3  [     3          ]^1/3      r
                V_HS(r) = = - Sum_a  q_a^bar  Y0_aa(r)  +  -  [ ---------  rho ]       *  -
                                                        2  [ 4pi^2 r^2      ]          2

            with rho(r) = Sum_a  q_a^bar * (Pa^2(r) + Qa^2(r))   ... charge density of all electrons.
            An Radial.Potential with V_HS(r)  is returned that is consistent with an effective charge Z(r).
    """
    function Basics.computePotentialHartreeSlater(grid::Radial.Grid, level::Level)
        basis = level.basis;    npoints = grid.NoPoints;    wx = zeros( npoints );    rho = zeros( npoints )
        # Sum_a ...
        for  a in basis.subshells
            occa = Basics.computeMeanSubshellOccupation(a, [level])
            orba = basis.orbitals[a];   nrho = length(orba.P);      rhoaa  = zeros(nrho)
            for  i = 1:nrho    rhoaa[i] = orba.P[i]^2 + orba.Q[i]^2    end
            for  i = 1:nrho    wx[i]    = wx[i]  - occa * RadialIntegrals.Yk_ab(0, grid.r[i], rhoaa, nrho, grid)   end
            for  i = 1:nrho    rho[i]   = rho[i] + occa * rhoaa[i]     end
            Yk = RadialIntegrals.Yk_ab(0, grid.r[nrho], rhoaa, nrho, grid)
            for  i = nrho+1:npoints       wx[i] = wx[i] - occa * Yk    end
        end
        ## for  i = 2:npoints     rho[i]   = rho[i] / (4pi * grid.r[i])   end;       rho[1] = 0.
        for  i = 2:npoints     wx[i]    = wx[i] + (3/2) * (3/(4pi^2 * grid.r[i]^2) * rho[i])^(1/3) * grid.r[i] / 2   end
        #
        wc = Radial.Potential("Hartree-Slater", wx, grid)
        return( wc )
    end

    """
    `Basics.computePotentialKohnSham(grid::Radial.Grid, level::Level)`
        ... to compute a (radial) Kohn-Sham potential for the given level; a potential::RadialPotential is returned.
            The Kohn-Sham potential is defined by

                                                                2     [   81                   ]^(1/3)
                V_KS(r) =  int_0^infty dr'  rho_t(r') / r_>  --  ---  [--------   r * rho_t(r) ]
                                                                3 r   [ 32 pi^2                ]

            with r_> = max(r,r')  and  rho_t(r) = sum_a (Pa^2(r) + Qa^2(r))   ... charge density of all electrons.
            An Radial.Potential with -r * V_KS(r)  is returned to be consistent with an effective charge Z(r).
    """
    function Basics.computePotentialKohnSham(grid::Radial.Grid, level::Level)
        basis = level.basis;       npoints = grid.NoPoints
        rhot = zeros( npoints );   wb = zeros( npoints );   wx = zeros( npoints )
        # Compute the charge density of the core orbitals for the given level
        for  sh in basis.subshells
            orb  = basis.orbitals[sh]
            occ  = Basics.computeMeanSubshellOccupation(sh, [level])
            nrho = length(orb.P)
            for    i = 1:nrho   rhot[i] = rhot[i] + occ * (orb.P[i]^2 + orb.Q[i]^2)    end
        end
        # Define the integrant and take the integrals (without alpha)
        for i = 1:npoints
            for j = 1:npoints    rg = max( grid.r[i],  grid.r[j]);    wx[j] = rhot[j]/rg   end
            wb[i] = JAC.RadialIntegrals.V0(wx, npoints, grid::Radial.Grid)
            wb[i] = wb[i] - 2 / (3grid.r[i]) * (81 /(32 * pi^2) * grid.r[i] * rhot[i])^(1/3)
        end
        # Define the potential with regard to Z(r)
        for i = 2:npoints    wb[i] = - wb[i] * grid.r[i]   end;    wb[1] = 0.
        #
        wc = JAC.Radial.Potential("Kohn-Sham", wb, grid)
        return( wc )
    end

    """
    `Basics.computePotentialDFS(grid::Radial.Grid, level::Level)`
        ... to compute a (radial) Dirac-Fock-Slater potential for the given level; a potential::RadialPotential is returned.
            The Dirac-Fock-Slater potential is defined by

                                                                [   3                   ]^(1/3)
                V_DFS(r) = int_0^infty dr'  rho_t(r') / r_>  -- [-----------   rho_t(r) ]
                                                                [ 4 pi^2 r^2            ]

            with r_> = max(r,r')  and  rho_t(r) = sum_a (Pa^2(r) + Qa^2(r))   ... charge density of all electrons.
            An Radial.Potential with -r * V_DFS(r)  is returned to be consistent with an effective charge Z(r).
    """
    function Basics.computePotentialDFS(grid::Radial.Grid, level::Level)
        basis = level.basis;    npoints = grid.NoPoints
        rhot = zeros( npoints );    wb = zeros( npoints );    wx = zeros( npoints )
        # Compute the charge density of the core orbitals for the given level
        for  sh in basis.subshells
            orb  = basis.orbitals[sh]
            occ  = Basics.computeMeanSubshellOccupation(sh, [level])
            nrho = length(orb.P)
            for    i = 1:nrho   rhot[i] = rhot[i] + occ * (orb.P[i]^2 + orb.Q[i]^2)    end
        end
        # Define the integrant and take the integrals
        for i = 1:npoints
            for j = 1:npoints    rg = max( grid.r[i],  grid.r[j]);    wx[j] = rhot[j]/rg   end
            wb[i] = JAC.RadialIntegrals.V0(wx, npoints, grid::Radial.Grid)
            wb[i] = wb[i] - (3 /(4*pi^2 * grid.r[i]^2) * rhot[i])^(1/3)
        end
        # Define the potential with regard to Z(r)
        for i = 2:npoints    wb[i] = - wb[i] * grid.r[i]   end;    wb[1] = 0.
        #
        wc = JAC.Radial.Potential("DFS", wb, grid)
        return( wc )
    end

    """
    `Basics.computePotentialDFS(grid::Radial.Grid, basis::Basis)`
        ... to compute the same but for the mean occupation of the orbitals in the given basis.
    """
    function Basics.computePotentialDFS(grid::Radial.Grid, basis::Basis)
        npoints = grid.NoPoints
        rhot = zeros( npoints );    wb = zeros( npoints );    wx = zeros( npoints )
        # Compute the charge density of the core orbitals for the given level
        for  sh in basis.subshells
            orb  = basis.orbitals[sh]
            occ  = Basics.computeMeanSubshellOccupation(sh, basis)
            nrho = length(orb.P)
            for    i = 1:nrho   rhot[i] = rhot[i] + occ * (orb.P[i]^2 + orb.Q[i]^2)    end
        end
        # Define the integrant and take the integrals
        for i = 1:npoints
            for j = 1:npoints    rg = max( grid.r[i],  grid.r[j]);    wx[j] = rhot[j]/rg   end
            wb[i] = JAC.RadialIntegrals.V0(wx, npoints, grid::Radial.Grid)
            wb[i] = wb[i] - (3 /(4*pi^2 * grid.r[i]^2) * rhot[i])^(1/3)
        end
        # Define the potential with regard to Z(r)
        for i = 2:npoints    wb[i] = - wb[i] * grid.r[i]   end;    wb[1] = 0.
        #
        wc = JAC.Radial.Potential("DFS for CSF basis", wb, grid)
        return( wc )
    end

    """
    `Basics.computePotentialExtendedHartree(grid::Radial.Grid, level::Level)`
        ... to compute a (radial) extended-Hartree potential for the given level; a potential::RadialPotential is returned.
            The extended-Hartreepotential is defined by Gu (2008, Eq.(8)) and is based on the Y^k_ab (r) function and various
            direct and exchange coefficients as the arise from average energy of the configuration.
    """
    function Basics.computePotentialExtendedHartree(grid::Radial.Grid, level::Level)
        basis = level.basis;    npoints = grid.NoPoints
        rhoab = zeros( npoints );    wx = zeros( npoints );    wg = zeros( npoints )
        # First term Sum_ab ...
        for  a in basis.subshells
            occa = Basics.computeMeanSubshellOccupation(a, [level])
            orba = basis.orbitals[a]
            nrho = length(orba.P);      rhoa  = zeros(nrho)
            for  i = 1:nrho    rhoa[i] = orba.P[i]^2 + orba.Q[i]^2    end
            for  b in basis.subshells
                occb = Basics.computeMeanSubshellOccupation(b, [level])
                orbb = basis.orbitals[b]
                nrho = length(orbb.P);   rhobb = zeros(nrho)
                if  a != b  wc = occa*occb   else   wc = occa * (occb -1.0)   end
                if  wc < 0.  error("Improper potential for subshells with mean occupation < 1; stop a")    end
                for  i = 1:nrho    rhobb[i] = orbb.P[i]^2 + orbb.Q[i]^2       end
                for  i = 1:length(orba.P)    wx[i] = wx[i] + wc * JAC.RadialIntegrals.Yk_ab(0, grid.r[i], rhobb, nrho, grid) * rhoa[i]   end
            end
        end
        # Second term Sum_a ...
        for  a in basis.subshells
            occa = Basics.computeMeanSubshellOccupation(a, [level])
            orba = basis.orbitals[a]
            nrho = length(orba.P);      rhoa  = zeros(nrho)
            for  i = 1:nrho    rhoa[i] = orba.P[i]^2 + orba.Q[i]^2   end
            wc = occa * (occa - 1.0)
            if  wc < 0.  error("Improper potential for subshells with mean occupation < 1; stop b")    end
            for  k = 1:10
                fk = JAC.AngularMomentum.fk_ab(a,a);    if  fk == 0   cycle    end
                for  i = 1:length(orba.P)    wx[i] = wx[i] + wc * fk * JAC.RadialIntegrals.Yk_ab(k, grid.r[i], rhoa, nrho, grid) * rhoa[i]   end
            end
        end
        # Third term Sum_a != b ...
        for  a in basis.subshells,   b in basis.subshells
            if  a == b   continue   end
            occa = Basics.computeMeanSubshellOccupation(a, [level])
            occb = Basics.computeMeanSubshellOccupation(b, [level])
            orba = basis.orbitals[a]
            orbb = basis.orbitals[b]
            nrho = min(length(orba.P), length(orbb.P));      rhoab  = zeros(nrho)
            for  i = 1:nrho    rhoab[i] = orba.P[i]*orbb.P[i] + orba.Q[i]*orbb.Q[i]   end
            wc = occa * occb
            for  k = 0:10
                gk = JAC.AngularMomentum.gk_ab(a,b);    if  gk == 0   continue    end
                for  i = 1:nrho    wx[i] = wx[i] + wc * gk * JAC.RadialIntegrals.Yk_ab(k, grid.r[i], rhoab, nrho, grid) * rhoab[i]   end
            end
        end
        # Weight factor
        for  a in basis.subshells
            occa = Basics.computeMeanSubshellOccupation(a, [level])
            orba = basis.orbitals[a]
            nrho = length(orba.P);      rhoa  = zeros(nrho)
            for  i = 1:nrho    rhoa[i] = orba.P[i]^2 + orba.Q[i]^2   end
            wc = occa
            for  i = 1:length(orba.P)    wg[i] = wg[i] + wc * rhoa[i] * grid.r[i]   end
        end
        #
        for  i = 1:npoints   if  wg[i] > 0.    wx[i] = wx[i]/wg[i]   else   wx[i] = 0.   end    end
        return( wx )
    end

    """
    `Basics.computeScfCoefficients(field::Basics.ALField, basis::Basis, subsh::Subshell)`
        ... to compute and sort out all angular coefficients that contribute to the self-consistent field of the subshell sh.
            For an average-level field, the energy functional is given by the sum of all diagonal matrix elements of the given
            basis; the function also adds all coefficients that give rise to the same XL_Coulomb interaction strengths.
            A tuple of two lists Tuple{Array{AngularTcoeff,1},Array{AngularVcoeff,1}} is returned.
    """
    function Basics.computeScfCoefficients(field::Basics.ALField, basis::Basis, subsh::Subshell)
        #  Select only angular coefficients for the Coulomb interaction
        function isCoulomb(L::Int64, sha::Subshell, shb::Subshell, shc::Subshell, shd::Subshell)
            la = Basics.subshell_l(sha);    ja2 = Basics.subshell_2j(sha)
            lb = Basics.subshell_l(shb);    jb2 = Basics.subshell_2j(shb)
            lc = Basics.subshell_l(shc);    jc2 = Basics.subshell_2j(shc)
            ld = Basics.subshell_l(shd);    jd2 = Basics.subshell_2j(shd)

            if  AngularMomentum.triangularDelta(ja2+1,jc2+1,L+L+1) * AngularMomentum.triangularDelta(jb2+1,jd2+1,L+L+1) == 0   ||
                rem(la+lc+L,2) == 1   ||   rem(lb+ld+L,2) == 1
                return( false )
            end
            return( true )
        end
        #
        Tcoeffs = JAC.AngularCoefficientsRatip2013.AngularTcoeff[];   allTcoeffs = JAC.AngularCoefficientsRatip2013.AngularTcoeff[]
        Vcoeffs = JAC.AngularCoefficientsRatip2013.AngularVcoeff[];   allVcoeffs = JAC.AngularCoefficientsRatip2013.AngularVcoeff[]
        # Compute all angular coefficients for the diagonal matrix elements
        for  csf in basis.csfs
            wa = Basics.compute("angular coefficients: e-e, Ratip2013", csf, csf)
            append!(allTcoeffs, wa[1])
            append!(allVcoeffs, wa[2])
        end
        ##x @show allTcoeffs
        ##x @show allVcoeffs
        # Determine a list reduced angular that just depend on subshell subsh
        tcoeffs = Tuple{Int64,Subshell,Subshell}[]
        vcoeffs = Tuple{Int64,Subshell,Subshell,Subshell,Subshell}[]
        for  coeff in allTcoeffs
            if   subsh == coeff.a
                wa = (coeff.nu, coeff.a, coeff.b)
                if      wa in tcoeffs
                else    push!(tcoeffs, wa)
                end
            end
        end
        for  coeff in allVcoeffs
            if   subsh == coeff.a   &&   isCoulomb(coeff.nu, coeff.a, coeff.b, coeff.c, coeff.d)
                wa = (coeff.nu, coeff.a, coeff.b, coeff.c, coeff.d)
                if      wa in vcoeffs
                else    push!(vcoeffs, wa)
                end
            end
            if   subsh == coeff.b   &&   isCoulomb(coeff.nu, coeff.b, coeff.a, coeff.d, coeff.c)
                wa = (coeff.nu, coeff.b, coeff.a, coeff.d, coeff.c)
                if      wa in vcoeffs
                else    push!(vcoeffs, wa)
                end
            end
        end
        # Now collect all contributions that belong to the selected reduced coefficients
        for  redCoeff in tcoeffs
            redT = 0.
            for  coeff  in  allTcoeffs
                wa = (coeff.nu, coeff.a, coeff.b)
                if  wa == redCoeff      redT = redT + coeff.T   end
            end
            push!( Tcoeffs, JAC.AngularCoefficientsRatip2013.AngularTcoeff( redCoeff[1], redCoeff[2], redCoeff[3], redT) )
        end
        for  redCoeff in vcoeffs
            redV = 0.
            for coeff  in  allVcoeffs
                ## V = coeff.V
                if      coeff.a == coeff.d   &&  coeff.a != coeff.b     V = coeff.V / 2.
                elseif  coeff.b == coeff.c   &&  coeff.a != coeff.b     V = coeff.V / 2.
                else                                                                V = coeff.V
                end
                wa = (coeff.nu, coeff.a, coeff.b, coeff.c, coeff.d);
                if  wa == redCoeff      redV = redV + V    end
                wa = (coeff.nu, coeff.b, coeff.a, coeff.d, coeff.c)
                if  wa == redCoeff      redV = redV + V    end
            end
            push!( Vcoeffs, JAC.AngularCoefficientsRatip2013.AngularVcoeff( redCoeff[1], redCoeff[2], redCoeff[3], redCoeff[4], redCoeff[5],  redV) )
        end
        ##x @show " "
        ##x @show Tcoeffs
        ##x @show Vcoeffs

        return( Tcoeffs, Vcoeffs )
    end


export RangeSlider

struct RangeSlider
    range::AbstractRange
    left::Number
    right::Number
    show_value::Bool
end

RangeSlider(range::AbstractRange; left=first(range), right=last(range), show_value=false) =
left > right ? error("Left value cannot be bigger than right") : RangeSlider(range, left, right, show_value)

"""
A `RangeSlider` is a two thumb slider which returns the range between the two thumbs.

If you set `show_value` to `false`, the slider won't display its value.

By default `show_value` is set to `true`

Use `left` and `right` to set default values for the slider thumbs.

## Examples
`@bind range RangeSlider(1:100)`

`@bind range RangeSlider(1:0.1:100, show_value=false)`

`@bind range RangeSlider(1:0.1:100, left=25, right=75)`
"""

function show(io::IO, ::MIME"text/html", slider::RangeSlider)
    js = read(joinpath(PKG_ROOT_DIR, "assets", "rangeslider.js"), String)
    css = read(joinpath(PKG_ROOT_DIR, "assets", "rangeslider.css"), String)

    result = """
    <multi-range-slider>

            <input type="range" id="input-left" step="$(step(slider.range))"
            min="$(first(slider.range))" max="$(last(slider.range))"
            value="$(slider.left)">
            <input type="range" id="input-right" step="$(step(slider.range))"
            min="$(first(slider.range))" max="$(last(slider.range))"
            value="$(slider.right)">

        $(slider.show_value ? """<output id=\"slider-output\"></output>""" : "")

    </multi-range-slider>
    """

    print(io, result)
end

get(slider::RangeSlider) = collect(slider.left:step(slider.range):slider.right)


module PortableMinskyMachineNotation
export @pmmn_str, compile, load
using SparseArrays

struct PMMNError <: Exception
    msg::String
end

function inc(memory::SparseVector{I}, n, i::Integer = oneunit(I)) where {I<:Integer}
    memory[n+oneunit(n)] += i
    nothing
end

function dec(memory::SparseVector{I}, n, i::Integer = oneunit(I)) where {I<:Integer}
    n += oneunit(n)
    before = memory[n]
    memory[n] = max(zero(I), memory[n] - i)
    memory[n] != before
end

function input(io::IO, memory, n::Integer)
    if !eof(io)
        memory[n+oneunit(n)] += read(io, UInt8) + 1
    end
    nothing
end

function output(io::IO, memory, n::Integer)
    n += oneunit(n)
    if iswritable(io) && !iszero(memory[n])
        write(io, Char((memory[n] - 1) & 255))
    end
    memory[n] = zero(memory[n])
    nothing
end

function scan(source)
    (m.match for m in eachmatch(
        r"\d+|}|end|if|else|while|inc|dec|input|output",
        replace(source, r"(
            /\*(?:[^/]*|(?<!\*)/)*\*/|      # C style multiline comments
            \#=(?:[^#]*|(?<!\=)\#)=\#|      # Julia style multiline comments
            (\#|//).*                       # single line comments
        )"x => "")
    ))
end

parse(tokens) = parse(Iterators.Stateful(tokens))
parse(tokens::Iterators.Stateful) = commandlist(tokens)
parse(tokens::AbstractString) = parse(scan(tokens))

function expect(value::AbstractString, token::AbstractString)
    if token != value
        throw(PMMNError("Expected '$(value)', but got '$(token)'."))
    end
    token
end

function expect(value::AbstractVector, token::AbstractString)
    if !(token in value)
        throw(PMMNError("Expected '$(first(value))', but got '$(token)'."))
    end
    token
end

expect(value, tokens) = expect(value, popfirst!(tokens))

function commandlist(tokens)
    result = Expr[]
    while !isempty(tokens) && peek(tokens) != "}" && peek(tokens) != "end"
        push!(result, command(tokens))
    end
    if isempty(result)
        throw(PMMNError("Expected a command list, but found nothing."))
    end
    Expr(:block, result...)
end

function command(tokens, token = popfirst!(tokens))
    if token == "inc" || token == "dec"
        expr = Expr(:call, Symbol(token), :memory, Base.parse(Int, popfirst!(tokens)))
        if all(isdigit, peek(tokens))
            push!(expr.args, Base.parse(Int, popfirst!(tokens)))
        end
        expr
    elseif token == "if"
        condition = command(tokens, expect("dec", token))
        ifblock = block(tokens)
        if peek(tokens) == "else"
            Expr(:if, condition, ifblock, block(tokens))
        else
            Expr(:if, condition, ifblock)
        end
    elseif token == "while"
        condition = command(tokens, expect("dec", tokens))
        whileblock = block(tokens)
        Expr(:while, condition, whileblock)
    elseif token == "input" || token == "output"
        Expr(
            :call,
            Symbol(token),
            Symbol(replace(token, "put" => "")),
            :memory,
            Base.parse(Int, popfirst!(tokens))
        )
    else
        PMMNError("Unexpected token: $(token)")
    end
end

function block(tokens)
    expr = commandlist(tokens)
    expect(["}", "end"], tokens)
    expr
end

function compile(commands::Expr)
    fn = :(
        function (in = stdin; out = IOBuffer())
            if in isa AbstractString
                in = IOBuffer(in)
            end
            memory = spzeros(BigInt, 2000000000)
        end
    )
    push!(
        last(fn.args).args,
        commands,
        :(
            if out != stdout
                readchomp(seekstart(out))
            end
        )
    )
    eval(fn)
end

"""

    compile(str::AbstractString)

Compiles a string containing a Portable Minsky Machine Notation program.
"""
compile(str::AbstractString) = compile(parse(str))

"""

    pmmn""

A convenience macro for writing Portable Minsky Machine Notation.
"""
macro pmmn_str(str)
    :(compile($(str)))
end

"""

    load(filepath::AbstractString)

Load a Minsky machine from the specified file.
"""
load(filepath::AbstractString) = compile(readchomp(filepath))

end # module


mutable struct Particle{T<:AbstractFloat}
    x::Vector{T}
    v::Vector{T}
    p::Vector{T}

    fx::T
    fp::T

    function Particle{T}(nDims::Integer) where {T}
        if nDims < 0
            throw(ArgumentError("nDims cannot be less than 0."))
        end
        return new{T}(Vector{T}(undef, nDims),
                      Vector{T}(undef, nDims),
                      Vector{T}(undef, nDims),
                      T(0.0), T(0.0))
    end
 end

function Particle{T}(::UndefInitializer) where {T}
    return Particle{T}(Vector{T}(undef, 0),
                    Vector{T}(undef, 0),
                    Vector{T}(undef, 0),
                    T(0.0), T(0.0))
end

# Particle methods
Base.length(p::Particle) = length(p.x)


@reexport module _Finance

using Base.Dates: year, month, day
using Base.Dates: yearmonthday, yearmonth, monthday
using Base.Dates: dayofyear, lastdayofmonth, isleapyear

using Roots: fzero

include("daycount.jl")
include("cashflow.jl")

end


# Tests for the PWDict type.
# Author: Noah C. Benson <n@nben.net>

@testset "PWSet" begin
    els = [:b => 20.0, :c => 30.0, :a => 10.0, :d => 40.0]
    p = PWSet{Symbol,Float64}()
    for el in els
        p = push(p, el)
    end
    (lst,mst) = (first(p), pop(p))
    @test lst == :d
    (lst,mst) = (first(mst), pop(mst))
    @test lst == :c
    (lst,mst) = (first(mst), pop(mst))
    @test lst == :b
    (lst,mst) = (first(mst), pop(mst))
    @test lst == :a
    @test isempty(mst)
    # make many samples and make sure they resemble the distribution
    counts = Dict(:a => 0.0, :b => 0.0, :c => 0.0, :d => 0.0)
    for _ in 1:100000
        sym = rand(p)
        counts[sym] += 1/1000
    end
    @test  8.5 < counts[:a] < 12.5
    @test 18.5 < counts[:b] < 22.5
    @test 28.5 < counts[:c] < 32.5
    @test 38.5 < counts[:d] < 42.5
end


##################################################################################
# This file is part of StateSpaceEcon.jl
# BSD 3-Clause License
# Copyright (c) 2020, Bank of Canada
# All rights reserved.
##################################################################################

using Printf

"""
    printmatrix(mat [, Val(F), colnames])

Display a matrix in full while controlling the formatting of each value and
optionally showing the column names.
  * `Val(F)` - display each number in the given format `F`. The format is in the
    form of a decimal point number where the whole part indicates the total
    width and the fractional part is the number of digits printed after the
    decimal point. Default is `Val(12.7)`
  * `colnames` - a list of names to display in the first row. The names are
    displayed as given, possibly with padding to match the width given in the
    `Val` argument. If any names are longer than that, they will not be
    truncated and so the display will not be aligned properly. Sorry about that!

"""
@inline printmatrix(mat::AbstractMatrix, args...) = printmatrix(stdout, mat, args...)
@inline printmatrix(io::IO, mat::AbstractMatrix, args...) = printmatrix(io, mat, Val(12.7), args...)
@generated function printmatrix(io::IO, mat::AbstractMatrix, ::Val{N}, cols = nothing) where N
    fmts = "% $(N)s "
    fmtn = "% $(N)f "
    return quote
        m, n = size(mat)
        if cols !== nothing
            s = ""
            for j ∈ 1:n
                s *= Printf.@sprintf($fmts, cols[j])
            end
            println(io, s)
        end
        for i in 1:m
            s = ""
            for j in 1:n
                s *= Printf.@sprintf($fmtn, mat[i,j])
            end
            println(io, s)
        end
        return nothing
    end
end
export printmatrix

import ModelBaseEcon: transform, inverse_transform

function transform(data::AbstractMatrix{Float64}, m::Model)
    tdata = similar(data)
    for (i, v) in enumerate(m.varshks)
        tdata[:, i] .= transform(data[:, i], v)
    end
    return tdata
end

function inverse_transform(data::AbstractMatrix{Float64}, m::Model)
    idata = similar(data)
    for (i, v) in enumerate(m.varshks)
        idata[:, i] .= inverse_transform(data[:, i], v)
    end
    return idata
end


import RNG: gen_seed, union_uint

"The key for AESNI."
type AESNIKey
    key1::UInt128
    key2::UInt128
    key3::UInt128
    key4::UInt128
    key5::UInt128
    key6::UInt128
    key7::UInt128
    key8::UInt128
    key9::UInt128
    key10::UInt128
    key11::UInt128
    AESNIKey() = new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
end

"""
```julia
AESNI1x <: R123Generator1x{UInt128}
AESNI1x([seed])
```

AESNI1x is one kind of AESNI Counter-Based RNGs. It generates one `UInt128` number at a time.

`seed` is an `Integer` which will be automatically converted to `UInt128`.

Only available when [`R123_USE_AESNI`](@ref).
"""
type AESNI1x <: R123Generator1x{UInt128}
    x::UInt128
    key::AESNIKey
    ctr::UInt128
    function AESNI1x(seed::Integer=gen_seed(UInt128))
        r = new(0, AESNIKey(), 0)
        srand(r, seed)
    end
end

function srand(r::AESNI1x, seed::Integer=gen_seed(UInt128))
    initkey(r, seed % UInt128)
    r.ctr = 0
    random123_r(r)
    r
end

"""
```julia
AESNI4x <: R123Generator4x{UInt32}
AESNI4x([seed])
```

AESNI4x is one kind of AESNI Counter-Based RNGs. It generates four `UInt32` numbers at a time.

`seed` is a `Tuple` of four `Integer`s which will all be automatically converted to `UInt32`.

Only available when [`R123_USE_AESNI`](@ref).
"""
type AESNI4x <: R123Generator4x{UInt32}
    x1::UInt32
    x2::UInt32
    x3::UInt32
    x4::UInt32
    key::AESNIKey
    ctr1::UInt128
    p::Int
    function AESNI4x(seed::NTuple{4, Integer}=gen_seed(UInt32, 4))
        r = new(0, 0, 0, 0, AESNIKey(), 0, 0)
        srand(r, seed)
    end
end

function srand(r::AESNI4x, seed::NTuple{4, Integer}=gen_seed(UInt32, 4))
    key = union_uint(map(x -> x % UInt32, seed))
    initkey(r, key)
    r.ctr1 = 0
    p = 0
    random123_r(r)
    r
end

@inline function initkey(r, key)
    k = Ptr{UInt128}(pointer_from_objref(r.key))
    ccall((:keyinit, librandom123), Void, (
    Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128},
    Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128},
    Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}
    ), Ptr{UInt128}(pointer_from_objref(key)), k, k + 16, k + 32, k + 48, k + 64, k + 80, k + 96,
    k + 112, k + 128, k + 144, k + 160)
end

@inline function aesni1xm128i(r, ctr)
    k = Ptr{UInt128}(pointer_from_objref(r.key))
    p = Ptr{UInt128}(pointer_from_objref(r))
    ccall((:aesni1xm128i, librandom123), Void, (
    Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128},
    Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128},
    Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128}, Ptr{UInt128},
    Ptr{UInt128}
    ), Ptr{UInt128}(pointer_from_objref(ctr)), k, k + 16, k + 32, k + 48, k + 64, k + 80, k + 96,
    k + 112, k + 128, k + 144, k + 160, p)
    unsafe_load(p, 1)
end

@inline function random123_r(r::AESNI1x)
    (aesni1xm128i(r, r.ctr),)
end

@inline function random123_r(r::AESNI4x)
    aesni1xm128i(r, r.ctr1)
    x = unsafe_wrap(Array, Ptr{UInt32}(pointer_from_objref(r)), 4)
    x[1], x[2], x[3], x[4]
end


module MarkovChainUtil

using ..Resampling
using ..Reproduce
import StatsBase

export
    env_settings!,
    get_experience,
    get_experience_interpolation,
    policy_settings,
    max_is_dict,
    get_policies

const policy_settings = Dict(
    "easy"=>([0.5, 0.5], [0.1, 0.9]),
    "hard"=>([0.9, 0.1], [0.1, 0.9]),
    "hardest"=>([0.99, 0.01], [0.01, 0.99]))

const max_is_dict = Dict(
    "easy"=>1.8,
    "hard"=>9.0,
    "hardest"=>99.0)

function env_settings!(s::ArgParseSettings)
    @add_arg_table s begin
        "--chainsize"
        arg_type=Int64
        default=10
        "--policy"
        help = "The policy settings"
        default="easy"
    end
end

function get_policies(policy)
    return policy_settings[policy][1], policy_settings[policy][2]
end

function get_policies(parsed::Dict)
    return get_policies(parsed["policy"])
end

function get_experience(buffer_size, μ, π; rng=Random.GLOBAL_RNG, chain_size=10, state_mod=identity)

    env = MarkovChain(chain_size)
    _, prev_state = start!(env; rng=rng)
    ϕ = state_mod(prev_state)

    er_names = [:s_t, :s_tp1, :a_t, :r, :γ_tp1, :terminal, :mu_t, :pi_t, :ρ]
    er_types = [typeof(ϕ), typeof(ϕ), Int64, Float64, Float64, Bool, Float64, Float64, Float64]
    ER = ExperienceReplay(buffer_size, er_types; column_names=er_names)
    WER = WeightedExperienceReplay(buffer_size, er_types; column_names=er_names)

    for i = 1:buffer_size

        action = StatsBase.sample(rng, get_actions(env), StatsBase.Weights(μ))
        _, state, reward, terminal = step!(env, action)

        experience = (state_mod(prev_state), state_mod(state), action, reward, 0.9, terminal,
                      μ[action], π[action], π[action]/μ[action])
        add!(ER, experience)
        add!(WER, experience, π[action]/μ[action])

        prev_state = copy(state)
        if terminal
            _, prev_state = start!(env; rng=rng)
        end
    end

    return ER, WER
end

function get_experience_interpolation(buffer_size, μ, π, sample_policy;
                                      rng=Random.GLOBAL_RNG, chain_size=10,
                                      state_mod=identity)

    env = MarkovChain(chain_size)
    _, prev_state = start!(env; rng=rng)
    ϕ = state_mod(prev_state)

    er_names = [:s_t, :s_tp1, :a_t, :r, :γ_tp1, :terminal, :mu_t, :pi_t, :ρ]
    er_types = [typeof(ϕ), typeof(ϕ), Int64, Float64, Float64, Bool, Float64, Float64, Float64]
    WER = WeightedExperienceReplay(buffer_size, er_types; column_names=er_names)

    for i = 1:buffer_size

        action = StatsBase.sample(rng, get_actions(env), StatsBase.Weights(μ))
        _, state, reward, terminal = step!(env, action)

        experience = (state_mod(prev_state), state_mod(state), action, reward, 0.9, terminal,
                      μ[action], π[action], π[action]/sample_policy[action])
        add!(WER, experience, sample_policy[action]/μ[action])

        prev_state = copy(state)
        if terminal
            _, prev_state = start!(env; rng=rng)
        end
    end

    return WER
end

end


using Gadfly, DataArrays, RDatasets, Distributions, Base.Test

set_default_plot_size(6inch, 3inch)

global density_dark_tested

p = Gadfly.with_theme(:dark) do
    plot(dataset("ggplot2", "diamonds"), x="Price", color="Cut", Geom.density)
end

# prevent these tests from running more than once
if !isdefined(:density_dark_tested)
    svg_str_dark = stringmime(MIME("image/svg+xml"), p)
    @test contains(svg_str_dark, hex(Gadfly.dark_theme.default_color))
    @test contains(svg_str_dark, hex(Gadfly.dark_theme.background_color))
    @test contains(svg_str_dark, hex(Gadfly.dark_theme.panel_fill))

    # Test reset.
    p2 = plot(dataset("ggplot2", "diamonds"), x="Price", color="Cut", Geom.density)
    svg_str_light = stringmime(MIME("image/svg+xml"), p2)
    @test !contains(svg_str_light, hex(Gadfly.dark_theme.default_color))
    @test !contains(svg_str_light, hex(Gadfly.dark_theme.background_color))
    @test !contains(svg_str_light, hex(Gadfly.dark_theme.panel_fill))

    density_dark_tested=true
end

p


using Celeste.SDSSIO
import Celeste.SDSSIO: SDSSBackground, RunCamcolField

function load_from_dataset(dataset)
    rcf = Celeste.RunCamcolField(4263, 5, 119)
    SDSSIO.load_field_images(dataset, rcf)
    SDSSIO.load_field_catalog(dataset, rcf)
end

@testset "sdssio" begin

@testset "sky interpolations" begin
    small_sky = [1.  2.  3.  4.;
                 5.  6.  7.  8.;
                 9. 10. 11. 12]
    sky_x = [0.1, 2.5]
    sky_y = [0.5, 2.5, 4.]
    sky = SDSSBackground(small_sky, sky_x, sky_y, ones(2))

    @test sky[1, 1] ≈ 1.0
    @test sky[2, 1] ≈ 7.0
    @test sky[1, 2] ≈ 2.5
    @test sky[2, 2] ≈ 8.5
    @test sky[1, 3] ≈ 4.0
    @test sky[2, 3] ≈ 10.0
end

@testset "test coordinates out of bounds" begin
    small_sky = [1.  2.  3.  4.;
            5.  6.  7.  8.;
            9. 10. 11. 12]
    sky_x = [-5.0, 4.0]
    sky_y = [-4.0, 5.0]
    sky = SDSSBackground(small_sky, sky_x, sky_y, ones(2))

    @test sky[1,1] == 1.0
    @test sky[1,2] == 4.0
    @test sky[2,1] == 9.0
    @test sky[2,2] == 12.0
end

@testset "read_photoobj handles missing table extensions" begin
    # get an example of a photoobj file with a missing table
    SampleData.get_sdss_catalog(6597, 4, 25)
end

@testset "SDSSDataSet variations" begin
    rcf = Celeste.RunCamcolField(4263, 5, 119)

    # #nsure relevant data is downloaded (this also tests I/O with
    # SDSSDataSet defaults)
    SampleData.get_sdss_images(4263, 5, 119)
    SampleData.get_sdss_catalog(4263, 5, 119)

    # test with slurp = true
    dataset = SDSSDataSet("data"; slurp = true)
    SDSSIO.load_field_images(dataset, rcf)
    SDSSIO.load_field_catalog(dataset, rcf)

    # test with SDSS layout
    rm("data/dr12", force = true, recursive=true)
    cd("data") do
        # Create the original SDSS directory layout for testing purposes
        mkpath("dr12/boss/photo/redux/301/4263/objcs/5")
        mkpath("dr12/boss/photoObj/frames/301/4263/5")
        mkpath("dr12/boss/photoObj/301/4263/5")
        for band in ['z','g','r','i','u']
            cp("4263/5/119/frame-$band-004263-5-0119.fits", "dr12/boss/photoObj/frames/301/4263/5/frame-$band-004263-5-0119.fits")
            cp("4263/5/119/fpM-004263-$(band)5-0119.fit", "dr12/boss/photo/redux/301/4263/objcs/5/fpM-004263-$(band)5-0119.fit")
        end
        cp("4263/5/119/psField-004263-5-0119.fit", "dr12/boss/photo/redux/301/4263/objcs/5/psField-004263-5-0119.fit")
        cp("4263/5/119/photoObj-004263-5-0119.fits", "dr12/boss/photoObj/301/4263/5/photoObj-004263-5-0119.fits")
        cp("4263/5/photoField-004263-5.fits", "dr12/boss/photoObj/301/4263/photoField-004263-5.fits")
    end

    dataset = SDSSDataSet("data/dr12"; dirlayout = :sdss)
    SDSSIO.load_field_images(dataset, rcf)
    SDSSIO.load_field_catalog(dataset, rcf)

    rm("data/dr12", recursive=true)

    worker = addprocs(1)[]
    fetch(@spawnat worker Base.require(:Celeste))

    # Test fetching over the network The basedir is relative, if the
    # worker doesn't try to fetch via the network after this, it'll
    # fail
    fetch(@spawnat worker cd("/tmp"))
    dataset = SDSSDataSet("data"; iostrategy = :masterrpc)
    remotecall_fetch(SDSSIO.load_field_images, worker, dataset, rcf)
    remotecall_fetch(SDSSIO.load_field_catalog, worker, dataset, rcf)
    rmprocs([worker])
end

end


using HDF5
using MCMCChains
using MCMCChainsStorage
using Test

@testset "Reading and writing a chain" begin
    # Test writing a chain out and reading it back in, with sections
    chain = Chains(randn(500, 4, 2), [:a, :b, :c, :d])
    chain = set_section(chain, Dict(:internals => [:c, :d], :parameters => [:a, :b]))

    mktemp() do path, io
        h5open(path, "w") do f
            write(f, chain)
        end

        chain2 = h5open(path, "r") do f
            read(f, Chains)
        end

        for par in [:a, :b, :c, :d]
            @test chain[par] == chain2[par]
        end
    end
end


function test4()
  filename = "test4.asc"
  # exectuablepath = null string will not run LTspice.exe.  Test parsing only.
  sim = LTspiceSimulation(filename,executablepath="")
  @test LTspice.does_circuitfilearray_file_match(sim)
  show(IOBuffer(),sim)

  asteps = [1.0,2.0]
  bsteps = [10.0,15.0,20.0,25.0]

  @test stepvalues(sim) == (asteps,bsteps)
  @test measurementnames(sim) == ("sum","sump1000")
  @test stepnames(sim) == ("a","b")

  @static if Sys.iswindows()
      @test circuitpath(sim) == filename
  end

  @test parametervalues(sim) == []
  @test measurementvalues(sim)[1,1,1] == 11.0
  @test executablepath(sim) == ""
  @test haskey(sim,"sum") == true
  @test length(sim) == 16

  for i in eachindex(asteps)
      for j in eachindex(bsteps)
          @test measurementvalues(sim)[i,j,1] == asteps[i]+bsteps[j]
          @test sim["sum"][i,j] == asteps[i]+bsteps[j]
          @test measurementvalues(sim)[i,j,2] == asteps[i]+bsteps[j]+1000
          @test sim["sump1000"][i,j] == asteps[i]+bsteps[j]+1000
      end
  end

  for line in perlineiterator(sim)
      @test (line[1]+line[2] == line[3])
      @test (line[3] + 1000 == line[4])
  end

  for line in perlineiterator(sim,resultnames=["sum"],steporder=["b","a"])
      @test (line[1]+line[2] == line[3])
  end

  show(IOBuffer(),sim)
end
test4()


module GMP

export BigInt

import Base: *, +, -, /, <, <<, >>, >>>, <=, ==, >, >=, ^, (~), (&), (|), ($),
             binomial, cmp, convert, div, divrem, factorial, fld, gcd, gcdx, lcm, mod,
             ndigits, promote_rule, rem, show, isqrt, string, isprime, powermod,
             widemul, sum, trailing_zeros, trailing_ones, count_ones, base, parseint,
             serialize, deserialize, bin, oct, dec, hex, isequal, invmod,
             prevpow2, nextpow2, ndigits0z, widen

if Clong == Int32
    typealias ClongMax Union(Int8, Int16, Int32)
    typealias CulongMax Union(Uint8, Uint16, Uint32)
else
    typealias ClongMax Union(Int8, Int16, Int32, Int64)
    typealias CulongMax Union(Uint8, Uint16, Uint32, Uint64)
end

type BigInt <: Integer
    alloc::Cint
    size::Cint
    d::Ptr{Culong}
    function BigInt()
        b = new(zero(Cint), zero(Cint), C_NULL)
        ccall((:__gmpz_init,:libgmp), Void, (Ptr{BigInt},), &b)
        finalizer(b, _gmp_clear_func)
        return b
    end
end

_gmp_clear_func = C_NULL
_mpfr_clear_func = C_NULL

function __init__()
    global _gmp_clear_func = cglobal((:__gmpz_clear, :libgmp))
    global _mpfr_clear_func = cglobal((:mpfr_clear, :libmpfr))
    ccall((:__gmp_set_memory_functions, :libgmp), Void,
          (Ptr{Void},Ptr{Void},Ptr{Void}),
          cglobal(:jl_gc_counted_malloc),
          cglobal(:jl_gc_counted_realloc_with_old_size),
          cglobal(:jl_gc_counted_free))
end

widen(::Type{Int128})  = BigInt
widen(::Type{Uint128}) = BigInt
widen(::Type{BigInt})  = BigInt

BigInt(x::BigInt) = x
BigInt(s::String) = parseint(BigInt,s)

function Base.parseint_nocheck(::Type{BigInt}, s::String, base::Int)
    s = bytestring(s)
    sgn, base, i = Base.parseint_preamble(true,s,base)
    z = BigInt()
    err = ccall((:__gmpz_set_str, :libgmp),
               Int32, (Ptr{BigInt}, Ptr{Uint8}, Int32),
               &z, convert(Ptr{Uint8},SubString(s,i)), base)
    err == 0 || error("invalid big integer: $(repr(s))")
    return sgn < 0 ? -z : z
end

function BigInt(x::Union(Clong,Int32))
    z = BigInt()
    ccall((:__gmpz_set_si, :libgmp), Void, (Ptr{BigInt}, Clong), &z, x)
    return z
end
function BigInt(x::Union(Culong,Uint32))
    z = BigInt()
    ccall((:__gmpz_set_ui, :libgmp), Void, (Ptr{BigInt}, Culong), &z, x)
    return z
end

BigInt(x::Bool) = BigInt(uint(x))

function BigInt(x::Float64)
    !isinteger(x) && throw(InexactError())
    z = BigInt()
    ccall((:__gmpz_set_d, :libgmp), Void, (Ptr{BigInt}, Cdouble), &z, x)
    return z
end

BigInt(x::Union(Float16,Float32)) = BigInt(float64(x))

function BigInt(x::Integer)
    if x < 0
        if typemin(Clong) <= x
            return BigInt(convert(Clong,x))
        end
        b = BigInt(0)
        shift = 0
        while x < -1
            b += BigInt(~uint32(x&0xffffffff))<<shift
            x >>= 32
            shift += 32
        end
        return -b-1
    else
        if x <= typemax(Culong)
            return BigInt(convert(Culong,x))
        end
        b = BigInt(0)
        shift = 0
        while x > 0
            b += BigInt(uint32(x&0xffffffff))<<shift
            x >>>= 32
            shift += 32
        end
        return b
    end
end

convert(::Type{BigInt}, x::Integer) = BigInt(x)
convert(::Type{BigInt}, x::FloatingPoint) = BigInt(x)

function convert(::Type{Int64}, x::BigInt)
    lo = int64(convert(Culong, x & typemax(Uint32)))
    hi = int64(convert(Clong, x >> 32))
    hi << 32 | lo
end
convert(::Type{Int32}, n::BigInt) = int32(convert(Clong, n))
convert(::Type{Int16}, n::BigInt) = int16(convert(Clong, n))
convert(::Type{Int8}, n::BigInt) = int8(convert(Clong, n))

function convert(::Type{Clong}, n::BigInt)
    fits = ccall((:__gmpz_fits_slong_p, :libgmp), Int32, (Ptr{BigInt},), &n) != 0
    if fits
        ccall((:__gmpz_get_si, :libgmp), Clong, (Ptr{BigInt},), &n)
    else
        throw(InexactError())
    end
end

function convert(::Type{Uint64}, x::BigInt)
    lo = uint64(convert(Culong, x & typemax(Uint32)))
    hi = uint64(convert(Culong, x >> 32))
    hi << 32 | lo
end
convert(::Type{Uint32}, x::BigInt) = uint32(convert(Culong, x))
convert(::Type{Uint16}, x::BigInt) = uint16(convert(Culong, x))
convert(::Type{Uint8}, x::BigInt) = uint8(convert(Culong, x))

function convert(::Type{Culong}, n::BigInt)
    fits = ccall((:__gmpz_fits_ulong_p, :libgmp), Int32, (Ptr{BigInt},), &n) != 0
    if fits
        ccall((:__gmpz_get_ui, :libgmp), Culong, (Ptr{BigInt},), &n)
    else
        throw(InexactError())
    end
end

if sizeof(Int32) == sizeof(Clong)
    function convert(::Type{Uint128}, x::BigInt)
        uint128(uint(x>>>96))<<96 +
        uint128(uint((x>>>64) & typemax(Uint32)))<<64 +
        uint128(uint((x>>>32) & typemax(Uint32)))<<32 +
        uint128(uint(x & typemax(Uint32)))
    end
end
if sizeof(Int64) == sizeof(Clong)
    function convert(::Type{Uint128}, x::BigInt)
        uint128(uint(x>>>64))<<64 +
        uint128(uint(x & typemax(Uint64)))
    end
end
convert(::Type{Int128}, x::BigInt) = copysign(int128(uint128(abs(x))),x)

function convert(::Type{Float64}, n::BigInt)
    # TODO: this should round to nearest but instead rounds to zero
    ccall((:__gmpz_get_d, :libgmp), Float64, (Ptr{BigInt},), &n)
end
convert(::Type{Float32}, n::BigInt) = float32(float64(n))
convert(::Type{Float16}, n::BigInt) = float16(float64(n))

promote_rule{T<:Integer}(::Type{BigInt}, ::Type{T}) = BigInt

# serialization

function serialize(s, n::BigInt)
    Base.serialize_type(s, BigInt)
    serialize(s, base(62,n))
end

deserialize(s, ::Type{BigInt}) = Base.parseint_nocheck(BigInt, deserialize(s), 62)

# Binary ops
for (fJ, fC) in ((:+, :add), (:-,:sub), (:*, :mul),
                 (:fld, :fdiv_q), (:div, :tdiv_q), (:mod, :fdiv_r), (:rem, :tdiv_r),
                 (:gcd, :gcd), (:lcm, :lcm),
                 (:&, :and), (:|, :ior), (:$, :xor))
    @eval begin
        function ($fJ)(x::BigInt, y::BigInt)
            z = BigInt()
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &x, &y)
            return z
        end
    end
end

function invmod(x::BigInt, y::BigInt)
    z = BigInt()
    y = abs(y)
    if y == 1
        return big(0)
    end
    if (y==0 || ccall((:__gmpz_invert, :libgmp), Cint, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &x, &y) == 0)
        error("no inverse exists")
    end
    return z
end

# More efficient commutative operations
for (fJ, fC) in ((:+, :add), (:*, :mul), (:&, :and), (:|, :ior), (:$, :xor))
    @eval begin
        function ($fJ)(a::BigInt, b::BigInt, c::BigInt)
            z = BigInt()
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &a, &b)
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &z, &c)
            return z
        end
        function ($fJ)(a::BigInt, b::BigInt, c::BigInt, d::BigInt)
            z = BigInt()
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &a, &b)
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &z, &c)
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &z, &d)
            return z
        end
        function ($fJ)(a::BigInt, b::BigInt, c::BigInt, d::BigInt, e::BigInt)
            z = BigInt()
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &a, &b)
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &z, &c)
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &z, &d)
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z, &z, &e)
            return z
        end
    end
end

# Basic arithmetic without promotion
function +(x::BigInt, c::Culong)
    z = BigInt()
    ccall((:__gmpz_add_ui, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &x, c)
    return z
end
+(c::Culong, x::BigInt) = x + c
+(c::CulongMax, x::BigInt) = x + convert(Culong, c)
+(x::BigInt, c::CulongMax) = x + convert(Culong, c)
+(x::BigInt, c::ClongMax) = c < 0 ? -(x, convert(Culong, -c)) : x + convert(Culong, c)
+(c::ClongMax, x::BigInt) = c < 0 ? -(x, convert(Culong, -c)) : x + convert(Culong, c)

function -(x::BigInt, c::Culong)
    z = BigInt()
    ccall((:__gmpz_sub_ui, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &x, c)
    return z
end
function -(c::Culong, x::BigInt)
    z = BigInt()
    ccall((:__gmpz_ui_sub, :libgmp), Void, (Ptr{BigInt}, Culong, Ptr{BigInt}), &z, c, &x)
    return z
end
-(x::BigInt, c::CulongMax) = -(x, convert(Culong, c))
-(c::CulongMax, x::BigInt) = -(convert(Culong, c), x)
-(x::BigInt, c::ClongMax) = c < 0 ? +(x, convert(Culong, -c)) : -(x, convert(Culong, c))
-(c::ClongMax, x::BigInt) = c < 0 ? -(x + convert(Culong, -c)) : -(convert(Culong, c), x)

function *(x::BigInt, c::Culong)
    z = BigInt()
    ccall((:__gmpz_mul_ui, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &x, c)
    return z
end
*(c::Culong, x::BigInt) = x * c
*(c::CulongMax, x::BigInt) = x * convert(Culong, c)
*(x::BigInt, c::CulongMax) = x * convert(Culong, c)
function *(x::BigInt, c::Clong)
    z = BigInt()
    ccall((:__gmpz_mul_si, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Clong), &z, &x, c)
    return z
end
*(c::Clong, x::BigInt) = x * c
*(x::BigInt, c::ClongMax) = x * convert(Clong, c)
*(c::ClongMax, x::BigInt) = x * convert(Clong, c)

# unary ops
for (fJ, fC) in ((:-, :neg), (:~, :com))
    @eval begin
        function ($fJ)(x::BigInt)
            z = BigInt()
            ccall(($(string(:__gmpz_,fC)), :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}), &z, &x)
            return z
        end
    end
end

function <<(x::BigInt, c::Int32)
    c < 0 && throw(DomainError())
    c == 0 && return x
    z = BigInt()
    ccall((:__gmpz_mul_2exp, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &x, c)
    return z
end

function >>(x::BigInt, c::Int32)
    c < 0 && throw(DomainError())
    c == 0 && return x
    z = BigInt()
    ccall((:__gmpz_fdiv_q_2exp, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &x, c)
    return z
end

>>>(x::BigInt, c::Int32) = x >> c

trailing_zeros(x::BigInt) = int(ccall((:__gmpz_scan1, :libgmp), Culong, (Ptr{BigInt}, Culong), &x, 0))
trailing_ones(x::BigInt) = int(ccall((:__gmpz_scan0, :libgmp), Culong, (Ptr{BigInt}, Culong), &x, 0))

count_ones(x::BigInt) = int(ccall((:__gmpz_popcount, :libgmp), Culong, (Ptr{BigInt},), &x))

function divrem(x::BigInt, y::BigInt)
    z1 = BigInt()
    z2 = BigInt()
    ccall((:__gmpz_tdiv_qr, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &z1, &z2, &x, &y)
    z1, z2
end

function cmp(x::BigInt, y::BigInt)
    ccall((:__gmpz_cmp, :libgmp), Int32, (Ptr{BigInt}, Ptr{BigInt}), &x, &y)
end

function isqrt(x::BigInt)
    z = BigInt()
    ccall((:__gmpz_sqrt, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}), &z, &x)
    return z
end

function ^(x::BigInt, y::Uint)
    z = BigInt()
    ccall((:__gmpz_pow_ui, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &x, y)
    return z
end

function bigint_pow(x::BigInt, y::Integer)
    if y<0; throw(DomainError()); end
    if x== 1; return x; end
    if x==-1; return isodd(y) ? x : -x; end
    if y>typemax(Uint); throw(DomainError()); end
    return x^uint(y)
end

^(x::BigInt , y::BigInt ) = bigint_pow(x, y)
^(x::BigInt , y::Bool   ) = y ? x : one(x)
^(x::BigInt , y::Integer) = bigint_pow(x, y)
^(x::Integer, y::BigInt ) = bigint_pow(BigInt(x), y)

function powermod(x::BigInt, p::BigInt, m::BigInt)
    p < 0 && throw(DomainError())
    r = BigInt()
    ccall((:__gmpz_powm, :libgmp), Void,
          (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}),
          &r, &x, &p, &m)
    return m < 0 && r > 0 ? r + m : r # choose sign conistent with mod(x^p, m)
end
powermod(x::BigInt, p::Integer, m::BigInt) = powermod(x, BigInt(p), m)
powermod(x::BigInt, p::Integer, m::Integer) = powermod(x, BigInt(p), BigInt(m))

function gcdx(a::BigInt, b::BigInt)
    if b == 0 # shortcut this to ensure consistent results with gcdx(a,b)
        return a < 0 ? (-a,-one(BigInt),zero(BigInt)) : (a,one(BigInt),zero(BigInt))
    end
    g = BigInt()
    s = BigInt()
    t = BigInt()
    ccall((:__gmpz_gcdext, :libgmp), Void,
        (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}),
        &g, &s, &t, &a, &b)
    if t == 0
        # work around a difference in some versions of GMP
        if a == b
            return g, t, s
        elseif abs(a)==abs(b)
            return g, t, -s
        end
    end
    g, s, t
end

function sum(arr::AbstractArray{BigInt})
    n = BigInt(0)
    for i in arr
        ccall((:__gmpz_add, :libgmp), Void,
            (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}),
            &n, &n, &i)
    end
    return n
end

function factorial(x::BigInt)
    x.size < 0 && return BigInt(0)
    z = BigInt()
    ccall((:__gmpz_fac_ui, :libgmp), Void, (Ptr{BigInt}, Culong), &z, x)
    return z
end

function binomial(n::BigInt, k::Uint)
    z = BigInt()
    ccall((:__gmpz_bin_ui, :libgmp), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &z, &n, k)
    return z
end
binomial(n::BigInt, k::Integer) = k < 0 ? throw(DomainError()) : binomial(n, uint(k))

==(x::BigInt, y::BigInt) = cmp(x,y) == 0
<=(x::BigInt, y::BigInt) = cmp(x,y) <= 0
>=(x::BigInt, y::BigInt) = cmp(x,y) >= 0
<(x::BigInt, y::BigInt) = cmp(x,y) < 0
>(x::BigInt, y::BigInt) = cmp(x,y) > 0

string(x::BigInt) = dec(x)
show(io::IO, x::BigInt) = print(io, string(x))

bin(n::BigInt) = base( 2, n)
oct(n::BigInt) = base( 8, n)
dec(n::BigInt) = base(10, n)
hex(n::BigInt) = base(16, n)

function base(b::Integer, n::BigInt)
    2 <= b <= 62 || error("invalid base: $b")
    p = ccall((:__gmpz_get_str,:libgmp), Ptr{Uint8}, (Ptr{Uint8}, Cint, Ptr{BigInt}), C_NULL, b, &n)
    len = int(ccall(:strlen, Csize_t, (Ptr{Uint8},), p))
    ASCIIString(pointer_to_array(p,len,true))
end

function ndigits0z(x::BigInt, b::Integer=10)
    # mpz_sizeinbase might return an answer 1 too big
    n = int(ccall((:__gmpz_sizeinbase,:libgmp), Culong, (Ptr{BigInt}, Int32), &x, b))
    abs(x) < big(b)^(n-1) ? n-1 : n
end
ndigits(x::BigInt, b::Integer=10) = x.size == 0 ? 1 : ndigits0z(x,b)

isprime(x::BigInt, reps=25) = ccall((:__gmpz_probab_prime_p,:libgmp), Cint, (Ptr{BigInt}, Cint), &x, reps) > 0

widemul(x::Int128, y::Uint128)  = BigInt(x)*BigInt(y)
widemul(x::Uint128, y::Int128)  = BigInt(x)*BigInt(y)

prevpow2(x::BigInt) = x.size < 0 ? -prevpow2(-x) : (x <= 2 ? x : one(BigInt) << (ndigits(x, 2)-1))
nextpow2(x::BigInt) = x.size < 0 ? -nextpow2(-x) : (x <= 2 ? x : one(BigInt) << ndigits(x-1, 2))

end # module


using SafeTestsets

@safetestset "Day08" begin
    using AdventOfCode.TestUtils
    using AdventOfCode.Day08
    using AdventOfCode.Day08: checksum, decode

    @test checksum("123456789012", 3*2) == 1
    @test decode("0222112222120000", 2, 2) == [0 1; 1 0]

    btest("8.1") do
        part1() == 1320
    end

    btest("8.2") do
        part2() == ("OOO   OO  O   OO  O OOO  \n"*
                    "O  O O  O O   OO O  O  O \n"*
                    "O  O O     O O OO   O  O \n"*
                    "OOO  O      O  O O  OOO  \n"*
                    "O O  O  O   O  O O  O O  \n"*
                    "O  O  OO    O  O  O O  O \n")
    end
end


"""
    OneHotTensor{N, T}(classes) <: Block

A block representing a one-hot encoded, N-dimensional array
categorical variable. For example, a single categorical label
is a `OneHotTensor{0, T}` (aliased to `OneHotTensor{T}`).

Use the [`OneHot`](#) encoding to one-hot encode [`Label`](#)s
or [`LabelMulti`](#)s.
"""
struct OneHotTensor{N, T} <: Block
    classes::AbstractVector{T}
end

const OneHotLabel{T} = OneHotTensor{0, T}

Base.summary(io::IO, ::OneHotLabel{T}) where T = print(io, "OneHotLabel{$T}")

function checkblock(block::OneHotTensor{N}, a::AbstractArray{T, M}) where {M, N, T}
    return N + 1 == M && last(size(a)) == length(block.classes)
end

mockblock(block::OneHotTensor{0}) = encode(
    OneHot(), Validation(), Label(block.classes), rand(block.classes))

function mockblock(block::OneHotTensor{N}) where N
    maskblock = Mask{N}(block.classes)
    return encode(OneHot(), Validation(), maskblock, mockblock(maskblock))
end

struct OneHotTensorMulti{N, T} <: Block
    classes::AbstractVector{T}
end

function checkblock(block::OneHotTensorMulti{N}, a::AbstractArray{T, M}) where {M, N, T}
    return N + 1 == M && last(size(a)) == length(block.classes)
end

function mockblock(block::OneHotTensorMulti{0}) where N
    labelblock = LabelMulti(block.classes)
    return encode(OneHot(), Validation(), labelblock, mockblock(labelblock))
end

"""
    OneHot()
    OneHot(T, threshold)

`Encoding` that turns categorical labels into one-hot encoded arrays of type `T`.

Encodes
```
      `Mask{N, U}` -> `OneHotTensor{N, T}`
`LabelMulti{N, U}` -> `OneHotTensorMulti{N, T}`
        `Label{U}` -> `OneHotTensor{N, T}`
```
"""
struct OneHot{TT<:Type} <: Encoding
    T::TT
    threshold::Float32
end

OneHot() = OneHot(Float32, 0.5f0)

# ### `Label` implementation
encodedblock(::OneHot, block::Label{T}) where T = OneHotTensor{0, T}(block.classes)
decodedblock(::OneHot, block::OneHotLabel) = Label(block.classes)

function encode(enc::OneHot, context, block::Label, obs)
    idx = findfirst(isequal(obs), block.classes)
    isnothing(idx) && error("$obs could not be found in `block.classes`: $(block.classes).")
    return DataAugmentation.onehot(enc.T, idx, length(block.classes))
end

function decode(::OneHot, context, block::OneHotLabel, obs)
    return block.classes[argmax(obs)]
end

# ### `LabelMulti` implementation

encodedblock(::OneHot, block::LabelMulti{T}) where T = OneHotTensorMulti{0, T}(block.classes)
decodedblock(::OneHot, block::OneHotTensorMulti{0}) = LabelMulti(block.classes)

function encode(enc::OneHot, _, block::LabelMulti, obs)
    return collect(enc.T, (c in obs for c in block.classes))
end

function decode(enc::OneHot, _, block::OneHotTensorMulti{0}, obs)
    return block.classes[softmax(obs) .> enc.threshold]
end

function blocklossfn(outblock::OneHotTensor{0}, yblock::OneHotTensor{0})
    outblock.classes == yblock.classes || error("Classes of $outblock and $yblock differ!")
    return Flux.Losses.logitcrossentropy
end

function blocklossfn(outblock::OneHotTensorMulti{0}, yblock::OneHotTensorMulti{0})
    outblock.classes == yblock.classes || error("Classes of $outblock and $yblock differ!")
    return Flux.Losses.logitbinarycrossentropy
end

# ## Tests

@testset "OneHot [encoding]" begin
    enc = OneHot()
    testencoding(enc, Label(1:10), 1)
    testencoding(enc, LabelMulti(1:10), [1])
    testencoding(enc, Mask{2}(1:10), rand(1:10, 50, 50))
end


#=
Statistics for Unicode character/string types

Copyright 2017 Gandalf Software, Inc., Scott P. Jones
Licensed under MIT License, see LICENSE.md
=#
struct LineCounts
    lines::Int
    chars::Int
    bytes::Int

    LineCounts(lines::Vector) = new(length(lines), sum(length, lines), sum(sizeof, lines))
    LineCounts(l::Integer, c::Integer) = new(Int(l), Int(c), 0)
    LineCounts(l::Integer, c::Integer, b::Integer) = new(Int(l), Int(c), Int(b))
end

LineCounts(old::LineCounts, lines::Vector) = LineCounts(old.lines, old.chars, sum(sizeof, lines))

struct CharTypes
    ascii::Int
    latin::Int
    utf2byte::Int
    ucs2::Int
    utf32::Int
    surr::Int
    invalid::Int
end

struct CharStat
    num::Int
    len::Int
    empty::Int
    minlen::Int
    maxlen::Int
    maxtyp::Int
    chars::CharTypes
    lines::CharTypes
end

maxbit(flags) = sizeof(flags)<<3 - leading_zeros(flags)

function calcstats(lines)
    numlines = length(lines)
    lenlines = sum(length, lines)
    charstat = [0,0,0,0,0,0,0]
    linestat = [0,0,0,0,0,0,0]
    emptylines = 0
    minlen = typemax(Int)
    maxlen = 0
    maxtyp = 0
    for l in lines
        len = length(l)
        minlen = min(minlen, len)
        maxlen = max(maxlen, len)
        flags = 0
        for chr in l
            ch = chr%UInt32
            t = ch <= 0x7f ? 1 :
                ch <= 0xff ? 2 :
                ch <= 0x7ff ? 3 :
                0xd800 <= ch <= 0xdfff ? 6 :
                ch <= 0xffff ? 4 :
                ch <= 0x10ffff ? 5 : 7
            charstat[t] += 1
            flags |= 1<<(t-1)
        end
        if flags == 0
            emptylines += 1
        else
            linestat[maxbit(flags & 0x1f)] += 1
            (flags & (1<<5)) != 0 && (linestat[6] += 1)
            (flags & (1<<6)) != 0 && (linestat[7] += 1)
        end
        maxtyp |= flags
    end
    CharStat(numlines, lenlines, emptylines, minlen, maxlen, maxbit(maxtyp & 0x1f),
             CharTypes(charstat...), CharTypes(linestat...))
end


function filter_station_DNN(df, stn_code)
    stn_df = @from i in df begin
        @where i.stationCode == stn_code
        @select i
        @collect DataFrame
    end

    stn_df
end

function process_raw_data_DNN!(stn_df::DataFrame)
    flush(stdout); flush(stderr)
    cols = [:SO2, :CO, :O3, :NO2, :PM10, :PM25,
        :temp, :pres, :u, :v, :humid, :prep, :snow]
    airkorea_cols = [:SO2, :CO, :O3, :NO2, :PM10, :PM25]
    weather_cols = [:temp, :u, :v, :pres, :humid, :prep, :snow]

    for col in cols
        stn_df[!, col] = Missings.coalesce.(stn_df[!, col], 0.0)
    end

    stn_df[!, :date] = ZonedDateTime.(stn_df[!, :date])

    # no and staitonCode must not have missing value
    @assert size(collect(skipmissing(stn_df[!, :stationCode])), 1) == size(stn_df, 1)

    DataFrames.dropmissing!(stn_df, [:stationCode])
    DataFrames.allowmissing!(stn_df, cols)
    for col in [:prep, :snow]
        stn_df[!, col] = Missings.coalesce.(stn_df[!, col], 0.0)
    end

    for col in airkorea_cols
        replace!(stn_df[!, col], -999 => missing)
    end

    # check remaining missing values
    for col in names(stn_df)
        @assert size(stn_df, 1) == size(collect(skipmissing(stn_df[!, col])), 1)
    end
    dropmissing!(stn_df, cols, disallowmissing=true)

    flush(stdout); flush(stderr)
end

"""
    load_data(pre_input_path, stns, src_input_path::String = "/input/input.csv")

Path -> DataFrame

there is a two scenario
1. I have pre-filtered data
    * load pre-filtered data
    * process data
2. I don't have pre-filtered data
    * load all data from source
    * select data by station
    * process data
    * save data
"""
function load_data_DNN(pre_input_path::String, stns::NamedTuple,
    src_input_path::String = "/input/input.csv") where I<:Integer

    # Check preprocessed path exists
    if Base.Filesystem.ispath(pre_input_path)
        filtered_df = CSV.read(pre_input_path, copycols=true)
        process_raw_data_DNN!(filtered_df)
        df = filtered_df
    else
        # if there is no preprocessed input, read source and create
        dfs = DataFrame[]

        # if not, read from source input path
        # note that at 2012/11 one hour have missing, needed to add empty data manually
        raw_df = CSV.read(src_input_path, copycols=true)
        for (name, code) in pairs(stns)
            @info "Preprocessing $name($code) data..."
            stn_df = filter_station_DNN(raw_df, code)

            process_raw_data_DNN!(stn_df)

            push!(dfs, stn_df)
        end

        # concatenate dataframe
        _df = vcat(dfs...)
        sort!(_df, [:stationCode, :date]);

        CSV.write(pre_input_path, _df)

        df = _df
    end

    df
end

"""
    filter_jongro(df)
Filter DataFrame by jongro station code (111123)
"""
function filter_jongro(df)
    jongro_stn = 111123
    jongro_df = @from i in df begin
        @where i.stationCode == jongro_stn
        @select i
        @collect DataFrame
    end

    cols = [:SO2, :CO, :O3, :NO2, :PM10, :PM25, :temp, :u, :v, :pres, :humid, :prep, :snow]
    for col in cols
        jongro_df[col] = Missings.coalesce.(jongro_df[col], 0.0)
    end

    return jongro_df
end

function save_jongro_df(input_path = "/input/input.csv")
    df = CSV.read(input_path, copycols=true)
    sort!(df, (:date, :stationCode))
    @show first(df, 5)
    j_df = filter_jongro(df)

    CSV.write("/input/jongro_single.csv", j_df)
end

function read_jongro(input_path="/input/jongro_single.csv")
    if Base.Filesystem.isfile(input_path) == false
        save_jongro_df()
    end

    df = DataFrame(CSV.read(input_path, copycols=true))

    @info "Start preprocessing..."
    flush(stdout); flush(stderr)
    if eltype(df[!, :date]) != ZonedDateTime
        df[!, :date] = ZonedDateTime.(df[!, :date])
    end

    # no and staitonCode must not have missing value
    @assert size(collect(skipmissing(df[!, :stationCode])), 1) == size(df, 1)

    DataFrames.dropmissing!(df, [:stationCode])
    cols = [:SO2, :CO, :O3, :NO2, :PM10, :PM25, :temp, :u, :v, :pres, :humid, :prep, :snow]
    airkorea_cols = [:SO2, :CO, :O3, :NO2, :PM10, :PM25]
    weather_cols = [:temp, :u, :v, :pres, :humid]

    plot_totaldata(df, :PM25, "/mnt/raw_")
    plot_totaldata(df, :PM10, "/mnt/raw_")
    flush(stdout); flush(stderr)

    DataFrames.allowmissing!(df, cols)
    for col in [:prep, :snow]
        df[!, col] = Missings.coalesce.(df[!, col], 0.0)
    end

    for col in airkorea_cols
        replace!(df[!, col], -999 => missing)
    end

    # check remaining missing values
    for col in names(df)
        @assert size(df, 1) == size(collect(skipmissing(df[!, col])), 1)
    end
    dropmissing!(df, cols, disallowmissing=true)

    @show first(df, 5)
    flush(stdout); flush(stderr)

    df
end

function perm_df(df, permed_idx, col, labels)
    X = df[permed_idx, labels]
    Y = df[permed_idx, col]

    X, Y
end


using DataDeps

register(DataDep(
  "wine-quality-white",
  "https://archive.ics.uci.edu/ml/datasets/Wine+Quality",
  "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv",
  "76c3f809815c17c07212622f776311faeb31e87610d52c26d87d6e361b169836",
  post_fetch_method=(path -> begin
    UCIData.process_dataset(path,
        target_index=12,
        feature_indices=1:11,
        delim=';',
        skipto=2,
    )
  end),
))


module ImageContrastAdjustment

using ColorVectorSpace
using ImageCore
using ImageTransformations: imresize
# Where possible we avoid a direct dependency to reduce the number of [compat] bounds
using ImageCore.MappedArrays
using Parameters: @with_kw # Same as Base.@kwdef but works on Julia 1.0

# TODO: port HistogramAdjustmentAPI to ImagesAPI
include("HistogramAdjustmentAPI/HistogramAdjustmentAPI.jl")
import .HistogramAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,
                                adjust_histogram, adjust_histogram!

# TODO Relax this to all image color types
const GenericGrayImage = AbstractArray{<:Union{Number, AbstractGray}}

include("core.jl")
include("build_histogram.jl")
include("algorithms/common.jl")
include("algorithms/adaptive_equalization.jl")
include("algorithms/equalization.jl")
include("algorithms/linear_stretching.jl")
include("algorithms/contrast_stretching.jl")
include("algorithms/gamma_correction.jl")
include("algorithms/matching.jl")
include("algorithms/midway_equalization.jl")
include("compat.jl")

export
    # main types and functions
    AdaptiveEqualization,
    Equalization,
    MidwayEqualization,
    Matching,
    GammaCorrection,
    LinearStretching,
    ContrastStretching,
    build_histogram,
    adjust_histogram,
    adjust_histogram!

end # module