"""
    SeisUnPatch(in,out;<keyword arguments>)

Reconstruct a 5D data volume from a set of 5D data patches.

# Arguments
* `in::Array{AbstractString,1}`: array containing filename of patches
* `out::AbstractString`: filename for reconstructed volume

# Keyword arguments
* `style="sxsygxgy"`: bin style. Options: "mxmyhxhy","mxmyhaz","sxsyhxhy","gxgyhxhy","sxsyhaz","gxgyhaz"
* `min_isx=0`,`max_isx=0`,`min_isy=0`,`max_isy=0`: grid extreme values for sources
* `min_igx=0`,`max_igx=0`,`min_igy=0`,`max_igy=0`: grid extreme values for receivers
* `min_imx=0`,`max_imx=0`,`min_imy=0`,`max_imy=0`: grid extreme values for midpoints
* `min_ihx=0`,`max_ihx=0`,`min_ihy=0`,`max_ihy=0`: grid extreme values for offsets
* `min_ih=0`,`max_ih=0`,`min_iaz=0`,`max_iaz=0`: grid extreme values for azimuth and offset
* `it_WL=9e9`,`it_WO=0` : length and overlapping samples in time patches
* `ix1_WL=9e9`,`ix1_WO=0`:length and overlapping samples in first space dimension
* `ix2_WL=9e9`,`ix2_WO=0`,`ix3_WL=9e9`,`ix3_WO=0`,`ix4_WL=9e9`,`ix4_WO=0`
* `nt=0`: time samples of reconstructed cube
* `ang=90`: inline direction measured in degrees CC from East
* `gamma=1`: vp/vs ratio for PS Asymptotic Conversion Point gathers (use gamma=1 for PP data)
* `osx=0`,`osy=0`,`ogx=0`,`ogy=0` : origin for source and receiver coordinate system
* `omx=0`,`omy=0`,`ohx=0`,`ohy=0`: origin for midpoint and offset coordinate system
* `oaz=0`,`oh=0` : origin for azimuth and offset coordinate system
* `dsx=1`,`dsy=1`,`dgx=1`,`dgy=1`: source and receiver step-size
* `dmx=1`,`dmy=1`,`dhx=1`,`dhy=1`: midpoint and offset step-size
* `dh=1`,`daz=1`: offset and azimuth step-size

# Output
In file `out`, the 5D reconstructed volume is created.

*Credits: A. Stanton, F Carozzi, 2017*
"""

function SeisUnPatch(patch_names::Array{AbstractString,1},out::AbstractString;style="sxsygxgy",min_isx=0,max_isx=0,min_isy=0,max_isy=0,min_igx=0,max_igx=0,min_igy=0,max_igy=0,min_imx=0,max_imx=0,min_imy=0,max_imy=0,min_ihx=0,max_ihx=0,min_ihy=0,max_ihy=0,min_ih=0,max_ih=0,min_iaz=0,max_iaz=0,it_WL=9e9,it_WO=0,ix1_WL=9e9,ix1_WO=0,ix2_WL=9e9,ix2_WO=0,ix3_WL=9e9,ix3_WO=0,ix4_WL=9e9,ix4_WO=0,nt=0,ang=90,gamma=1,osx=0,osy=0,ogx=0,ogy=0,omx=0,omy=0,ohx=0,ohy=0,oh=0,oaz=0,dsx=1,dsy=1,dgx=1,dgy=1,dmx=1,dmy=1,dhx=1,dhy=1,dh=1,daz=1)

    if (style == "sxsygxgy")
	key = ["t","isx","isy","igx","igy"]
	min_ix1 = min_isx
	max_ix1 = max_isx
	min_ix2 = min_isy
	max_ix2 = max_isy
	min_ix3 = min_igx
	max_ix3 = max_igx
	min_ix4 = min_igy
	max_ix4 = max_igy
    elseif (style=="mxmyhxhy")
	key = ["t","imx","imy","ihx","ihy"]
	min_ix1 = min_imx
	max_ix1 = max_imx
	min_ix2 = min_imy
	max_ix2 = max_imy
	min_ix3 = min_ihx
	max_ix3 = max_ihx
	min_ix4 = min_ihy
	max_ix4 = max_ihy
    elseif (style=="mxmyhaz")
	key = ["t","imx","imy","ih","iaz"]
	min_ix1 = min_imx
	max_ix1 = max_imx
	min_ix2 = min_imy
	max_ix2 = max_imy
	min_ix3 = min_ih
	max_ix3 = max_ih
	min_ix4 = min_iaz
	max_ix4 = max_iaz
    elseif (style=="sxsyhxhy")
	key = ["t","isx","isy","ihx","ihy"]
	min_ix1 = min_isx
	max_ix1 = max_isx
	min_ix2 = min_isy
	max_ix2 = max_isy
	min_ix3 = min_ihx
	max_ix3 = max_ihx
	min_ix4 = min_ihy
	max_ix4 = max_ihy
    elseif (style=="gxgyhxhy")
	key = ["t","igx","igy","ihx","ihy"]
	min_ix1 = min_igx
	max_ix1 = max_igx
	min_ix2 = min_igy
	max_ix2 = max_igy
	min_ix3 = min_ihx
	max_ix3 = max_ihx
	min_ix4 = min_ihy
	max_ix4 = max_ihy
    elseif (style=="sxsyhaz")
	key = ["t","isx","isy","ih","iaz"]
	min_ix1 = min_isx
	max_ix1 = max_isx
	min_ix2 = min_isy
	max_ix2 = max_isy
	min_ix3 = min_ih
	max_ix3 = max_ih
	min_ix4 = min_iaz
	max_ix4 = max_iaz
    elseif (style=="gxgyhaz")
	key = ["t","igx","igy","ih","iaz"]
	min_ix1 = min_igx
	max_ix1 = max_igx
	min_ix2 = min_igy
		max_ix2 = max_igy
	min_ix3 = min_ih
	max_ix3 = max_ih
	min_ix4 = min_iaz
	max_ix4 = max_iaz
    else
	error("style not defined.")
    end
    nx1 = max_ix1 - min_ix1 + 1
    nx2 = max_ix2 - min_ix2 + 1
    nx3 = max_ix3 - min_ix3 + 1
    nx4 = max_ix4 - min_ix4 + 1

    it_WL  = it_WL  > nt  ? nt  : it_WL
    ix1_WL = ix1_WL > nx1 ? nx1 : ix1_WL
	ix2_WL = ix2_WL > nx2 ? nx2 : ix2_WL
    ix3_WL = ix3_WL > nx3 ? nx3 : ix3_WL
    ix4_WL = ix4_WL > nx4 ? nx4 : ix4_WL

    rad2deg = 180/pi
    deg2rad = pi/180
    gammainv = 1/gamma
    if (ang > 90)
	ang2=-deg2rad*(ang-90)
    else
	ang2=deg2rad*(90-ang)
    end

    nsx = max_isx - min_isx + 1
    nsy = max_isy - min_isy + 1
    ngx = max_igx - min_igx + 1
    ngy = max_igy - min_igy + 1

    nmx = max_imx - min_imx + 1
    nmy = max_imy - min_imy + 1
    nhx = max_ihx - min_ihx + 1
    nhy = max_ihy - min_ihy + 1
    nh = max_ih - min_ih + 1
    naz = max_iaz - min_iaz + 1

    if (style=="sxsygxgy")
	nx1=nsx; nx2=nsy; nx3=ngx; nx4=ngy
    elseif (style=="mxmyhxhy")
	nx1=nmx; nx2=nmy; nx3=nhx; nx4=nhy
    elseif (style=="mxmyhaz")
	nx1=nmx; nx2=nmy; nx3=nh; nx4=naz
    elseif (style=="sxsyhxhy")
	nx1=nsx; nx2=nsy; nx3=nhx; nx4=nhy
    elseif (style=="gxgyhxhy")
	nx1=ngx; nx2=ngy; nx3=nhx; nx4=nhy
    elseif (style=="sxsyhaz")
	nx1=nsx; nx2=nsy; nx3=nh; nx4=naz
    elseif (style=="gxgyhaz")
	nx1=ngx; nx2=ngy; nx3=nh; nx4=naz
	else
	    error("style not recognized.")
	end

    filename_data = ParseDataName(patch_names[1])
    filename_headers = ParseHeaderName(patch_names[1])
    extent = ReadTextHeader(patch_names[1])

    dt = extent.d1
    ot = extent.o1
    d = zeros(Float32,nt,1)
    h = Array(Header,1)
    h[1] = InitSeisHeader()

    extent = Seismic.Extent(nt, max_ix1-min_ix1+1, max_ix2-min_ix2+1,
                            max_ix3-min_ix3+1, max_ix4-min_ix4+1,convert(Float32,ot), 0, 0, 0,
                            0, convert(Float32,dt), 1, 1, 1, 1, "", "", "", "", "", "", "", "",
                            "", "", "")
