module plate_w_hole_examples
using FinEtools
using FinEtoolsDeforLinear
using FinEtoolsDeforLinear.AlgoDeforLinearModule
using FinEtools.MeshExportModule
using LinearAlgebra: norm
using SparseArrays: cholesky
using Statistics: mean

function plate_w_hole_H20_stress()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.3;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential, nThickness = 6, 8, 1;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    convergencestudy = FDataDict[]
    for ref in 0:1:2
        println("ref = $(ref)")
        # Thickness = H
        Thickness = H/2^ref
        tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

        fens,fes = H20block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 2^ref*nThickness)

        bdryfes = meshboundary(fes);
        icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

        for i=1:count(fens)
            t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
            fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
        end

        geom = NodalField(fens.xyz)
        u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

        l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
        setebc!(u,l1,true, 2, 0.0)
        l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
        setebc!(u,l1,true, 1, 0.0)
        # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
        l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
        setebc!(u,l1,true, 3, 0.0)
        # If this was enabled, the plane-strain  constraint would be enforced.
        # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
        # setebc!(u,l1,true, 3, 0.0)

        applyebc!(u)
        numberdofs!(u)
        el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), GaussRule(2, 3)))
        function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
            local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
            nx = XYZ[1]/r; ny = XYZ[2]/r
            forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
            forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
            forceout[3] = 0.0
            return forceout
        end
        fi = ForceIntensity(FFlt, 3, pfun);
        F2 = distribloads(el1femm, geom, u, fi, 2);

        MR = DeforModelRed3D

        material = MatDeforElastIso(MR, E, nu)

        femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3, 2)), material)

        # The geometry field now needs to be associated with the FEMM
        femm = associategeometry!(femm, geom)

        K = stiffness(femm, geom, u)
        K = cholesky(K)
        U = K\(F2)
        scattersysvec!(u, U[:])

        stressfields = elemfieldfromintegpoints(femm, geom, u, :Cauchy, collect(1:6))

        push!(convergencestudy, FDataDict(
        "elementsize"=> 1.0 / 2^ref,
        "fens"=>fens,
        "fes"=>fes,
        "geom"=>geom,
        "u"=>u,
        "femm"=>femm,
        "stressfields"=>[stressfields],
        "tolerance"=>tolerance)
        )
    end # for ref in

    # File = "mplate_w_hole_H20m_stress"
    # open(File * ".jls", "w") do file
    #     serialize(file, convergencestudy)
    # end

end # plate_w_hole_H20_stress