println("creating headers")
   j = 1
    for ix4 = 1 : nx4
	     for ix3 = 1 : nx3
	        for ix2 = 1 : nx2
		          for ix1 = 1 : nx1
		              h[1].tracenum = convert(typeof(h[1].tracenum),j)

        h[1].o1 = convert(typeof(h[1].o1),ot)
		    h[1].n1 = convert(typeof(h[1].n1),nt)
		    h[1].d1 = convert(typeof(h[1].d1),dt)

		    if (style=="sxsygxgy")
			h[1].isx = convert(typeof(h[1].isx),ix1 - 1 + min_isx)
			h[1].isy = convert(typeof(h[1].isy),ix2 - 1 + min_isy)
			h[1].igx = convert(typeof(h[1].igx),ix3 - 1 + min_igx)
			h[1].igy = convert(typeof(h[1].igy),ix4 - 1 + min_igy)
			sx_rot = convert(Float32,(ix1 - 1 + min_isx)*dsx + osx)
			sy_rot = convert(Float32,(ix2 - 1 + min_isy)*dsy + osy)
			gx_rot = convert(Float32,(ix3 - 1 + min_igx)*dgx + ogx)
			gy_rot = convert(Float32,(ix4 - 1 + min_igy)*dgy + ogy)
			h[1].sx =  (sx_rot-osx)*cos(ang2) + (sy_rot-osy)*sin(ang2) + osx
			h[1].sy = -(sx_rot-osx)*sin(ang2) + (sy_rot-osy)*cos(ang2) + osy
			h[1].gx =  (gx_rot-ogx)*cos(ang2) + (gy_rot-ogy)*sin(ang2) + ogx
			h[1].gy = -(gx_rot-ogx)*sin(ang2) + (gy_rot-ogy)*cos(ang2) + ogy
			h[1].hx = h[1].gx - h[1].sx
			h[1].hy = h[1].gy - h[1].sy
			h[1].h = sqrt((h[1].hx^2) + (h[1].hy^2))
			h[1].az = rad2deg*atan2((h[1].gy-h[1].sy),(h[1].gx-h[1].sx))
			if (h[1].az < 0)
			    h[1].az += 360.0
			end
			h[1].mx = h[1].sx + h[1].hx/(1 + gammainv)
			h[1].my = h[1].sy + h[1].hy/(1 + gammainv)
			mx_rot = (h[1].mx-omx)*cos(ang2) - (h[1].my-omy)*sin(ang2) + omx
			my_rot = (h[1].mx-omx)*sin(ang2) + (h[1].my-omy)*cos(ang2) + omy
			hx_rot = (h[1].hx-ohx)*cos(ang2) - (h[1].hy-ohy)*sin(ang2) + ohx
			hy_rot = (h[1].hx-ohx)*sin(ang2) + (h[1].hy-ohy)*cos(ang2) + ohy
			h[1].imx = convert(Int32,round((mx_rot-omx)/dmx))
			h[1].imy = convert(Int32,round((my_rot-omy)/dmy))
			h[1].ihx = convert(Int32,round((hx_rot-ohx)/dhx))
			h[1].ihy = convert(Int32,round((hy_rot-ohy)/dhy))
			h[1].ih = convert(Int32,round((h[1].h-oh)/dh))
			h[1].iaz = convert(Int32,round((h[1].az-oaz)/daz))
		    elseif (style=="mxmyhxhy")
			h[1].imx = convert(typeof(h[1].imx),ix1 - 1 + min_imx)
 			h[1].imy = convert(typeof(h[1].imy),ix2 - 1 + min_imy)
 			h[1].ihx = convert(typeof(h[1].ihx),ix3 - 1 + min_ihx)
			h[1].ihy = convert(typeof(h[1].ihy),ix4 - 1 + min_ihy)
			mx_rot = convert(Float32,(ix1 - 1 + min_imx)*dmx + omx)
			my_rot = convert(Float32,(ix2 - 1 + min_imy)*dmy + omy)
			hx_rot = convert(Float32,(ix3 - 1 + min_ihx)*dhx + ohx)
			hy_rot = convert(Float32,(ix4 - 1 + min_ihy)*dhy + ohy)
			h[1].mx =  (mx_rot-omx)*cos(ang2) + (my_rot-omy)*sin(ang2) + omx
			h[1].my = -(mx_rot-omx)*sin(ang2) + (my_rot-omy)*cos(ang2) + omy
			h[1].hx =  (hx_rot-ohx)*cos(ang2) + (hy_rot-ohy)*sin(ang2) + ohx
			h[1].hy = -(hx_rot-ohx)*sin(ang2) + (hy_rot-ohy)*cos(ang2) + ohy
			h[1].sx = h[1].mx - h[1].hx/(1 + gammainv)
			h[1].sy = h[1].my - h[1].hy/(1 + gammainv)
			h[1].gx = h[1].mx + h[1].hx*(1-(1/(1 + gammainv)))
			h[1].gy = h[1].my + h[1].hy*(1-(1/(1 + gammainv)))
			sx_rot = (h[1].sx-osx)*cos(ang2) - (h[1].sy-osy)*sin(ang2) + osx
			sy_rot = (h[1].sx-osx)*sin(ang2) + (h[1].sy-osy)*cos(ang2) + osy
			gx_rot = (h[1].gx-ogx)*cos(ang2) - (h[1].gy-ogy)*sin(ang2) + ogx
			gy_rot = (h[1].gx-ogx)*sin(ang2) + (h[1].gy-ogy)*cos(ang2) + ogy
			h[1].isx = convert(Int32,round((sx_rot-osx)/dsx))
			h[1].isy = convert(Int32,round((sy_rot-osy)/dsy))
			h[1].igx = convert(Int32,round((gx_rot-ogx)/dgx))
			h[1].igy = convert(Int32,round((gy_rot-ogy)/dgy))
			h[1].h = sqrt((h[1].hx^2) + (h[1].hy^2))
			h[1].az = rad2deg*atan2((h[1].gy-h[1].sy),(h[1].gx-h[1].sx))
			if (h[1].az < 0)
			    h[1].az += 360.0
			end
			h[1].ih = convert(Int32,round((h[1].h-oh)/dh))
			h[1].iaz = convert(Int32,round((h[1].az-oaz)/daz))
		    elseif (style=="mxmyhaz")
			h[1].imx = convert(typeof(h[1].imx),ix1 - 1 + min_imx)
			h[1].imy = convert(typeof(h[1].imy),ix2 - 1 + min_imy)
			h[1].ih = convert(typeof(h[1].ih),  ix3 - 1 + min_ih)
			h[1].iaz = convert(typeof(h[1].iaz),ix4 - 1 + min_iaz)
			mx_rot = convert(Float32,(ix1 - 1 + min_imx)*dmx + omx)
			my_rot = convert(Float32,(ix2 - 1 + min_imy)*dmy + omy)
			h[1].mx =  (mx_rot-omx)*cos(ang2) + (my_rot-omy)*sin(ang2) + omx
			h[1].my = -(mx_rot-omx)*sin(ang2) + (my_rot-omy)*cos(ang2) + omy
			h[1].h = convert(Float32,(ix3 - 1 + min_ih)*dh + oh)
			h[1].az = convert(Float32,(ix4 - 1 + min_iaz)*daz + oaz)
			if (h[1].az <= 90)
			    h[1].hx = h[1].h*cos(deg2rad*h[1].az)
			    h[1].hy = h[1].h*sin(deg2rad*h[1].az)
			elseif (h[1].az > 90 && h[1].az <= 180)
			    h[1].hx =-h[1].h*cos(pi-(deg2rad*h[1].az))
			    h[1].hy = h[1].h*sin(pi-(deg2rad*h[1].az))
			elseif (h[1].az > 180 && h[1].az <= 270)
			    h[1].hx =-h[1].h*cos((deg2rad*h[1].az)-pi)
			    h[1].hy =-h[1].h*sin((deg2rad*h[1].az)-pi)
			else
			    h[1].hx = h[1].h*cos(2*pi-(deg2rad*h[1].az))
			    h[1].hy =-h[1].h*sin(2*pi-(deg2rad*h[1].az))
			end
			h[1].sx = h[1].mx - h[1].hx/(1 + gammainv)
			h[1].sy = h[1].my - h[1].hy/(1 + gammainv)
			h[1].gx = h[1].mx + h[1].hx*(1-(1/(1 + gammainv)))
			h[1].gy = h[1].my + h[1].hy*(1-(1/(1 + gammainv)))
			sx_rot = (h[1].sx-osx)*cos(ang2) - (h[1].sy-osy)*sin(ang2) + osx
			sy_rot = (h[1].sx-osx)*sin(ang2) + (h[1].sy-osy)*cos(ang2) + osy
			gx_rot = (h[1].gx-ogx)*cos(ang2) - (h[1].gy-ogy)*sin(ang2) + ogx
			gy_rot = (h[1].gx-ogx)*sin(ang2) + (h[1].gy-ogy)*cos(ang2) + ogy
			h[1].isx = convert(Int32,round((sx_rot-osx)/dsx))
			h[1].isy = convert(Int32,round((sy_rot-osy)/dsy))
			h[1].igx = convert(Int32,round((gx_rot-ogx)/dgx))
			h[1].igy = convert(Int32,round((gy_rot-ogy)/dgy))
			hx_rot = (h[1].hx-ohx)*cos(ang2) - (h[1].hy-ohy)*sin(ang2) + ohx
			hy_rot = (h[1].hx-ohx)*sin(ang2) + (h[1].hy-ohy)*cos(ang2) + ohy
			h[1].ihx = convert(Int32,round((hx_rot-ohx)/dhx))
			h[1].ihy = convert(Int32,round((hy_rot-ohy)/dhy))
		    elseif (style=="sxsyhxhy")
			h[1].isx = convert(typeof(h[1].isx),ix1 - 1 + min_isx)
			h[1].isy = convert(typeof(h[1].isy),ix2 - 1 + min_isy)
			h[1].ihx = convert(typeof(h[1].ihx),ix3 - 1 + min_ihx)
			h[1].ihy = convert(typeof(h[1].ihy),ix4 - 1 + min_ihy)
			sx_rot = convert(Float32,(ix1 - 1 + min_isx)*dsx + osx)
			sy_rot = convert(Float32,(ix2 - 1 + min_isy)*dsy + osy)
			hx_rot = convert(Float32,(ix3 - 1 + min_ihx)*dhx + ohx)
			hy_rot = convert(Float32,(ix4 - 1 + min_ihy)*dhy + ohy)
			h[1].sx =  (sx_rot-osx)*cos(ang2) + (sy_rot-osy)*sin(ang2) + osx
			h[1].sy = -(sx_rot-osx)*sin(ang2) + (sy_rot-osy)*cos(ang2) + osy
			h[1].hx =  (hx_rot-ohx)*cos(ang2) + (hy_rot-ohy)*sin(ang2) + ohx
			h[1].hy = -(hx_rot-ohx)*sin(ang2) + (hy_rot-ohy)*cos(ang2) + ohy
			h[1].gx = h[1].sx + h[1].hx
			h[1].gy = h[1].sy + h[1].hy
			h[1].h = sqrt((h[1].hx^2) + (h[1].hy^2))
			h[1].az = rad2deg*atan2((h[1].gy-h[1].sy),(h[1].gx-h[1].sx))
			if (h[1].az < 0)
			    h[1].az += 360.0
			end
			h[1].mx = h[1].sx + h[1].hx/(1 + gammainv)
			h[1].my = h[1].sy + h[1].hy/(1 + gammainv)
			mx_rot = (h[1].mx-omx)*cos(ang2) - (h[1].my-omy)*sin(ang2) + omx
			my_rot = (h[1].mx-omx)*sin(ang2) + (h[1].my-omy)*cos(ang2) + omy
			hx_rot = (h[1].hx-ohx)*cos(ang2) - (h[1].hy-ohy)*sin(ang2) + ohx
			hy_rot = (h[1].hx-ohx)*sin(ang2) + (h[1].hy-ohy)*cos(ang2) + ohy
			h[1].imx = convert(Int32,round((mx_rot-omx)/dmx))
			h[1].imy = convert(Int32,round((my_rot-omy)/dmy))
			h[1].ihx = convert(Int32,round((hx_rot-ohx)/dhx))
			h[1].ihy = convert(Int32,round((hy_rot-ohy)/dhy))
			h[1].ih = convert(Int32,round((h[1].h-oh)/dh))
			h[1].iaz = convert(Int32,round((h[1].az-oaz)/daz))
		    elseif (style=="gxgyhxhy")
			h[1].igx = convert(typeof(h[1].igx),ix1 - 1 + min_igx)
			h[1].igy = convert(typeof(h[1].igy),ix2 - 1 + min_igy)
			h[1].ihx = convert(typeof(h[1].ihx),ix3 - 1 + min_ihx)
			h[1].ihy = convert(typeof(h[1].ihy),ix4 - 1 + min_ihy)
			gx_rot = convert(Float32,(ix1 - 1 + min_igx)*dgx + ogx)
			gy_rot = convert(Float32,(ix2 - 1 + min_igy)*dgy + ogy)
			hx_rot = convert(Float32,(ix3 - 1 + min_ihx)*dhx + ohx)
			hy_rot = convert(Float32,(ix4 - 1 + min_ihy)*dhy + ohy)
			h[1].gx =  (gx_rot-ogx)*cos(ang2) + (gy_rot-ogy)*sin(ang2) + ogx
			h[1].gy = -(gx_rot-ogx)*sin(ang2) + (gy_rot-ogy)*cos(ang2) + ogy
			h[1].hx =  (hx_rot-ohx)*cos(ang2) + (hy_rot-ohy)*sin(ang2) + ohx
			h[1].hy = -(hx_rot-ohx)*sin(ang2) + (hy_rot-ohy)*cos(ang2) + ohy
			h[1].sx = h[1].gx - h[1].hx
			h[1].sy = h[1].gy - h[1].hy
			h[1].h = sqrt((h[1].hx^2) + (h[1].hy^2))
			h[1].az = rad2deg*atan2((h[1].gy-h[1].sy),(h[1].gx-h[1].sx))
			if (h[1].az < 0)
			    h[1].az += 360.0
			end
			h[1].mx = h[1].sx + h[1].hx/(1 + gammainv)
			h[1].my = h[1].sy + h[1].hy/(1 + gammainv)
			mx_rot = (h[1].mx-omx)*cos(ang2) - (h[1].my-omy)*sin(ang2) + omx
			my_rot = (h[1].mx-omx)*sin(ang2) + (h[1].my-omy)*cos(ang2) + omy
			hx_rot = (h[1].hx-ohx)*cos(ang2) - (h[1].hy-ohy)*sin(ang2) + ohx
			hy_rot = (h[1].hx-ohx)*sin(ang2) + (h[1].hy-ohy)*cos(ang2) + ohy
			h[1].imx = convert(Int32,round((mx_rot-omx)/dmx))
			h[1].imy = convert(Int32,round((my_rot-omy)/dmy))
			h[1].ihx = convert(Int32,round((hx_rot-ohx)/dhx))
			h[1].ihy = convert(Int32,round((hy_rot-ohy)/dhy))
			h[1].ih = convert(Int32,round((h[1].h-oh)/dh))
			h[1].iaz = convert(Int32,round((h[1].az-oaz)/daz))
		    elseif (style=="sxsyhaz")
			error("sxsyhaz not developed yet.")
		    elseif (style=="gxgyhaz")
			error("gxgyhaz not developed yet.")
		    end
		    h[1].selev = convert(typeof(h[1].selev),0)
		    h[1].gelev = convert(typeof(h[1].gelev),0)
		    h[1].trid = convert(typeof(h[1].trid),0)
		    SeisWrite(out,d,h,extent,itrace=j)
		    j += 1
		end
	    end
	end
    end

    DATAPATH = get(ENV,"DATAPATH",join([pwd(),"/"]))
    out_d = join([DATAPATH out "@data@"])
    out_h = join([DATAPATH out "@headers@"])

    for ipatch = 1 : length(patch_names)

   println("ipatch= ",ipatch," ",patch_names[ipatch])
	stream_d = open(out_d,"a+")
	stream_h = open(out_h,"a+")

	d_patch,h_patch,e_patch = SeisRead(patch_names[ipatch])

	nx_patch=e_patch.n2*e_patch.n3*e_patch.n4*e_patch.n5

	nt_patch = h_patch[1].n1

	d_patch=reshape(d_patch,Int(nt_patch),Int(nx_patch))

	ot_patch = h_patch[1].o1
	min_it_patch = Int(floor((ot_patch-ot)/dt)+1)
	max_it_patch = Int(floor((ot_patch-ot)/dt)+nt_patch)

	min_ix1_patch = getfield(h_patch[1],Symbol(key[2]))
	max_ix1_patch = getfield(h_patch[nx_patch],Symbol(key[2]))
	min_ix2_patch = getfield(h_patch[1],Symbol(key[3]))
	max_ix2_patch = getfield(h_patch[nx_patch],Symbol(key[3]))
	min_ix3_patch = getfield(h_patch[1],Symbol(key[4]))
	max_ix3_patch = getfield(h_patch[nx_patch],Symbol(key[4]))
	min_ix4_patch = getfield(h_patch[1],Symbol(key[5]))
	max_ix4_patch = getfield(h_patch[nx_patch],Symbol(key[5]))

	tapti  =  min_it_patch >       1 ? it_WO  : 0
	taptf  =  max_it_patch <      nt ? it_WO  : 0
	tapx1i = min_ix1_patch > min_ix1 ? ix1_WO : 0
	tapx1f = max_ix1_patch < max_ix1 ? ix1_WO : 0
	tapx2i = min_ix2_patch > min_ix2 ? ix2_WO : 0
	tapx2f = max_ix2_patch < max_ix2 ? ix2_WO : 0
	tapx3i = min_ix3_patch > min_ix3 ? ix3_WO : 0
	tapx3f = max_ix3_patch < max_ix3 ? ix3_WO : 0
	tapx4i = min_ix4_patch > min_ix4 ? ix4_WO : 0
	tapx4f = max_ix4_patch < max_ix4 ? ix4_WO : 0

	d_patch = Taper(d_patch,
                        max_it_patch  -  min_it_patch + 1,
		        max_ix1_patch - min_ix1_patch + 1,
		        max_ix2_patch - min_ix2_patch + 1,
		        max_ix3_patch - min_ix3_patch + 1,
		        max_ix4_patch - min_ix4_patch + 1,
		        tapti,taptf,tapx1i,tapx1f,tapx2i,tapx2f,tapx3i,tapx3f,tapx4i,tapx4f)

	for j = 1 : nx_patch

	    h = h_patch[j]
	    if (style=="sxsygxgy")
        itrace = (h.igy - min_igy)*nx3*nx2*nx1 + (h.igx - min_igx)*nx2*nx1 + (h.isy - min_isy)*nx1 + h.isx - min_isx + 1
	    elseif (style=="mxmyhxhy")
          itrace = (h.ihy - min_ihy)*nx3*nx2*nx1 + (h.ihx - min_ihx)*nx2*nx1 + (h.imy - min_imy)*nx1 + h.imx - min_imx + 1
	    elseif (style=="mxmyhaz")
		      itrace = (h.imx - min_imx)*nx2*nx3*nx4 + (h.imy - min_imy)*nx3*nx4 + (h.ih - min_ih)*nx4 + h.iaz - min_iaz + 1

	    elseif (style=="sxsyhxhy")
		      itrace = (h.isx - min_isx)*nx2*nx3*nx4 + (h.isy - min_isy)*nx3*nx4 + (h.ihx - min_ihx)*nx4 + h.ihy - min_ihy + 1
	    elseif (style=="gxgyhxhy")
		      itrace = (h.igx - min_igx)*nx2*nx3*nx4 + (h.igy - min_igy)*nx3*nx4 + (h.ihx - min_ihx)*nx4 + h.ihy - min_ihy + 1
	    elseif (style=="sxsyhaz")
		      itrace = (h.isx - min_isx)*nx2*nx3*nx4 + (h.isy - min_isy)*nx3*nx4 + (h.ih - min_ih)*nx4 + h.iaz - min_iaz + 1
	    elseif (style=="gxgyhaz")
		      itrace = (h.igx - min_igx)*nx2*nx3*nx4 + (h.igy - min_igy)*nx3*nx4 + (h.ih - min_ih)*nx4 + h.iaz - min_iaz + 1
	    end

      if j==1
        println(min_it_patch," ",max_it_patch," ",nt," ",nx_patch," ",ipatch," ",size(d_patch))
      end

      h.tracenum = itrace
	    h.n1 = nt
	    h.o1 = ot
	    position_d = 4*nt*(itrace - 1)
	    seek(stream_d,position_d)
	    d = read(stream_d,Float32,nt)
	    d[min_it_patch:max_it_patch] += d_patch[:,j]

	    seek(stream_d,position_d)
	    write(stream_d,d)
	    PutHeader(stream_h,h,itrace)
	end
	close(stream_d)
	close(stream_h)
    end

end

function Taper(d,nt,nx1,nx2,nx3,nx4,tapti,taptf,tapx1i,tapx1f,tapx2i,tapx2f,tapx3i,tapx3f,tapx4i,tapx4f)
    tx1=1; tx2=1; tx3=1; tx4=1
stream1=open("salida.txt","a")
stream2=open("salida2.txt","a")

    for ix1 = 1 : nx1
	     if (ix1>=1   && ix1<=tapx1i)
	        tx1 = 1.0/(tapx1i-1)*(ix1-1)
	     end
	     if (ix1>tapx1i && ix1<=nx1-tapx1f)
	        tx1 = 1
	     end
	     if (ix1>nx1-tapx1f && ix1<=nx1)
	        tx1 = 1-1.0/(tapx1f-1)*(ix1-1-nx1+tapx1f)
	     end
	     for ix2 = 1 : nx2
	        if (ix2>=1   && ix2<=tapx2i)
		          tx2 = 1.0/(tapx2i-1)*(ix2-1)
	        end
	        if (ix2>tapx2i && ix2<=nx2-tapx2f)
		          tx2 = 1
	        end
	        if (ix2>nx2-tapx2f && ix2<=nx2)
		          tx2 = 1-1.0/(tapx2f-1)*(ix2-1-nx2+tapx2f)
	        end
	        for ix3 = 1 : nx3
		          if (ix3>=1   && ix3<=tapx3i)
		              tx3 = 1.0/(tapx3i-1)*(ix3-1)
		          end
		          if (ix3>tapx3i && ix3<=nx3-tapx3f)
		              tx3 = 1
		          end
		          if (ix3>nx3-tapx3f && ix3<=nx3)
		              tx3 = 1-1.0/(tapx3f-1)*(ix3-1-nx3+tapx3f)
              end
		          for ix4 = 1 : nx4
		              if (ix4>=1   && ix4<=tapx4i)
			                 tx4 = 1.0/(tapx4i-1)*(ix4-1)
		              end
		              if (ix4>tapx4i && ix4<=nx4-tapx4f)
			                 tx4 = 1
		              end
		              if (ix4>nx4-tapx4f && ix4<=nx4)
                      tx4 = 1-1.0/(tapx4f-1)*(ix4-1-nx4+tapx4f)
		              end
		    ix = (ix4-1)*nx2*nx3*nx1 + (ix3-1)*nx1*nx2 + (ix2-1)*nx1 + ix1
		    for it = 1 : nt
			       if (it>=1 && it<=tapti)
			            tt = 1.0/(tapti-1)*(it-1)
			       end
			       if (it>tapti && it<=nt-taptf)
			          tt = 1
			      end
			      if (it>nt-taptf && it<=nt)
			          tt = 1-1.0/(taptf-1)*(it-1-nt+taptf)
			      end
			         d[it,ix] = tt*tx1*tx2*tx3*tx4*d[it,ix]
		    end
		end
	 end
	end
 end
 return d
end


@testset "219.contains-duplicate-ii.jl" begin
    @test contains_nearby_duplicate([1, 2, 3, 1], 3) == true
    @test contains_nearby_duplicate([1, 0, 1, 1], 1) == true
    @test contains_nearby_duplicate([1, 2, 3, 1, 2, 3], 2) == false
    @test contains_nearby_duplicate([1, 1, 3, 2], 0) == false
end


using Scallop.Book

@assert 12 == xbook(6)


module MoreAnalysis

export bound_variable_connections_graph

import ..Pluto
import ..Pluto: Cell, Notebook, NotebookTopology, ExpressionExplorer

"Find all subexpressions of the form `@bind symbol something`, and extract the `symbol`s."
function find_bound_variables(expr)
	found = Set{Symbol}()
	_find_bound_variables!(found, ExpressionExplorer.maybe_macroexpand(expr; recursive=true, expand_bind=false))
	found
end

function _find_bound_variables!(found::Set{Symbol}, expr::Expr)
	if expr.head === :macrocall && expr.args[1] === Symbol("@bind") && length(expr.args) == 4 && expr.args[3] isa Symbol
		push!(found, expr.args[3])
		_find_bound_variables!(found, expr.args[4])
    elseif expr.args === :quote
        found
    else
		for a in expr.args
			_find_bound_variables!(found, a)
		end
	end
end

function _find_bound_variables!(found::Set{Symbol}, expr::Any) end

"Return the given cells, and all cells that depend on them (recursively)."
function downstream_recursive(notebook::Notebook, topology::NotebookTopology, from::Union{Vector{Cell},Set{Cell}})::Set{Cell}
    found = Set{Cell}(copy(from))
    _downstream_recursive!(found, notebook, topology, from)
    found
end

function _downstream_recursive!(found::Set{Cell}, notebook::Notebook, topology::NotebookTopology, from::Vector{Cell})::Nothing
    for cell in from
        one_down = Pluto.where_referenced(notebook, topology, cell)
        for next in one_down
            if next ∉ found
                push!(found, next)
                _downstream_recursive!(found, notebook, topology, Cell[next])
            end
        end
    end
end

"Return all cells that are depended upon by any of the given cells."
function upstream_recursive(notebook::Notebook, topology::NotebookTopology, from::Union{Vector{Cell},Set{Cell}})::Set{Cell}
    found = Set{Cell}(copy(from))
    _upstream_recursive!(found, notebook, topology, from)
    found
end

function _upstream_recursive!(found::Set{Cell}, notebook::Notebook, topology::NotebookTopology, from::Vector{Cell})::Nothing
    for cell in from
        references = topology.nodes[cell].references
        for upstream in Pluto.where_assigned(notebook, topology, references)
            if upstream ∉ found
                push!(found, upstream)
                _upstream_recursive!(found, notebook, topology, Cell[upstream])
            end
        end
    end
end

"All cells that can affect the outcome of changing the given variable."
function codependents(notebook::Notebook, topology::NotebookTopology, var::Symbol)::Set{Cell}
    assigned_in = filter(notebook.cells) do cell
        var ∈ topology.nodes[cell].definitions
    end

    downstream = collect(downstream_recursive(notebook, topology, assigned_in))

    downupstream = upstream_recursive(notebook, topology, downstream)
end

"Return a `Dict{Symbol,Vector{Symbol}}` where the _keys_ are the bound variables of the notebook.

For each key (a bound symbol), the value is the list of (other) bound variables whose values need to be known to compute the result of setting the bond."
function bound_variable_connections_graph(notebook::Notebook)::Dict{Symbol,Vector{Symbol}}
    topology = notebook.topology
    bound_variables = union(map(notebook.cells) do cell
        find_bound_variables(topology.codes[cell].parsedcode)
    end...)
    Dict{Symbol,Vector{Symbol}}(
        var => let
            cells = codependents(notebook, topology, var)
            defined_there = union!(Set{Symbol}(), (topology.nodes[c].definitions for c in cells)...)
            # Set([var]) ∪
            collect((defined_there ∩ bound_variables))
        end
        for var in bound_variables
    )
end
end


using Bokeh

plotfile("display.html")

x = linspace(0, 2pi)

# plot returns a Plot object
myplot = plot(x, sin.(x))
# which can then be displayed with
showplot(myplot)

# if you wish to generate a plot but not display it, you can use genplot
genplot(myplot)

# you can also specify the filename directly to genplot:
genplot(myplot, "display1.html")

# underneither genplot there's renderplot which returns the actual html
html = renderplot(myplot)
println(html[1:100], "...")

# however bokeh.jl also keeps a reference to the current plot, which you can access
plot(x, cos.(x))
showplot(curplot())

# for convienience there's a shorthand for this:
showplot()

# Note: in this example, we've generated display.html and opened it in the browser
# 3 times, however probably all three plots will show the final plot,
# that's because the browser is too slow for julia which has generated the last version of
# display.html before the browser has time to opening it the first two times
# (or so I assume????)


# !! auto-generated code, do not edit !!
module sensor_msgs
using RobotOSData.Messages
using RobotOSData.StdMsgs
using RobotOSData.CommonMsgs
struct MagneticField <: Readable
    header::Header
    magnetic_field::geometry_msgs.Vector3
    magnetic_field_covariance::SVector{9, Float64}
end
struct NavSatStatus <: Readable
    status::Int8
    service::UInt16
end
struct LaserScan <: Readable
    header::Header
    angle_min::Float32
    angle_max::Float32
    angle_increment::Float32
    time_increment::Float32
    scan_time::Float32
    range_min::Float32
    range_max::Float32
    ranges::Vector{Float32}
    intensities::Vector{Float32}
end
struct BatteryState <: Readable
    header::Header
    voltage::Float32
    current::Float32
    charge::Float32
    capacity::Float32
    design_capacity::Float32
    percentage::Float32
    power_supply_status::UInt8
    power_supply_health::UInt8
    power_supply_technology::UInt8
    present::Bool
    cell_voltage::Vector{Float32}
    location::String
    serial_number::String
end
struct JoyFeedback <: Readable
    type_::UInt8
    id::UInt8
    intensity::Float32
end
struct JoyFeedbackArray <: Readable
    array::Vector{JoyFeedback}
end
struct Temperature <: Readable
    header::Header
    temperature::Float64
    variance::Float64
end
struct PointField <: Readable
    name::String
    offset::UInt32
    datatype::UInt8
    count::UInt32
end
struct PointCloud2 <: Readable
    header::Header
    height::UInt32
    width::UInt32
    fields::Vector{PointField}
    is_bigendian::Bool
    point_step::UInt32
    row_step::UInt32
    data::Vector{UInt8}
    is_dense::Bool
end
struct Illuminance <: Readable
    header::Header
    illuminance::Float64
    variance::Float64
end
struct LaserEcho <: Readable
    echoes::Vector{Float32}
end
struct MultiEchoLaserScan <: Readable
    header::Header
    angle_min::Float32
    angle_max::Float32
    angle_increment::Float32
    time_increment::Float32
    scan_time::Float32
    range_min::Float32
    range_max::Float32
    ranges::Vector{LaserEcho}
    intensities::Vector{LaserEcho}
end
struct ChannelFloat32 <: Readable
    name::String
    values::Vector{Float32}
end
struct PointCloud <: Readable
    header::Header
    points::Vector{geometry_msgs.Point32}
    channels::Vector{ChannelFloat32}
end
struct NavSatFix <: Readable
    header::Header
    status::NavSatStatus
    latitude::Float64
    longitude::Float64
    altitude::Float64
    position_covariance::SVector{9, Float64}
    position_covariance_type::UInt8
end
struct TimeReference <: Readable
    header::Header
    time_ref::ROSTime
    source::String
end
struct Imu <: Readable
    header::Header
    orientation::geometry_msgs.Quaternion
    orientation_covariance::SVector{9, Float64}
    angular_velocity::geometry_msgs.Vector3
    angular_velocity_covariance::SVector{9, Float64}
    linear_acceleration::geometry_msgs.Vector3
    linear_acceleration_covariance::SVector{9, Float64}
end
struct RegionOfInterest <: Readable
    x_offset::UInt32
    y_offset::UInt32
    height::UInt32
    width::UInt32
    do_rectify::Bool
end
struct CompressedImage <: Readable
    header::Header
    format::String
    data::Vector{UInt8}
end
struct Image <: Readable
    header::Header
    height::UInt32
    width::UInt32
    encoding::String
    is_bigendian::UInt8
    step::UInt32
    data::Vector{UInt8}
end
struct Joy <: Readable
    header::Header
    axes::Vector{Float32}
    buttons::Vector{Int32}
end
struct Range <: Readable
    header::Header
    radiation_type::UInt8
    field_of_view::Float32
    min_range::Float32
    max_range::Float32
    range::Float32
end
struct FluidPressure <: Readable
    header::Header
    fluid_pressure::Float64
    variance::Float64
end
struct JointState <: Readable
    header::Header
    name::Vector{String}
    position::Vector{Float64}
    velocity::Vector{Float64}
    effort::Vector{Float64}
end
struct MultiDOFJointState <: Readable
    header::Header
    joint_names::Vector{String}
    transforms::Vector{geometry_msgs.Transform}
    twist::Vector{geometry_msgs.Twist}
    wrench::Vector{geometry_msgs.Wrench}
end
struct RelativeHumidity <: Readable
    header::Header
    relative_humidity::Float64
    variance::Float64
end
struct CameraInfo <: Readable
    header::Header
    height::UInt32
    width::UInt32
    distortion_model::String
    D::Vector{Float64}
    K::SVector{9, Float64}
    R::SVector{9, Float64}
    P::SVector{12, Float64}
    binning_x::UInt32
    binning_y::UInt32
    roi::RegionOfInterest
end
end


include("m_step.jl")

function calibrate_item_mmle!(
    item::AbstractItem,
    examinees::Vector{<:AbstractExaminee},
    responses::Vector{Response};
    clean = false,
    int_opt_x_tol_rel::Float64 = 0.0001,
    int_opt_max_time::Float64 = 10.0,
    int_opt_f_tol_rel::Float64 = 0.00001,
)
    if !clean
        responses = sort(filter(r -> r.item_idx == item.idx, responses), by = r -> r.examinee_idx)
        examinees_idx = map(r -> r.examinee_idx, responses)
        examinees = sort(filter( e -> e.idx in examinees_idx, examinees), by = e -> e.idx)
    end
    item.parameters = m_step(
        item.parameters,
        get_latents(examinees),
        map(r -> r.val, responses),
        [Float64(int_opt_max_time), int_opt_x_tol_rel, int_opt_f_tol_rel]
        )
    #pmap( i -> calibrate_item_mmle!(i, examinees, responses; int_opt_x_tol_rel =int_opt_x_tol_rel, int_opt_max_time = int_opt_max_time, int_opt_f_tol_rel = int_opt_f_tol_rel), items)
    return nothing
end

function calibrate_item_mmle!(
    items::Vector{AbstractItem},
    examinees::Vector{<:AbstractExaminee},
    responses::Vector{Response};
    clean = false,
    int_opt_x_tol_rel::Float64 = 0.0001,
    int_opt_max_time::Float64 = 10.0,
    int_opt_f_tol_rel::Float64 = 0.00001,
)
    pmap( i -> calibrate_item_mmle!(
            i,
            examinees,
            responses;
            int_opt_max_time = int_opt_max_time,
            int_opt_x_tol_rel = int_opt_x_tol_rel,
            int_opt_f_tol_rel = int_opt_f_tol_rel,
            clean = clean,
        ),
        items
    )
    return nothing
end


@enum MetisScheme begin
    metis_scheme_recursive_bisection = Metis.METIS_PTYPE_RB
    metis_scheme_direct_k_way = Metis.METIS_PTYPE_KWAY
end