function plate_w_hole_MSH8_convergence()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.49995;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential=3, 5;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigxderrs = Dict{Symbol, FFltVec}()
    sigyderrs = Dict{Symbol, FFltVec}()
    numelements = []
    numnodes = []
    for extrap in [:extraptrend :extrapmean]
        sigxderrs[extrap] = FFltVec[]
        sigyderrs[extrap] = FFltVec[]
        numelements = []
        numnodes = []
        for ref in 0:1:2
            # Thickness = H
            Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = H8block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 1)

            bdryfes = meshboundary(fes);
            icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

            for i=1:count(fens)
                t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
                fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
            end

            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H8)
            # @async run(`"paraview.exe" $File`)

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # If this was enabled, the plane-strain  constraint would be enforced.
            # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            # setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)
            el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), GaussRule(2, 2)))
            function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
                nx = XYZ[1]/r; ny = XYZ[2]/r
                # local sx, sy, txy
                # sx, sy, txy = sigmaxx(XYZ), sigmayy(XYZ), sigmaxy(XYZ)
                # sn = sx * nx^2 + sy * ny^2 + 2 * nx * ny * txy
                # tn = -(sx - sy) * nx * ny + (nx^2 - ny^2) * txy
                # forceout[1] = sn * nx - tn * ny
                # forceout[2] = sn * ny + tn * nx
                # forceout[3] = 0.0
                forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
                forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfun);
            F2 = distribloads(el1femm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinearMSH8(MR, IntegDomain(fes, GaussRule(3, 2)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(F2)
            scattersysvec!(u, U[:])

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            # thecorneru = zeros(FFlt,length(nlA),3)
            # gathervalues_asmat!(u, thecorneru, nl);
            # thecorneru = mean(thecorneru, 1)[1]/phun("mm")
            # println("displacement = $(thecorneru) vs -0.10215 [MM]")

            println("Extrapolation: $( extrap )--------------- ")
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1;
            nodevalmethod = :averaging, reportat = extrap)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2;
            nodevalmethod = :averaging, reportat = extrap)
            sigyA = mean(sigy.values[nlA,1], dims = 1)[1]
            sigyAtrue = sigmatt([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlB,1], dims = 1)[1]
            sigxBtrue = sigmatt([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            push!(numnodes, count(fens))
            push!(numelements, count(fes))
            push!(sigxderrs[extrap], abs(sigxB/sigxBtrue - 1.0))
            push!(sigyderrs[extrap], abs(sigyA/sigyAtrue - 1.0))
            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, geom.values,
            # FinEtools.MeshExportModule.H8; vectors=[("u", u.values)],
            # scalars=[("sigmax", sigx.values/phun("MEGA*PA"))])
            # @async run(`"paraview.exe" $File`)
        end
    end

    File = "plate_w_hole_MSH8_convergence.CSV"
    savecsv(File, numelements=vec(numelements), numnodes=vec(numnodes), sigxderrtrend=vec(sigxderrs[:extraptrend]), sigxderrdefault=vec(sigxderrs[:extrapmean]), sigyderrtrend=vec(sigyderrs[:extraptrend]), sigyderrdefault=vec(sigyderrs[:extrapmean]))

end # plate_w_hole_MSH8_convergence

function plate_w_hole_MSH8_PE_convergence()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.49995;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential=3, 5;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigxderrs = Dict{Symbol, FFltVec}()
    sigyderrs = Dict{Symbol, FFltVec}()
    numelements = []
    numnodes = []
    for extrapolation in [:extraptrend :extrapmean]
        sigxderrs[extrapolation] = FFltVec[]
        sigyderrs[extrapolation] = FFltVec[]
        numelements = []
        numnodes = []
        for ref in 0:1:2
            # Thickness = H
            Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = H8block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 1)

            bdryfes = meshboundary(fes);
            icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

            for i=1:count(fens)
                t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
                fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
            end

            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H8)
            # @async run(`"paraview.exe" $File`)

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # If this was enabled, the plane-strain  constraint would be enforced.
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)
            el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), GaussRule(2, 2)))
            function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
                nx = XYZ[1]/r; ny = XYZ[2]/r
                # local sx, sy, txy
                # sx, sy, txy = sigmaxx(XYZ), sigmayy(XYZ), sigmaxy(XYZ)
                # sn = sx * nx^2 + sy * ny^2 + 2 * nx * ny * txy
                # tn = -(sx - sy) * nx * ny + (nx^2 - ny^2) * txy
                # forceout[1] = sn * nx - tn * ny
                # forceout[2] = sn * ny + tn * nx
                # forceout[3] = 0.0
                forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
                forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfun);
            F2 = distribloads(el1femm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinearMSH8(MR, IntegDomain(fes, GaussRule(3, 2)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(F2)
            scattersysvec!(u, U[:])

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            # thecorneru = zeros(FFlt,length(nlA),3)
            # gathervalues_asmat!(u, thecorneru, nl);
            # thecorneru = mean(thecorneru, 1)[1]/phun("mm")
            # println("displacement = $(thecorneru) vs -0.10215 [MM]")

            println("Extrapolation: $( extrapolation )")
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1;
            nodevalmethod = :averaging, reportat = extrapolation)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2;
            nodevalmethod = :averaging, reportat = extrapolation)
            sigyA = mean(sigy.values[nlA,1], dims = 1)[1]
            sigyAtrue = sigmatt([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlB,1], dims = 1)[1]
            sigxBtrue = sigmatt([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            push!(numnodes, count(fens))
            push!(numelements, count(fes))
            push!(sigxderrs[extrapolation], abs(sigxB/sigxBtrue - 1.0))
            push!(sigyderrs[extrapolation], abs(sigyA/sigyAtrue - 1.0))
            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, geom.values,
            # FinEtools.MeshExportModule.H8; vectors=[("u", u.values)],
            # scalars=[("sigmax", sigx.values/phun("MEGA*PA"))])
            # @async run(`"paraview.exe" $File`)
        end
    end

    File = "plate_w_hole_PE_MSH8_convergence.CSV"
    savecsv(File, numelements=vec(numelements), numnodes=vec(numnodes), sigxderrtrend=vec(sigxderrs[:extraptrend]), sigxderrdefault=vec(sigxderrs[:extrapmean]), sigyderrtrend=vec(sigyderrs[:extraptrend]), sigyderrdefault=vec(sigyderrs[:extrapmean]))

end # plate_w_hole_MSH8_PE_convergence

function plate_w_hole_MST10_convergence()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.49995;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential=3, 5;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigxderrs = Dict{Symbol, FFltVec}()
    sigyderrs = Dict{Symbol, FFltVec}()
    numelements = []
    numnodes = []
    for extrapolation in [:extraptrend :extrapmean]
        sigxderrs[extrapolation] = FFltVec[]
        sigyderrs[extrapolation] = FFltVec[]
        numelements = []
        numnodes = []
        for ref in 0:1:3
            # Thickness = H
            Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = T10block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 1)

            bdryfes = meshboundary(fes);
            icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

            for i=1:count(fens)
                t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
                fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
            end

            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H8)
            # @async run(`"paraview.exe" $File`)

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # If this was enabled, the plane-strain  constraint would be enforced.
            # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            # setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)
            el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), TriRule(3)))
            function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
                nx = XYZ[1]/r; ny = XYZ[2]/r
                # local sx, sy, txy
                # sx, sy, txy = sigmaxx(XYZ), sigmayy(XYZ), sigmaxy(XYZ)
                # sn = sx * nx^2 + sy * ny^2 + 2 * nx * ny * txy
                # tn = -(sx - sy) * nx * ny + (nx^2 - ny^2) * txy
                # forceout[1] = sn * nx - tn * ny
                # forceout[2] = sn * ny + tn * nx
                # forceout[3] = 0.0
                forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
                forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfun);
            F2 = distribloads(el1femm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinearMST10(MR, IntegDomain(fes, TetRule(4)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(F2)
            scattersysvec!(u, U[:])

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            # thecorneru = zeros(FFlt,length(nlA),3)
            # gathervalues_asmat!(u, thecorneru, nl);
            # thecorneru = mean(thecorneru, 1)[1]/phun("mm")
            # println("displacement = $(thecorneru) vs -0.10215 [MM]")

            println("Extrapolation: $( extrapolation )")
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1;            nodevalmethod = :averaging, reportat = extrapolation)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2;            nodevalmethod = :averaging, reportat = extrapolation)
            sigyA = mean(sigy.values[nlA,1], dims = 1)[1]
            sigyAtrue = sigmatt([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlB,1], dims = 1)[1]
            sigxBtrue = sigmatt([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            push!(numnodes, count(fens))
            push!(numelements, count(fes))
            push!(sigxderrs[extrapolation], abs(sigxB/sigxBtrue - 1.0))
            push!(sigyderrs[extrapolation], abs(sigyA/sigyAtrue - 1.0))
            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, geom.values,
            # FinEtools.MeshExportModule.H8; vectors=[("u", u.values)],
            # scalars=[("sigmax", sigx.values/phun("MEGA*PA"))])
            # @async run(`"paraview.exe" $File`)
        end
    end

    File = "plate_w_hole_MST10_convergence.CSV"
    savecsv(File, numelements=vec(numelements), numnodes=vec(numnodes), sigxderrtrend=vec(sigxderrs[:extraptrend]), sigxderrdefault=vec(sigxderrs[:extrapmean]), sigyderrtrend=vec(sigyderrs[:extraptrend]), sigyderrdefault=vec(sigyderrs[:extrapmean]))

end # plate_w_hole_MST10_convergence

function plate_w_hole_MST10_PE_convergence()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.49995;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential=3, 5;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigxderrs = Dict{Symbol, FFltVec}()
    sigyderrs = Dict{Symbol, FFltVec}()
    numelements = []
    numnodes = []
    for extrapolation in [:extraptrend :extrapmean]
        sigxderrs[extrapolation] = FFltVec[]
        sigyderrs[extrapolation] = FFltVec[]
        numelements = []
        numnodes = []
        for ref in 0:1:3
            # Thickness = H
            Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = T10block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 1)

            bdryfes = meshboundary(fes);
            icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

            for i=1:count(fens)
                t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
                fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
            end

            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H8)
            # @async run(`"paraview.exe" $File`)

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # If this was enabled, the plane-strain  constraint would be enforced.
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)
            el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), TriRule(3)))
            function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
                nx = XYZ[1]/r; ny = XYZ[2]/r
                # local sx, sy, txy
                # sx, sy, txy = sigmaxx(XYZ), sigmayy(XYZ), sigmaxy(XYZ)
                # sn = sx * nx^2 + sy * ny^2 + 2 * nx * ny * txy
                # tn = -(sx - sy) * nx * ny + (nx^2 - ny^2) * txy
                # forceout[1] = sn * nx - tn * ny
                # forceout[2] = sn * ny + tn * nx
                # forceout[3] = 0.0
                forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
                forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfun);
            F2 = distribloads(el1femm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinearMST10(MR, IntegDomain(fes, TetRule(4)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(F2)
            scattersysvec!(u, U[:])

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            # thecorneru = zeros(FFlt,length(nlA),3)
            # gathervalues_asmat!(u, thecorneru, nl);
            # thecorneru = mean(thecorneru, 1)[1]/phun("mm")
            # println("displacement = $(thecorneru) vs -0.10215 [MM]")

            println("Extrapolation: $( extrapolation )")
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1;
            nodevalmethod = :averaging, reportat = extrapolation)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2;
            nodevalmethod = :averaging, reportat = extrapolation)
            sigyA = mean(sigy.values[nlA,1], dims = 1)[1]
            sigyAtrue = sigmatt([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlB,1], dims = 1)[1]
            sigxBtrue = sigmatt([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            push!(numnodes, count(fens))
            push!(numelements, count(fes))
            push!(sigxderrs[extrapolation], abs(sigxB/sigxBtrue - 1.0))
            push!(sigyderrs[extrapolation], abs(sigyA/sigyAtrue - 1.0))
            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, geom.values,
            # FinEtools.MeshExportModule.H8; vectors=[("u", u.values)],
            # scalars=[("sigmax", sigx.values/phun("MEGA*PA"))])
            # @async run(`"paraview.exe" $File`)
        end
    end

    File = "plate_w_hole_PE_MST10_convergence.CSV"
    savecsv(File, numelements=vec(numelements), numnodes=vec(numnodes), sigxderrtrend=vec(sigxderrs[:extraptrend]), sigxderrdefault=vec(sigxderrs[:extrapmean]), sigyderrtrend=vec(sigyderrs[:extraptrend]), sigyderrdefault=vec(sigyderrs[:extrapmean]))

end # plate_w_hole_MST10_PE_convergence

function plate_w_hole_MST10_stress()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.3;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential, nThickness = 6, 8, 1;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    convergencestudy = FDataDict[]
    for ref in 0:1:2
        println("ref = $(ref)")
        # Thickness = H
        Thickness = H/2^ref
        tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

        fens,fes = T10block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 2^ref*nThickness)

        bdryfes = meshboundary(fes);
        icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

        for i=1:count(fens)
            t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
            fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
        end

        geom = NodalField(fens.xyz)
        u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

        l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
        setebc!(u,l1,true, 2, 0.0)
        l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
        setebc!(u,l1,true, 1, 0.0)
        # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
        l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
        setebc!(u,l1,true, 3, 0.0)
        # If this was enabled, the plane-strain  constraint would be enforced.
        # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
        # setebc!(u,l1,true, 3, 0.0)

        applyebc!(u)
        numberdofs!(u)
        el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), TriRule(3)))
        function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
            local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
            nx = XYZ[1]/r; ny = XYZ[2]/r
            forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
            forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
            forceout[3] = 0.0
            return forceout
        end
        fi = ForceIntensity(FFlt, 3, pfun);
        F2 = distribloads(el1femm, geom, u, fi, 2);

        MR = DeforModelRed3D

        material = MatDeforElastIso(MR, E, nu)

        femm = FEMMDeforLinearMST10(MR, IntegDomain(fes, TetRule(4)), material)

        # The geometry field now needs to be associated with the FEMM
        femm = associategeometry!(femm, geom)

        K = stiffness(femm, geom, u)
        K = cholesky(K)
        U = K\(F2)
        scattersysvec!(u, U[:])

        stressfields = elemfieldfromintegpoints(femm, geom, u, :Cauchy, collect(1:6))

        push!(convergencestudy, FDataDict(
        "elementsize"=> 1.0 / 2^ref,
        "fens"=>fens,
        "fes"=>fes,
        "geom"=>geom,
        "u"=>u,
        "femm"=>femm,
        "stressfields"=>[stressfields],
        "tolerance"=>tolerance)
        )
    end # for ref in

    # File = "mplate_w_hole_MST10m_stress"
    # open(File * ".jls", "w") do file
    #     serialize(file, convergencestudy)
    # end

end # plate_w_hole_MST10_stress