@enum MetisObjective begin
    metis_objective_edge_cut = Metis.METIS_OBJTYPE_CUT
    metis_objective_communication_volume = Metis.METIS_OBJTYPE_VOL
end

@enum MetisCoarsen begin
    metis_coarsen_random = Metis.METIS_CTYPE_RM
    metis_coarsen_sorted_heavy_edge = Metis.METIS_CTYPE_SHEM
end

@enum MetisInitialize begin
    metis_initialize_grow = Metis.METIS_IPTYPE_GROW
    metis_initialize_random = Metis.METIS_IPTYPE_RANDOM
    metis_initialize_edge = Metis.METIS_IPTYPE_EDGE
    metis_initialize_node = Metis.METIS_IPTYPE_NODE
end

@enum MetisRefine begin
    metis_refine_fm = Metis.METIS_RTYPE_FM
    metis_refine_greedy = Metis.METIS_RTYPE_GREEDY
    metis_refine_fm_two_sided = Metis.METIS_RTYPE_SEP2SIDED
    metis_refine_fm_one_sided = Metis.METIS_RTYPE_SEP1SIDED
end

struct MetisPartitioner
    assumesymmetry::Bool
    checksymmetry::Bool
    scheme::Union{Nothing, MetisScheme}
    objective::Union{Nothing, MetisObjective}
    coarsen::Union{Nothing, MetisCoarsen}
    initialize::Union{Nothing, MetisInitialize}
    refine::Union{Nothing, MetisRefine}
    ncuts::Union{Nothing, Int}
    niterations::Union{Nothing, Int}
    seed::Union{Nothing, Int}
    minimizeconnectivity::Union{Nothing, Bool}
    maketwohops::Union{Nothing, Bool}
    forcecontiguous::Union{Nothing, Bool}
    compressgraph::Union{Nothing, Bool}
    imbalance::Union{Nothing, Float64}
    weight::Any
    verbose_info::Bool
    verbose_time::Bool
    verbose_coarsen::Bool
    verbose_refine::Bool
    verbose_initialize::Bool
    verbose_moves::Bool
    verbose_separators::Bool
    verbose_connectivity::Bool
    verbose_contiguous::Bool
    verbose_memory::Bool
end

MetisPartitioner(;
    assumesymmetry = false,
    checksymmetry = true,
    scheme = nothing,
    objective = nothing,
    coarsen = nothing,
    initialize = nothing,
    refine = nothing,
    ncuts = nothing,
    niterations = nothing,
    seed = nothing,
    minimizeconnectivity = nothing,
    maketwohops = nothing,
    forcecontiguous = nothing,
    compressgraph = nothing,
    imbalance = nothing,
    weight = VertexCount(),
    verbose_info = false,
    verbose_time = false,
    verbose_coarsen = false,
    verbose_refine = false,
    verbose_initialize = false,
    verbose_moves = false,
    verbose_separators = false,
    verbose_connectivity = false,
    verbose_contiguous = false,
    verbose_memory = false,
) = MetisPartitioner(
    assumesymmetry,
    checksymmetry,
    scheme,
    objective,
    coarsen,
    initialize,
    refine,
    ncuts,
    niterations,
    seed,
    minimizeconnectivity,
    maketwohops,
    forcecontiguous,
    compressgraph,
    imbalance,
    weight,
    verbose_info,
    verbose_time,
    verbose_coarsen,
    verbose_refine,
    verbose_initialize,
    verbose_moves,
    verbose_separators,
    verbose_connectivity,
    verbose_contiguous,
    verbose_memory,
)

function metis_partition(A::SparseMatrixCSC, wgt, K, method::MetisPartitioner)
    old_options = copy(Metis.options)

    fill!(Metis.options, Cint(-1))
    Metis.options[Metis.METIS_OPTION_NUMBERING] = 1

    if method.scheme === nothing
        if K > 8
            alg = :KWAY
        else
            alg = :RECURSIVE
        end
    else
        if method.scheme == metis_scheme_direct_k_way
            alg = :KWAY
        elseif method.scheme == metis_scheme_recursive_bisection
            alg = :RECURSIVE
        end
    end

    if method.objective !== nothing
        Metis.options[Metis.METIS_OPTION_OBJTYPE] = Cint(method.objective)
    end
    if method.coarsen !== nothing
        Metis.options[Metis.METIS_OPTION_CTYPE] = Cint(method.coarsen)
    end
    if method.initialize !== nothing
        Metis.options[Metis.METIS_OPTION_IPTYPE] = Cint(method.initialize)
    end
    if method.refine !== nothing
        Metis.options[Metis.METIS_OPTION_RTYPE] = Cint(method.refine)
    end
    if method.ncuts !== nothing
        @assert method.ncuts >= 1
        Metis.options[Metis.METIS_OPTION_NCUTS] = Cint(method.ncuts)
    end
    if method.niterations !== nothing
        @assert method.niterations >= 0
        Metis.options[Metis.METIS_OPTION_NITER] = Cint(method.niterations)
    end
    if method.seed !== nothing
        Metis.options[Metis.METIS_OPTION_SEED] = Cint(method.seed)
    end
    if method.minimizeconnectivity !== nothing
        Metis.options[Metis.METIS_OPTION_MINCONN] = Cint(method.minimizeconnectivity)
    end
    if method.maketwohops !== nothing
        Metis.options[Metis.METIS_OPTION_NO2HOP] = Cint(!method.maketwohops)
    end
    if method.forcecontiguous !== nothing
        Metis.options[Metis.METIS_OPTION_CONTIG] = Cint(method.forcecontiguous)
    end
    if method.compressgraph !== nothing
        Metis.options[Metis.METIS_OPTION_COMPRESS] = Cint(method.compressgraph)
    end
    if method.imbalance !== nothing
        imbalance = ceil(Cint, method.imbalance * 1000)
        @assert imbalance > 0
        Metis.options[Metis.METIS_OPTION_UFACTOR] = imbalance
    end

    verbose = Cint(0)
    if method.verbose_info verbose |= Metis.METIS_DBG_INFO end
    if method.verbose_time verbose |= Metis.METIS_DBG_TIME end
    if method.verbose_coarsen verbose |= Metis.METIS_DBG_COARSEN end
    if method.verbose_refine verbose |= Metis.METIS_DBG_REFINE end
    if method.verbose_initialize verbose |= Metis.METIS_DBG_IPART end
    if method.verbose_moves verbose |= Metis.METIS_DBG_MOVEINFO end
    if method.verbose_separators verbose |= Metis.METIS_DBG_SEPINFO end
    if method.verbose_connectivity verbose |= Metis.METIS_DBG_CONNINFO end
    if method.verbose_contiguous verbose |= Metis.METIS_DBG_CONTIGINFO end
    if method.verbose_memory verbose |= Metis.METIS_DBG_MEMORY end

    g = Metis.graph(A)
    if wgt !== nothing
        g = Metis.Graph(g.nvtxs, g.xadj, g.adjncy, convert(Vector{typeof(g.nvtxs)}, wgt))
    end

    if verbose == 0
        asg = @suppress Metis.partition(A, K, alg=alg)
    else
        asg = Metis.partition(A, K, alg=alg)
    end

    Metis.options .= old_options

    return asg
end

function partition_stripe(A::Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, K, method::MetisPartitioner; kwargs...) where {Tv, Ti}
    wgt = method.weight isa VertexCount ? nothing : compute_weight(A, method.weight)
    asg = metis_partition(pattern(SparseMatrixCSC(A)), wgt, K, method)
    return MapPartition(K, asg)
end

function partition_stripe(A::SparseMatrixCSC, K, method::MetisPartitioner; kwargs...)
    return partition_plaid(A, K, method)[end]
end

function partition_plaid(A::Symmetric{Tv, SparseMatrixCSC{Tv, Ti}}, K, method::MetisPartitioner; kwargs...) where {Tv, Ti}
    Φ = partition_stripe(A, K, method, kwargs...)
    return (Φ, Φ)
end

function partition_plaid(A::SparseMatrixCSC, K, method::MetisPartitioner; kwargs...)
    if method.assumesymmetry || (method.checksymmetry && issymmetric(pattern(A)))
        wgt = method.weight isa VertexCount ? nothing : compute_weight(A, method.weight)
        asg = metis_partition(pattern(A), wgt, K, method)
        return (MapPartition(K, asg), MapPartition(K, asg))
    else
        m, n = size(A)
        AP = pattern(A)
        B = hvcat((2, 2), spzeros(Bool, m, m), AP, adjointpattern(AP), spzeros(Bool, n, n))
        wgt = compute_weight(A, method.weight)
        wgt = vcat(zeros(eltype(wgt), m), wgt)
        asg = metis_partition(B, wgt, K, method)
        return (MapPartition(K, asg[1:m]), MapPartition(K, asg[m + 1:end]))
    end
end


digits_bw_labels() = CSV.read(datadir("digits_bw/labels.csv"), DataFrame)
digits_rgb_labels() = CSV.read(datadir("digits_rgb/labels.csv"), DataFrame)
morpho_mnist_labels() = CSV.read(datadir("morpho_mnist/labels.csv"), DataFrame)

"""
	filter_data(full_labels::DataFrame, filter_dict::Dict)

Filter the data given the full labels and a dict of filters.

Example:
filter_dict_a = Dict(
    "shape" => [1],
    "normalized_orientation" => ["<=", 0.1],
    "scale" => ["==", 0.8],
    "posX" => ["<", 0.5],
    "posY" => ["<", 0.5],
    )
"""
function filter_data(full_labels, filter_dict; categorical_key=nothing)
    ks = keys(filter_dict)
    bin_inds = !isnothing(categorical_key)  ?
        map(x-> x in filter_dict[categorical_key], full_labels[!,Symbol(categorical_key)]) :
        Bool.(ones(size(full_labels,1)))
    for k in filter(k->k != categorical_key, ks)
        kp = filter_dict[k]
        #ff(x) = eval(Meta.parse("$x $(kp[1]) $(kp[2])")) # this is slow
        val = Float64(Meta.parse("$(kp[2])"))
        # this is ugly but fast
        ff(x) = if kp[1] == "=="
            x == val
        elseif kp[1] == "<="
            x <= val
        elseif kp[1] == "<"
            x < val
        elseif kp[1] == ">="
            x >= val
        elseif kp[1] == ">"
            x > val
        elseif kp[1] == "!="
            x != val
        else
            error("Unknown operator, please support one of [==, <, <=, >, >=, !=]")
        end
        bin_inds = bin_inds .& map(ff, full_labels[!,Symbol(k)])
    end
    return bin_inds
end

hyphen_join(x,y) = "$(x)-$(y)"

"""
	safe_savename(params, args...; kwargs...)

Equivalent to DrWatson.savename but preserves arrays and tuples.
"""
function safe_savename(params, args...; kwargs...)
    outparams = Dict()
    for (k,v) in pairs(params)
        if typeof(v) <: Vector
            outparams[k] = reduce(hyphen_join,v)
        else
            outparams[k] = v
        end
    end
    return replace(savename(outparams, args...; kwargs...), " "=>"")
end

function _load_mnist(type::String)
	(type in ["train", "test"]) ? nothing : error("Suggested MNIST data type $type not available.")
    x = eval(Meta.parse("MLDatasets.MNIST.$(type)tensor(Float32)"))
    x = permutedims(x, (2,1,3))
    s = size(x)
    reshape(x, s[1], s[2], 1, s[3])
end

"""
	load_mnist(type="")

Loads either all/test/train MNIST data.
"""
function load_mnist(type="")
	if type == ""
		return cat(_load_mnist("train"), _load_mnist("test"), dims=4)
	else
		return _load_mnist(type)
	end
end

"""
	load_train_val_test_data(dataset, filter_dict=Dict(); ratios=(0.6,0.2,0.2), seed=nothing,
        categorical_key=nothing)

Dataset is one of "morhpo_mnist"/"digits_bw"/"digits_rgb".
"""
function load_train_val_test_data(dataset, filter_dict=Dict(); ratios=(0.6,0.2,0.2),
    seed=nothing, categorical_key=nothing)
    if dataset == "morpho_mnist"
	    full_data = load_mnist()
    elseif dataset == "shapes2D"
        full_data,_ = load_shapes2D()
	else
		raw_data = load(datadir("$(dataset)/digits.bson"))[:digits]
		N = length(raw_data)
		full_data = zeros(Float32, size(raw_data[1])..., N);
		for i in 1:N
		    full_data[:,:,:,i] .= raw_data[i]
		end
	end
    full_labels = CSV.read(datadir("$(dataset)/labels.csv"), DataFrame)
    included_inds = filter_data(full_labels, filter_dict; categorical_key=categorical_key)

    # now split the data
    normal_data = full_data[:,:,:,included_inds]
    anomalous_data = full_data[:,:,:,.!included_inds]

    # further split the normal into train and val
    trinds, valinds, tstinds = train_val_test_inds(1:size(normal_data,4),
        ratios; seed=seed)
    tr_x = normal_data[:,:,:,trinds]
    val_x = normal_data[:,:,:,valinds]
    tst_x = normal_data[:,:,:,tstinds]
    a_x = anomalous_data

    tr_y = full_labels[included_inds,:][trinds,:]
	val_y = full_labels[included_inds,:][valinds,:]
	tst_y = full_labels[included_inds,:][tstinds,:]
	a_y = full_labels[.!included_inds,:]

    (tr_x, tr_y), (val_x, val_y), (tst_x, tst_y), (a_x, a_y)
end

# stuff for run scripts
function get_filter_info(experiment_argnames, args, arg_table)
    # construct the Dictionary with filters and their values
    filter_keys = filter(k->!(k in experiment_argnames),keys(args))
    filter_dict = Dict(zip(filter_keys, [args[k] for k in filter_keys]))

    # also, set which arguments are non-default
    non_default_filters = []
    for k in filter_keys
        argind = findfirst(map(f->f.dest_name == k,arg_table.args_table.fields))
        (arg_table.args_table.fields[argind].default == filter_dict[k]) ? nothing :
            push!(non_default_filters, k)
    end

    filter_dict, non_default_filters
end


abstract type PrecipitationBC end

"""
    OutflowPrecipitation() :: PrecipitationBC

Free flux out of the domain.
"""
struct OutflowPrecipitation <: PrecipitationBC end
function atmos_precipitation_boundary_state!(
    nf,
    bc_precipitation::OutflowPrecipitation,
    atmos,
    _...,
) end
function atmos_precipitation_normal_boundary_flux_second_order!(
    nf,
    bc_precipitation::OutflowPrecipitation,
    atmos,
    _...,
) end


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

"""
Unit tests for the *(x, y) function
"""

# --- Imports

# Standard library
import InteractiveUtils.subtypes
using Test

# GeometricAlgebra.jl
using GeometricAlgebra

# --- File inclusions

# Test utilities
include("test_utils.jl")

# --- Tests

# ------ M::Multivector

@testset "*(M::Multivector, N::Multivector)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::Blade)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::Pseudoscalar)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::Scalar)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::One)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::Zero)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::Real)" begin
    @test_skip 1
end

@testset "*(M::Multivector, N::Vector)" begin
    @test_skip 1
end

# ------ B::Blade

@testset "*(B::Blade, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::Blade, C::Blade)" begin
    @test_skip 1
end

@testset "*(B::Blade, C::Pseudoscalar)" begin
    @test_skip 1
end

@testset "*(B::Blade, C::Scalar)" begin
    test_dim = 10
    B = Blade(rand(test_dim, 3))

    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = Scalar(test_value)

    @test B * C ≈ Blade(basis(B), volume=value(C) * volume(B))
end

@testset "*(B::Blade, C::One)" begin
    test_dim = 10
    B = Blade(rand(test_dim, 3))
    C = One()

    @test B * C === B
end

@testset "*(B::Blade, C::Zero)" begin
    test_dim = 10
    B = Blade(rand(test_dim, 3))
    C = Zero()

    @test B * C === C
end

@testset "*(B::Blade, C::Real)" begin
    test_dim = 10
    B = Blade(rand(test_dim, 3))

    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = test_value

    @test B * C ≈ Blade(basis(B), volume=C * volume(B))
end

@testset "*(B::Blade, C::Vector)" begin
    @test_skip 1
end

# ------ B::Pseudoscalar

@testset "*(B::Pseudoscalar, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::Pseudoscalar, C::Blade)" begin
    @test_skip 1
end

@testset "*(B::Pseudoscalar, C::Pseudoscalar)" begin
    # B != 1 / C
    test_value_1 = get_random_value(1)  # add 1 to keep value away from 0
    test_value_2 = abs(1 / test_value_1) + rand() + 1
    test_value_2 = rand() > 0.5 ? test_value_2 : -test_value_2

    for test_dim in 5:8
        B = Pseudoscalar(test_dim, test_value_1)
        C = Pseudoscalar(test_dim, test_value_2)

        B_times_C = B * C
        expected_result = mod(test_dim, 4) < 2 ?
            test_value_1 * test_value_2 :
           -test_value_1 * test_value_2

        @test B_times_C isa Scalar
        @test B_times_C.value == expected_result
    end

    # B == 1 / C
    # hard-coded a value for test_value_1 to avoid floating-point arithmetic errors
    # when computing the inverse of the value, which could result in the product
    # being different from one
    test_value_1 = 2

    for test_dim in 5:8
        test_value_2 = mod(test_dim, 4) < 2 ?
            1 / test_value_1 :
            -1 / test_value_1

        B = Pseudoscalar(test_dim, test_value_1)
        C = Pseudoscalar(test_dim, test_value_2)

        B_times_C = B * C
        @test B_times_C isa One
    end

    # B ≈ 1 / C
    test_value_1 = 0.7621865484887302

    for test_dim in 5:8
        test_value_2 = mod(test_dim, 4) < 2 ?
            1 / test_value_1 :
            -1 / test_value_1

        B = Pseudoscalar(test_dim, test_value_1)
        C = Pseudoscalar(test_dim, test_value_2)

        B_times_C = B * C
        @test B_times_C ≈ 1
    end
end

@testset "*(B::Pseudoscalar, C::Scalar)" begin
    test_dim = 10
    test_value_1 = get_random_value(1)  # add 1 to keep value away from 0
    B = Pseudoscalar(test_dim, test_value_1)

    test_value_2 = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C isa Pseudoscalar
    @test B_times_C.dim == test_dim
    @test B_times_C.value == test_value_1 * test_value_2
end

@testset "*(B::Pseudoscalar, C::One)" begin
    test_dim = 10
    test_value = get_random_value(1)  # add 1 to keep value away from 0
    B = Pseudoscalar(test_dim, test_value)

    C = One()

    @test B * C === B
end

@testset "*(B::Pseudoscalar, C::Zero)" begin
    test_dim = 10
    test_value = get_random_value(1)  # add 1 to keep value away from 0
    B = Pseudoscalar(test_dim, test_value)

    C = Zero()

    @test B * C === C
end

@testset "*(B::Pseudoscalar, C::Real)" begin
    # --- Preparation

    test_dim = 10
    test_value_1 = get_random_value(1)  # add 1 to keep value away from 0
    B = Pseudoscalar(test_dim, test_value_1)

    # --- Tests

    # C != 0
    test_value_2 = get_random_value(1)  # add 1 to keep value away from 0
    C = test_value_2

    B_times_C = B * C
    @test B_times_C isa Pseudoscalar
    @test B_times_C.dim == test_dim
    @test B_times_C.value == test_value_1 * test_value_2

    # C == 0
    C = 0

    B_times_C = B * C
    @test B_times_C isa Zero
end

@testset "*(B::Pseudoscalar, C::Vector)" begin
    @test_skip 1
end

# ------ B::Scalar

@testset "*(B::Scalar, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::Scalar, C::Blade)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value)

    test_dim = 10
    C = Blade(rand(test_dim, 3))

    @test B * C ≈ Blade(basis(C), volume=value(B) * volume(C))
end

@testset "*(B::Scalar, C::Pseudoscalar)" begin
    test_value_1 = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value_1)

    test_dim = 10
    test_value_2 = get_random_value(1)  # add 1 to keep value away from 0
    C = Pseudoscalar(test_dim, test_value_2)

    B_times_C = B * C
    @test B_times_C isa Pseudoscalar
    @test B_times_C.dim == test_dim
    @test B_times_C.value == test_value_1 * test_value_2
end

@testset "*(B::Scalar, C::Scalar)" begin
    # C != 1 / B
    test_value_1 = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value_1)

    test_value_2 = abs(1 / test_value_1) + rand() + 1
    test_value_2 = rand() > 0.5 ? test_value_2 : -test_value_2
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C isa Scalar
    @test B_times_C.value == test_value_1 * test_value_2

    # C == 1 / B
    # hard-coded a value for test_value_1 to avoid floating-point arithmetic errors
    # when computing the inverse of the value, which could result in the product
    # being different from one
    test_value_1 = 2
    B = Scalar(test_value_1)

    test_value_2 = 1 / test_value_1
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C isa One

    # C ≈ 1 / B
    test_value_1 = 0.7621865484887302
    B = Scalar(test_value_1)

    test_value_2 = 1 / test_value_1
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C ≈ 1
end

@testset "*(B::Scalar, C::One)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value)

    C = One()

    @test B * C === B
end

@testset "*(B::Scalar, C::Zero)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value)

    C = Zero()

    @test B * C === C
end

@testset "*(B::Scalar, C::Real)" begin
    # C != 0, C != 1 / B
    test_value_1 = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value_1)

    test_value_2 = abs(1 / test_value_1) + rand() + 1
    test_value_2 = rand() > 0.5 ? test_value_2 : -test_value_2
    C = test_value_2

    B_times_C = B * C
    @test B_times_C isa Scalar
    @test B_times_C.value == test_value_1 * test_value_2

    # C == 0
    C = 0

    B_times_C = B * C
    @test B_times_C isa Zero

    # C == 1 / B
    # hard-coded a value for test_value_1 to avoid floating-point arithmetic errors
    # when computing the inverse of the value, which could result in the product
    # being different from one
    test_value_1 = 2
    B = Scalar(test_value_1)

    test_value_2 = 1 / test_value_1
    C = test_value_2

    B_times_C = B * C
    @test B_times_C isa One

    # C ≈ 1 / B
    test_value_1 = 0.7621865484887302
    B = Scalar(test_value_1)

    test_value_2 = 1 / test_value_1
    C = test_value_2

    B_times_C = B * C
    @test B_times_C ≈ 1
end

@testset "*(B::Scalar, C::Vector)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = Scalar(test_value)

    # C is not zero
    C = rand(5)

    B_times_C = B * C
    @test B_times_C isa Blade
    @test B_times_C ≈ test_value * Blade(C)

    # C is zero
    C = [0; 0; 0; 0; 0]
    @test iszero(B * C)
end

# ------ B::One

@testset "*(B::One, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::One, C::Blade)" begin
    B = One()
    test_dim = 10
    C = Blade(rand(test_dim, 3))

    @test B * C === C
end

@testset "*(B::One, C::Pseudoscalar)" begin
    B = One()

    test_dim = 10
    test_value = get_random_value(1)  # add 1 to keep value away from 0
    C = Pseudoscalar(test_dim, test_value)

    @test B * C === C
end

@testset "*(B::One, C::Scalar)" begin
    B = One()

    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = Scalar(test_value)

    @test B * C === C
end

@testset "*(B::One, C::One)" begin
    B = One()
    C = One()

    B_times_C = B * C
    @test B_times_C === B
end

@testset "*(B::One, C::Zero)" begin
    B = One()
    C = Zero()

    B_times_C = B * C
    @test B_times_C === C
end

@testset "*(B::One, C::Real)" begin
    # --- Preparations

    B = One()

    # --- Tests

    # C != 0, C != 1
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = test_value

    B_times_C = B * C
    @test B_times_C isa Scalar
    @test B_times_C.value == test_value

    # C == 0
    C = 0

    B_times_C = B * C
    @test B_times_C isa Zero

    # C == 1
    C = 1

    B_times_C = B * C
    @test B_times_C isa One
end

@testset "*(B::One, C::Vector)" begin
    C = One()

    # B is not zero
    B = rand(5)
    B_times_C = B * C
    @test B_times_C isa Blade
    @test B_times_C == Blade(B)

    # B is zero
    B = [0; 0; 0; 0; 0]
    @test iszero(B * C)
end

# ------ B::Zero

@testset "*(B::Zero, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::Zero, C::Blade)" begin
    B = Zero()
    test_dim = 10
    C = Blade(rand(test_dim, 3))

    @test B * C === B
end

@testset "*(B::Zero, C::Pseudoscalar)" begin
    B = Zero()

    test_dim = 10
    test_value = get_random_value(1)  # add 1 to keep value away from 0
    C = Pseudoscalar(test_dim, test_value)

    @test B * C === B
end

@testset "*(B::Zero, C::Scalar)" begin
    B = Zero()

    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = Scalar(test_value)

    @test B * C === B
end

@testset "*(B::Zero, C::One)" begin
    B = Zero()
    C = One()

    B_times_C = B * C
    @test B_times_C === B
end

@testset "*(B::Zero, C::Zero)" begin
    B = Zero()
    C = Zero()

    B_times_C = B * C
    @test B_times_C === B
end

@testset "*(B::Zero, C::Real)" begin
    B = Zero()

    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = test_value

    @test B * C === B
end

@testset "*(B::Zero, C::Vector)" begin
    B = Zero()

    # C is not zero
    C = rand(5)
    @test B * C === B

    # C is zero
    C = [0; 0; 0; 0; 0]
    @test B * C === B
end

# ------ B::Real

@testset "*(B::Real, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::Real, C::Blade)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = test_value

    test_dim = 10
    C = Blade(rand(test_dim, 3))

    @test B * C ≈ Blade(basis(C), volume=B * volume(C))
end

@testset "*(B::Real, C::Pseudoscalar)" begin
    # --- Preparations

    test_dim = 10
    test_value_2 = get_random_value(1)  # add 1 to keep value away from 0
    C = Pseudoscalar(test_dim, test_value_2)

    # --- Tests

    # B != 0
    test_value_1 = get_random_value(1)  # add 1 to keep value away from 0
    B = test_value_1

    B_times_C = B * C
    @test B_times_C isa Pseudoscalar
    @test B_times_C.dim == test_dim
    @test B_times_C.value == test_value_1 * test_value_2

    # B == 0
    B = 0

    B_times_C = B * C
    @test B_times_C isa Zero
end

@testset "*(B::Real, C::Scalar)" begin
    # B != 0, B != 1 / C
    test_value_1 = get_random_value(1)  # add 1 to keep value away from 0
    B = test_value_1

    test_value_2 = abs(1 / test_value_1) + rand() + 1
    test_value_2 = rand() > 0.5 ? test_value_2 : -test_value_2
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C isa Scalar
    @test B_times_C.value == test_value_1 * test_value_2

    # B == 0
    B = 0

    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = Scalar(test_value)

    B_times_C = B * C
    @test B_times_C isa Zero

    # B == 1 / C
    # hard-coded a value for test_value_1 to avoid floating-point arithmetic errors
    # when computing the inverse of the value, which could result in the product
    # being different from one
    test_value_1 = 2
    B = test_value_1

    test_value_2 = 1 / test_value_1
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C isa One

    # C ≈ 1 / B
    test_value_1 = 0.7621865484887302
    B = test_value_1

    test_value_2 = 1 / test_value_1
    C = Scalar(test_value_2)

    B_times_C = B * C
    @test B_times_C ≈ 1
end

@testset "*(B::Real, C::One)" begin
    # --- Preparations

    C = One()

    # --- Tests

    # B != 0, B != 1
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = test_value

    B_times_C = B * C
    @test B_times_C isa Scalar
    @test B_times_C.value == test_value

    # B == 0
    B = 0

    B_times_C = B * C
    @test B_times_C isa Zero

    # B == 1
    B = 1

    B_times_C = B * C
    @test B_times_C isa One
end

@testset "*(B::Real, C::Zero)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    B = test_value

    C = Zero()

    @test iszero(B * C)
end

# ------ B::Vector

@testset "*(B::Vector, M::Multivector)" begin
    @test_skip 1
end

@testset "*(B::Vector, C::Blade)" begin
    @test_skip 1
end

@testset "*(B::Vector, C::Pseudoscalar)" begin
    @test_skip 1
end

@testset "*(B::Vector, C::Scalar)" begin
    test_value = get_random_value(2)  # add 2 to keep value away from 0 and 1
    C = Scalar(test_value)

    # B is not zero
    B = rand(5)

    B_times_C = B * C
    @test B_times_C isa Blade
    @test B_times_C ≈ test_value * Blade(B)

    # B is zero
    B = [0; 0; 0; 0; 0]
    @test iszero(B * C)
end

@testset "*(B::Vector, C::One)" begin
    B = One()

    # C is not zero
    C = rand(5)
    B_times_C = B * C
    @test B_times_C isa Blade
    @test B_times_C == Blade(C)

    # C is zero
    C = [0; 0; 0; 0; 0]
    @test iszero(B * C)
end

@testset "*(B::Vector, C::Zero)" begin
    C = Zero()

    # B is not zero
    B = rand(5)
    @test B * C === C

    # B is zero
    B = [0; 0; 0; 0; 0]
    @test B * C === C
end


using BHAPtfem

ProjDir = dirname(@__FILE__)
ProjName = split(ProjDir, "/")[end]