function plate_w_hole_RECT_H20_convergence()
    E = 210000*phun("MEGA*PA");# 210e3 MPa
    nu = 0.3;
    Ri= 0.15*phun("M"); # hole radius
    Re = 2*Ri; # outer radius
    H = 0.01*phun("M") # thickness of the plate
    nRadial, nCircumferential=6, 3;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(vec(x[1:2]));
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r = norm(vec(x[1:2]));
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r = norm(vec(x[1:2]));
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(vec(x[1:2]));
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(vec(x[1:2]));
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(vec(x[1:2]));
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigyderrs = Dict{Symbol, FFltVec}()

    nelems = []
    for extrapolation in [:meanonly]
        sigyderrs[extrapolation] = FFltVec[]
        nelems = []
        for ref in [1]
            Thickness = H
            # Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = H8elliphole(Ri, Ri, Re, Re, Thickness,
            2^ref*nCircumferential, 2^ref*nCircumferential, 2^ref*nRadial, 1)
            fens,fes = H8toH20(fens,fes)
            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H20)
            # @async run(`"paraview.cexe" $File`)

            println("My mesh=>$((count(fens), count(fes)))")
            #
            # output = import_ABAQUS("plane_w_hole_m_debug.inp")
            # fens1,fes1 = output["fens"], output["fesets"][1]
            # println("Matlab mesh=>$((count(fens1), count(fes1[1])))")
            #
            #  fens3, newfes1, fes2 = mergemeshes(fens,fes, fens1,fes1[1], tolerance)
            #  fes3 = cat(2, newfes1)
            #  println("Merged mesh=>$((count(fens3), count(fes3)))")

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            # setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)

            bdryfes = meshboundary(fes);
            # ixl = selectelem(fens, bdryfes, plane=[1.0, 0.0, 0.0, Re], thickness=tolerance);
            ixl = selectelem(fens, bdryfes, box=[Re, Re, -Inf, +Inf, -Inf, +Inf], inflate = tolerance);
            elxfemm =  FEMMBase(IntegDomain(subset(bdryfes,ixl), GaussRule(2, 2)))
            function pfunx(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                forceout[1] = sigmaxx(XYZ)
                forceout[2] = sigmaxy(XYZ)
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfunx);
            Fx = distribloads(elxfemm, geom, u, fi, 2);
            # iyl = selectelem(fens, bdryfes, plane=[0.0, 1.0, 0.0, Re], thickness=tolerance);
            iyl = selectelem(fens, bdryfes, box=[-Inf, +Inf, Re, Re, -Inf, +Inf], inflate = tolerance);
            elyfemm =  FEMMBase(IntegDomain(subset(bdryfes,iyl), GaussRule(2, 2)))
            function pfuny(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                forceout[1] = sigmaxy(XYZ)
                forceout[2] = sigmayy(XYZ)
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfuny);
            Fy = distribloads(elyfemm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3, 2)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(Fx + Fy)
            scattersysvec!(u, U[:])
            println("oof load = $(norm(Fx + Fy, 2))")

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, 00.0], inflate=tolerance);
            pointu = zeros(FFlt,length(nlA),3)
            gathervalues_asmat!(u, pointu, nlA);
            println("disp@A = $(pointu/phun("mm")) [MM]")
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, 0.0], inflate=tolerance);
            pointu = zeros(FFlt,length(nlB),3)
            gathervalues_asmat!(u, pointu, nlB);
            println("disp@B = $(pointu/phun("mm")) [MM]")
            nlC = selectnode(fens, box=[Re, Re, Re, Re, Thickness, Thickness], inflate=tolerance);
            pointu = zeros(FFlt,length(nlC),3)
            gathervalues_asmat!(u, pointu, nlC);
            println("disp@C = $(pointu/phun("mm")) [MM]")

            nlAallz = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlBallz = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1; nodevalmethod = :invdistance, reportat = extrapolation)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2; nodevalmethod = :invdistance, reportat = extrapolation)
            sigyA = mean(sigy.values[nlAallz,1], dims = 1)[1]
            sigyAtrue = sigmayy([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlBallz,1], dims = 1)[1]
            sigxBtrue = sigmaxx([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            # println("$extrapolation, $(count(fes)), $(sigyd/phun("MPa"))")
            # push!(nelems, count(fes))
            # push!(sigyderrs[extrapolation], abs(sigyd/sigma_yD - 1.0))
            File =  "a.vtk"
            vtkexportmesh(File, fes.conn, geom.values,
            FinEtools.MeshExportModule.H20; vectors=[("u", u.values)],
            scalars=[("sigmax", sigx.values/phun("MEGA*PA")),
            ("sigmay", sigy.values/phun("MEGA*PA"))])
            @async run(`"paraview.exe" $File`)
        end
    end

    # df = DataFrame(nelems=vec(nelems),
    #     sigyderrtrend=vec(sigyderrs[:extraptrend]),
    #     sigyderrdefault=vec(sigyderrs[:extrapmean]))
    # File = "LE1NAFEMS_MSH8_convergence.CSV"
    # CSV.write(File, df)
    # @async run(`"paraview.exe" $File`)

end # plate_w_hole_RECT_H20_convergence

function plate_w_hole_RECT_MSH8_convergence()
    E = 210000*phun("MEGA*PA");# 210e3 MPa
    nu = 0.3;
    Ri= 0.1*phun("M"); # hole radius
    Re = 2*Ri; # outer radius
    H = 0.01*phun("M") # thickness of the plate
    nRadial, nCircumferential=6, 3;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigyderrs = Dict{Symbol, FFltVec}()

    nelems = []
    for extrapolation in [:extraptrend :extrapmean]
        sigyderrs[extrapolation] = FFltVec[]
        nelems = []
        for ref in [2]
            # Thickness = H
            Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = H8elliphole(Ri, Ri, Re, Re, Thickness,
            2^ref*nCircumferential, 2^ref*nCircumferential, 2^ref*nRadial, 1)

            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H8)
            # @async run(`"paraview.exe" $File`)

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            # setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)

            bdryfes = meshboundary(fes);
            ixl = selectelem(fens, bdryfes, plane=[1.0, 0.0, 0.0, Re], thickness=tolerance);
            elxfemm =  FEMMBase(IntegDomain(subset(bdryfes,ixl), GaussRule(2, 2)))
            function pfunx(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                forceout[1] = sigmaxx(XYZ)
                forceout[2] = sigmaxy(XYZ)
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfunx);
            Fx = distribloads(elxfemm, geom, u, fi, 2);
            iyl = selectelem(fens, bdryfes, plane=[0.0, 1.0, 0.0, Re], thickness=tolerance);
            elyfemm =  FEMMBase(IntegDomain(subset(bdryfes,iyl), GaussRule(2, 2)))
            function pfuny(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                forceout[1] = -sigmaxy(XYZ)
                forceout[2] = sigmayy(XYZ)
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfuny);
            Fy = distribloads(elyfemm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinearMSH8(MR, IntegDomain(fes, GaussRule(3, 2)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(Fx + Fy)
            scattersysvec!(u, U[:])

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            # thecorneru = zeros(FFlt,length(nlA),3)
            # gathervalues_asmat!(u, thecorneru, nl);
            # thecorneru = mean(thecorneru, 1)[1]/phun("mm")
            # println("displacement = $(thecorneru) vs -0.10215 [MM]")

            println("Extrapolation: $( extrapolation )")
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1; nodevalmethod = :averaging, reportat = extrapolation)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2; nodevalmethod = :averaging, reportat = extrapolation)
            sigyA = mean(sigy.values[nlA,1], dims = 1)[1]
            sigyAtrue = sigmatt([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlB,1], dims = 1)[1]
            sigxBtrue = sigmatt([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            # println("$extrapolation, $(count(fes)), $(sigyd/phun("MPa"))")
            # push!(nelems, count(fes))
            # push!(sigyderrs[extrapolation], abs(sigyd/sigma_yD - 1.0))
            File =  "a.vtk"
            vtkexportmesh(File, fes.conn, geom.values,
            FinEtools.MeshExportModule.H8; vectors=[("u", u.values)],
            scalars=[("sigmax", sigx.values/phun("MEGA*PA")),
            ("sigmay", sigy.values/phun("MEGA*PA"))])
            @async run(`"paraview.exe" $File`)
        end
    end

    # df = DataFrame(nelems=vec(nelems),
    #     sigyderrtrend=vec(sigyderrs[:extraptrend]),
    #     sigyderrdefault=vec(sigyderrs[:extrapmean]))
    # File = "LE1NAFEMS_MSH8_convergence.CSV"
    # CSV.write(File, df)
    # @async run(`"paraview.exe" $File`)

end # plate_w_hole_RECT_MSH8_convergence

function plate_w_hole_T10_PE_convergence()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.49995;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential=3, 5;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    sigxderrs = Dict{Symbol, FFltVec}()
    sigyderrs = Dict{Symbol, FFltVec}()
    numelements = []
    numnodes = []
    for extrapolation in [:extraptrend :extrapmean]
        sigxderrs[extrapolation] = FFltVec[]
        sigyderrs[extrapolation] = FFltVec[]
        numelements = []
        numnodes = []
        for ref in 0:1:2
            # Thickness = H
            Thickness = H/2^ref
            tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

            fens,fes = T10block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 1)

            bdryfes = meshboundary(fes);
            icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

            for i=1:count(fens)
                t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
                fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
            end

            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, fens.xyz,
            #     FinEtools.MeshExportModule.H8)
            # @async run(`"paraview.exe" $File`)

            geom = NodalField(fens.xyz)
            u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

            l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 2, 0.0)
            l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 1, 0.0)
            # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)
            # If this was enabled, the plane-strain  constraint would be enforced.
            l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
            setebc!(u,l1,true, 3, 0.0)

            applyebc!(u)
            numberdofs!(u)
            el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), TriRule(3)))
            function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
                local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
                nx = XYZ[1]/r; ny = XYZ[2]/r
                # local sx, sy, txy
                # sx, sy, txy = sigmaxx(XYZ), sigmayy(XYZ), sigmaxy(XYZ)
                # sn = sx * nx^2 + sy * ny^2 + 2 * nx * ny * txy
                # tn = -(sx - sy) * nx * ny + (nx^2 - ny^2) * txy
                # forceout[1] = sn * nx - tn * ny
                # forceout[2] = sn * ny + tn * nx
                # forceout[3] = 0.0
                forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
                forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
                forceout[3] = 0.0
                return forceout
            end
            fi = ForceIntensity(FFlt, 3, pfun);
            F2 = distribloads(el1femm, geom, u, fi, 2);

            MR = DeforModelRed3D

            material = MatDeforElastIso(MR, E, nu)

            femm = FEMMDeforLinear(MR, IntegDomain(fes, TetRule(4)), material)

            # The geometry field now needs to be associated with the FEMM
            femm = associategeometry!(femm, geom)

            K = stiffness(femm, geom, u)
            K = cholesky(K)
            U = K\(F2)
            scattersysvec!(u, U[:])

            nlA = selectnode(fens, box=[Ri, Ri, 0.0, 0.0, 0.0, Thickness], inflate=tolerance);
            nlB = selectnode(fens, box=[0.0, 0.0, Ri, Ri, 0.0, Thickness], inflate=tolerance);
            # thecorneru = zeros(FFlt,length(nlA),3)
            # gathervalues_asmat!(u, thecorneru, nl);
            # thecorneru = mean(thecorneru, 1)[1]/phun("mm")
            # println("displacement = $(thecorneru) vs -0.10215 [MM]")

            println("Extrapolation: $( extrapolation )")
            sigx = fieldfromintegpoints(femm, geom, u, :Cauchy, 1)
            sigy = fieldfromintegpoints(femm, geom, u, :Cauchy, 2)
            sigyA = mean(sigy.values[nlA,1], dims = 1)[1]
            sigyAtrue = sigmatt([Ri, 0.0, 0.0])
            println("sig_y@A =$(sigyA/phun("MPa")) vs $(sigyAtrue/phun("MPa")) [MPa]")
            sigxB = mean(sigx.values[nlB,1], dims = 1)[1]
            sigxBtrue = sigmatt([0.0, Ri, 0.0])
            println("sig_x@B =$(sigxB/phun("MPa")) vs $(sigxBtrue/phun("MPa")) [MPa]")
            push!(numnodes, count(fens))
            push!(numelements, count(fes))
            push!(sigxderrs[extrapolation], abs(sigxB/sigxBtrue - 1.0))
            push!(sigyderrs[extrapolation], abs(sigyA/sigyAtrue - 1.0))
            # File =  "a.vtk"
            # vtkexportmesh(File, fes.conn, geom.values,
            # FinEtools.MeshExportModule.H8; vectors=[("u", u.values)],
            # scalars=[("sigmax", sigx.values/phun("MEGA*PA"))])
            # @async run(`"paraview.exe" $File`)
        end
    end

    File = "plate_w_hole_PE_T10_convergence.CSV"
    savecsv(File, numelements=vec(numelements), numnodes=vec(numnodes), sigxderrtrend=vec(sigxderrs[:extraptrend]), sigxderrdefault=vec(sigxderrs[:extrapmean]), sigyderrtrend=vec(sigyderrs[:extraptrend]), sigyderrdefault=vec(sigyderrs[:extrapmean]))