segments = [
# Element type,  Material,    Length,     OD,         ID,        fc,     noofelements
  :bit,                    :steel ,        0.00,      2.75,      12.25,     0.0,         0,
  :collar ,              :monel ,    25.00,      2.75,       7.75,      0.0,       25,
  :stabilizer,          :steel,         0.00,       2.75,     12.125,    0.0,         0,
  :collar,                :steel,         30.00,      2.75,       7.75,      0.0,        30,
  :stabilizer,          :steel,          0.00,       2.75,     12.125,    0.0,          0,
  :collar,                :steel,        30.00,       2.75,      7.75,      0.0,         30,
  :stabilizer,          :steel,          0.00,       2.75,     12.125,    0.0,          0,
  :collar,               :steel,        90.00,       2.75,      7.75,      0.0,         90,
  :stabilizer,          :steel,          0.00,       2.75,     12.125,    0.0,          0,
  :pipe,                  :steel,     100.00,       2.75,      7.75,       0.0,       100
];

traj = [
#   Heading,      Bit diameter
        60.0,             12.25
]

wobrange = 45:5:55
inclinationrange = 35:5:55           # Or e.g. incls = [5, 10]

println()

prob = problem(segments, traj, wobrange, inclinationrange,
  medium=:lightmud, computefunction=PtFEM.p44, pdir=ProjDir)

@time results = solve!(prob)

sleep(1) # Wait for all processes to complete

println("\nSize of result  array of tuples = $(size(results))")
println("Size of results[1] in results tuple = $(size(results[3][2]))\n")

println(results[1][2])

#=
Needs to generate something like:

data = Dict(
  # Frame(nels, nn, ndim, nst, nip, finite_element(nod, nodof))
  :struc_el => Frame(10, 11, 2, 1, 1, Line(2, 3)),
  :properties => [
    1.07207e9 4.53116e9;],
  :x_coords =>0.0:1.0:10.0,
  :y_coords => [0.0 for i in 1:11],
  :g_num => [
    1 2 3 4 5 6 7 8 9 10;
    2 3 4 5 6 7 8 9 10 11],
  :support => [
    (1, [1 0 1]),
    (5, [1 0 1]),
    (11, [0, 1, 1])
    ],
  :loaded_nodes => [
    (1, [10000.0 -600.0 -600.0]),
    (2, [0.0 -600.0 0.0]),
    (3, [0.0 -600.0 0.0]),
...
    (10, [0.0 -600.0 0.0]),
    (11, [0.0 -600.0 -600.0])
    ],
  :penalty => 1e19,
  :eq_nodal_forces_and_moments => [
    (1, [0.0 -60.0 -60.0 0.0 -60.0 60.0]),
    (2, [0.0 -120.0 -140.0 0.0 -120.0 140.0]),
    (3, [0.0 -20.0 -6.67 0.0 -20.0 6.67])
  ]
)
  =#


@testset "RandomWalk1D" begin

    end_rewards = 3 => 5
    env = RandomWalk1D(; rewards = end_rewards)

    RLBase.test_interfaces!(env)
    RLBase.test_runnable!(env)

    rng = StableRNG(123)
    N = 50_000
    rewards = []
    for _ in 1:N
        while !is_terminated(env)
            env(rand(rng, legal_action_space(env)))
        end
        push!(rewards, reward(env))
        reset!(env)
    end

    @test isapprox(mean(rewards), mean(end_rewards); atol = 0.01)
end


using HorizonSideRobots

function paint_perimeter(robot::Robot)
    c = 0
    while !isborder(robot, West)
        move!(robot, West)
        c += 1
    end

    sides = [Nord, Ost, Sud, West]
    for side in sides
        while !isborder(robot, side)
            putmarker!(robot)
            move!(robot, side)
        end
    end

    while !ismarker(robot)
        putmarker!(robot)
        move!(robot, Nord)
    end

    while c > 0
        move!(robot, Ost)
        c -= 1
    end
end


struct Currency{C} end

struct Cash{C<:Currency, T<:Real} <: Number
    value::T
end


@testset "VecAttrib" begin
    using MeshCore: VecAttrib, datavaluetype
    attribute = VecAttrib([i for i in 1:10])

    @test IndexStyle(attribute) == IndexLinear()
    @test size(attribute) == (10,)
    for i in 1:10
        @test attribute[i] == i
    end
    @test attribute[1,2,3,2,1] == [1,2,3,2,1]
    attribute[1] = 100
    @test attribute[1] == 100
    attribute[1,2,10] = [100,200,1000]
    @test attribute[1,2,10] == [100,200,1000]
    @test datavaluetype(attribute) == Int
end

@testset "FunAttrib" begin
    using MeshCore: FunAttrib, datavaluetype
    fun_attribute = FunAttrib(0, 10, x -> x)

    @test IndexStyle(fun_attribute) == IndexLinear()
    @test size(fun_attribute) == (10,)
    for i in 1:10
        @test fun_attribute[i] == i
    end
    @test fun_attribute[1,2,3,2,1] == [1,2,3,2,1]
    @test datavaluetype(fun_attribute) == Int
end


using OffsetArrays
using Test
ambs = detect_ambiguities(Base, Core)  # in case these have ambiguities of their own
using PaddedViews
@testset "ambiguities" begin
    @test isempty(setdiff(detect_ambiguities(PaddedViews, Base, Core), ambs))
end

@testset "PaddedView" begin
    for n = 0:5
        a = @inferred(PaddedView(0, ones(Int,ntuple(d->1,n)), ntuple(x->x+1,n)))
        @test axes(a) == ntuple(x->1:x+1,n)
        @test @inferred(a[1]) == 1
        n > 0 && @test @inferred(a[2]) == 0
        @test @inferred(a[ntuple(x->1,n)...]) == 1
        n > 0 && @test @inferred(a[2, ntuple(x->1,n-1)...]) == 0
    end
    a0 = reshape([3])
    a = @inferred(PaddedView(-1, a0, ()))
    @test axes(a) == ()
    @test ndims(a) == 0
    @test a[] == 3

    a = reshape(1:9, 3, 3)
    A = @inferred(PaddedView(0.0, a, (Base.OneTo(4), Base.OneTo(5))))
    @test eltype(A) == Int
    @test ndims(A) == 2
    @test size(A) === (4,5)
    @test @inferred(axes(A)) === (Base.OneTo(4), Base.OneTo(5))
    @test @inferred(axes(A, 3)) === Base.OneTo(1)
    @test A == [1 4 7 0 0;
                2 5 8 0 0;
                3 6 9 0 0;
                0 0 0 0 0]

    A = @inferred(PaddedView(0.0, a, (0:4, -1:5)))
    @test eltype(A) == Int
    @test ndims(A) == 2
    @test size(A) == (5, 7)
    @test @inferred(axes(A)) === (0:4, -1:5)
    @test @inferred(axes(A, 3)) === 1:1
    @test A == OffsetArray([0 0 0 0 0 0 0;
                            0 0 1 4 7 0 0;
                            0 0 2 5 8 0 0;
                            0 0 3 6 9 0 0;
                            0 0 0 0 0 0 0], 0:4, -1:5)

    A = @inferred(PaddedView(0.0, a, (Base.OneTo(5), Base.OneTo(5)), (2:4, 2:4)))
    @test A == [0 0 0 0 0;
                0 1 4 7 0;
                0 2 5 8 0;
                0 3 6 9 0;
                0 0 0 0 0]
    @test A == @inferred(PaddedView(0.0, a, (5, 5), (2, 2)))

    @test PaddedView(0, ones(4), (3,), (1,)) == PaddedView(0, ones(4), (3,))
    let a = reshape(1:6, 2, 3) # [1 3 5; 2 4 6]
        @test @inferred(PaddedView(-1, a, (2, 2))) == [1 3
                                                       2 4]

        @test @inferred(PaddedView(-1, a, (1, 4)))  == [1 3 5 -1 ]

        @test @inferred(PaddedView(-1, a, (2, 4), (1, 1)))  == [1 3 5 -1
                                                                2 4 6 -1]

        @test @inferred(PaddedView(-1, a, (2, 4), (1, 2)))  == [-1 1 3 5
                                                                -1 2 4 6]

        @test @inferred(PaddedView(-1, a, (2, 3), (0, 2)))  == [-1  2  4
                                                                -1 -1 -1]

        @test @inferred(PaddedView(-1, a, (1, 4), (1, -1))) == [5 -1 -1 -1]

        # Create an OffsetArray with axes (0:1, 2:4)
        oa = OffsetArray(a, -1, 1)
        # Make a PaddedView of its elements (1:3, 1:2) (i.e pv[1, 1] == oa[1, 1] == "a[2, 0]" == -1)
        pv = @inferred PaddedView(-1, oa, (3, 2))
        @test pv[1:3, 1:2] == [-1 2; -1 -1; -1 -1]
        # Put oa[1, 1] in pv[2, 0] and make a PaddedView of the resulting (1:3, 1:2) elements
            # i.e. same as putting oa[0,2] in pv[1, 1]
        pv = @inferred PaddedView(-1, oa, (3, 2), (2, 0))
        @test pv[1:3, 1:2] == [1 3; 2 4; -1 -1]
    end
end