end # plate_w_hole_T10_PE_convergence

function plate_w_hole_T10_stress()
    E = 2.4*phun("MEGA*PA");# 210e3 MPa
    nu = 0.3;
    Re = 0.3*phun("M"); # outer radius
    Ri= 0.1*phun("M"); # hole radius
    H = 0.1*phun("M") # thickness of the plate
    nRadial, nCircumferential, nThickness = 6, 8, 1;
    sigma0=1*phun("MEGA*PA");

    function sigmaxx(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0*(1-Ri^2/r^2*(3/2*cos(2*th)+cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmayy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*cos(2*th)-cos(4*th))+3/2*Ri^4/r^4*cos(4*th));
    end
    function sigmaxy(x)
        local r=norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0*(Ri^2/r^2*(1/2*sin(2*th)+sin(4*th))-3/2*Ri^4/r^4*sin(4*th));
    end
    function sigmarr(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1-Ri^2/r^2) + sigma0/2*(1-4*Ri^2/r^2+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmatt(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return sigma0/2*(1+Ri^2/r^2) - sigma0/2*(1+3*Ri^4/r^4)*cos(2*th)
    end
    function sigmart(x)
        local r = norm(x[1:2]);
        local th = atan(x[2],x[1]);
        return -sigma0/2*(1+2*Ri^2/r^2-3*Ri^4/r^4)*sin(2*th)
    end

    convergencestudy = FDataDict[]
    for ref in 0:1:2
        println("ref = $(ref)")
        # Thickness = H
        Thickness = H/2^ref
        tolerance = Thickness/2^ref/1000.; # Geometrical tolerance

        fens,fes = T10block(1.0, pi/2, Thickness, 2^ref*nRadial, 2^ref*nCircumferential, 2^ref*nThickness)

        bdryfes = meshboundary(fes);
        icl = selectelem(fens, bdryfes, box=[1.0, 1.0, 0.0, pi/2, 0.0, Thickness], inflate=tolerance);

        for i=1:count(fens)
            t=fens.xyz[i,1]; a=fens.xyz[i,2]; z=fens.xyz[i,3]
            fens.xyz[i,:] = [(t*Re+(1-t)*Ri)*cos(a), (t*Re+(1-t)*Ri)*sin(a), z];
        end

        geom = NodalField(fens.xyz)
        u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

        l1 =selectnode(fens; box=[0.0, Inf, 0.0, 0.0, 0.0, Thickness], inflate = tolerance)
        setebc!(u,l1,true, 2, 0.0)
        l1 =selectnode(fens; box=[0.0, 0.0, 0.0, Inf, 0.0, Thickness], inflate = tolerance)
        setebc!(u,l1,true, 1, 0.0)
        # Plane-stress constraint: assume the plane z=0 is the plane of symmetry of the plate
        l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, 0.0, 0.0], inflate = tolerance)
        setebc!(u,l1,true, 3, 0.0)
        # If this was enabled, the plane-strain  constraint would be enforced.
        # l1 =selectnode(fens; box=[0.0, Inf, 0.0, Inf, Thickness, Thickness], inflate = tolerance)
        # setebc!(u,l1,true, 3, 0.0)

        applyebc!(u)
        numberdofs!(u)
        el1femm =  FEMMBase(IntegDomain(subset(bdryfes,icl), TriRule(3)))
        function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
            local r = sqrt(XYZ[1]^2 + XYZ[2]^2)
            nx = XYZ[1]/r; ny = XYZ[2]/r
            forceout[1] = sigmarr(XYZ) * nx - sigmart(XYZ) * ny
            forceout[2] = sigmarr(XYZ) * ny + sigmart(XYZ) * nx
            forceout[3] = 0.0
            return forceout
        end
        fi = ForceIntensity(FFlt, 3, pfun);
        F2 = distribloads(el1femm, geom, u, fi, 2);

        MR = DeforModelRed3D

        material = MatDeforElastIso(MR, E, nu)

        femm = FEMMDeforLinear(MR, IntegDomain(fes, TetRule(4)), material)

        # The geometry field now needs to be associated with the FEMM
        femm = associategeometry!(femm, geom)

        K = stiffness(femm, geom, u)
        K = cholesky(K)
        U = K\(F2)
        scattersysvec!(u, U[:])

        stressfields = elemfieldfromintegpoints(femm, geom, u, :Cauchy, collect(1:6))

        push!(convergencestudy, FDataDict(
        "elementsize"=> 1.0 / 2^ref,
        "fens"=>fens,
        "fes"=>fes,
        "geom"=>geom,
        "u"=>u,
        "femm"=>femm,
        "stressfields"=>[stressfields],
        "tolerance"=>tolerance)
        )
    end # for ref in

    # File = "mplate_w_hole_T10m_stress"
    # open(File * ".jls", "w") do file
    #     serialize(file, convergencestudy)
    # end

end # plate_w_hole_T10_stress

function allrun()
    println("#####################################################")
    println("# plate_w_hole_T10_PE_convergence ")
    plate_w_hole_T10_PE_convergence()
    # println("#####################################################")
    # println("# plate_w_hole_T10_stress ")
    # plate_w_hole_T10_stress()
    # println("#####################################################")
    # println("# plate_w_hole_RECT_MSH8_convergence ")
    # plate_w_hole_RECT_MSH8_convergence()
    # println("#####################################################")
    # println("# plate_w_hole_H20_stress ")
    # plate_w_hole_H20_stress()
    # println("#####################################################")
    # println("# plate_w_hole_MSH8_convergence ")
    # plate_w_hole_MSH8_convergence()
    # println("#####################################################")
    # println("# plate_w_hole_MSH8_PE_convergence ")
    # plate_w_hole_MSH8_PE_convergence()
    # println("#####################################################")
    # println("# plate_w_hole_MST10_convergence ")
    # plate_w_hole_MST10_convergence()
    # println("#####################################################")
    # println("# plate_w_hole_MST10_PE_convergence ")
    # plate_w_hole_MST10_PE_convergence()
    # println("#####################################################")
    # println("# plate_w_hole_MST10_stress ")
    # plate_w_hole_MST10_stress()
    # println("#####################################################")
    # println("# plate_w_hole_RECT_H20_convergence ")
    # plate_w_hole_RECT_H20_convergence()
    return true
end # function allrun

end # module plate_w_hole_examples


export ruleSPChanceConstraintOutG

const default_atol = 1e-4

"""
Computes normalizing constant and central moments of a truncated Gaussian distribution.
"""
function truncatedGaussianMoments(m::Float64, V::Float64, a::Float64, b::Float64)
    # Normalize parameters
    sigma = sqrt(clamp(V, tiny, huge))
    alpha = (a - m)/sigma
    beta = (b - m)/sigma

    # Compute normalizing constant
    Phi_alpha = normcdf(alpha)
    Phi_beta = normcdf(beta)
    Z = Phi_beta - Phi_alpha

    # Compute moments if truncation is valid
    if Z < tiny
        # Invalid region; return undefined mean and variance of truncated distribution
        Z    = 0.0
        m_tr = 0.0
        V_tr = 0.0
    else
        phi_alpha = normpdf(alpha)
        phi_beta = normpdf(beta)
        alpha_phi_alpha = clamp(alpha, -huge, huge)*phi_alpha
        beta_phi_beta = clamp(beta, -huge, huge)*phi_beta

        m_tr = m + (phi_alpha - phi_beta)/Z*sigma
        V_tr = V*( 1.0 + (alpha_phi_alpha - beta_phi_beta)/Z - ((phi_alpha - phi_beta)/Z)^2 )
    end

    return (Z, m_tr, V_tr)
end

#-------------
# Update rules
#-------------

function ruleSPChanceConstraintOutG(msg_out::Message{<:Gaussian, Univariate}, G::Tuple, epsilon::Float64; atol=default_atol)
    (m_bw, V_bw) = unsafeMeanCov(msg_out.dist)
    (xi_bw, W_bw) = unsafeWeightedMeanPrecision(msg_out.dist)

    # Default forward message is vague; only corrected when constraint is active
    xi_fw = 0.0
    W_fw = tiny

    # Correct marginal belief if constraint is active (1-D only)
    (min_G, max_G) = G # Extract minimum and maximum of safe region G
    (Phi_G, m_G, V_G) = truncatedGaussianMoments(m_bw, V_bw, min_G, max_G) # Compute statistics (and normalizing constant) of q in G
    if epsilon <= 1.0 - Phi_G # If constraint is active
        # Initialize statistics of uncorrected belief
        m_tilde = m_bw
        V_tilde = V_bw
        for i = 1:100 # Iterate at most this many times
            (Phi_lG, m_lG, V_lG) = truncatedGaussianMoments(m_tilde, V_tilde, -Inf, min_G) # Statistics for q in region left of G
            (Phi_rG, m_rG, V_rG) = truncatedGaussianMoments(m_tilde, V_tilde, max_G, Inf) # Statistics for q in region right of G

            # Compute moments of non-G region as a mixture of left and right truncations
            Phi_nG = Phi_lG + Phi_rG
            m_nG = Phi_lG/Phi_nG*m_lG + Phi_rG/Phi_nG*m_rG
            V_nG = Phi_lG/Phi_nG*(V_lG + m_lG^2) + Phi_rG/Phi_nG*(V_rG + m_rG^2) - m_nG^2

            # Compute moments of corrected belief as a mixture of G and non-G regions
            m_tilde = (1.0-epsilon)*m_G + epsilon*m_nG
            V_tilde = (1.0-epsilon)*(V_G + m_G^2) + epsilon*(V_nG + m_nG^2) - m_tilde^2

            # Re-compute statistics (and normalizing constant) of corrected belief
            (Phi_G, m_G, V_G) = truncatedGaussianMoments(m_tilde, V_tilde, min_G, max_G)

            if (1.0 - Phi_G) < (1.0 + atol)*epsilon
                break # Break the loop if the belief is sufficiently corrected
            end
        end

        # Convert moments of corrected belief to canonical form
        W_tilde = cholinv(V_tilde)
        xi_tilde = W_tilde*m_tilde

        # Compute canonical parameters of forward message
        xi_fw = xi_tilde - xi_bw
        W_fw  = W_tilde - W_bw
    end

    return Message(Univariate, GaussianWeightedMeanPrecision, xi=xi_fw, w=W_fw)
end

#--------------------------
# Custom inbounds collector
#--------------------------

function collectSumProductNodeInbounds(node::ChanceConstraint, entry::ScheduleEntry)
    inbounds = Any[]

    interface_to_schedule_entry = current_inference_algorithm.interface_to_schedule_entry
    for node_interface in node.interfaces
        inbound_interface = ultimatePartner(node_interface)
        if isa(inbound_interface.node, Clamp)
            # Hard-code outbound message of constant node in schedule
            push!(inbounds, assembleClamp!(inbound_interface.node, Message))
        else
            # Collect message from previous result
            push!(inbounds, interface_to_schedule_entry[inbound_interface])
        end
    end

    # Push function and value to calling signature; these need to be defined in the scope of the user
    push!(inbounds, Dict{Symbol, Any}(:G => node.G,
                                      :keyword => false))
    push!(inbounds, Dict{Symbol, Any}(:epsilon => node.epsilon,
                                      :keyword => false))
    if node.atol != nothing
        push!(inbounds, Dict{Symbol, Any}(:atol => node.atol,
                                          :keyword => true))
    end

    return inbounds
end


# Calcium buffering dynamics
include("common.jl")

# Calcium buffering parameters
@with_kw struct CABUFParams
    KM_CA
    ET
end

_β_ca(ca, KM, ET) = _mm((ca + KM)^2, KM * ET)
_β_ca(ca, p::CABUFParams) = _β_ca(ca, p.KM_CA, p.ET)


using Distributed
@everywhere using Mamba, LinearAlgebra

## Data
dyes = Dict{Symbol, Any}(
  :y =>
    [1545, 1440, 1440, 1520, 1580,
     1540, 1555, 1490, 1560, 1495,
     1595, 1550, 1605, 1510, 1560,
     1445, 1440, 1595, 1465, 1545,
     1595, 1630, 1515, 1635, 1625,
     1520, 1455, 1450, 1480, 1445],
  :batches => 6,
  :samples => 5
)

dyes[:batch] = vcat([fill(i, dyes[:samples]) for i in 1:dyes[:batches]]...)
dyes[:sample] = vcat(fill(collect(1:dyes[:samples]), dyes[:batches])...)

## Model Specification

model = Model(

  y = Stochastic(1,
    (mu, batch, s2_within) -> MvNormal(mu[batch], sqrt(s2_within)),
    false
  ),

  mu = Stochastic(1,
    (theta, batches, s2_between) -> Normal(theta, sqrt(s2_between))
  ),

  theta = Stochastic(
    () -> Normal(0, 1000)
  ),

  s2_within = Stochastic(
    () -> InverseGamma(0.001, 0.001)
  ),

  s2_between = Stochastic(
    () -> InverseGamma(0.001, 0.001)
  )

)

## Initial Values
inits = [
  Dict(:y => dyes[:y], :theta => 1500, :s2_within => 1, :s2_between => 1,
       :mu => fill(1500, dyes[:batches])),
  Dict(:y => dyes[:y], :theta => 3000, :s2_within => 10, :s2_between => 10,
       :mu => fill(3000, dyes[:batches]))
]

## Sampling Schemes
scheme = [NUTS([:mu, :theta]),
          Slice([:s2_within, :s2_between], 1000.0)]

scheme2 = [MALA(:theta, 50.0),
           MALA(:mu, 50.0, Matrix{Float64}(I, dyes[:batches], dyes[:batches])),
           Slice([:s2_within, :s2_between], 1000.0)]

scheme3 = [HMC(:theta, 10.0, 5),
           HMC(:mu, 10.0, 5, Matrix{Float64}(I, dyes[:batches], dyes[:batches])),
           Slice([:s2_within, :s2_between], 1000.0)]

scheme4 = [RWM(:theta, 50.0, proposal=Cosine),
           RWM(:mu, 50.0),
           Slice([:s2_within, :s2_between], 1000.0)]

## MCMC Simulations
setsamplers!(model, scheme)
sim = mcmc(model, dyes, inits, 10000, burnin=2500, thin=2, chains=2)
describe(sim)

setsamplers!(model, scheme2)
sim2 = mcmc(model, dyes, inits, 10000, burnin=2500, thin=2, chains=2)
describe(sim2)

setsamplers!(model, scheme3)
sim3 = mcmc(model, dyes, inits, 10000, burnin=2500, thin=2, chains=2)
describe(sim3)

setsamplers!(model, scheme4)
sim4 = mcmc(model, dyes, inits, 10000, burnin=2500, thin=2, chains=2)
describe(sim4)


module reproSim
using Pkg
Pkg.add("Distributions")
Pkg.add("StatsBase")
Pkg.add("CSV")
Pkg.add("DataFrames")
using Distributions
using StatsBase
using CSV
using DataFrames
function getPop(n)
    """return array of length n. with index, x, y chomosome values"""
return hcat(1:n,rand(Normal(0,0.5),n),rand(Normal(0,0.5),n))
end

function fselect(pop,n)
    """"returns population with additional column for fecundity"""
        # get samples
    fitList = sample(pop[:,1], Weights(pop[:, 4]), n, replace=true)
    #add fecundity column to pop.
 pop = hcat(pop,[sum(i .== fitList) for i in pop[:,1]])
 #return pop. without non-reproducers
return pop[pop[:,5] .!= 0.6,:]
end

function sselect(pop,n)
    """" return half of pop. weighted by fitness"""

return pop[Int.(sample(Int.(1:n), Weights(pop[:, 5]), Int(n/2), replace=false)),:]
end

function ggselect(pop,n)
    """" return half of pop. weighted by fitness"""

return pop[Int.(sample(Int.(1:n/2), Weights(pop[:, 1]), Int(n/2), replace=true)),:]
end

function clone(pop,n,mut)
    x = []
    y = []

    for mother in 1:size(pop)[1] # for each mother

        for fecundity in 1:pop[mother,7] #according to fecundity
            push!(x,pop[mother,2])
            push!(y,pop[mother,3])

        end
    end

    return hcat(1:n,x .+ rand(Normal(0,mut),n),y .+ rand(Normal(0,mut),n))

end

function sex(pop,fathers,n,mut)
    x = []
    y = []

    for mother in 1:size(pop)[1] # for each mother
        father = fathers[mother,:] # pick a father

        for fecundity in 1:pop[mother,7] #according to fecundity
            push!(x,pop[mother,rand(2:3)])
            push!(y,father[rand(2:3)])
        end
    end

    return hcat(1:n,x .+ rand(Normal(0,mut),n), y .+ rand(Normal(0,mut),n))
end

function fitPop(pop,f,s,gg,theta)
    z = (pop[:,2] .+ pop[:,3]) ./2

    for c in [1-f,1-s,1-gg]
        pop= hcat(pop, c .+ ((1 .-c) .*exp.((-1/2) .*(z .- theta) .^2)))
    end

    return pop
end

function getW(pop,theta)
    z = pop[:,2] .+ pop[:,3] ./2

    return [mean(exp.((-1/2) .*(z .- theta) .^2)) var(z)]
end

function sim(vartheta,lambda,rMode,mut,n,generations,f,s,gg)
    theta = 0
    noise = vartheta*(1-(lambda^2))
    pop = getPop(n)
    pop = fitPop(pop,f,s,gg, theta)
    dat = Array{Float64}(undef, 0, 2)
    r = Int(n/2)

    for t in 1:generations

        pop = sselect(pop,n)

        if rMode == "clone"
            pop = fselect(pop,n)
            pop = clone(pop,n,mut)
        else
            fathers = ggselect(pop,n)
            pop = fselect(pop,n)
            pop = sex(pop,fathers,n,mut)
        end

        theta = theta*lambda +rand(Normal(0,noise))
        pop = fitPop(pop,f,s,gg, theta)
        dat = vcat(dat,getW(pop,theta))
    end

    wg = geomean(dat[:,1])
    wa = mean(dat[:,1])
    meanv = mean(dat[:,2])
    vmean = var(dat[:,1])

    return [wg wa meanv vmean vartheta lambda rMode mut n generations f s gg]
end

end # module


#!/usr/bin/env julia

using Luxor

function ellipse2c(focus1::Point, focus2::Point, k; stepvalue=pi/100)
    # k is sum of distances to focii of any points on the ellipse)
    a = k/2  # Major axis
    cpoint = midpoint(focus1, focus2)
    dc = norm(focus1, cpoint) # Distance of the foci to the center
    b = sqrt(abs(a^2 - dc^2)) # Minor axis
    phi = slope(O, focus2) # Angle between the major axis and the x-axis
    drawing = false
    for t in 0:stepvalue:2pi
        xt = cpoint.x + a * cos(t) * cos(phi) - b * sin(t) * sin(phi)
        yt = cpoint.y + a * cos(t) * sin(phi) + b * sin(t) * cos(phi)
        if drawing
            line(Point(xt, yt))
        else
            move(Point(xt, yt))
            drawing = true
        end
    end
    closepath()
    stroke()
end

"""
Draw a logo with ellipse and colored circles
"""
function draw_logo(fname)
    Drawing(500, 500, fname)
    setline(3)
    origin()
    cpoints = ngon(O, 200, 3, vertices=true)

    ellipse2c(O, cpoints[1], 280)
    ellipse2c(O, cpoints[2], 280)
    ellipse2c(O, cpoints[3], 280)

    darker_purple = (0.584, 0.345, 0.698)
    lighter_purple  = (0.667, 0.475, 0.757)
    darker_green  = (0.22, 0.596, 0.149)
    lighter_green  = (0.376, 0.678, 0.318)
    darker_red  = (0.796, 0.235, 0.2)
    lighter_red  = (0.835, 0.388, 0.361)

    ballradius = 40
    sethue(darker_red...)
    circle(cpoints[1], 0.75ballradius, :fill)
    sethue(lighter_red...)
    circle(cpoints[1], 0.65ballradius, :fill)

    sethue(darker_green...)
    circle(cpoints[2], 0.75ballradius, :fill)
    sethue(lighter_green...)
    circle(cpoints[2], 0.65ballradius, :fill)

    sethue(darker_purple...)
    circle(cpoints[3], 0.75ballradius, :fill)
    sethue(lighter_purple...)
    circle(cpoints[3], 0.65ballradius, :fill)

    sethue("gold")
    star(O, 35, 12, 0.7, pi/3, :fill)
    sethue("yellow")
    star(O, 35, 15, 0.3, 0, :fill)
    finish()
    preview()
end

draw_logo("/tmp/logo.png")


export Size, Rect, width, height, isinside, isintersecting,
       origin, center

import Base: size, +, *, /

struct Size{T <: Number}
  width::T
  height::T
end

struct Rect{T <: Number}
  x::T
  y::T
  width::T
  height::T
end

size(r::Rect) = Size(r.width, r.height)
width(r::Union{Rect, Size}) = r.width
height(r::Union{Rect, Size}) = r.height

+(p::Point, sz::Size) = Point(p.x + sz.width, p.y + sz.height)
+(sz::Size, p::Point) = p + sz
*(sz::Size, k::Number) = Size(sz.width * k, sz.height * k)
*(k::Number, sz::Size) = sz * k
/(sz::Size, k::Number) = Size(sz.width/k, sz.height/k)

origin(r::Rect) = Point(r.x, r.y)
center(r::Rect) = origin(r) + 0.5*size(r)

function isinside(r::Rect, p::Point)
    origin(r) ≤ p ≤ origin(r) + size(r)
end

function isintersecting(r::Rect, s::Rect)
	d = abs(center(s) - center(r))
	h1 = size(r)/2
	h2 = size(s)/2

	d.dx <= h1.width + h2.width &&
	d.dy <= h1.height + h2.height
end


# function for parameters

"""
    check_parameters(par::PTParameters)

Check errors in `par`.
Return `nothing` if there is no error.
"""
function check_parameters(par::PTParameters, warn::Bool=false)
   if par.var_p<0
      error("negative phenotypic variance")
   end
   if par.var_g_sim<0
      error("negative QMSim genetic variance")
   end
   if par.h2_poly<0 || par.h2_poly>1 || par.h2_qtl<0 || par.h2_qtl>1 || (par.h2_poly+par.h2_qtl)<0 || (par.h2_poly+par.h2_qtl)>1
      error("heritability out of range")
   end
   if par.rep>0 && (par.rep < par.h2_poly || par.rep < par.h2_qtl || par.rep < (par.h2_poly + par.h2_qtl))
      error("repeatability>0 but smaller than heritability")
   end
   if par.rep < 0 || par.rep > 1
      error("repeatability out of range")
   end

   if warn && get_var_pe(par)<=0.0
      @warn "PE variance is set to 0 (rep==0 or rep==h2). Be aware that the repeatability model would not be appropriate if you have repeated records."
   end
end

function get_var_poly(par::PTParameters)
   return par.h2_poly * par.var_p
end

function get_var_qtl(par::PTParameters)
   return par.h2_qtl * par.var_p
end

function get_var_g_sim(par::PTParameters)
   return par.var_g_sim
end

function get_var_pe(par::PTParameters)
   var_u = get_var_poly(par) + get_var_qtl(par)
   return max((par.rep * par.var_p)-var_u, 0.0)
end

function get_var_error(par::PTParameters)
   return par.var_p - (get_var_poly(par) + get_var_qtl(par) + get_var_pe(par))
end


"""
绘制bubble贡献的图
"""

using Plots

"""
bubb的贡献
"""
function bubb(x, y)
    eps_k = x
    neps_kp = y
    lamb = 1e-2
    if abs(eps_k - neps_kp) < 1.e-10
        #如果两个数值比较接近, Pi^{-}和Pi^{+}的公式完全一样，就是第二个能量要加个负号
        # lim (eps_k -> -eps_kp) Pi^{-} =
        # 1/T (e^{eps/T} (-eps/T*e^{eps/T} + eps/T + e^{eps/T} + 1))
        #/ (e^{eps/T} + 1)^3
        bval = eps_k / lamb
        if bval > 25
            #@info "数值不稳定"
            return 0.
        end
        expb = exp(bval)
        num = expb * (-bval * expb + bval + expb + 1)
        den = (1+expb)^3
        d_val = num / den / lamb
    else
        if (eps_k / lamb) > 25
            #@info "数值不稳定"
            num_left = 0.
        else
            #e^{epsilon_k / T}
            exp_k_t = exp(eps_k / lamb)
            num_left = eps_k / lamb * exp_k_t / ((1 + exp_k_t)^2)
        end
        if (neps_kp / lamb) > 25
            #@info "数值不稳定"
            num_righ = 0.
        else
            #e^{-epsilon_{-k+q} / T}
            exp_nkp_t = exp(neps_kp / lamb)
            num_righ = neps_kp / lamb * exp_nkp_t /
            ((1 + exp_nkp_t)^2)
        end
        d_val = (num_left - num_righ) / (eps_k - neps_kp)
    end # end if 小区域的贡献计算完
    return d_val
end

surface(-4:0.1:4, -4:0.1:4, bubb)

savefig("bubb_contrib2.png")


abstract type MSetSolver end
function build!(solver::MSetSolver, reward::Matrix{Distribution})
  nothing
end

struct MSet <: CombinatorialInstance{Int}
  # Mandatory properties.
  n_arms::Int
  optimal_average_reward::Float64
  m::Int

  # Probability distributions for the arm rewards.
  reward::Vector{Distribution}

  # Internal solver.
  solver::MSetSolver

  function MSet(reward::Vector{Distribution}, m::Int, solver::MSetSolver)
    # Basically, any combination of parameters is allowable, even if m is
    # larger than the number of arms.

    # Prepare the solver to be used (if required).
    build!(solver, m, length(reward))

    avg_reward = Dict{Int, Float64}(i => mean(reward[i...]) for i in all_arm_indices(reward))
    opt_sol = solve_linear(solver, avg_reward)
    opt = sum(avg_reward[i] for i in opt_sol)

    # Done!
    return new(length(reward), opt, m, reward, solver)
  end
end

function is_feasible(instance::MSet, arms::Vector{Int})
  if length(arms) > instance.m
    return false
  end

  if length(arms) != length(unique(arms))
    return false
  end

  return true
end

function is_partially_acceptable(instance::MSet, arms::Vector{Int})
  return is_feasible(instance, arms)
end


"""
Naive implementation of Dynamic Mode Decomposition with control (DMDc)

Defaults to predicting the second input as a function of the first.
    Can also be passed a 'mode', "discrete" or "continuous" to process the
    data 'X' to produce the variable to be solved for
"""
function dmdc(X, X_grad=nothing, U=nothing; mode="discrete")
    if X_grad == nothing
        if mode=="discrete"
            X_grad = X[:,2:end]
            X = X[:,1:end-1]
        elseif mode=="continuous"
            X_grad = numerical_derivative(X)
        end
    end

    U == nothing ? (Ω = X) : (Ω = vcat(X,U))
    # Solve for dynamics and separate out
    AB = X_grad / Ω
    if U == nothing
        A = AB
        B = nothing
    else
        n, m = size(X)
        A = AB[:, 1:n]
        B = AB[:, n+1:end]
    end

    return (A=A, B=B)
end

export dmdc


@testset "RayClassGroup" begin

  @testset "Big prime" begin
    k, _ = wildanger_field(2, 13)
    Zk = maximal_order(k)
    p = next_prime(fmpz(2)^100)
    r, mr = ray_class_group(ideal(Zk, p), n_quo = 2)
    @test order(domain(mr)) == 2
  end

  @testset "quadratic field" begin

    Qx,x=PolynomialRing(FlintQQ,"x")
    K,a=NumberField(x^2+199,"a")
    O=maximal_order(K)
    C,mC=class_group(O)

    for i=9:13
      R,mR=ray_class_group(ideal(O,i), p_part=3)
      for r in R
        @test r== mR\(mR(r))
      end
      R1,mR1=ray_class_group(ideal(O,i), n_quo=3)
      for r in R1
        @test r== mR1\(mR1(r))
      end
      q,mq=quo(R,3)
      @test Hecke.isisomorphic(R1,q)
    end

    Qx,x=PolynomialRing(FlintQQ,"x")
    K,a=NumberField(x^2-5,"a")
    O=maximal_order(K)
    C,mC=class_group(O, redo = true)
    inf_plc=real_places(K)

    for i=9:13
      R,mR=ray_class_group(ideal(O,i),inf_plc,p_part=2)
      for r in R
        @test r== mR\(mR(r))
      end
      R1,mR1=ray_class_group(ideal(O,i),inf_plc)
      for r in R1
        @test r== mR1\(mR1(r))
      end
      R2,mR2=ray_class_group(ideal(O,i), inf_plc, n_quo=2)
      for r in R2
        @test r== mR2\(mR2(r))
      end
      q,mq=quo(R,2)
      @test Hecke.isisomorphic(q,R2)
      q1,mq1=quo(R1,2)
      @test Hecke.isisomorphic(q1,R2)
    end

  end

  @testset "stable subgroups" begin

    Qx,x=PolynomialRing(FlintQQ,"x");
    f=x^2+1;
    K,a=NumberField(f,"a");
    O=maximal_order(K);
    C,mC=class_group(O);
    r,mr=ray_class_group(ideal(O,3*5*7), n_quo=8);
    act=Hecke._act_on_ray_class(mr);
    x=Hecke.stable_subgroups(r,[8],act,op = quo);
    y=subgroups(r, quotype=[8])
    i=0
    for el in y
      if Hecke.is_stable(act,el[2])
        i+=1
      end
    end
    @test length(x)==i

    x=Hecke.stable_subgroups(r,[2,4],act,op = quo);
    y=subgroups(r, quotype=[2,4])
    i=0
    for el in y
      if Hecke.is_stable(act,el[2])
        i+=1
      end
    end
    @test length(x)==i

    r,mr=ray_class_group(ideal(O,9*19*29), n_quo=9);
    act=Hecke._act_on_ray_class(mr);
    x=Hecke.stable_subgroups(r,[9],act,op = quo);
    y=subgroups(r, quotype=[9])
    i=0
    for el in y
      if Hecke.is_stable(act,el[2])
        i+=1
      end
    end
    @test length(x)==i

    x=Hecke.stable_subgroups(r,[3,9],act,op = quo);
    y=subgroups(r, quotype=[3,9])
    i=0
    for el in y
      if Hecke.is_stable(act,el[2])
        i+=1
      end
    end
    @test length(x)==i
  end

end


"""
    add_tail_and_baseline(wf, fadc, daq)

RDWaveform, GenericFADC, GenericDAQ -> RDWaveform

Extend tail and add baseline of <wf> based on <fadc> and <daq> parameters.

Extended conservatively to ensure enough samples for the future
    sliding trap filter window and the resulting DAQ baseline.
"""
function add_tail_and_baseline(wf::RDWaveform, fadc::GenericFADC, daq::GenericDAQ)
    factor::Integer = uconvert(u"ns", fadc.Δt) / uconvert(u"ns", step(wf.time))
    # double the number of samples should be enough
    # ideally infinite flat baseline and tail
    SolidStateDetectors.add_baseline_and_extend_tail(wf, daq.baseline_length*factor*2, daq.nsamples*factor*2)
end

"""
    differentiate(wf)

RDWaveform -> RDWaveform

Differentiate a waveform using a Biquad filter
"""
function differentiate(wf::RDWaveform)
    diff_biquad_filter = RadiationDetectorDSP.differentiator_filter(T(1)) # We want a gain of 1 here
    filter_output = filt(diff_biquad_filter, wf.value)
    # Implement a method do parse a RDWaveform to `filt`? (ToDo)
    RDWaveform(wf.time, filter_output)
end


module DeconvOptim

export gpu_or_cpu

 # to check wether CUDA is enabled
using Requires
# for fast array regularizers
using Tullio
# optional CUDA dependency
include("requires.jl")

 # for optimization
using Optim
 #mean
using Statistics
using StatsBase
using FFTW
FFTW.set_num_threads(12)
using LineSearches

# possible up_sampling
using Interpolations

# for defining custom derivatives
using ChainRulesCore
using LinearAlgebra

using FillArrays

gpu_or_cpu(x) = Array

include("forward_models.jl")
include("lossfunctions.jl")
include("mappings.jl")
# special CUDA regularizers
include("regularizer_cuda.jl")
include("regularizer.jl")
include("utils.jl")
include("conv.jl")
include("generic_invert.jl")
include("lucy_richardson.jl")
include("deconvolution.jl")
include("analysis_tools.jl")

# refresh Zygote to load the custom rrules defined with ChainRulesCore
using Zygote: gradient

# end module
end


using Distributions
using Random
using LinearAlgebra: diagm
using SMC

struct LinearGaussianInfer{T}
    α::T
    K::Int
    Y::Matrix{Float64}
    end_t::Int
end

function LinearGaussianInfer(K, Y, end_t)
    sigma_dist = LogNormal(log(9.0), 0.1)
    tau_dist = LogNormal(log(1.0), 0.1)
    return LinearGaussianInfer((σ = sigma_dist, τ = tau_dist), K, Y, end_t)
end

function rprior(m::LinearGaussianInfer, K)
    [map(rand, m.α) for _ = 1:K]
end

function dprior(m::LinearGaussianInfer, p)
    sum(map(logpdf, m.α, p))
end

function dprior(m::LinearGaussianInfer, ps::Array)
    [dprior(m, p) for p in ps]
end

function loglik_fn(m::LinearGaussianInfer, p)
    F0(t) = [0.0, 0.0, 0.0]
    G0(t) = [0.0, 0.0, 0.0]
    F(t) = diagm([1.0, 1.0, 1.0])
    G(t) = diagm(([1.0, 1.0, 1.0]))
    Σ(t) = p.σ * diagm(([1.0, 1.0, 1.0]))
    Tau(t) = diagm([p.τ, p.τ, p.τ])
    μ0 = vcat(0.0, 0.0, 0.0)
    Tau0 = 100.0 * diagm([1.0, 1.0, 1.0])

    smc_fns = lgc_smc_model(m.K, F0, F, G0, G, Tau, Σ, μ0, Tau0, m.Y)

    smc_filter = SMC.SMCModel(
        record_history = true,
        rinit = smc_fns.rinit,
        rproposal = smc_fns.rp,
        dinit = smc_fns.dinit,
        dprior = smc_fns.dpr,
        dproposal = smc_fns.dp,
        dtransition = smc_fns.dt,
        dmeasure = smc_fns.dm,
        dpre = smc_fns.dpre,
        threshold = 1.0,
    )

    return smc_filter(Random.GLOBAL_RNG, m.end_t).loglik
end

function (m::LinearGaussianInfer)(p)
    loglik_fn(m, p)
end


n = 20  # Number of observations
p = 5   # Number of parameters

info("Testing ", MOD.pca_components_eig!)
for T in FloatingPointTypes
    X = rand(T, n, p)
    μ = mean(X,1)
    H = (X .- μ)
    Σ = H'H
    for α in (zero(T), convert(T,0.5), one(T)), ϵ in (zero(T), one(T))
        V1, D1 = MOD.pca_components_eig!(copy(H), α, ϵ)
        V2, D2 = MOD.components_eig!(MOD.perturb!(MOD.regularize!(copy(Σ), α, trace(Σ)/p), ϵ))
        @test_approx_eq V1 V2
        @test_approx_eq D1 D2
    end
end

info("Testing ", MOD.pca_components_svd!)
for T in FloatingPointTypes
    X = rand(T, n, p)
    μ = mean(X,1)
    H = (X .- μ)
    V1, D1 = MOD.pca_components_svd!(copy(H))
    V2, D2 = MOD.components_svd!(copy(H))
    @test_approx_eq V1 V2
    @test_approx_eq D1 D2
end

info("Testing ", MOD.pca)
for T in FloatingPointTypes
    X = rand(T, n, p)
    μ = mean(X,1)
    H = (X .- μ)
    Σ = H'H/(n-1)
    for α in (zero(T), convert(T,0.5), one(T)), ϵ in (zero(T), one(T))
        V1, D1 = MOD.pca(copy(X), α, ϵ)
        D2, V2 = eig(MOD.perturb!(MOD.regularize!(copy(Σ), α, trace(Σ)/p), ϵ))
        @test_approx_eq abs(V1) abs(V2[:,end:-1:1])
        @test_approx_eq abs(D1) abs(D2[end:-1:1])
        @test_approx_eq abs(transform_pca(V1,X)) abs(X*(V2[:,end:-1:1]))
    end
end

info("Testing ", MOD.kpca)
for T in FloatingPointTypes
    X = rand(T, n, p)
    Xc = X .- mean(X,1)
    Σ = Xc'Xc/(n-1)
    κ = PolynomialKernel(one(T),zero(T),one(T))  # Equivalent to dot product
    for ϵ in (zero(T), one(T))
        V1, D1 = MOD.kpca(copy(X), κ, zero(T), ϵ)
        V2, D2 = MOD.pca(copy(X), zero(T), ϵ)
        @test_approx_eq (V1'Xc*Xc'V1/(n-1))[1:p,1:p] (V2'Σ*V2)
        @test_approx_eq abs(D1)[1:p] abs(D2)
    end
end


using NCDatasets
using Formatting
using ArgParse
using Statistics
using JSON

include("constants.jl")

function mreplace(x)
    return replace(x, missing=>NaN)
end

function integrate(x, dydx)
    y = copy(dydx) * 0.0

    for i = 2:length(x)
        y[i] = y[i-1] + (dydx[i-1] + dydx[i]) * (x[i] - x[i-1]) / 2.0
    end

    return y
end

function parse_commandline()

    s = ArgParseSettings()
    @add_arg_table s begin

        "--data-file"
            help = "Ocean data file."
            arg_type = String
            required = true

        "--output-file"
            help = "Output file."
            arg_type = String
            required = true

        "--domain-file"
            help = "Domain file."
            arg_type = String
            required = true

        "--beg-year"
            help = "Year of begin."
            arg_type = Int64
            required = true

        "--end-year"
            help = "Year of end."
            arg_type = Int64
            required = true

    end

    return parse_args(ARGS, s)
end

parsed = parse_commandline()
print(json(parsed, 4))

Dataset(parsed["domain-file"], "r") do ds

    global mask = ds["mask"][:] |> mreplace
    global lat = ds["yc"][1, :] |> mreplace

    global lat_weight = 2π * Re^2.0 * cos.(deg2rad.(lat))
    global ϕ          = deg2rad.(lat)

#    global O_idx = (mask .== 0)

end

Dataset(parsed["data-file"], "r") do ds

    beg_t = (parsed["beg-year"] - 1) * 12 + 1
    end_t = (parsed["end-year"] - 1) * 12 + 12
    rng = (:,:,beg_t:end_t)

    getData = (varname) -> mean( replace(ds[varname][rng...], missing=>0.0, NaN=>0.0), dims=(1, ))[1, :, :]

    global neb = getData("neb")
    global Q_clim = getData("Q_clim")
    global qflx     = getData("qflx")
    global wT       = getData("wT") * ρc
    global nswflx   = getData("nswflx")
    global swflx    = getData("swflx")
    global dHdt    = getData("dHdt")

    global qflx2atm = replace(ds["qflx2atm"][rng...], missing=>0.0, NaN=>0.0)
    qflx2atm[qflx2atm .< 0.0] .= 0.0
    qflx2atm = mean( qflx2atm, dims=(1, ))[1, :, :]

    global (Ny, Nt) = size(neb)

end

IET_neb       = zeros(Float64, Ny, Nt)
IET_Q_clim    = zeros(Float64, Ny, Nt)
IET_qflx      = zeros(Float64, Ny, Nt)
IET_wT        = zeros(Float64, Ny, Nt)
IET_nswflx    = zeros(Float64, Ny, Nt)
IET_swflx     = zeros(Float64, Ny, Nt)
IET_dHdt      = zeros(Float64, Ny, Nt)

for t = 1:Nt
    IET_neb[:, t]    = integrate(ϕ, neb[:, t] .* lat_weight)
    IET_Q_clim[:, t] = integrate(ϕ, Q_clim[:, t] .* lat_weight)
    IET_qflx[:, t]   = integrate(ϕ, qflx[:, t] .* lat_weight)
    IET_wT[:, t]     = integrate(ϕ, wT[:, t] .* lat_weight)
    IET_nswflx[:, t] = integrate(ϕ, nswflx[:, t] .* lat_weight)
    IET_swflx[:, t]  = integrate(ϕ, swflx[:, t] .* lat_weight)
    IET_dHdt[:, t]   = integrate(ϕ, dHdt[:, t] .* lat_weight)
end

Dataset(parsed["output-file"], "c") do ds

    defDim(ds, "Nx",  1)
    defDim(ds, "Ny", Ny)
    defDim(ds, "Nz",  1)
    defDim(ds, "time", Inf)

    for (varname, vardata, vardim, attrib) in [
        ("IET_neb",        reshape(IET_neb,    1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("IET_Q_clim",     reshape(IET_Q_clim, 1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("IET_qflx",       reshape(IET_qflx,   1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("IET_wT",         reshape(IET_wT,     1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("IET_nswflx",     reshape(IET_nswflx, 1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("IET_swflx",      reshape(IET_swflx,  1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("IET_dHdt",       reshape(IET_dHdt,   1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("neb",            reshape(neb,        1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("Q_clim",         reshape(Q_clim,     1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("qflx",           reshape(qflx,       1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("wT",             reshape(wT,         1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("nswflx",         reshape(nswflx,     1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("swflx",          reshape(swflx,      1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
        ("dHdt",           reshape(dHdt,       1, Ny, 1, Nt), ("Nx", "Ny", "Nz", "time"), Dict()),
    ]

        println("Doing var: ", varname)

        var = defVar(ds, varname, Float64, vardim)
        var.attrib["_FillValue"] = 1e20

        var = ds[varname]

        for (k, v) in attrib
            var.attrib[k] = v
        end

        rng = []
        for i in 1:length(vardim)-1
            push!(rng, Colon())
        end
        push!(rng, 1:size(vardata)[end])
        var[rng...] = vardata

    end

end


#Include KG_Module
include("../../src/KG_Module.jl")

p_animation_run = Param(

    #Temporal variables
    tmax           = 5000.0   ,
    out_every_t    = 0.5     ,
    deltat         = 0.05   ,

    #Spacial variables
    xmin           =  -30.0   ,
    xmax           =  30.0    ,
    xnodes         =  512    ,

    #Spacial variables
    ymin           =  -30.0   ,
    ymax           =  30.0    ,
    ynodes         =   512     ,

    #Initial Conditions
    A0              =   5.0   ,
    σ               =   1.0   ,
    r0              =   17.0  ,
    ω               =   0.5   ,
    m               =   2  ,

    #Klein Gordon Mass
    μ               =   0.0     ,

    #Dissipation terms
    dissipation     =  :true    ,

    R_orbit         = 3.0       ,
    R1              = 1.5       ,
    R2              = 1.5       ,

    Ω               = (3.0)^(-3.0/2.0)       ,
    alpha1          = 10.0       ,
    alpha2          = 10.0       ,

    #Periodic BC?
    Boundaries      = :radial , # :square :periodic

    #Data variables
    folder          = "/binary_bh_rotating_animation/" ,       #must have a bar before the folder
    fname = "binary_bh_rotating_radius_1.5_alpha_10.0_Rorbit_3.0"
)

solve_wave_equation_2D(p_animation_run)