@testset "paddedviews" begin
    a1 = reshape([1,2], 2, 1)
    a2 = [1.0,2.0]'
    a1p, a2p = @inferred(paddedviews(0, a1, a2))
    @test a1p == [1 0; 2 0]
    @test a2p == [1.0 2.0; 0.0 0.0]
    @test eltype(a1p) == Int
    @test eltype(a2p) == Float64
    @test axes(a1p) === axes(a2p) === (Base.OneTo(2), Base.OneTo(2))

    a3 = OffsetArray([1.0,2.0]', (0,-1))
    a1p, a3p = @inferred(paddedviews(0, a1, a3))
    @test a1p == OffsetArray([0 1; 0 2], 1:2, 0:1)
    @test a3p == OffsetArray([1.0 2.0; 0.0 0.0], 1:2, 0:1)
    @test eltype(a1p) == Int
    @test eltype(a3p) == Float64
    @test axes(a1p) === axes(a3p) === (1:2, 0:1)

    @test @inferred(paddedviews(3)) == ()
    @test_throws ErrorException PaddedViews.outerinds()
    # But a zero-dimensional input should not trigger that error
    a = reshape([5])
    @test paddedviews(-1, a) == (a,)
end

nothing


macro params(struct_expr)
    header = struct_expr.args[2]
    fields = @view struct_expr.args[3].args[2:2:end]
    params = []
    for i in 1:length(fields)
        x = fields[i]
        T = gensym()
        if x isa Symbol
            push!(params, T)
            fields[i] = :($x::$T)
        elseif x.head == :(::)
            abstr = x.args[2]
            var = x.args[1]
            push!(params, :($T <: $abstr))
            fields[i] = :($var::$T)
        end
    end
    if header isa Symbol && length(params) > 0
        struct_expr.args[2] = :($header{$(params...)})
    elseif header.head == :curly
        append!(struct_expr.args[2].args, params)
    elseif header.head == :<:
        if struct_expr.args[2].args[1] isa Symbol
            name = struct_expr.args[2].args[1]
            struct_expr.args[2].args[1] = :($name{$(params...)})
        elseif header.head == :<: && struct_expr.args[2].args[1] isa Expr
            append!(struct_expr.args[2].args[1].args, params)
        else
            error("Unidentified type definition.")
        end
    else
        error("Unidentified type definition.")
    end
    esc(struct_expr)
end

@define_cu(IterativeSolvers.CGStateVariables, :u, :r, :c)

struct RaggedArray{TO, TV}
    offsets::TO
    values::TV
end
whichdevice(ra::RaggedArray) = whichdevice(ra.offsets)

function RaggedArray(vv::Vector{Vector{T}}) where T
    offsets = [1; 1 .+ accumulate(+, collect(length(v) for v in vv))]
    values = Vector{T}(undef, offsets[end]-1)
    for (i, v) in enumerate(vv)
        r = offsets[i]:offsets[i+1]-1
        values[r] .= v
    end
    RaggedArray(offsets, values)
end
@define_cu(RaggedArray, :offsets, :values)

function Base.getindex(ra::RaggedArray, i)
    @assert 1 <= i < length(ra.offsets)
    r = ra.offsets[i]:ra.offsets[i+1]-1
    @assert 1 <= r[1] && r[end] <= length(ra.values)
    return @view ra.values[r]
end
function Base.getindex(ra::RaggedArray, i, j)
    @assert 1 <= j < length(ra.offsets)
    r = ra.offsets[j]:ra.offsets[j+1]-1
    @assert 1 <= i <= length(r)
    return ra.values[r[i]]
end
function Base.setindex!(ra::RaggedArray, v, i, j)
    @assert 1 <= j < length(ra.offsets)
    r = ra.offsets[j]:ra.offsets[j+1]-1
    @assert 1 <= i <= length(r)
    ra.values[r[i]] = v
end

function find_varind(black, white, ::Type{TI}=Int) where TI
    nel = length(black)
    nel == length(white) || throw("Black and white vectors should be of the same length")
    varind = zeros(TI, nel)
    k = 1
    for i in 1:nel
        if !black[i] && !white[i]
            varind[i] = k
            k += 1
        end
    end
    return varind
end

function find_black_and_white(dh)
    black = falses(getncells(dh.grid))
    white = falses(getncells(dh.grid))
    if haskey(dh.grid.cellsets, "black")
        for c in grid.cellsets["black"]
            black[c] = true
        end
    end
    if haskey(dh.grid.cellsets, "white")
        for c in grid.cellsets["white"]
            white[c] = true
        end
    end

    return black, white
end

YoungsModulus(p) = getE(p)
PoissonRatio(p) = getν(p)

function compliance(Ke, u, dofs)
    comp = zero(eltype(u))
    for i in 1:length(dofs)
        for j in 1:length(dofs)
            comp += u[dofs[i]]*Ke[i,j]*u[dofs[j]]
        end
    end
    comp
end

function meandiag(K::AbstractMatrix)
    z = zero(eltype(K))
    for i in 1:size(K, 1)
        z += abs(K[i, i])
    end
    return z / size(K, 1)
end

density(var, xmin) = var*(1-xmin) + xmin

macro debug(expr)
    return quote
        if DEBUG[]
            $(esc(expr))
        end
    end
end


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct TGTypeI <: TurbineGov
        R::Float64
        Ts::Float64
        Tc::Float64
        T3::Float64
        T4::Float64
        T5::Float64
        valve_position_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
        P_ref::Float64
        ext::Dict{String, Any}
        states::Vector{Symbol}
        n_states::Int
        internal::InfrastructureSystemsInternal
    end

Parameters of a Turbine Governor Type I.

# Arguments
- `R::Float64`: Droop parameter, validation range: `(0, nothing)`
- `Ts::Float64`: Governor time constant, validation range: `(0, nothing)`
- `Tc::Float64`: Servo time constant, validation range: `(0, nothing)`
- `T3::Float64`: Transient gain time constant, validation range: `(0, nothing)`
- `T4::Float64`: Power fraction time constant, validation range: `(0, nothing)`
- `T5::Float64`: Reheat time constant, validation range: `(0, nothing)`
- `valve_position_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}`: Valve position limits in MW
- `P_ref::Float64`: Reference Power Set-point, validation range: `(0, nothing)`
- `ext::Dict{String, Any}`
- `states::Vector{Symbol}`: The states of the TGTypeI model are:
	x_g1: Governor state,
	x_g2: Servo state,
	x_g3: Reheat state
- `n_states::Int`: TGTypeI has 3 states
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct TGTypeI <: TurbineGov
    "Droop parameter"
    R::Float64
    "Governor time constant"
    Ts::Float64
    "Servo time constant"
    Tc::Float64
    "Transient gain time constant"
    T3::Float64
    "Power fraction time constant"
    T4::Float64
    "Reheat time constant"
    T5::Float64
    "Valve position limits in MW"
    valve_position_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
    "Reference Power Set-point"
    P_ref::Float64
    ext::Dict{String, Any}
    "The states of the TGTypeI model are:
	x_g1: Governor state,
	x_g2: Servo state,
	x_g3: Reheat state"
    states::Vector{Symbol}
    "TGTypeI has 3 states"
    n_states::Int
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function TGTypeI(R, Ts, Tc, T3, T4, T5, valve_position_limits, P_ref=1.0, ext=Dict{String, Any}(), )
    TGTypeI(R, Ts, Tc, T3, T4, T5, valve_position_limits, P_ref, ext, [:x_g1, :x_g2, :x_g3], 3, InfrastructureSystemsInternal(), )
end

function TGTypeI(; R, Ts, Tc, T3, T4, T5, valve_position_limits, P_ref=1.0, ext=Dict{String, Any}(), states=[:x_g1, :x_g2, :x_g3], n_states=3, internal=InfrastructureSystemsInternal(), )
    TGTypeI(R, Ts, Tc, T3, T4, T5, valve_position_limits, P_ref, ext, states, n_states, internal, )
end

# Constructor for demo purposes; non-functional.
function TGTypeI(::Nothing)
    TGTypeI(;
        R=0,
        Ts=0,
        Tc=0,
        T3=0,
        T4=0,
        T5=0,
        valve_position_limits=(min=0.0, max=0.0),
        P_ref=0,
        ext=Dict{String, Any}(),
    )
end

"""Get [`TGTypeI`](@ref) `R`."""
get_R(value::TGTypeI) = value.R
"""Get [`TGTypeI`](@ref) `Ts`."""
get_Ts(value::TGTypeI) = value.Ts
"""Get [`TGTypeI`](@ref) `Tc`."""
get_Tc(value::TGTypeI) = value.Tc
"""Get [`TGTypeI`](@ref) `T3`."""
get_T3(value::TGTypeI) = value.T3
"""Get [`TGTypeI`](@ref) `T4`."""
get_T4(value::TGTypeI) = value.T4
"""Get [`TGTypeI`](@ref) `T5`."""
get_T5(value::TGTypeI) = value.T5
"""Get [`TGTypeI`](@ref) `valve_position_limits`."""
get_valve_position_limits(value::TGTypeI) = value.valve_position_limits
"""Get [`TGTypeI`](@ref) `P_ref`."""
get_P_ref(value::TGTypeI) = value.P_ref
"""Get [`TGTypeI`](@ref) `ext`."""
get_ext(value::TGTypeI) = value.ext
"""Get [`TGTypeI`](@ref) `states`."""
get_states(value::TGTypeI) = value.states
"""Get [`TGTypeI`](@ref) `n_states`."""
get_n_states(value::TGTypeI) = value.n_states
"""Get [`TGTypeI`](@ref) `internal`."""
get_internal(value::TGTypeI) = value.internal

"""Set [`TGTypeI`](@ref) `R`."""
set_R!(value::TGTypeI, val) = value.R = val
"""Set [`TGTypeI`](@ref) `Ts`."""
set_Ts!(value::TGTypeI, val) = value.Ts = val
"""Set [`TGTypeI`](@ref) `Tc`."""
set_Tc!(value::TGTypeI, val) = value.Tc = val
"""Set [`TGTypeI`](@ref) `T3`."""
set_T3!(value::TGTypeI, val) = value.T3 = val
"""Set [`TGTypeI`](@ref) `T4`."""
set_T4!(value::TGTypeI, val) = value.T4 = val
"""Set [`TGTypeI`](@ref) `T5`."""
set_T5!(value::TGTypeI, val) = value.T5 = val
"""Set [`TGTypeI`](@ref) `valve_position_limits`."""
set_valve_position_limits!(value::TGTypeI, val) = value.valve_position_limits = val
"""Set [`TGTypeI`](@ref) `P_ref`."""
set_P_ref!(value::TGTypeI, val) = value.P_ref = val
"""Set [`TGTypeI`](@ref) `ext`."""
set_ext!(value::TGTypeI, val) = value.ext = val


using BangBang
using Setfield

"""
    lenses(t::Tuple)
    lenses(nt::NamedTuple)
    lenses(NT::Type{NamedTuple{K,V}})

Build a Tuple of lenses for a given value or type

Example:
    julia> nt = (a=(b=[1,2],c=(d=[3,4],e=[5,6])),f=[7,8]);

    julia> lenses(nt)
    ((@lens _.a.b), (@lens _.a.c.d), (@lens _.a.c.e), (@lens _.f))

    julia> lenses(typeof(nt))
    ((@lens _.a.b), (@lens _.a.c.d), (@lens _.a.c.e), (@lens _.f))
"""
function lenses end

lenses(t::Tuple) = _lenses(t, ())

lenses(nt::NamedTuple) = _lenses(nt, ())
lenses(NT::Type{NamedTuple{K,V}}) where {K,V} = lenses(fromtype(NT))

function _lenses(t::Tuple, acc)
    result = ()
    for (k,v) in enumerate(t)
        acc_k = push!!(acc, Setfield.IndexLens((k,)))
        ℓ = _lenses(v, acc_k)
        result = append!!(result, ℓ)
    end
    return result
end

function _lenses(nt::NamedTuple, acc)
    result = ()
    for k in keys(nt)
        nt_k = getproperty(nt, k)
        # Add "breadcrumb" steps to the accumulator as we descend into the tree
        acc_k = push!!(acc, Setfield.PropertyLens{k}())
        ℓ = _lenses(nt_k, acc_k)
        result = append!!(result, ℓ)
    end
    return result
end

# When we reach a leaf node (an array), compose the steps to get a lens
function _lenses(a::AbstractArray, acc)
    return (Setfield.compose(acc...),)
end

function _lenses(::Type{T}, acc) where {T}
    return (Setfield.compose(acc...),)
end


module TestSyncBarriers

using Test
using SyncBarriers
using SyncBarriers: Internal

function test_barrier()
    barrier_factories = [
        SyncBarriers.CentralizedBarrier,
        SyncBarriers.DisseminationBarrier,
        SyncBarriers.TreeBarrier{2},
        SyncBarriers.TreeBarrier{3},
        SyncBarriers.FlatTreeBarrier{2},
        SyncBarriers.FlatTreeBarrier{3},
        SyncBarriers.StaticTreeBarrier{4,2},
        SyncBarriers.StaticTreeBarrier{5,3},
        # ...
    ]
    ntasks_list = unique([1, 2, Threads.nthreads(), 2 * Threads.nthreads()])
    @testset for f in barrier_factories, ntasks in ntasks_list
        test_barrier(f, ntasks)
    end
end

function test_barrier(factory, ntasks)
    @debug "`test_barrier($factory, $ntasks)`"
    states = use_barrier(factory, ntasks)
    ncycles = size(states, 1)
    desired = repeat((1:ncycles) .* ntasks, 1, ntasks)
    @test states[:, 1] == (1:ncycles) .* ntasks
    @test all(states[:, 1] .== states)
    @test states == desired
end

function use_barrier(factory, ntasks; kwargs...)
    barrier = factory(ntasks)
    if factory isa Type
        @test barrier isa factory
    end
    return use_barrier(barrier; kwargs...)
end

function use_barrier(barrier::SyncBarriers.Barrier; ncycles = 1000)
    ntasks = length(barrier)
    states = zeros(Int, ncycles, ntasks)
    return use_barrier!(states, barrier)
end

function use_barrier!(states::Matrix, barrier::SyncBarriers.Barrier, spin = nothing)
    ncycles, ntasks = size(states)
    @assert ntasks == length(barrier)
    value = Threads.Atomic{Int}(0)
    @sync for i in 1:ntasks
        Threads.@spawn try
            for k in 1:ncycles
                Threads.atomic_add!(value, 1)
                SyncBarriers.cycle!(barrier[i], spin)
                states[k, i] = value[]
                SyncBarriers.cycle!(barrier[i], spin)
            end
        catch err
            @error(
                "`use_barrier` failed",
                exception = (err, catch_backtrace()),
                i,
                current_task()
            )
            # TODO: close(barrier)
            rethrow()
        end
    end
    return states
end

function test_default_barriers()
    @test SyncBarriers.Barrier(2) isa SyncBarriers.CentralizedBarrier
    @test SyncBarriers.fuzzy_barrier(2) isa SyncBarriers.CentralizedBarrier
    @test SyncBarriers.fuzzy_reduce_barrier(+, Int, 2) isa SyncBarriers.FlatTreeBarrier{2,Int}
    @test SyncBarriers.reduce_barrier(+, Int, 2) isa SyncBarriers.FlatTreeBarrier{2,Int}

    @test SyncBarriers.Barrier(32) isa SyncBarriers.Barrier
    @test SyncBarriers.fuzzy_barrier(32) isa SyncBarriers.Barrier
    @test SyncBarriers.fuzzy_reduce_barrier(+, Int, 32) isa SyncBarriers.Barrier
    @test SyncBarriers.reduce_barrier(+, Int, 32) isa SyncBarriers.Barrier
end

function test_default_barriers_internal()
    @testset for n in 1:32, nthreads in 1:32
        @test Internal._Barrier(n, nthreads) isa SyncBarriers.Barrier
        @test Internal._fuzzy_barrier(n, nthreads) isa SyncBarriers.Barrier
        @test Internal._fuzzy_reduce_barrier(+, Int, n, nthreads) isa SyncBarriers.Barrier
        @test Internal._reduce_barrier(+, Int, n, nthreads) isa SyncBarriers.Barrier
    end
end

end  # module


module Lora

using Distributions
using Formatting
using StatsBase

import Base:
  ==,
  Dict,
  close,
  convert,
  copy!,
  eltype,
  flush,
  getindex,
  isequal,
  keys,
  mark,
  mean,
  mean!,
  open,
  rand,
  read!,
  read,
  reset,
  run,
  show,
  write,
  writemime

import Distributions:
  @check_args,
  @distr_support,
  failprob,
  logpdf,
  params,
  pdf,
  succprob

import ForwardDiff

import ReverseDiffSource

export
  ### Types
  AM,
  AMState,
  ARS,
  ARSState,
  AcceptanceRateMCTuner,
  BasicContMuvParameter,
  BasicContMuvParameterNState,
  BasicContMuvParameterState,
  BasicContParamIOStream,
  BasicContUnvParameter,
  BasicContUnvParameterNState,
  BasicContUnvParameterState,
  BasicDiscMuvParameter,
  BasicDiscMuvParameterNState,
  BasicDiscMuvParameterState,
  BasicDiscParamIOStream,
  BasicDiscUnvParameter,
  BasicDiscUnvParameterNState,
  BasicDiscUnvParameterState,
  BasicGibbsJob,
  BasicMCJob,
  BasicMCRange,
  BasicMCTune,
  BasicMavVariableNState,
  BasicMavVariableState,
  BasicMuvVariableNState,
  BasicMuvVariableState,
  BasicUnvVariableNState,
  BasicUnvVariableState,
  BasicVariableIOStream,
  Binary,
  Constant,
  ContMuvMarkovChain,
  ContUnvMarkovChain,
  ContinuousParameter,
  ContinuousParameterNState,
  ContinuousParameterState,
  Data,
  Dependence,
  DependenceVector,
  Deterministic,
  DiscMuvMarkovChain,
  DiscUnvMarkovChain,
  DiscreteParameter,
  DiscreteParameterNState,
  DiscreteParameterState,
  DualAveragingMCTuner,
  GenericModel,
  GibbsJob,
  HMC,
  HMCSampler,
  HMCSamplerState,
  HMCState,
  Hyperparameter,
  IntegerVector,
  LMCSampler,
  LMCSamplerState,
  MALA,
  MALAState,
  MCJob,
  MCRange,
  MCSampler,
  MCSamplerState,
  MCTuner,
  MCTunerState,
  MH,
  MHSampler,
  MHSamplerState,
  MHState,
  MarkovChain,
  MatrixvariateParameter,
  MatrixvariateParameterNState,
  MatrixvariateParameterState,
  MultivariateParameter,
  MultivariateParameterNState,
  MultivariateParameterState,
  MuvAMState,
  MuvAMWGState,
  MuvHMCState,
  MuvMALAState,
  MuvSMMALAState,
  Parameter,
  ParameterIOStream,
  ParameterNState,
  ParameterState,
  ParameterStateVector,
  ParameterVector,
  RAM,
  RAMState,
  Random,
  RealMatrix,
  RealVector,
  RobertsRosenthalMCTune,
  RobertsRosenthalMCTuner,
  SMMALA,
  SMMALAState,
  Sampleability,
  Transformation,
  UnivariateParameter,
  UnivariateParameterNState,
  UnivariateParameterState,
  UnvAMWGState,
  UnvHMCState,
  UnvMALAState,
  UnvSMMALAState,
  VanillaMCTuner,
  Variable,
  VariableIOStream,
  VariableNState,
  VariableState,
  VariableStateVector,
  VariableVector,

  ### Functions
  acceptance,
  add_dimension,
  add_edge!,
  add_vertex!,
  codegen,
  count!,
  covariance!,
  dataset,
  datasets,
  diagnostics,
  edge_index,
  edges,
  erf_rate_score,
  ess,
  examples,
  failprob,
  iact,
  in_degree,
  in_edges,
  in_neighbors,
  indices,
  is_directed,
  is_indexed,
  iterate,
  job2dot,
  likelihood_model,
  logistic,
  logistic_rate_score,
  lognormalise,
  logpdf,
  lzv,
  make_edge,
  mcse,
  mcvar,
  model2dot,
  multivecs,
  normalise,
  num_edges,
  num_vertices,
  out_degree,
  out_edges,
  out_neighbors,
  output,
  params,
  pdf,
  qzv,
  rate!,
  reset!,
  reset_burnin!,
  revedge,
  run,
  sampler_state,
  save!,
  save,
  setpdf!,
  setprior!,
  softabs,
  sort_by_index,
  source,
  succprob,
  target,
  tune!,
  tuner_state,
  vertex_index,
  vertex_key,
  vertices

include("base.jl")

include("format.jl")

include("data.jl")

include("codegen.jl")

include("stats/logistic.jl")

include("distributions/Binary.jl")
include("distributions/TruncatedNormal.jl")

include("autodiff/reverse.jl")
include("autodiff/forward.jl")

include("states/VariableStates.jl")
include("states/ParameterStates/ParameterStates.jl")
include("states/ParameterStates/BasicDiscUnvParameterState.jl")
include("states/ParameterStates/BasicDiscMuvParameterState.jl")
include("states/ParameterStates/BasicContUnvParameterState.jl")
include("states/ParameterStates/BasicContMuvParameterState.jl")

include("nstates/VariableNStates.jl")
include("nstates/ParameterNStates/ParameterNStates.jl")
include("nstates/ParameterNStates/BasicDiscUnvParameterNState.jl")
include("nstates/ParameterNStates/BasicDiscMuvParameterNState.jl")
include("nstates/ParameterNStates/BasicContUnvParameterNState.jl")
include("nstates/ParameterNStates/BasicContMuvParameterNState.jl")

include("iostreams/VariableIOStreams.jl")
include("iostreams/ParameterIOStreams/ParameterIOStreams.jl")
include("iostreams/ParameterIOStreams/BasicDiscParamIOStream.jl")
include("iostreams/ParameterIOStreams/BasicContParamIOStream.jl")

include("variables/variables.jl")
include("variables/parameters/parameters.jl")
include("variables/parameters/BasicDiscUnvParameter.jl")
include("variables/parameters/BasicDiscMuvParameter.jl")
include("variables/parameters/BasicContUnvParameter.jl")
include("variables/parameters/BasicContMuvParameter.jl")
include("variables/dependencies.jl")

include("models/GenericModel.jl")
include("models/generators.jl")

include("ranges/ranges.jl")
include("ranges/BasicMCRange.jl")

include("tuners/tuners.jl")
include("tuners/VanillaMCTuner.jl")
include("tuners/AcceptanceRateMCTuner.jl")
include("tuners/DualAveragingMCTuner.jl")

include("samplers/samplers.jl")
include("samplers/ARS.jl")
include("samplers/MH.jl")
include("samplers/AM.jl")
include("samplers/RAM.jl")
include("samplers/HMC.jl")
# include("samplers/NUTS.jl")
include("samplers/MALA.jl")
include("samplers/SMMALA.jl")

include("jobs/jobs.jl")
include("jobs/BasicMCJob.jl")
include("jobs/BasicGibbsJob.jl")

include("samplers/iterate/ARS.jl")
include("samplers/iterate/MH.jl")
include("samplers/iterate/AM.jl")
include("samplers/iterate/RAM.jl")
include("samplers/iterate/HMC.jl")
# include("samplers/iterate/NUTS.jl")
include("samplers/iterate/MALA.jl")
include("samplers/iterate/SMMALA.jl")
include("samplers/iterate/iterate.jl")

include("stats/acceptance.jl")

include("stats/mean.jl")

include("stats/variance/mcvar.jl")
include("stats/variance/zv.jl")

include("stats/covariance.jl")

include("stats/convergence/ess.jl")
include("stats/convergence/iact.jl")

include("stats/metrics.jl")

end


include("src/init.jl")# Include all relevant functions

include("src/sac/policy.jl")
include("src/sac/gradients.jl")
include("src/sac/updates.jl")

#If the architectur should be modified a bit, use this algorithm:
include("src/td3/episodes.jl")

#Pl  = init_θ_pol_SAC(params.structure_net_pl)
Pl  = init_θ_single(params.structure_net_pl)

Vf  = init_θ_single(params.structure_net_vf)
Qf1 = init_θ_single(params.structure_net_qf)
Qf2 = init_θ_single(params.structure_net_qf)
Vf_tar = copy(Vf)

best_net = copy(Pl)
res = zeros(params.n_iter)
prec = zeros(params.n_iter)
all_traj  = init_all_traj(params)

function soft_actor_critic(params::Params, Pl::Any, Vf::Any, Qf1::Any, Qf2::Any, Vf_tar::Any, res::Array{Float64,1},
    prec::Array{Float64,1}, all_traj::Traj_Data, best_net::Any)

    #Init Part:
    best_net = copy(Pl)
    min_cost = Inf
    #------------------------
    #Initialize the Policy
    opt_para_pl  = optimizers(Pl,  Adam; lr = params.lr_pl)
    opt_para_vf  = optimizers(Vf,  Adam; lr = params.lr_vf)
    opt_para_qf1 = optimizers(Qf1, Adam; lr = 2.0*params.lr_qf)
    opt_para_qf2 = optimizers(Qf2, Adam; lr = 0.5*params.lr_qf)

    #Init Progress bar
    progress = Progress(params.n_iter,1)

    # Init rms
    rms_all = init_rms(params.s_init)
    rms_loc = init_rms(params.s_init)

    a_t_old = all_traj.traj[1].a_t
    weights_old = zeros(length(net_to_vec(Pl)),2)

    #buffer = Traj_Data(undef,0,0) #empty buffer
    buffer = Traj_Data()
    buffer.traj = Array{Trajectory,1}(undef,0)
    buffer.timesteps = 0
    buffer.n_traj = 0

    #Initialize with supervised Training of Value function:
    #parallel_episodes_SAC(params,Pl,Vf,all_traj,rms_all)
    parallel_episodes(params,Pl,Vf,all_traj,rms_all)

    #two_pass_variance!(all_traj, rms_all, params)

    mini_batch = build_minibatches(all_traj,params)

    pump_buffer!(buffer, mini_batch, params)

    for i_iter = 1:params.n_iter

        #Save data in intervalls
        if floor(i_iter/100) == ceil(i_iter/100)
            #make_video(all_traj,params, i_iter)
            save_data(res, prec, Pl, Vf, all_traj, i_iter)
        end

        #-----------------------------------------------------------------------
        #Solve it in Parallel!
        res[i_iter], prec[i_iter] = parallel_episodes_SAC(params,Pl,Vf,all_traj,rms_all)

        #Reorganice Data in minibatches
        mini_batch = build_minibatches(all_traj,params)

        #Pump the data into the buffer
        pump_buffer!(buffer, mini_batch, params)

        #Update all networks going over the collected data:
        println(" ")
        do_sac_update_parallel!(Pl, Qf1, Qf2, Vf, Vf_tar, buffer,
        params, opt_para_pl, opt_para_qf1, opt_para_qf2, opt_para_vf)

        if min_cost > res[i_iter]
            min_cost = res[i_iter]
            best_net = copy(Pl)
            println("new_best: ",min_cost)
        end

        #5.Visualize Progress
        ProgressMeter.update!(progress,i_iter) #update progress bar

        #6. Plot data of interest
        if floor(i_iter/5) == ceil(i_iter/5)
            print("  new visualization: ")
            a_t_old ,weights_old = plot_stuff(params, all_traj, a_t_old,
            weights_old, i_iter,Pl, Qf1, res, prec/params.n_traj, Pl, Pl)
        end
        print("  Result:  ", res[i_iter])
    end
end

soft_actor_critic(params, Pl, Vf, Qf1, Qf2, Vf_tar, res, prec, all_traj, best_net)


using Base.Test

using Celeste: Model, SensitiveFloats
using Celeste.DeterministicVI.ConstraintTransforms: ParameterConstraint,
                        ConstraintBatch, BoxConstraint, SimplexConstraint
using Celeste.DeterministicVI: ElboArgs
using Celeste.DeterministicVI.ElboMaximize: ElboConfig, maximize!, elbo_optim_options
using Optim

function verify_sample_galaxy(vs, pos)
    @test vs[ids.is_star[2]] >= 0.99

    @test isapprox(vs[ids.pos[1]], pos[1], atol=0.1)
    @test isapprox(vs[ids.pos[2]], pos[2], atol=0.1)

    @test isapprox(vs[ids.gal_axis_ratio] , 0.7, atol=0.05)
    @test isapprox(vs[ids.gal_frac_dev]  , 0.1, atol=0.08)
    @test isapprox(vs[ids.gal_radius_px], 4.0, atol=0.2)

    phi_hat = vs[ids.gal_angle]
    phi_hat -= floor(phi_hat / pi) * pi
    five_deg = 5 * pi/180
    @test isapprox(phi_hat, pi/4, atol=five_deg)

    brightness_hat = exp(vs[ids.flux_loc[2]] + 0.5 * vs[ids.flux_scale[2]])
    @test isapprox(brightness_hat / sample_galaxy_fluxes[3], 1.0, atol=0.05)

    true_colors = log.(sample_galaxy_fluxes[2:5] ./ sample_galaxy_fluxes[1:4])
    for b in 1:4
        @test isapprox(vs[ids.color_mean[b, 2]], true_colors[b], atol=0.2)
    end
end

#########################################################

function test_single_source_optimization()
    ea, vp, catalog = gen_three_body_dataset();

    s = 2
    ea = make_elbo_args(ea.images, catalog, active_source=s, include_kl=false);
    vp_original = deepcopy(vp);

    cfg = ElboConfig(ea, vp; loc_width=1.0)
    maximize!(ea, vp, cfg)

    # Test that it only optimized source s
    @test vp[s] != vp_original[s]
    for other_s in setdiff(1:ea.S, s)
        @test vp[other_s] ≈ vp_original[other_s]
    end
end

function test_galaxy_optimization()
    ea, vp, catalog = gen_sample_galaxy_dataset(; include_kl = false);
    cfg = ElboConfig(ea, vp; loc_width=3.0)
    maximize!(ea, vp, cfg)
    verify_sample_galaxy(vp[1], [8.5, 9.6])
end

function test_full_elbo_optimization()
    ea, vp, catalog = gen_sample_galaxy_dataset(perturb=true);
    cfg = ElboConfig(ea, vp; loc_width=1.0,
                 optim_options=elbo_optim_options(xtol_abs=0.0))
    maximize!(ea, vp, cfg)
    verify_sample_galaxy(vp[1], [8.5, 9.6]);
end

@testset "optimization" begin
    test_single_source_optimization()
    test_full_elbo_optimization()
    test_galaxy_optimization()
end


#ДАНО: Робот - в произвольной клетке ограниченного прямоугольной рамкой поля без внутренних перегородок и маркеров.
#РЕЗУЛЬТАТ: Робот - в исходном положении в центре косого креста (в форме X) из маркеров.
function mark_x(r)
    for i in 0:3
        mark_line(r,i)
    end
    putmarker!(r)
end
function mark_line(r,i)
    while isborder(r,HorizonSide(i % 4))==0 && isborder(r,HorizonSide((i+1) % 4))==0
        move!(r,HorizonSide(i % 4))
        move!(r,HorizonSide((i+1) % 4))
        putmarker!(r)
    end
    while ismarker(r)==1
        move!(r,HorizonSide((i+2) % 4))
        move!(r,HorizonSide((i+3) % 4))
    end
end
mark_x(r)


include("runtests_preamble.jl")

using LESbrary.TurbulenceStatistics:
    horizontally_averaged_tracers,
    velocity_covariances,
    tracer_covariances,
    third_order_velocity_statistics,
    third_order_tracer_statistics,
    first_order_statistics,
    second_order_statistics,
    third_order_statistics,
    first_through_second_order,
    first_through_third_order,
    subfilter_momentum_fluxes,
    subfilter_tracer_fluxes,
    turbulent_kinetic_energy_budget

using Oceananigans.Fields: XYReducedField

function output_works(simulation, output, output_name="")
    model = simulation.model
    model.clock.time = 0
    model.clock.iteration = 0
    simulation.stop_iteration = 1

    simulation.output_writers[:test] = JLD2OutputWriter(model, output,
                                                        schedule = IterationInterval(1),
                                                        prefix = "test",
                                                        dir = ".")

    success = try
        run!(simulation)
        true
    catch err
        @warn "Output test for $output_name failed with " * sprint(showerror, err)
        false
    finally
        rm("test.jld2")
        pop!(simulation.output_writers, :test)
    end

    return success
end

for arch in architectures
    @testset "Turbulence Statistics [$(typeof(arch))]" begin
        @info "Testing turbulence statistics [$(typeof(arch))]..."

        model = NonhydrostaticModel(grid = RectilinearGrid(arch, size=(1, 1, 1), extent=(1, 1, 1)),
                                    tracers = :b,
                                    buoyancy = BuoyancyTracer(),
                                    closure = AnisotropicMinimumDissipation())

        simulation = Simulation(model, Δt=1.0, stop_iteration=1)

        C  = horizontally_averaged_tracers(model)
        u² = velocity_covariances(model)
        c² = tracer_covariances(model)
        u³ = third_order_velocity_statistics(model)
        u³ = third_order_tracer_statistics(model)

        ψ¹ = first_order_statistics(model)
        ψ² = second_order_statistics(model)
        ψ³ = third_order_statistics(model)

        ψ¹_ψ² = first_through_second_order(model)
        ψ¹_ψ³ = first_through_third_order(model)

        Qᵘ = subfilter_momentum_fluxes(model)
        Qᶜ = subfilter_tracer_fluxes(model)

        tke_budget = turbulent_kinetic_energy_budget(model)

        @test all(ϕ isa XYReducedField for ϕ in values( C     ))
        @test all(ϕ isa XYReducedField for ϕ in values( u²    ))
        @test all(ϕ isa XYReducedField for ϕ in values( c²    ))
        @test all(ϕ isa XYReducedField for ϕ in values( u³    ))
        @test all(ϕ isa XYReducedField for ϕ in values( u³    ))

        @test all(ϕ isa XYReducedField for ϕ in values( ψ¹    ))
        @test all(ϕ isa XYReducedField for ϕ in values( ψ²    ))
        @test all(ϕ isa XYReducedField for ϕ in values( ψ³    ))

        @test all(ϕ isa XYReducedField for ϕ in values( ψ¹_ψ² ))
        @test all(ϕ isa XYReducedField for ϕ in values( ψ¹_ψ³ ))

        @test all(ϕ isa XYReducedField for ϕ in values( Qᵘ    ))
        @test all(ϕ isa XYReducedField for ϕ in values( Qᶜ    ))

        @test output_works(simulation, C, "Horizontally averaged tracers")
        @test output_works(simulation, u², "Velocity covariances")
        @test output_works(simulation, c², "Tracer covariances")
        @test output_works(simulation, ψ¹, "First-order statistics")

        # Individually test outputs for better failure messages
        for (name, output) in ψ³
            @test output_works(simulation, Dict(name => output), name)
        end

        for (name, output) in tke_budget
            @test output_works(simulation, Dict(name => output), name)
        end

        for (name, output) in Qᵘ
            @test output_works(simulation, Dict(name => output), name)
        end

        for (name, output) in Qᶜ
            @test output_works(simulation, Dict(name => output), name)
        end
    end
end