using GridPotentialFlow
using Plots
Lx = 10.0
Ly = 4.0
xlim = (-Lx/2,Lx/2)
ylim = (-Ly/2,Ly/2)
g = PhysicalGrid(xlim, ylim, 0.04)

Rc = 1
circle = Circle(Rc,2*cellsize(g))
pfb = PotentialFlowBody(circle)

Δs = dlength(circle);

y∞ = Rc/2
v1 = Vortex(-Lx/2+10*cellsize(g),y∞,1.0);
v2 = Vortex(-Lx/2+10*cellsize(g),-y∞,-1.0);

x(r) = sqrt(r^2-(y∞^2*(r^2-1)^2)/((r^2-1)^2-4*y∞^2))
y(r) = y∞*(r^2-1)/sqrt((r^2-1)^2-4*y∞^2)
s = 0:0.001:π/2
rmin = 1.4375649
c1 = rmin/(-Lx/2+rmin)
c2 = -Lx/2*c1
r = c2 ./ (cos.(s).-c1)
x_trajectory = -reverse(x.(r)); append!(x_trajectory,x.(r))
y_trajectory_upper = reverse(y.(r)); append!(y_trajectory_upper,y.(r))
y_trajectory_lower = -y_trajectory_upper;

Px_func(x,y) = y - y/(x^2+y^2) + y - y/(x^2+y^2);
Py_func(x,y) = -x + x/(x^2+y^2) + x - x/(x^2+y^2);
Px_exact = Px_func.(x_trajectory,y_trajectory_upper);
Py_exact = Py_func.(x_trajectory,y_trajectory_upper);

model = VortexModel(g,bodies=[pfb],vortices=[v1,v2]);

function rhs(X,model,t)
    setvortexpositions!(model,X)
    Ẋ = vortexvelocities!(model)
    return Ẋ
end

import OrdinaryDiffEq
X = getvortexpositions(model)
prob = OrdinaryDiffEq.ODEProblem(rhs,X,(0.0,64.0),model);
sol = OrdinaryDiffEq.solve(prob,dt=0.1,OrdinaryDiffEq.RK4(),dense=false,adaptive=false);

Px_numerical_hist = []
Py_numerical_hist = []
for u in sol.u
    setvortexpositions!(model,u)
    Px, Py = impulse(model)
    push!(Px_numerical_hist,Px)
    push!(Py_numerical_hist,Py)
end

plot(circle,fillcolor=:black,fillrange=0,fillalpha=0.25,linecolor=:black,linewidth=2,xlabel="x",ylabel="y")
scatter!(model.vortices.x,model.vortices.y,color=:red)
plot!(x_trajectory,y_trajectory_upper,linecolor=:red,label="exact")
plot!(x_trajectory,y_trajectory_lower,linecolor=:red,label="")
plot!(map(s->s.u[1],sol.u),map(s->s.v[1],sol.u),color=:blue,linestyle=:dash,label="simulated")
plot!(map(s->s.u[2],sol.u),map(s->s.v[2],sol.u),color=:blue,linestyle=:dash,label="")

plot(x_trajectory,Px_exact,color=:red,label="exact",xlabel="x",ylabel="Px")
plot!(map(s->s.u[1],sol.u),Px_numerical_hist,color=:blue,linestyle=:dash,label="simulated")

c = 1.0;   # chord length

ċ = -1.0;  # translational velocity
α = -π/3;   # angle of attack

σLE = 0.0;
σTE = 0.0;

Δt = 5e-2;
tf = 1.0;
T = 0.0:Δt:tf;

using PotentialFlow
function compute_ẋ!(ẋ, x, t)
    plate, ambient_sys = x
    motion = ẋ[1]
    motion.ċ, motion.c̈, motion.α̇ = motion.kin(t)
    Plates.enforce_no_flow_through!(plate, motion, ambient_sys, t)
    reset_velocity!(ẋ, x)
    self_induce_velocity!(ẋ, x, t)
end
function shed_new_vorticity!(blobs, plate, motion, t, lesp = 0.0, tesp = 0.0)
    z₊ = (blobs[end-1].z + 2plate.zs[end])/3
    z₋ = (blobs[end].z + 2plate.zs[1])/3
    blob₊ = PotentialFlow.Vortex.Blob(z₊, 1.0, δ)
    blob₋ = PotentialFlow.Vortex.Blob(z₋, 1.0, δ)
    Plates.enforce_no_flow_through!(plate, motion, blobs, t)
    Γ₊, Γ₋, _, _ = Plates.vorticity_flux!(plate, blob₊, blob₋, t, lesp, tesp);
    push!(blobs, PotentialFlow.Vortex.Blob(z₊, Γ₊, blobs[1].δ), PotentialFlow.Vortex.Blob(z₋, Γ₋, blobs[1].δ))
end
N = 128 # number of plate control points (distributed along a extrema Chebyshev grid)
δ = 0.01
plate = PotentialFlow.Plate(N, c, zero(ComplexF64), α)
motion = Plates.RigidBodyMotion(ċ, 0.0);

Δz₀ = im*3Δt*exp(im*plate.α) # vectors perpendicular to the plate
z₋, z₊ = plate.zs[[1,N]] # LE and TE
blobs = PotentialFlow.Vortex.Blob.(Δz₀ .+ [z₊, z₋], 1.0, δ) # First two point vortices are placed close to the LE and TE with unit strength
Plates.enforce_no_flow_through!(plate, motion, (), 0)
Γ₊, Γ₋, _, _ = Plates.vorticity_flux!(plate, blobs[1], blobs[2], 0.0, σLE, σTE); # Determine strength of first two vortices
blobs = PotentialFlow.Vortex.Blob.(Δz₀ .+ [z₊, z₋], [Γ₊, Γ₋], δ) # Create first two point vortices now with calculated strengths
sys₀ = (plate, blobs)
sys = deepcopy(sys₀)
sys₊ = deepcopy(sys₀) # Used for storage during time-marching
ẋs = (motion, allocate_velocity(blobs))
imp = ComplexF64[];
t_hist = Float64[]
global t = 0
push!(t_hist,t)

for tloc in T[2:end]
    global t += Δt
    global sys
    global sys₊
    push!(imp,Elements.impulse(sys))
    push!(t_hist,t)
    local plate, ambient_ω = sys
    local motion, ambient_u = ẋs
    resize!(sys₊[2], length(sys[2]))
    forward_euler!(sys₊, sys, t, Δt, compute_ẋ!, advect!, ẋs)
    sys, sys₊ = sys₊, sys
    shed_new_vorticity!(sys[2], sys[1], ẋs[1], t, σLE, σTE)
end
force = -diff(imp)/Δt;

Δx = 0.01
xlim = (-0.5,2)
ylim = (-1,1)
g = PhysicalGrid(xlim,ylim,Δx);

dsdx = 2
plate = RigidBodyTools.Plate(c,dsdx*Δx)
pfb = PotentialFlowBody(plate,edges=[1,length(plate)],σ=[SuctionParameter(σLE),SuctionParameter(σTE)])
transform = RigidTransform((0.0,0.0),-π/3)
transform(plate);
Δs = dlength(plate)
maximum(Δs/Δx);

firstvLE = GridPotentialFlow.Vortex(plate.x[1]+3Δt*(-ċ)*cos(plate.α+π/2),plate.y[1]+3Δt*(-ċ)*sin(plate.α+π/2),0.0);
firstvTE = GridPotentialFlow.Vortex(plate.x[end]+3Δt*(-ċ)*cos(plate.α+π/2),plate.y[end]+3Δt*(-ċ)*sin(plate.α+π/2),0.0);

function createsheddedvortices(plate,oldvortices)

    vLE = GridPotentialFlow.Vortex(2/3*plate.x[1]+1/3*oldvortices[end-1].x,2/3*plate.y[1]+1/3*oldvortices[end-1].y,0.0)
    vTE = GridPotentialFlow.Vortex(2/3*plate.x[end]+1/3*oldvortices[end].x,2/3*plate.y[end]+1/3*oldvortices[end].y,0.0)

    return vLE,vTE
end

model = VortexModel(g,bodies=[pfb],vortices=[firstvLE,firstvTE],U∞=(-ċ,0.0));

Px_hist = Float64[];
Py_hist = Float64[];
sol = solve(model);
for tloc in T[2:end]
    X = getvortexpositions(model) # gets bigger every time step because we add vortices
    Ẋ = deepcopy(X)

    solve!(sol, model)
    setvortexstrengths!(model, sol.δΓ_vec, length(X.u)-1:length(X.u))
    subtractcirculation!(model.bodies, sol.δΓ_vec)
    Px, Py = impulse(model)

    vortexvelocities!(Ẋ, model, sol.ψ)
    X .= X .+ Ẋ*Δt
    setvortexpositions!(model, X)

    vLE, vTE = createsheddedvortices(plate,model.vortices)
    pushvortices!(model,vLE,vTE)
    push!(Px_hist,Px)
    push!(Py_hist,Py)
end

Fx_hist = -diff(Px_hist)/Δt;
Fy_hist = -diff(Py_hist)/Δt;

plot(plate,fillcolor=:black,fillrange=0,fillalpha=0.25,linecolor=:black,linewidth=2,xlim=xlim,ylim=ylim,xlabel="x",ylabel="y")
scatter!(real.((v->v.z).(sys[2])).-tf*ċ,imag.((v->v.z).(sys[2])),color=:red,markersize=4,label="PotentialFlow.jl")
scatter!(model.vortices.x,model.vortices.y,color=:blue,markersize=2,label="GridPotentialFlow.jl")

plot(xlabel="t",ylabel="Py")
plot!(T[1:end-1],imag.(imp),color=:blue,label="PotentialFlow.jl")
plot!(T[1:end-1],Py_hist,color=:red,label="GridPotentialFlow.jl")

plot(xlabel="t",ylabel="Fy")
plot!(T[2:end-1],imag.(force),color=:blue,label="PotentialFlow.jl")
plot!(T[2:end-1],Fy_hist,color=:red,label="GridPotentialFlow.jl")

Lx = 2.0
xlim = (-Lx/2,Lx/2)
ylim = (-Lx/2,Lx/2)
g = PhysicalGrid(xlim,ylim,0.01)
Δx = cellsize(g);

a = 0.5
b = 0.25
ellipse = Ellipse(a,b,Δx)
pfb = PotentialFlowBody(ellipse)
model = VortexModel(g,bodies=[pfb])
M = GridPotentialFlow.addedmass(model)

using Test
@test isapprox(M[1,1], π*b^2, rtol=1e-1)

@test isapprox(M[2,2], π*a^2, atol=1e-1)

GRratios = [0.1,0.2,0.3,0.4,0.5,0.6,0.8,1.0,1.2,1.4,1.6];

function rectangulararray(rows,columns,spacing)
     centers = VectorData(rows*columns)
     for j in 1:columns
        for i in 1:rows
            xc = (i-1)*spacing
            yc = (j-1)*spacing
            centers.u[(j-1)*rows+i] = xc
            centers.v[(j-1)*rows+i] = yc
        end
    end
    centers.u .-= mean(centers.u)
    centers.v .-= mean(centers.v)
    return centers
 end

n = 100
rows = 3
columns = 3
N = rows*columns
R = 0.20
𝒱 = π*R^2
bodies = fill(Circle(R,2*cellsize(g)),N);
Δs = minimum(dlength(bodies[1]));

using Statistics: mean
using LinearAlgebra: eigen

λoverMratios = zeros(length(GRratios))
for idx in 1:length(GRratios)
    global bodies
    gap = GRratios[idx]*R
    spacing = 2*R+gap
    bodycenters = rectangulararray(rows,columns,spacing)
    for i in 1:N
        Tf = RigidTransform((bodycenters.u[i],bodycenters.v[i]),0.0)
        global bodies[i] = Tf(deepcopy(bodies[i]))
    end
    body_list = PotentialFlowBody.(bodies)
    vm = VortexModel(g,bodies=body_list);
    M = GridPotentialFlow.addedmass(vm)/𝒱
    eigM = eigen(M);
    max_eig_value_coef = maximum(real(eigM.values))
    max_self_added_mass_coef = maximum(M)
    λoverMratios[idx] = max_eig_value_coef/max_self_added_mass_coef
end

nineRodArrayChen = [2.3637,2.2092,2.1007,2.0120,1.9350,1.8665,1.7494,1.6531,1.5732,1.5066,1.4508];
plot(xlabel="GR",ylabel="λ/max(Mij)")
plot!(GRratios,nineRodArrayChen,label="Chen1975")
plot!(GRratios,λoverMratios,label="GridPotentialFlow.jl")

# This file was generated using Literate.jl, https://github.com/fredrikekre/Literate.jl


@testset "1D Test Propagations with single slit example" begin

    function test_prop_1D(M, d, z, λ, L_slit)
	    slit = zeros(Float64, (1, M))
	    xs = LinRange(-L_slit / 2, L_slit / 2, M)
	    slit[1, (abs.(xs) .< d / 2)] .= 1

        slit_prop = abs2.(propagate(slit, L_slit, z, kernel=fresnel_kernel, λ=λ))
        slit_proprs = abs2.(propagate(slit, L_slit, z, kernel=rs_kernel, λ=λ))
	    # normalize for a better plot
	    slit_prop ./= maximum(slit_prop)
	    slit_proprs ./= maximum(slit_proprs)

	    analytical(x) = begin
	    	x_0 = π .* d .* x ./ λ ./ z
	    	return (sin.(x_0) ./ x_0).^2
	    end

        @test   ≈(analytical.(xs)[200:800], slit_prop[200:800], rtol=0.01)
        @test   ≈(analytical.(xs)[200:800], slit_proprs[200:800], rtol=0.01)

        return slit_prop
    end

    x_1 = test_prop_1D(1000, 100e-6, 0.2, 550e-9, 10e-3)
    x_2 = test_prop_1D(1000, 100e-6, 0.2, 450e-9, 10e-3)
    test_prop_1D(1000, 120e-6, 0.2, 450e-9, 10e-3)

    @test ~ ≈(x_1, x_2, rtol=0.01)
end

@testset "Confirm that lens_propagate, four_f_propagate, Debye integral match analytical solution and are consistent to each other" begin
    function lens_test(L, N, f, radius, shift)

        NA = radius/f

	    E0 = zeros((N, N))
        # place point source at center
	    E0[center_pos(N), center_pos(N)] = 1
	    E1, L1 = lens_propagate(E0, L, f)
	    E1_ = circ(E1, L1, radius)
	    E2, L2 = lens_propagate(E1_, L1, f)
	    psf_ = abs2.(E2)
	    psf = psf_ ./ sum(psf_)
	    psf1 = psf ./ maximum(psf)

        psf_analytical = jinc_psf((N, N), L, radius, f; shift=shift)
	    psf2 = psf_analytical ./ maximum(psf_analytical)

        E_deb = debye_focus(L, N, NA, 0.0)
        psf_deb = abs2.(E_deb)
        psf_deb ./= sum(psf_deb)
        psf_deb ./= maximum(psf_deb)

        psf_4f, L2_4f = four_f_propagate(E0, L, f, f, NA)
	    psf_4f = normabs2(psf_4f)
        if ~ shift
            psf_deb = ifftshift(psf_deb)
            psf1 = ifftshift(psf1)
            psf_4f = ifftshift(psf_4f)
            # exlude borders of the fields because they can differ
            slice = 1 : round(Int, 0.5 * N)
        else
            slice = round(Int, 0.25 * N) : round(Int, 0.75 * N)
        end

        @test L2 == L2_4f
	    @test ≈(psf1[slice, slice], psf2[slice, slice], rtol=0.005)
	    @test ≈(psf_4f[slice, slice], psf2[slice, slice], rtol=0.005)
        @test ≈(psf_deb[slice, slice], psf_4f[slice, slice], rtol=0.01)

    end

    lens_test(10e-3, 2048, 100e-3, 1e-3, true)
    lens_test(1e-3, 2049, 100e-3, 10e-3, true)
    lens_test(100e-6, 513, 1e-3, 0.2e-3, false)
end

@testset "Point source propagation tests" begin
    N = 100
    L = 100e-6
    ps = point_source_propagate(L, (N, N), Point(0.0, 0.0, 0.0))
    ref = zeros(ComplexF32, (N, N))
    ref[center_pos(N), center_pos(N)] = 1

    @test ps == ref

    ps = point_source_propagate(L, (N, N), Point(10e-6, 0.0, 10.0e-6))
    ref = zeros(ComplexF32, (N, N))

    k = 2π/550f-9
    for (j, x) in enumerate(fftpos(L, N))
        for (i, y) in enumerate(fftpos(L, N))
            r = sqrt((x-10e-6)^2 + (y)^2 + (10.0e-6)^2)
            ref[i, j] = 1/r .* exp(-1im * k * r);
        end
    end
    ref ./= ref[argmax(abs2.(ref))]
    @test ≈(ComplexF32.(ps), ComplexF32.(ref), rtol=1e-4)

    ps = point_source_propagate(L, (N, N), Point(-50.0e-6, -50.0e-6, 0.0))
    @test ps[1, 1] == 1

end

@testset "Compare Debye with 4F for different NAs" begin

    function compare(N, L, NA)
	    out_s = (N, N)
	    #f = 50-3
	    out = debye_focus(L, N, NA, 0e-6)
	    h1 = abs2.(out)
	    h1 ./= sum(h1)

        E0 = zeros(ComplexF32, (N, N))
	    E0[center_pos(N), center_pos(N)] = 1
	    E1, L2 = four_f_propagate(E0, L, 100e-3, 100e-3, NA)
	    h2 = abs2.(E1)
	    h2 ./= sum(h2)

        slice = round(Int, 0.3*N):round(Int, 0.7 * N)
        @test ≈(h1[slice, slice], h2[slice, slice], rtol=0.05)
    end

    compare(256, 20e-6, 0.2)
    compare(256, 50e-6, 0.1)
    compare(256, 10e-6, 0.7)
    compare(512, 10e-6, 0.4)
end

@testset "Compare Point source propagate with different lens_prop" begin
    L = 50e-6

    f = 20e-3
    z = 50e-6
    N = 256
    E1 = point_source_propagate(L, (N, N), Point(0.0e-3, 0.0, z), dtype=ComplexF32)
    E1, L1 = lens_propagate(E1, L, f, d=f)
    quadratic!(E1, L1, L1/9)
    E1, L1 = lens_propagate(E1, L1, f)
    I1 = abs2.(E1)
    I1 ./= sum(I1)

    E2 = point_source_propagate(L, (N, N), Point(0.0e-3, 0.0, 0.0), dtype=ComplexF32)
    E2, L2 = lens_propagate(E2, L, f, d=f-z)
    quadratic!(E2, L2, L2/9)
    E2, L2 = lens_propagate(E2, L2, f)
    I2 = abs2.(E2)
    I2 ./= sum(I2)
    @test .≈(I1 .+ 1, I2 .+ 1, rtol=1e-5)  |> all
end


function apply_row(func::Function, w::Array{Float64,2})
      δ = zeros(size(w,1));
      for (index, value) in enumerate(w[:,1])
           δ[index] = func(w[index,:]);
      end
      return(δ);
end

using Optim
using Optim: converged, maximum, maximizer, minimizer, iterations #some extra functions
# Generate IV regression data
begin "Test Optim"
      n = 3000;
      sig = [01.0 0.5 0.5;
             0.0 0.5 0.0 ;
             0.0 0.0 1.0];

      mu = [1,9,0];
      mn = MvNormal(mu,sig * sig')
      W = rand(mn,n)'
      x = W[:,1];z=W[:,2];ϵ=W[:,3];
      y = x * 3 + ϵ;
      β_OLS = inv(x'*x)*(x'*y);
      β_IV = inv(z'*x)*(z'*y);
      w = hcat(x,y,z);
      obj = θ->(begin
            η = (w[:,1] .* θ - w[:,2]) .* w[:,3];
            η'*η
            end)
      result = optimize(obj,zeros(1),Newton(),autodiff = :forward)
      # @show converged(result) || error("Failed to converge in $(iterations(result)) iterations")
      @show xmin = result.minimizer
      @show result.minimum
end

@testset begin "GMM"
      @test β_OLS - result.minimizer[1] < 0.1

end


# reset_default_graph()
# using SymPy

# x, y = @vars x y
# u = x*(1-x)*y*(1-y)
# v = x*(1-x)*y*(1-y)^2

# p = x*(1-x)*y*(1-y)

# ux = diff(u,x)
# uy = diff(u,y)
# vx = diff(v,x)
# vy = diff(v,y)
# px = diff(p,x)
# py = diff(p,y)
# f = u*ux + v*uy - (diff(ux,x)+diff(uy,y)) + px
# g = u*vx + v*vy - (diff(vx,x)+diff(vy,y)) + py
# h = diff(u, x) + diff(v, y)
# println(replace(replace(sympy.julia_code(simplify(f)), ".^"=>"^"), ".*"=>"*"))
# println(replace(replace(sympy.julia_code(simplify(g)), ".^"=>"^"), ".*"=>"*"))
# println(replace(replace(sympy.julia_code(simplify(h)), ".^"=>"^"), ".*"=>"*"))

function u_exact(x,y)
    if y == 0.0
        return 1.0
    else
        return 0.0
    end
end

function v_exact(x,y)
    0.0
end

function p_exact(x,y)
    0.0
end

function ffunc(x, y)
    #-x^2*y*(x - 1)^2*(y - 1)^2*(2*y - 1) + x*y^2*(x - 1)*(2*x - 1)*(y - 1)^2 + x*y*(y - 1) - 2*x*(x - 1) + y*(x - 1)*(y - 1) - 2*y*(y - 1)
    0.0
end
function gfunc(x, y)
    #x^2*y*(x - 1)^2*(y - 1)^3*(3*y - 1) - x*y^2*(x - 1)*(2*x - 1)*(y - 1)^3 + 3*x*y*(x - 1) + 5*x*(x - 1)*(y - 1) + 2*y*(y - 1)^2
    0.0
end

function hfunc(x,y)
    # (y - 1)*(-2*x*y*(x - 1) + x*y - x*(x - 1)*(y - 1) + y*(x - 1))
    0.0
end

# nn to estimate space varying viscosity
function nu_exact(x, y)
    1 + 6 * x^2 + x / (1 + 2 * y^2)
    # 1 + x^2 + x / (1 + 2 * y^2)
    # 1 + x^2
end

using LinearAlgebra
using MAT
using AdFem
using PyPlot
using SparseArrays

m = 20
n = 20
h = 1/n
# nu = 0.01

F1 = compute_fem_source_term1(eval_f_on_gauss_pts(ffunc, m, n, h), m, n, h)
F2 = compute_fem_source_term1(eval_f_on_gauss_pts(gfunc, m, n, h), m, n, h)
H = h^2*eval_f_on_fvm_pts(hfunc, m, n, h)
B = constant(compute_interaction_matrix(m, n, h))

# compute F
xy = fem_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
u0 = @. u_exact(x,y)
v0 = @. v_exact(x,y)

xy = fvm_nodes(m, n, h)
x, y = xy[:,1], xy[:,2]
p0 = @. p_exact(x,y)
# nu_gauss = Variable(ones(4*m*n))
nu_fvm = Variable(ones(m*n))
nu_gauss = reshape(repeat(nu_fvm, 1, 4), (-1,))

Laplace = compute_fem_laplace_matrix1(nu_gauss, m, n, h)

# Laplace = constant(nu*compute_fem_laplace_matrix1(m, n, h))
function compute_residual(S)
    u, v, p = S[1:(m+1)*(n+1)], S[(m+1)*(n+1)+1:2(m+1)*(n+1)], S[2(m+1)*(n+1)+1:2(m+1)*(n+1)+m*n]
    G = eval_grad_on_gauss_pts([u;v], m, n, h)
    ugauss = fem_to_gauss_points(u, m, n, h)
    vgauss = fem_to_gauss_points(v, m, n, h)
    ux, uy, vx, vy = G[:,1,1], G[:,1,2], G[:,2,1], G[:,2,2]

    interaction = compute_interaction_term(p, m, n, h) # julia kernel needed
    f1 = compute_fem_source_term1(ugauss.*ux, m, n, h)
    f2 = compute_fem_source_term1(vgauss.*uy, m, n, h)
    f3 = -interaction[1:(m+1)*(n+1)]
    f4 = Laplace*u
    f5 = -F1
    F = f1 + f2 + f3 + f4 + f5

    g1 = compute_fem_source_term1(ugauss.*vx, m, n, h)
    g2 = compute_fem_source_term1(vgauss.*vy, m, n, h)
    g3 = -interaction[(m+1)*(n+1)+1:end]
    g4 = Laplace*v
    g5 = -F2
    G = g1 + g2 + g3 + g4 + g5

    H0 = -B * [u;v] + H

    R = [F;G;H0]
    return R
end

function compute_jacobian(S)
    u, v, p = S[1:(m+1)*(n+1)], S[(m+1)*(n+1)+1:2(m+1)*(n+1)], S[2(m+1)*(n+1)+1:2(m+1)*(n+1)+m*n]
    G = eval_grad_on_gauss_pts([u;v], m, n, h)
    ugauss = fem_to_gauss_points(u, m, n, h)
    vgauss = fem_to_gauss_points(v, m, n, h)
    ux, uy, vx, vy = G[:,1,1], G[:,1,2], G[:,2,1], G[:,2,2]

    M1 = constant(compute_fem_mass_matrix1(ux, m, n, h))
    M2 = constant(compute_fem_advection_matrix1(constant(ugauss), constant(vgauss), m, n, h)) # a julia kernel needed
    M3 = Laplace
    Fu = M1 + M2 + M3

    Fv = constant(compute_fem_mass_matrix1(uy, m, n, h))

    N1 = constant(compute_fem_mass_matrix1(vy, m, n, h))
    N2 = constant(compute_fem_advection_matrix1(constant(ugauss), constant(vgauss), m, n, h))
    N3 = Laplace
    Gv = N1 + N2 + N3

    Gu = constant(compute_fem_mass_matrix1(vx, m, n, h))

    J0 = [Fu Fv
          Gu Gv]
    J = [J0 -B'
        -B spdiag(zeros(size(B,1)))]
end

NT = 5
# S = zeros(m*n+2(m+1)*(n+1), NT+1)
# S[1:m+1, 1] = ones(m+1,1)

bd = bcnode("all", m, n, h)
# bd = [bd; bd .+ (m+1)*(n+1); ((1:m) .+ 2(m+1)*(n+1))]
bd = [bd; bd .+ (m+1)*(n+1); 2*(m+1)*(n+1)+(n-1)*m+1:2*(m+1)*(n+1)+(n-1)*m+2] # only apply Dirichlet to velocity

function solve_steady_cavityflow_one_step(S)
    residual = compute_residual(S)
    J = compute_jacobian(S)

    J, _ = fem_impose_Dirichlet_boundary_condition1(J, bd, m, n, h)
    # residual[bd] .= 0.0
    residual = scatter_update(residual, bd, zeros(length(bd)))

    d = J\residual
    S_new = S - d
    return S_new
end

function condition(i, S_arr)
    i <= NT + 1
end

function body(i, S_arr)
    S = read(S_arr, i-1)
    op = tf.print("i=",i)
    i = bind(i, op)
    S_new = solve_steady_cavityflow_one_step(S)
    S_arr = write(S_arr, i, S_new)
    return i+1, S_arr
end

# for i = 1:NT
#     residual = compute_residual(S[:,i])
#     J = compute_jacobian(S[:,i])

#     J, _ = fem_impose_Dirichlet_boundary_condition1(J, bd, m, n, h)
#     residual[bd] .= 0.0

#     d = J\residual
#     S[:,i+1] = S[:,i] - d
#     @info i, norm(residual)
# end

function plot_velocity_pressure_viscosity(k)

    figure(figsize=(14,4));
    subplot(131)
    visualize_scalar_on_fem_points(S_true[end, 1:(m+1)*(n+1)], m, n, h); title("velocity x data")
    subplot(132)
    visualize_scalar_on_fem_points(run(sess, S[end,1:(m+1)*(n+1)]), m, n, h); title("velocity x prediction")
    subplot(133)
    visualize_scalar_on_fem_points(S_true[end, 1:(m+1)*(n+1)] - run(sess, S[end,1:(m+1)*(n+1)]), m, n, h); title("velocity x error")
    savefig("figures_ones6_fvm/steady_xy_nn_velox$k.png")
    # savefig("steady_xy_ones_velox.png")

    figure(figsize=(14,4));
    subplot(131)
    visualize_scalar_on_fem_points(S_true[end, (m+1)*(n+1)+1:2*(m+1)*(n+1)], m, n, h); title("velocity y data")
    subplot(132)
    visualize_scalar_on_fem_points(run(sess, S[end,(m+1)*(n+1)+1:2*(m+1)*(n+1)]), m, n, h); title("velocity y prediction")
    subplot(133)
    visualize_scalar_on_fem_points(S_true[end, (m+1)*(n+1)+1:2*(m+1)*(n+1)] - run(sess, S[end,(m+1)*(n+1)+1:2*(m+1)*(n+1)]), m, n, h); title("velocity y error")
    savefig("figures_ones6_fvm/steady_xy_nn_veloy$k.png")
    # savefig("steady_xy_ones_veloy.png")

    figure(figsize=(14,4));
    subplot(131)
    visualize_scalar_on_fvm_points(S_true[end, 2*(m+1)*(n+1)+1:end], m, n, h); title("pressure data")
    subplot(132)
    visualize_scalar_on_fvm_points(run(sess, S[end,2*(m+1)*(n+1)+1:end]), m, n, h); title("pressure prediction")
    subplot(133)
    visualize_scalar_on_fvm_points(S_true[end, 2*(m+1)*(n+1)+1:end] - run(sess, S[end,2*(m+1)*(n+1)+1:end]), m, n, h); title("pressure difference")
    savefig("figures_ones6_fvm/steady_xy_nn_pres$k.png")
    # savefig("steady_xy_ones_pres.png")

    figure(figsize=(14,4));
    subplot(131)
    visualize_scalar_on_fvm_points(nu_exact.(x,y), m, n, h); title("viscosity exact")
    subplot(132)
    visualize_scalar_on_gauss_points(run(sess, nu_gauss), m, n, h); title("viscosity prediction");gca().invert_yaxis()
    subplot(133)
    visualize_scalar_on_fvm_points(nu_exact.(x,y).-run(sess, nu_gauss)[1:4:end], m, n, h); title("viscosity difference")
    savefig("figures_ones6_fvm/steady_xy_nn_visc$k.png")
    # savefig("steady_xy_ones_visc.png")

end

S_arr = TensorArray(NT+1)
S_arr = write(S_arr, 1, [u0; v0; p0])

i = constant(2, dtype=Int32)

_, S = while_loop(condition, body, [i, S_arr])
S = set_shape(stack(S), (NT+1, 2*(m+1)*(n+1)+m*n))

S_true = matread("steady_cavity_data.mat")["V"]
loss = mean((S[end,1:2*(m+1)*(n+1)] - S_true[end,1:2*(m+1)*(n+1)])^2)

# loss = mean((S[end,:] - S_true[end,:])^2)
loss = loss * 1e10

sess = Session(); init(sess)
@info run(sess, loss)

max_iter = 1000

# ADCME.load(sess, "nn$k.mat")

for k = 1:100
    loss_ = BFGS!(sess, loss, max_iter)
    matwrite("figures_ones6_fvm/loss$k.mat", Dict("L"=>loss_))
    close("all"); semilogy(loss_); title("loss vs. iteration")
    savefig("figures_ones6_fvm/steady_xy_loss$k.png")
    plot_velocity_pressure_viscosity(k)
    ADCME.save(sess, "figures_ones6_fvm/nn$k.mat")
end
# savefig("steady_xy_ones_loss.png")


@testset "normalized array" begin
    grdt!(buf, _) = (buf .= 0; buf[1] = 1; buf)
    result = optimize(x->x[1], grdt!, randn(2,2), ConjugateGradient(manifold=Sphere()))
    @test result.minimizer ≈ [-1 0; 0 0]
    @test result.minimum ≈ -1
end

@testset "input types" begin
    f(X) = (10 - X[1])^2 + (0 - X[2])^2 + (0 - X[3])^2 + (5 - X[4])^2

    function g!(storage, x)
        storage[1] = -20 + 2 * x[1]
        storage[2] = 2 * x[2]
        storage[3] = 2 * x[3]
        storage[4] = -10 + 2 * x[4]
        return
    end
    @testset "vector" begin
        for m in (AcceleratedGradientDescent, ConjugateGradient, BFGS, LBFGS, NelderMead, GradientDescent, MomentumGradientDescent, NelderMead, ParticleSwarm, SimulatedAnnealing, NGMRES, OACCEL)
            debug_printing && printstyled("Solver: "*string(m); color = :green)
            res = optimize(f, g!, [1., 0., 1., 0.], m())
            @test typeof(Optim.minimizer(res)) <: Vector
            if !(m in (NelderMead, SimulatedAnnealing, ParticleSwarm))
                @test norm(Optim.minimizer(res) - [10.0, 0.0, 0.0, 5.0]) < 10e-8
            end
        end
    end

    @testset "matrix" begin
        for m in (AcceleratedGradientDescent, ConjugateGradient, BFGS, LBFGS, ConjugateGradient,  GradientDescent, MomentumGradientDescent, ParticleSwarm, SimulatedAnnealing, NGMRES, OACCEL)
            res = optimize(f, g!, Matrix{Float64}(I, 2, 2), m())
            @test typeof(Optim.minimizer(res)) <: Matrix
            if !(m in (SimulatedAnnealing, ParticleSwarm))
                @test norm(Optim.minimizer(res) - [10.0 0.0; 0.0 5.0]) < 10e-8
            end
        end
    end

    @testset "tensor" begin
        eye3 = zeros(2,2,1)
        eye3[:,:,1] = Matrix{Float64}(I, 2, 2)
        for m in (AcceleratedGradientDescent, ConjugateGradient, BFGS, LBFGS, ConjugateGradient,  GradientDescent, MomentumGradientDescent, ParticleSwarm, SimulatedAnnealing, NGMRES, OACCEL)
            res = optimize(f, g!, eye3, m())
            _minimizer = Optim.minimizer(res)
            @test typeof(_minimizer) <: Array{Float64, 3}
            @test size(_minimizer) == (2,2,1)
            if !(m in (SimulatedAnnealing, ParticleSwarm))
                @test norm(_minimizer - [10.0 0.0; 0.0 5.0]) < 10e-8
            end
        end
    end
end

using RecursiveArrayTools
@testset "arraypartition input" begin

    function polynomial(x)
            return (10.0 - x[1])^2 + (7.0 - x[2])^4 + (108.0 - x[3])^4
        end

    function polynomial_gradient!(storage, x)
            storage[1] = -2.0 * (10.0 - x[1])
            storage[2] = -4.0 * (7.0 - x[2])^3
            storage[3] = -4.0 * (108.0 - x[3])^3
        end

    function polynomial_hessian!(storage, x)
            storage[1, 1] = 2.0
            storage[1, 2] = 0.0
            storage[1, 3] = 0.0
            storage[2, 1] = 0.0
            storage[2, 2] = 12.0 * (7.0 - x[2])^2
            storage[2, 3] = 0.0
            storage[3, 1] = 0.0
            storage[3, 2] = 0.0
            storage[3, 3] = 12.0 * (108.0 - x[3])^2
        end

    ap = ArrayPartition(rand(1), rand(2))

    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, NelderMead())
    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, ParticleSwarm())
    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, SimulatedAnnealing())

    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, GradientDescent())
    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, AcceleratedGradientDescent())
    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, MomentumGradientDescent())

    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, ConjugateGradient())

    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, BFGS())
    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, LBFGS())

    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, Newton())
    optimize(polynomial, polynomial_gradient!, polynomial_hessian!, ap, NewtonTrustRegion())
end


export measure

"""
Computes the outcome probabilities for a quantum measurement. The conditional
probabilities are determined by the Born rule, ``P(i|j) = \\text{Tr}[\\Pi_i \\rho_j]``,
where ``\\Pi_j`` is a POVM element and ``\\rho_j`` is a density matrix.

Measurement of a single `Ket` or  `DensityMatrix`:

    measure(
        Π :: POVM,
        ρ :: State
    ) :: Conditionals

    measure(
        Π :: POVM,
        ψ :: Ket
    ) :: Conditionals

Measurement of an ensemble of `Ket` or `DensityMatrix` types:

    measure(
        Π :: POVM,
        ρ_states :: Vector{<:State}
    ) :: Conditionals

    measure(
        Π :: POVM,
        ψ_kets :: Vector{<:Ket}
    ) :: Conditionals
"""
function measure(
    Π :: POVM,
    ρ :: State
) :: Probabilities
    Probabilities( real.(map(Π_el -> tr(Π_el * ρ), Π)) )
end
function measure(
    Π :: POVM,
    states :: Vector{<:State}
) :: Conditionals
    Conditionals( real.(tr.(transpose(states * transpose(Π))) ))
end
function measure(
    Π :: POVM,
    ψ :: Ket
) :: Probabilities
    Probabilities( real.(map( Π_el -> ψ' * Π_el * ψ, Π)) )
end
function measure(
    Π :: POVM,
    ψ_kets :: Vector{<:Ket}
) :: Conditionals
    measure(Π, pure_state.(ψ_kets))
end


using Test
using LinearAlgebra
using SummationByPartsOperators

# check construction of interior part of upwind operators
@testset "Check interior parts" begin
  N = 21
  xmin = 0.
  xmax = Float64(N + 1)
  interior = 10:N-10

  for acc_order in 2:7
    Dp_bounded = derivative_operator(Mattsson2017(:plus   ), 1, acc_order, xmin, xmax, N)
    Dm_bounded = derivative_operator(Mattsson2017(:minus  ), 1, acc_order, xmin, xmax, N)
    Dc_bounded = derivative_operator(Mattsson2017(:central), 1, acc_order, xmin, xmax, N)
    for compact in (true, false)
      show(IOContext(devnull, :compact=>false), Dp_bounded)
      show(IOContext(devnull, :compact=>false), Dm_bounded)
      show(IOContext(devnull, :compact=>false), Dc_bounded)
    end
    M = mass_matrix(Dp_bounded)
    @test M == mass_matrix(Dm_bounded)
    @test M == mass_matrix(Dc_bounded)
    Dp_periodic = periodic_derivative_operator(1, acc_order, xmin, xmax, N-1, -(acc_order - 1) ÷ 2)
    Dm_periodic = periodic_derivative_operator(1, acc_order, xmin, xmax, N-1, -acc_order + (acc_order - 1) ÷ 2)
    Dp = Matrix(Dp_bounded)
    Dm = Matrix(Dm_bounded)
    @test Dp[interior,interior] ≈ Matrix(Dp_periodic)[interior,interior]
    @test Dm[interior,interior] ≈ Matrix(Dm_periodic)[interior,interior]
    res = M * Dp + Dm' * M
    res[1,1] += 1
    res[end,end] -= 1
    @test norm(res) < N * eps()
    x = grid(Dp_bounded)
    for D in (Dp_bounded, Dm_bounded, Dc_bounded)
      @test norm(D * x.^0) < N * eps()
      for k in 1:acc_order÷2
        @test D * x.^k ≈ k .* x.^(k-1)
      end
      for k in acc_order÷2+1:acc_order
        @test (D * x.^k)[interior] ≈ (k .* x.^(k-1))[interior]
      end
    end
    diss = M * (Dp - Dm)
    @test diss ≈ diss'
    @test maximum(eigvals(Symmetric(diss))) < N * eps()
  end
end


using Logging
using IterTools
using LinearAlgebra

include("../../phantoms/sperm_phantom.jl")
include("../snake_forward.jl")
include("../snake.jl")
include("../curve_utils.jl")

cwd = @__DIR__
savepath = normpath(joinpath(@__DIR__, "result"))
!isdir(savepath) && mkdir(savepath)

#object to store center and radius of circle in complex plane
struct circle
    center::Complex{Float64}
    radius::Float64
end

#return the parametric equation for a circle in the complex plane
function get_parametric_circle_equation(c::circle)
    return t -> c.radius*ℯ^(t*im) + c.center
end

H,W = 609, 609
detmin, detmax = -38.0, 38.0
grid = collect(detmin:0.1:detmax)
r(s) = 1.0

images, tracks = get_sperm_phantom(301,r,grid)

grid = collect(detmin:0.1:detmax)
bins = collect(detmin:0.125:detmax)
angles = collect(range(0,π,length=9))[1:end-1]

@info  "using " length(angles) " angles"
iterations = 1000
α = 0.01
β = 0.3
step_size = 0.2
r(s) = 1.0
tol = 0.001

num_points = 62
t = range(0, 2π, length=num_points)
c = circle((0.0-0.0*im), 15.0)
γ = get_parametric_circle_equation(c)
compl_circ = γ.(t)
outline_templ = cat(real(compl_circ), imag(compl_circ), dims=2)

frames2reconstruct = collect(1:10:301)
reconstructions = zeros(num_points,2,length(frames2reconstruct))

best_residual = ones(length(frames2reconstruct),4)

for step_size in collect(1.0:-0.1:0.01)
    for α in collect(1.0:-0.1:0.01)
        for β in collect(1.0:-0.1:0.01)
            for (iter, frame_nr) in Base.Iterators.reverse(enumerate(frames2reconstruct))
                @info iter frame_nr

                #Get projection
                @info "making forward projection for frame: " frame_nr
                outline, normals = get_outline(tracks[frame_nr], r)
                sinogram = parallel_forward(outline, angles, bins)

                #Add noise
                @info "adding gaussian noise at level 0.01"
                rho = 0.01
                e = randn(size(sinogram));
                e = rho*norm(sinogram)*e/norm(e);
                sinogram = sinogram + e;
                #plot(Gray.(sinogram./maximum(sinogram)))
                #cd(savepath)
                #savefig(@sprintf "sinogram_%d" frame_nr)

                @info "reconstructing"
                try
                    reconstruction, residuals = vedrana(outline_templ,angles,bins,sinogram, iterations, α, β, step_size, tol)

                if minimum(residuals) > 0.01 || minimum(residuals) > best_residual[iter,1]
                    @info "combination not better:" frame_nr step_size α β
                    continue
                else
                    best_residual[iter,:] =  [minimum(residuals), α, β, step_size]
                end

                reconstructions[:,:,iter] = reconstruction

                @info "plotting"
                heatmap(grid, grid, Gray.(images[:,:,frame_nr]), yflip=false, aspect_ratio=:equal, framestyle=:none, legend=true)
                plot!(reconstruction[:,1], reconstruction[:,2], aspect_ratio=:equal, linewidth=2, label=frame_nr)
                plot!(outline_templ[:,1], outline_templ[:,2], aspect_ratio=:equal, linewidth=2, label="template")
                cd(savepath)
                savefig(@sprintf "result_original_vedrana_%d" frame_nr)

                @info "plotting"
                plot(collect(1:1:length(residuals)), residuals)
                cd(savepath)
                savefig(@sprintf "residuals_%d" frame_nr)
                catch
                    @info "combination not error:" frame_nr step_size α β
                    continue
                end
            end
        end
    end
end

global ps = AbstractPlot[]
for (iter, frame_nr) in enumerate(frames2reconstruct)
    best_recon = reconstructions[:,:,iter]

    l = @layout [a b c]
    p = heatmap(grid, grid, Gray.(images[:,:,frame_nr]), yflip=false, aspect_ratio=:equal, framestyle=:none, legend=false)
    plot!(best_recon[:,1],best_recon[:,2], aspect_ratio=:equal, linewidth=5)
    push!(ps,p)
    if length(ps) == 3
        plot(ps[1], ps[2], ps[3], layout = l, size=(2000,600), linewidth=5)
        savefig(@sprintf "result_all_original_vedrana_%d_%d" length(angles) frame_nr)
        global ps = AbstractPlot[]
    end
end
cd(cwd)


function give_rand_p(p_range,p_fixed=nothing,indices=nothing)
    if p_fixed == nothing
        p = [(p_range[j][2] -p_range[j][1])*rand() + p_range[j][1] for j in 1:length(p_range)]
    else
        p =  zeros(length(p_range))
        j = 1
        for i in 1:length(p_range)
            if i in indices
                p[i] = p_fixed[j]
                j += 1
            else
                p[i] = (p_range[i][2] -p_range[i][1])*rand() + p_range[i][1]
            end
        end
    end
    p
end

function calc_mean_var(f,p_range,N)
    y1 = Array(f(give_rand_p(p_range)))
    y0 = zero(y1)
    v = zero(y1)
    for i in 1:N
        y1 = Array(f(give_rand_p(p_range)))
        @. y0 += y1
        @. v += y1^2
    end
    y0 = @. y0/N
    y0_sq = [i.^2 for i in y0]
    v = @. v/N - y0_sq
    y0,v
end

function first_order_var(f,p_range,N,y0)
    ys = Array{typeof(y0)}(undef,length(p_range))
    for i in 1:length(p_range)
        y = zero(y0)
        for j in 1:N
            p2 = give_rand_p(p_range)
            p1 = give_rand_p(p_range,[p2[i]],[i])
            yer =  Array(f(p1)) .* Array(f(p2))
            @. y += yer
        end
        y = @. y/N - y0^2
        ys[i] = copy(y)
    end
    ys
end

function second_order_var(f,p_range,N,y0)
    ys = Array{typeof(y0)}(undef,Int((length(p_range)*(length(p_range)-1))/2))
    curr = 1
    for i in 1:length(p_range)
        for j in i+1:length(p_range)
            y = zero(y0)
            for k in 1:N
                p2 = give_rand_p(p_range)
                p1 = give_rand_p(p_range,[p2[i],p2[j]],[i,j])
                y .+=  Array(f(p1)) .* Array(f(p2))
            end
            y = @. y/N - y0^2
            ys[curr] = copy(y)
            curr += 1
        end
    end
    ys_frst_order = first_order_var(f,p_range,N,y0)
    j = 1
    for i in 1:length(p_range)
        for k in i+1:length(p_range)
            ys[j] = @. ys[j] - ( ys_frst_order[i] + ys_frst_order[k] )
            j += 1
        end
    end
    ys
end

function total_var(f,p_range,N,y0)
    ys = Array{typeof(y0)}(undef,length(p_range))
    for i in 1:length(p_range)
        y = zero(y0)
        for j in 1:N
            p_fixed_all = []
            p_fixed_indices = []
            p2 = give_rand_p(p_range)
            for j in 1:length(p2)
                if j != i
                    push!(p_fixed_all,p2[j])
                    push!(p_fixed_indices,j)
                end
            end
            p1 = give_rand_p(p_range,p_fixed_all,p_fixed_indices)
            yer =  Array(f(p1)) .* Array(f(p2))
            @. y += yer
        end
        y = @. y/N - y0^2
        ys[i] = copy(y)
    end
    ys
end

function sobol_sensitivity(f,p_range,N,order=2)
    y0,v = calc_mean_var(f,p_range,N)
    if order == 1
        first_order = first_order_var(f,p_range,N,y0)
        for i in 1:length(first_order)
            first_order[i] = @. first_order[i] / v
        end
        first_order
    elseif order == 2
        second_order = second_order_var(f,p_range,N,y0)
        for i in 1:length(second_order)
            second_order[i] = @. second_order[i] / v
        end
        second_order
    else
        total_indices = total_var(f,p_range,N,y0)
        for i in 1:length(total_indices)
            total_indices[i] = @. 1 - (total_indices[i] / v)
        end
        total_indices
    end
end

function sobol_sensitivity(prob::DiffEqBase.DEProblem,alg,t,p_range,N,order=2)
    f = function (p)
        prob1 = remake(prob;p=p)
        Array(solve(prob1,alg;saveat=t))
    end
    @assert length(prob.p) == length(p_range)
    sobol_sensitivity(f,p_range,N,order)
end


# TODO: uncomment vexity checks once SDP on vars/constraints changes vexity of problem
@add_problem sdp function sdp_sdp_variables(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    y = Variable((2,2), :Semidefinite)
    p = minimize(y[1,1]; numeric_type = T)

    # @fact vexity(p) --> ConvexVexity()
    handle_problem!(p)
    if test
        @test p.optval ≈ 0 atol=atol rtol=rtol
    end

    y = Variable((3,3), :Semidefinite)
    p = minimize(y[1,1], y[2,2]==1; numeric_type = T)

    # @fact vexity(p) --> ConvexVexity()
    handle_problem!(p)
    if test
        @test p.optval ≈ 0 atol=atol rtol=rtol
    end

    # Solution is obtained as y[2,2] -> infinity
    # This test fails on Mosek. See
    # https://github.com/JuliaOpt/Mosek.jl/issues/29
    # y = Variable((2, 2), :Semidefinite)
    # p = minimize(y[1, 1], y[1, 2] == 1; numeric_type = T)

    # # @fact vexity(p) --> ConvexVexity()
    # handle_problem!(p)
    # @fact p.optval --> roughly(0, atol)

    y = Semidefinite(3)
    p = minimize(sum(diag(y)), y[1, 1] == 1; numeric_type = T)

    # @fact vexity(p) --> ConvexVexity()
    handle_problem!(p)
    if test
        @test p.optval ≈ 1 atol=atol rtol=rtol
    end

    y = Variable((3, 3), :Semidefinite)
    p = minimize(tr(y), y[2,1]<=4, y[2,2]>=3; numeric_type = T)

    # @fact vexity(p) --> ConvexVexity()
    handle_problem!(p)
    if test
        @test p.optval ≈ 3 atol=atol rtol=rtol
    end

    x = Variable(Positive())
    y = Semidefinite(3)
    p = minimize(y[1, 2], y[2, 1] == 1; numeric_type = T)

    # @fact vexity(p) --> ConvexVexity()
    handle_problem!(p)
    if test
        @test p.optval ≈ 1 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_sdp_constraints(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    x = Variable(Positive())
    y = Variable((3, 3))
    p = minimize(x + y[1, 1], isposdef(y), x >= 1, y[2, 1] == 1; numeric_type = T)

    # @fact vexity(p) --> ConvexVexity()
    handle_problem!(p)
    if test
        @test p.optval ≈ 1 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_nuclear_norm_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    y = Semidefinite(3)
    p = minimize(nuclearnorm(y), y[2,1]<=4, y[2,2]>=3, y[3,3]<=2; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 3 atol=atol rtol=rtol
        @test evaluate(nuclearnorm(y)) ≈ 3 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_nuclear_norm_atom_complex(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    A = [1 2im 3 4; 4im 3im 2 1; 4 5 6 7]
    y = ComplexVariable(3, 4)
    p = minimize(nuclearnorm(y), y == A; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ sum(svdvals(A)) atol=atol rtol=rtol
        @test evaluate(nuclearnorm(y)) ≈ sum(svdvals(A)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_operator_norm_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    y = Variable((3,3))
    p = minimize(opnorm(y), y[2,1]<=4, y[2,2]>=3, sum(y)>=12; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 4 atol=atol rtol=rtol
        @test evaluate(opnorm(y)) ≈ 4 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_operator_norm_atom_complex(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    A = [1 2im 3 4; 4im 3im 2 1; 4 5 6 7]
    y = ComplexVariable(3, 4)
    p = minimize(opnorm(y), y == A; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ maximum(svdvals(A)) atol=atol rtol=rtol
        @test evaluate(opnorm(y)) ≈ maximum(svdvals(A)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_sigma_max_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    y = Variable((3,3))
    p = minimize(sigmamax(y), y[2,1]<=4, y[2,2]>=3, sum(y)>=12; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 4 atol=atol rtol=rtol
        @test evaluate(sigmamax(y)) ≈ 4 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_dual_lambda_max_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    y = Semidefinite(3)
    p = minimize(eigmax(y), y[1,1]>=4; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 4 atol=atol rtol=rtol
        @test evaluate(eigmax(y)) ≈ 4 atol=atol rtol=rtol
    end

    # https://github.com/jump-dev/Convex.jl/issues/337
    x = ComplexVariable(2, 2)
    p = minimize( eigmax(x), [ x[1,2] == im, x[2,2] == 1.0, x ⪰ - eye(2) ]; numeric_type = T)
    handle_problem!(p)
    if test
        @test p.optval ≈ 1.5 atol=atol rtol=rtol
        @test p.constraints[1].dual ≈ im atol=atol rtol=rtol
        @test p.constraints[2].dual ≈ 0.75 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_lambda_min_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    y = Semidefinite(3)
    p = maximize(eigmin(y), tr(y)<=6; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 2 atol=atol rtol=rtol
        @test evaluate(eigmin(y)) ≈ 2 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_matrix_frac_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    x = [1, 2, 3]
    P = Variable(3, 3)
    p = minimize(matrixfrac(x, P), P <= 2*eye(3), P >= 0.5 * eye(3); numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 7 atol=atol rtol=rtol
        @test (evaluate(matrixfrac(x, P)))[1] ≈ 7 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_matrix_frac_atom_both_arguments_variable(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    x = Variable(3)
    P = Variable(3, 3)
    p = minimize(matrixfrac(x, P), eigmax(P) <= 2, x[1] >= 1; numeric_type = T)

    if test
        @test vexity(p) == ConvexVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 0.5 atol=atol rtol=rtol
        @test (evaluate(matrixfrac(x, P)))[1] ≈ 0.5 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_sum_largest_eigs(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    x = Semidefinite(3)
    p = minimize(sumlargesteigs(x, 2), x >= 1; numeric_type = T)

    handle_problem!(p)

    if test
        @test p.optval ≈ 3 atol=atol rtol=rtol
        @test evaluate(x) ≈ ones(3, 3) atol=atol rtol=rtol
    end

    x = Semidefinite(3)
    p = minimize(sumlargesteigs(x, 2) + sumlargesteigs(x,0), [x[i,:] >= i for i=1:3]...; numeric_type = T)

    handle_problem!(p)

    if test
        @test p.optval ≈ 8.4853 atol=atol rtol=rtol
    end

    A = [1   -2im  3   4
         2im  7    3im 5
         3   -3im  2   9
         4    5    9   4]

    x = ComplexVariable(4, 4)
    p = minimize(sumlargesteigs(x, 3), x == A; numeric_type = T)

    handle_problem!(p)

    if test
        @test p.optval ≈ sum(eigvals(A)[2:end]) atol=atol rtol=rtol
    end

    x1 = Semidefinite(3)
    p1 = minimize(eigmax(x1), x1[1,1]>=4; numeric_type = T)

    handle_problem!(p1)

    x2 = Semidefinite(3)
    p2 = minimize(sumlargesteigs(x2, 1), x2[1,1]>=4; numeric_type = T)

    handle_problem!(p2)

    if test
        @test p1.optval ≈ p2.optval atol=atol rtol=rtol
    end

    x1 = Semidefinite(3)
    p1 = minimize(eigmax(x1), [x1[i,:] >= i for i=1:3]...; numeric_type = T)

    handle_problem!(p1)

    x2 = Semidefinite(3)
    p2 = minimize(sumlargesteigs(x2, 1), [x2[i,:] >= i for i=1:3]...; numeric_type = T)

    handle_problem!(p2)

    if test
        @test p1.optval ≈ p2.optval atol=atol rtol=rtol
    end

end

@add_problem sdp function sdp_kron_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    id = eye(4)
    X = Semidefinite(4)
    W = kron(id, X)
    p = maximize(tr(W), tr(X) ≤ 1; numeric_type = T)

    if test
        @test vexity(p) == AffineVexity()
    end
    handle_problem!(p)
    if test
        @test p.optval ≈ 4 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_Partial_trace(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    A = Semidefinite(2)
    B = [1 0; 0 0]
    ρ = kron(B, A)
    constraints = [partialtrace(ρ, 1, [2; 2]) == [0.09942819 0.29923607; 0.29923607 0.90057181], ρ in :SDP]
    p = satisfy(constraints; numeric_type = T)

    handle_problem!(p)
    if test
        @test evaluate(ρ) ≈ [0.09942819 0.29923607 0 0; 0.299237 0.900572 0 0; 0 0 0 0; 0 0 0 0] atol=atol rtol=rtol
        @test evaluate(partialtrace(ρ, 1, [2; 2])) ≈ [0.09942819 0.29923607; 0.29923607 0.90057181] atol=atol rtol=rtol
    end

    function rand_normalized(n)
        A = 5*randn(n, n) + im*5*randn(n, n)
        A / tr(A)
    end

    As = [ rand_normalized(3) for _ = 1:5]
    Bs = [ rand_normalized(2) for _ = 1:5]
    p = rand(5)

    AB = sum(i -> p[i]*kron(As[i],Bs[i]), 1:5)
    if test
        @test partialtrace(AB, 2, [3, 2]) ≈ sum( p .* As ) atol=atol rtol=rtol
        @test partialtrace(AB, 1, [3, 2]) ≈ sum( p .* Bs ) atol=atol rtol=rtol
    end

    A, B, C = rand(5,5), rand(4,4), rand(3,3)
    ABC = kron(kron(A, B), C)
    if test
        @test kron(A,B)*tr(C) ≈ partialtrace(ABC, 3, [5, 4, 3]) atol=atol rtol=rtol
    end

    # Test 281
    A = rand(6,6)
    expr = partialtrace(Constant(A), 1, [2, 3])
    if test
        @test size(expr) == size(evaluate(expr))

        @test_throws ArgumentError partialtrace(rand(6, 6), 3, [2, 3])
        @test_throws ArgumentError partialtrace(rand(6, 6), 1, [2, 4])
        @test_throws ArgumentError partialtrace(rand(3, 4), 1, [2, 3])
    end
end

@add_problem sdp function sdp_Real_Variables_with_complex_equality_constraints(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 10 # variable dimension (parameter)
    m = 5 # number of constraints (parameter)
    xo = rand(n)
    A = randn(m,n) + im*randn(m,n)
    b = A * xo
    x = Variable(n)
    p1 = minimize(sum(x), A*x == b, x>=0; numeric_type = T)

    handle_problem!(p1)

    if test
        x1 = copy(evaluate(x))
    end

    p2 = minimize(sum(x), real(A)*x == real(b), imag(A)*x==imag(b), x>=0; numeric_type = T)

    handle_problem!(p2)
    if test
        x2 = evaluate(x)
        @test x1 == x2
    end
end

@add_problem sdp function sdp_Complex_Variable_with_complex_equality_constraints(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 10 # variable dimension (parameter)
    m = 5 # number of constraints (parameter)
    xo = rand(n)+im*rand(n)
    A = randn(m,n) + im*randn(m,n)
    b = A * xo
    x = ComplexVariable(n)
    p1 = minimize(real(sum(x)), A*x == b, real(x)>=0, imag(x)>=0; numeric_type = T)

    handle_problem!(p1)

    if test
        x1 = evaluate(x)
    end

    xr = Variable(n)
    xi = Variable(n)
    p2 = minimize(sum(xr), real(A)*xr-imag(A)*xi == real(b), imag(A)*xr+real(A)*xi == imag(b), xr>=0, xi>=0; numeric_type = T)

    handle_problem!(p2)

    if test
        real_diff = real(x1) - evaluate(xr)
        @test real_diff ≈ zeros(10, 1) atol=atol rtol=rtol

        imag_diff = imag(x1) - evaluate(xi)
        @test imag_diff ≈ zeros(10, 1) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_Issue_198(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    ρ = HermitianSemidefinite(2)
    constraints = [ρ == [ 1. 0.; 0.  1.]]
    p = satisfy(constraints; numeric_type = T)

    handle_problem!(p)
    if test
        @test p.status == MOI.OPTIMAL
        @test evaluate(ρ) ≈ [ 1. 0.; 0.  1.] atol=atol rtol=rtol
        @test p.optval ≈ 0 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_socp_norm2_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    a = 2+4im
    x = ComplexVariable()
    objective = norm2(a-x)
    c1 = real(x)>=0
    p = minimize(objective,c1; numeric_type = T)

    handle_problem!(p)
    if test
        @test p.optval ≈ 0 atol=atol rtol=rtol
        @test evaluate(objective) ≈ 0 atol=atol rtol=rtol

        real_diff = real(evaluate(x)) - real(a)
        imag_diff = imag(evaluate(x)) - imag(a)
        @test real_diff ≈ 0 atol=atol rtol=rtol
        @test imag_diff ≈ 0 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_socp_sumsquares_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    a = [2+4im;4+6im]
    x = ComplexVariable(2)
    objective = sumsquares(a-x)
    c1 = real(x)>=0
    p = minimize(objective,c1; numeric_type = T)

    handle_problem!(p)
    if test
        @test p.optval ≈ 0 atol=atol rtol=rtol
        @test evaluate(objective) ≈ 0.0 atol=atol rtol=rtol

        real_diff = real.(evaluate(x)) - real.(a)
        imag_diff = imag.(evaluate(x)) - imag.(a)
        @test real_diff ≈ zeros(2, 1) atol=atol rtol=rtol
        @test imag_diff ≈ zeros(2, 1) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_socp_abs_atom(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    a = 5-4im
    x = ComplexVariable()
    objective = abs(a-x)
    c1 = real(x)>=0
    p = minimize(objective,c1; numeric_type = T)

    handle_problem!(p)
    if test
        @test p.optval ≈ 0 atol=atol rtol=rtol
        @test evaluate(objective) ≈ 0.0 atol=atol rtol=rtol

        real_diff = real(evaluate(x)) .- real(a)
        imag_diff = imag(evaluate(x)) .- imag(a)
        @test real_diff ≈ 0.0 atol=atol rtol=rtol
        @test imag_diff ≈ 0.0 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_Complex_Semidefinite_constraint(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 10
    A = rand(n,n) + im*rand(n,n)
    A = A + A' # now A is hermitian
    x = ComplexVariable(n,n)
    objective = sumsquares(A - x)
    c1 = x in :SDP
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)
    # test that X is approximately equal to posA:
    l,v = eigen(A)
    posA = v*Diagonal(max.(l,0))*v'

    if test
        real_diff = real.(evaluate(x)) - real.(posA)
        imag_diff = imag.(evaluate(x)) - imag.(posA)
        @test real_diff ≈ zeros(n, n) atol=atol rtol=rtol
        @test imag_diff ≈ zeros(n, n) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_argcheck(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DimensionMismatch GeomMeanHypoCone(   zeros(2,3),    zeros(2,3), 1//2)
        @test_throws DimensionMismatch GeomMeanHypoCone(   zeros(2,3), Variable(2,3), 1//2)
        @test_throws DimensionMismatch GeomMeanHypoCone(Variable(2,3),    zeros(2,3), 1//2)
        @test_throws DimensionMismatch GeomMeanHypoCone(Variable(2,3), Variable(2,3), 1//2)

        @test_throws DimensionMismatch GeomMeanHypoCone(   zeros(2,2),    zeros(3,3), 1//2)
        @test_throws DimensionMismatch GeomMeanHypoCone(   zeros(2,2), Variable(3,3), 1//2)
        @test_throws DimensionMismatch GeomMeanHypoCone(Variable(2,2),    zeros(3,3), 1//2)
        @test_throws DimensionMismatch GeomMeanHypoCone(Variable(2,2), Variable(2,3), 1//2)

        @test_throws DimensionMismatch Variable(2,2) in GeomMeanHypoCone(Variable(3,3), Variable(3,3), 1//2)

        @test_throws DomainError GeomMeanHypoCone(Variable(3,3), Variable(3,3), -1//2)
        @test_throws DomainError GeomMeanHypoCone(Variable(3,3), Variable(3,3), 3//2)
    end
end

@add_problem sdp function sdp_geom_mean_epicone_argcheck(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DimensionMismatch GeomMeanEpiCone(   zeros(2,3),    zeros(2,3), -1//2)
        @test_throws DimensionMismatch GeomMeanEpiCone(   zeros(2,3), Variable(2,3), -1//2)
        @test_throws DimensionMismatch GeomMeanEpiCone(Variable(2,3),    zeros(2,3), -1//2)
        @test_throws DimensionMismatch GeomMeanEpiCone(Variable(2,3), Variable(2,3), -1//2)

        @test_throws DimensionMismatch GeomMeanEpiCone(   zeros(2,2),    zeros(3,3), -1//2)
        @test_throws DimensionMismatch GeomMeanEpiCone(   zeros(2,2), Variable(3,3), -1//2)
        @test_throws DimensionMismatch GeomMeanEpiCone(Variable(2,2),    zeros(3,3), -1//2)
        @test_throws DimensionMismatch GeomMeanEpiCone(Variable(2,2), Variable(2,3), -1//2)

        @test_throws DimensionMismatch Variable(2,2) in GeomMeanEpiCone(Variable(3,3), Variable(3,3), -1//2)
        @test_throws DomainError GeomMeanEpiCone(Variable(3,3), Variable(3,3), -3//2)
        @test_throws DomainError GeomMeanEpiCone(Variable(3,3), Variable(3,3), 1//2)
        @test_throws DomainError GeomMeanEpiCone(Variable(3,3), Variable(3,3), 5//2)
    end
end

@add_problem sdp function sdp_relative_entropy_argcheck(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(2,3),    zeros(2,3), 3, 3, eye(2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(2,3), Variable(2,3), 3, 3, eye(2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(2,3),    zeros(2,3), 3, 3, eye(2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(2,3), Variable(2,3), 3, 3, eye(2))

        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(2,2),    zeros(3,3), 3, 3)
        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(2,2), Variable(3,3), 3, 3)
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(2,2),    zeros(3,3), 3, 3)
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(2,2), Variable(2,3), 3, 3)

        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(3,3),    zeros(3,3), 3, 3, zeros(2, 2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(3,3), Variable(3,3), 3, 3, zeros(2, 2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(3,3),    zeros(3,3), 3, 3, zeros(2, 2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(3,3), Variable(2,3), 3, 3, zeros(2, 2))

        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(3,3),    zeros(3,3), 3, 3, zeros(2, 3))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(3,3), Variable(3,3), 3, 3, zeros(2, 3))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(3,3),    zeros(3,3), 3, 3, zeros(2, 3))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(3,3), Variable(2,3), 3, 3, zeros(2, 3))

        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(3,3),    zeros(3,3), 3, 3, zeros(2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(   zeros(3,3), Variable(3,3), 3, 3, zeros(2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(3,3),    zeros(3,3), 3, 3, zeros(2))
        @test_throws DimensionMismatch RelativeEntropyEpiCone(Variable(3,3), Variable(2,3), 3, 3, zeros(2))

        @test_throws DimensionMismatch Variable(2,2) in RelativeEntropyEpiCone(Variable(3,3), Variable(3,3), 3, 3)
    end
end

@add_problem sdp function sdp_relative_entropy(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    for cplx in [false, true]
        n = 4
        X = eye(n)
        if cplx
            Y = ComplexVariable(n,n)
        else
            Y = Variable(n,n)
        end

        c1 = eye(n) in RelativeEntropyEpiCone(X, Y)
        objective = real(tr(Y))
        p = minimize(objective, c1; numeric_type = T)

        handle_problem!(p)

        if test
            @test Y.value ≈ eye(n)*exp(-1) atol=atol rtol=rtol
        end
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_real_0(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = Variable(n,n)
    B = randn(n,n)
    B = B * B' # now A is positive semidefinite
    B += 0.2 * I # prevent numerical instability

    c1 = eye(n) in GeomMeanHypoCone(A, B, 0)
    objective = tr(A)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    if test
        @test A.value ≈ eye(n) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_real_1(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = Variable(n,n)
    B = randn(n,n)
    B = B * B' # now A is positive semidefinite
    B += 0.2 * I # prevent numerical instability

    c1 = eye(n) in GeomMeanHypoCone(B, A, 1)
    objective = tr(A)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    if test
        @test A.value ≈ eye(n) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_real_1_2(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = Variable(n,n)

    c1 = eye(n) in GeomMeanHypoCone(A, B, 1//2)
    objective = tr(B)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    if test
        @test B.value ≈ A^-1 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_real_3_8(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = Variable(n,n)

    c1 = eye(n) in GeomMeanHypoCone(A, B, 3//8)
    objective = tr(B)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B.value ≈ A^(-5//3) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_real_3_5(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = Variable(n,n)

    c1 = eye(n) in GeomMeanHypoCone(A, B, 3//5)
    objective = tr(B)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B.value ≈ A^(-2//3) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_cplx_1_2(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = randn(ComplexF64, n, n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = ComplexVariable(n,n)

    c1 = eye(n) in GeomMeanHypoCone(A, B, 1//2)
    objective = real(tr(B))
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A^-1) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A^-1) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_cplx_3_8(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = randn(ComplexF64, n, n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = ComplexVariable(n,n)

    c1 = eye(n) in GeomMeanHypoCone(A, B, 3//8)
    objective = real(tr(B))
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test real.(B.value) ≈ real.(A^(-5//3)) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A^(-5//3)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_cplx_3_5(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 4
    A = randn(ComplexF64, n, n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = ComplexVariable(n,n)

    c1 = eye(n) in GeomMeanHypoCone(A, B, 3//5)
    objective = real(tr(B))
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test real.(B.value) ≈ real.(A^(-2//3)) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A^(-2//3)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_hypocone_fullhyp(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # These matrices satisfy A^(1/2) ⪰ B and so (A,eye(2),B) \in hyp_{1/2}
    # A and B however do not satisfy A ⪰ B^2 and so [A B; B eye(2)] not
    # psd, and using fullhyp=false will give an infeasible SDP.
    A = [6.25 0; 0 16]
    B = [2 1; 1 2]
    S = Semidefinite(2)

    p = minimize(0, [B in GeomMeanHypoCone(A, eye(2), 1//2, false)]; numeric_type = T)
    handle_problem!(p)
    if test
        @test p.status == MOI.INFEASIBLE
    end

    p = minimize(0, [B in GeomMeanHypoCone(A, eye(2), 1//2)]; numeric_type = T)
    handle_problem!(p)
    if test
        @test p.status == MOI.OPTIMAL
    end
end

@add_problem sdp function sdp_geom_mean_epicone_real_neg1_optA(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    B = randn(n,n)
    B = B * B' # now B is positive semidefinite
    B += 0.2 * I # prevent numerical instability
    A = Variable(n,n)

    c1 = eye(n) in GeomMeanEpiCone(A, B, -1)
    objective = tr(A)
    p = maximize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B ≈ A.value^2 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_epicone_real_neg1_optB(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    A /= tr(A) # solver has problems if B is large
    B = Variable(n,n)

    c1 = eye(n) in GeomMeanEpiCone(A, B, -1)
    objective = tr(B)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B.value ≈ A^2 atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_epicone_real_neg3_5_optA(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    B = randn(n,n)
    B = B * B' # now B is positive semidefinite
    B += 0.2 * I # prevent numerical instability
    A = Variable(n,n)

    c1 = eye(n) in GeomMeanEpiCone(A, B, -3//5)
    objective = tr(A)
    p = maximize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B ≈ A.value^(8//3) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_epicone_real_neg3_5_optB(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    A /= tr(A) # solver has problems if B is large
    B = Variable(n,n)

    c1 = eye(n) in GeomMeanEpiCone(A, B, -3//5)
    objective = tr(B)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B.value ≈ A^(8//3) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_epicone_real_8_5_optA(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    B = randn(n,n)
    B = B * B' # now B is positive semidefinite
    B += 0.2 * I # prevent numerical instability
    B /= tr(B) # solver has problems if B is large
    A = Variable(n,n)

    c1 = eye(n) in GeomMeanEpiCone(A, B, 8//5)
    objective = tr(A)
    p = minimize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B ≈ A.value^(3//8) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_geom_mean_epicone_real_8_5_optB(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    B = Variable(n,n)

    c1 = eye(n) in GeomMeanEpiCone(A, B, 8//5)
    objective = tr(B)
    p = maximize(objective, c1; numeric_type = T)

    handle_problem!(p)

    # A #_t B = I  =>  B = A^(1-1/t)
    if test
        @test B.value ≈ A^(3//8) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_quantum_relative_entropy_argcheck(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DimensionMismatch quantum_relative_entropy(   zeros(2,3),    zeros(2,3))
        @test_throws DimensionMismatch quantum_relative_entropy(   zeros(2,3), Variable(2,3))
        @test_throws DimensionMismatch quantum_relative_entropy(Variable(2,3),    zeros(2,3))
        @test_throws DimensionMismatch quantum_relative_entropy(Variable(2,3), Variable(2,3))

        @test_throws DimensionMismatch quantum_relative_entropy(   zeros(2,2),    zeros(3,3))
        @test_throws DimensionMismatch quantum_relative_entropy(   zeros(2,2), Variable(3,3))
        @test_throws DimensionMismatch quantum_relative_entropy(Variable(2,2),    zeros(3,3))
        @test_throws DimensionMismatch quantum_relative_entropy(Variable(2,2), Variable(2,3))

        z = zeros(2,2)
        v = Variable(2,2)
        nh = [ 1 1 ; 0 1 ] # not hermitian
        np = [ 1 2 ; 2 1 ] # not positive semidefinite
        @test_throws DomainError quantum_relative_entropy(z, nh)
        @test_throws DomainError quantum_relative_entropy(z, np)
        @test_throws DomainError quantum_relative_entropy(nh, z)
        @test_throws DomainError quantum_relative_entropy(np, z)
        @test_throws DomainError quantum_relative_entropy(v, nh)
        @test_throws DomainError quantum_relative_entropy(v, np)
        @test_throws DomainError quantum_relative_entropy(nh, v)
        @test_throws DomainError quantum_relative_entropy(np, v)
    end
end

function sdp_quantum_relative_entropy_impl(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}, lowrank::Bool, mode::Integer) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    X = randn(ComplexF64, n, n)
    if lowrank
        X[:,1] .= 0
    end
    X = X * X' # now X is positive semidefinite
    X /= tr(X) # make it a quantum state

    A = ComplexVariable(n,n)
    B = ComplexVariable(n,n)
    constraints = Array{Constraint, 1}([
        A == X,
    ])
    if mode != 5
        push!(constraints, tr(B) == 1)
    end

    if mode == 1
        objective = quantum_relative_entropy(A, B)
    elseif mode == 2
        objective = quantum_relative_entropy(X, B)
    elseif mode == 3
        objective = quantum_relative_entropy(B, A)
    elseif mode == 4
        objective = quantum_relative_entropy(B, X)
    elseif mode == 5
        objective = quantum_relative_entropy(X, X)
    end

    p = minimize(objective, constraints; numeric_type = T)
    handle_problem!(p)

    if test
        @test real.(evaluate(A)) ≈ real.(X) atol=atol rtol=rtol
        @test imag.(evaluate(A)) ≈ imag.(X) atol=atol rtol=rtol
        if mode != 5
            @test real.(evaluate(B)) ≈ real.(X) atol=atol rtol=rtol
            @test imag.(evaluate(B)) ≈ imag.(X) atol=atol rtol=rtol
        end
        @test p.optval ≈ 0 atol=atol rtol=rtol
        if mode == 1
            @test p.optval ≈ evaluate(quantum_relative_entropy(A, B)) atol=atol rtol=rtol
        elseif mode == 2
            @test p.optval ≈ evaluate(quantum_relative_entropy(X, B)) atol=atol rtol=rtol
        elseif mode == 3
            @test p.optval ≈ evaluate(quantum_relative_entropy(B, A)) atol=atol rtol=rtol
        elseif mode == 4
            @test p.optval ≈ evaluate(quantum_relative_entropy(B, X)) atol=atol rtol=rtol
        elseif mode == 5
            @test p.optval ≈ evaluate(quantum_relative_entropy(X, X)) atol=atol rtol=rtol
        end
    end
end

@add_problem sdp function sdp_quantum_relative_entropy1_fullrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, false, 1)
end

@add_problem sdp function sdp_quantum_relative_entropy2_fullrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, false, 2)
end

@add_problem sdp function sdp_quantum_relative_entropy3_fullrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, false, 3)
end

@add_problem sdp function sdp_quantum_relative_entropy4_fullrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, false, 4)
end

@add_problem sdp function sdp_quantum_relative_entropy5_fullrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, false, 5)
end

@add_problem sdp function sdp_quantum_relative_entropy1_lowrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, true, 1)
end

@add_problem sdp function sdp_quantum_relative_entropy2_lowrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, true, 2)
end

@add_problem sdp function sdp_quantum_relative_entropy3_lowrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, true, 3)
end

@add_problem sdp function sdp_quantum_relative_entropy4_lowrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, true, 4)
end

@add_problem sdp function sdp_quantum_relative_entropy5_lowrank(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    sdp_quantum_relative_entropy_impl(handle_problem!, Val(test), atol, rtol, T, true, 5)
end

@add_problem sdp function sdp_quantum_relative_entropy_const(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test quantum_relative_entropy(diagm(0=>[0.5, 0.5]), diagm(0=>[0.5, 0.5])) ≈ 0 atol=atol rtol=rtol
        @test quantum_relative_entropy(diagm(0=>[1.0, 0.0]), diagm(0=>[1.0, 0.0])) ≈ 0 atol=atol rtol=rtol
        @test quantum_relative_entropy(diagm(0=>[1.0, 0.0]), diagm(0=>[0.5, 0.5])) ≈ log(2) atol=atol rtol=rtol
        @test isinf(quantum_relative_entropy(diagm(0=>[0.5, 0.5]), diagm(0=>[1.0, 0.0])))
    end
end

@add_problem sdp function sdp_quantum_channel_capacity(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Capacity of classical-quantum channel.  See chapter 3 of arxiv:1705.06671,
    # Fawzi, Fawzi "Efficient optimization of the quantum relative entropy".
    # For channels with two symbols, both pure states, we have an exact formula to compare against.

    n = 3

    for cplx in [false, true]
        if cplx
            ψ1 = normalize(randn(ComplexF64, n))
            ψ2 = normalize(randn(ComplexF64, n))
        else
            ψ1 = normalize(randn(Float64, n))
            ψ2 = normalize(randn(Float64, n))
        end
        ρ1 = ψ1 * ψ1'
        ρ2 = ψ2 * ψ2'

        p1 = Variable()
        p2 = Variable()

        objective = (quantum_entropy(p1*ρ1 + p2*ρ2) - p1*quantum_entropy(ρ1) - p2*quantum_entropy(ρ2)) / log(2)
        constraints = [ p1 >= 0, p2 >= 0, p1+p2 == 1 ]
        p = maximize(objective, constraints; numeric_type = T)
        handle_problem!(p)

        ϵ = abs(ψ1' * ψ2)
        q = (1 + ϵ)/2
        r = [q, 1-q]
        v = -r' * log2.(r)

        if test
            @test p.optval ≈ v atol=atol rtol=rtol
            @test evaluate(objective) ≈ v atol=atol rtol=rtol

            @test_throws DimensionMismatch quantum_entropy(Variable(2, 3))
        end
    end
end

@add_problem sdp function sdp_trace_mpower_argcheck(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DimensionMismatch trace_mpower(   zeros(2,3), 1//2, zeros(2,3))
        @test_throws DimensionMismatch trace_mpower(Variable(2,3), 1//2, zeros(2,3))

        @test_throws DimensionMismatch trace_mpower(   zeros(2,2), 1//2, zeros(3,3))
        @test_throws DimensionMismatch trace_mpower(Variable(2,2), 1//2, zeros(3,3))

        @test_throws DomainError trace_mpower(Variable(3,3),  5//2, zeros(3,3))

        nh = [ 1 1 ; 0 1 ] # not hermitian
        np = [ 1 2 ; 2 1 ] # not positive semidefinite
        @test_throws DomainError trace_mpower(Variable(2,2), 1//2, nh)
        @test_throws DomainError trace_mpower(Variable(2,2), 1//2, np)
    end
end

@add_problem sdp function sdp_trace_mpower_real_2_3(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    C = randn(n,n)
    C = C * C' # now C is positive semidefinite
    t = 2//3

    B = Semidefinite(n,n)

    constraints = [
        B ⪯ A
    ]
    objective = trace_mpower(B, t, C)
    p = maximize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*A^t) atol=atol rtol=rtol
        @test p.optval ≈ trace_mpower(A, t, C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_mpower(B, t, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_mpower_cplx_2_3(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    A = randn(ComplexF64, n, n)
    A = A * A' # now A is positive semidefinite
    C = randn(ComplexF64, n, n)
    C = C * C' # now C is positive semidefinite
    t = 2//3

    B = HermitianSemidefinite(n,n)

    constraints = [
        B ⪯ A
    ]
    objective = trace_mpower(B, t, C)
    p = maximize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*A^t) atol=atol rtol=rtol
        @test p.optval ≈ trace_mpower(A, t, C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_mpower(B, t, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_mpower_real_5_4(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    C = randn(n,n)
    C = C * C' # now C is positive semidefinite
    t = 5//4

    B = Semidefinite(n,n)

    constraints = [
        # note: B ⪰ A doesn't work for this unit test because trace_mpower is not operator monotone
        # unless t ∈ [0,1].
        B == A
    ]
    objective = trace_mpower(B, t, C)
    p = minimize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*A^t) atol=atol rtol=rtol
        @test p.optval ≈ trace_mpower(A, t, C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_mpower(B, t, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_mpower_cplx_5_4(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    A = randn(ComplexF64, n, n)
    A = A * A' # now A is positive semidefinite
    C = randn(ComplexF64, n, n)
    C = C * C' # now C is positive semidefinite
    t = 5//4

    B = HermitianSemidefinite(n,n)

    constraints = [
        # note: B ⪰ A doesn't work for this unit test because trace_mpower is not operator monotone
        # unless t ∈ [0,1].
        B == A
    ]
    objective = trace_mpower(B, t, C)
    p = minimize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*A^t) atol=atol rtol=rtol
        @test p.optval ≈ trace_mpower(A, t, C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_mpower(B, t, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_mpower_real_neg1_4(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    A = randn(n,n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    C = randn(n,n)
    C = C * C' # now C is positive semidefinite
    t = -1//4

    B = Semidefinite(n,n)

    constraints = [
        # -trace_mpower is operator monotone for t ∈ [-1, 0].
        B ⪯ A
    ]
    objective = trace_mpower(B, t, C)
    p = minimize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*A^t) atol=atol rtol=rtol
        @test p.optval ≈ trace_mpower(A, t, C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_mpower(B, t, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_mpower_cplx_neg1_4(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    # Too slow for n>3.  Anything that can be done to speed it up?
    n = 3
    A = randn(ComplexF64, n, n)
    A = A * A' # now A is positive semidefinite
    A += 0.2 * I # prevent numerical instability
    C = randn(ComplexF64, n, n)
    C = C * C' # now C is positive semidefinite
    t = -1//4

    B = HermitianSemidefinite(n,n)

    constraints = [
        # -trace_mpower is operator monotone for t ∈ [-1, 0].
        B ⪯ A
    ]
    objective = trace_mpower(B, t, C)
    p = minimize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(B.value) ≈ real.(A) atol=atol rtol=rtol
        @test imag.(B.value) ≈ imag.(A) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*A^t) atol=atol rtol=rtol
        @test p.optval ≈ trace_mpower(A, t, C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_mpower(B, t, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_logm_argcheck(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DimensionMismatch trace_logm(   zeros(2,3), zeros(2,3))
        @test_throws DimensionMismatch trace_logm(Variable(2,3), zeros(2,3))

        @test_throws DimensionMismatch trace_logm(   zeros(2,2), zeros(3,3))
        @test_throws DimensionMismatch trace_logm(Variable(2,2), zeros(3,3))

        nh = [ 1 1 ; 0 1 ] # not hermitian
        np = [ 1 2 ; 2 1 ] # not positive semidefinite
        @test_throws DomainError trace_logm(Variable(2,2), nh)
        @test_throws DomainError trace_logm(Variable(2,2), np)
    end
end

@add_problem sdp function sdp_trace_logm_real(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    C = randn(Float64, n, n)
    C = C * C' # now C is positive semidefinite

    X = Variable(n,n)

    constraints = [
        X ⪯ eye(n)
    ]
    objective = trace_logm(X, C)
    p = maximize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(X.value) ≈ real.(eye(n)) atol=atol rtol=rtol
        @test imag.(X.value) ≈ imag.(eye(n)) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*log(evaluate(X))) atol=atol rtol=rtol
        @test p.optval ≈ trace_logm(evaluate(X), C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_logm(X, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_trace_logm_cplx(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    n = 3
    C = randn(ComplexF64, n, n)
    C = C * C' # now C is positive semidefinite

    X = ComplexVariable(n,n)

    constraints = [
        X ⪯ eye(n)
    ]
    objective = trace_logm(X, C)
    p = maximize(objective, constraints; numeric_type = T)

    handle_problem!(p)

    if test
        @test real.(X.value) ≈ real.(eye(n)) atol=atol rtol=rtol
        @test imag.(X.value) ≈ imag.(eye(n)) atol=atol rtol=rtol
        @test p.optval ≈ tr(C*log(evaluate(X))) atol=atol rtol=rtol
        @test p.optval ≈ trace_logm(evaluate(X), C) atol=atol rtol=rtol
        @test p.optval ≈ evaluate(trace_logm(X, C)) atol=atol rtol=rtol
    end
end

@add_problem sdp function sdp_lieb_ando(handle_problem!, ::Val{test}, atol, rtol, ::Type{T}) where {T, test}
    if test
        @test_throws DomainError lieb_ando(   zeros(3,3),    zeros(3,3), eye(3), -3//2)
        @test_throws DomainError lieb_ando(   zeros(3,3),    zeros(3,3), eye(3), 5//2)
        @test_throws DomainError lieb_ando(   zeros(3,3), Variable(3,3), eye(3), -3//2)
        @test_throws DomainError lieb_ando(   zeros(3,3), Variable(3,3), eye(3), 5//2)
        @test_throws DomainError lieb_ando(Variable(3,3),    zeros(3,3), eye(3), -3//2)
        @test_throws DomainError lieb_ando(Variable(3,3),    zeros(3,3), eye(3), 5//2)
        @test_throws DomainError lieb_ando(Variable(3,3), Variable(3,3), eye(3), -3//2)
        @test_throws DomainError lieb_ando(Variable(3,3), Variable(3,3), eye(3), 5//2)
    end

    for n in [2, 3]
        for t in [1//2, 1//4, 3//4, 1//8, 3//2, 5//4]
            for cplx in [false, true]
                #@show n,t,cplx

                if cplx
                    A = randn(ComplexF64, n, n)
                    B = randn(ComplexF64, n, n)
                    X = ComplexVariable(n, n)
                    Y = ComplexVariable(n, n)
                else
                    A = randn(n, n)
                    B = randn(n, n)
                    X = Variable(n, n)
                    Y = Variable(n, n)
                end
                A = A*A'
                B = B*B'
                A = A/tr(A)
                B = B/tr(B)

                QtAB = lieb_ando(A,B,eye(n),t)

                objective = lieb_ando(X,B,eye(n),t)
                if t >= 0 && t <= 1
                    p = maximize(objective, [X == A]; numeric_type = T)
                else
                    p = minimize(objective, [X == A]; numeric_type = T)
                end
                handle_problem!(p)
                if test
                    @test p.optval ≈ QtAB atol=atol*5 rtol=rtol
                end

                objective = lieb_ando(A,Y,eye(n),t)
                if t >= 0 && t <= 1
                    p = maximize(objective, [Y == B]; numeric_type = T)
                else
                    p = minimize(objective, [Y == B]; numeric_type = T)
                end
                handle_problem!(p)
                if test
                    @test p.optval ≈ QtAB atol=atol*5 rtol=rtol
                end

                objective = lieb_ando(X,Y,eye(n),t)
                if t >= 0 && t <= 1
                    p = maximize(objective, [X == A, Y == B]; numeric_type = T)
                else
                    p = minimize(objective, [X == A, Y == B]; numeric_type = T)
                end
                handle_problem!(p)
                if test
                    @test p.optval ≈ QtAB atol=atol*5 rtol=rtol
                end
            end
        end
    end
end


function checkord(A::Matrix{Ty}, tol=10) where {Ty<:Complex}
    n = size(A,1)
    ulp = eps(real(Ty))

    S = schur(A)
    T2 = copy(S.T)
    Z2 = copy(S.Z)
    select = fill(true,n)
    for i=1:(n>>1)
        select[i] = false
    end
    T2,Z2,v = invoke(LinearAlgebra._ordschur!,
           Tuple{StridedMatrix{T}, StridedMatrix{T},
                 Union{Vector{Bool},BitVector}} where T <: Complex,
        T2,Z2,select)

    # usual tests for Schur
    @test all(tril(T2,-1) .== 0)
    @test norm(Z2*T2*Z2' - A) / (n * norm(A) * ulp) < tol
    @test norm(I - Z2 * Z2') / (n * ulp) < tol

    # make sure we got the ones we asked for
    nwanted = count(select)
    wwanted = [S.values[j] for j=1:n if select[j]]
    errs = zeros(nwanted)
    for i=1:nwanted
        w = T2[i,i]
        errs[i] = minimum(abs.(wwanted .- w)) / (ulp + abs(w))
    end
    @test all(errs .< tol)
end

using LinearAlgebra.LAPACK: trsen!, BlasInt

function checkecond(A::Matrix{T}, nsub=3) where T
    n = size(A,1)
    select = fill(false,n)
    inds = rand(1:n,nsub)
    for i in inds
        select[i] = true
    end
    S = schur(A)

    @assert real(T) <: BlasFloat
    T1 = copy(S.T)
    Z1 = copy(S.Z)
    fselect = BlasInt.([x ? 1 : 0 for x in select])
    T1,Z1,w1,s1,sep1 = trsen!('B','V',fselect,T1,Z1)

    S2 = ordschur(S, select)
    s2 = eigvalscond(S2, count(select))
    sep2 = subspacesep(S2, count(select))

    @test s1 ≈ s2
    @test sep1 ≈ sep2

end

for Ty in [Complex{Float64}]
    @testset "ordschur $Ty" begin
        n = 32
        A = rand(Ty,n,n)
        checkord(A)
    end
    @testset "subspace condition $Ty" begin
        n = 32
        A = rand(Ty,n,n)
        checkecond(A)
    end
end

function checkord(A::Matrix{Ty}, tol=10) where {Ty<:Real}
    n = size(A,1)
    ulp = eps(real(Ty))

    S = schur(A)
    v = S.values
    # cases to try: 2 reals, 1 complex pair, 1 real & 1 pair, 2 pairs
    nr = [2,0,1,0]
    ni = [0,2,2,4]
    for icase in 1:4
        T2 = copy(S.T)
        Z2 = copy(S.Z)
        select = fill(false,n)
        i = n+1
        nr_needed = nr[icase]
        ni_needed = ni[icase]
        while (nr_needed + ni_needed > 0) && i>2
            i -= 1
            if nr_needed > 0
                if imag(v[i]) == 0
                    select[i] = true
                    nr_needed -= 1
                end
            end
            if ni_needed > 0
                ivi = imag(v[i])
                if (abs(ivi) > 1000ulp) && (imag(v[i-1]) == -ivi)
                    select[i] = true
                    select[i-1] = true
                    ni_needed -= 2
                    i -= 1
                end
            end
        end
        if nr_needed + ni_needed > 0
            @warn "failed to find acceptable subset (class $icase) for checking ordschur"
            continue
        end
        T2,Z2,v = invoke(LinearAlgebra._ordschur!,
                         Tuple{StridedMatrix{T}, StridedMatrix{T},
                               Union{Vector{Bool},BitVector}} where T <: Real,
                         T2,Z2,select)

        # usual tests for Schur
        @test all(tril(T2,-2) .== 0)
        @test norm(Z2*T2*Z2' - A) / (n * norm(A) * ulp) < tol
        @test norm(I - Z2 * Z2') / (n * ulp) < tol

        # make sure we got the ones we asked for
        nwanted = count(select)
        wwanted = [S.values[j] for j=1:n if select[j]]
        errs = zeros(nwanted)
        for i=1:nwanted
            w = T2[i,i]
            errs[i] = minimum(abs.(wwanted .- w)) / (ulp + abs(w))
        end
        @test all(errs .< tol)
    end
end

for Ty in [Float64, BigFloat]
    @testset "ordschur $Ty" begin
        # should be big enough to be nearly certain of meaningful selection
        n = 20
        A = rand(Ty,n,n)
        checkord(A)
    end
end


export GradientBoost

function GradientBoost(prob::Problem{L, B, CS},
                       rate::Real,
                       subsolver::AbstractNextBasisSolver=defaultsolver(prob);
                       ad=false,
                       maxiter::Int=300) where {L, F, B<:AbBasis{F}, CS}
    ens = GBEnsemble(prob)
    enses = BBEnsemble{F,B}[]
    iter = 0
    currloss = trainerr(ens)
    # llog(txt="") = printfmtln("{:3d}) {:.6f} {:.2f}({}) {}", iter, currloss, sum(abs, ens.β), countnz(ens.β), txt)
    llog(t="") = nothing
    llog()

    for iter in 1:maxiter
        prevloss = currloss
        objtv, grad = lossngrad(prob, ens.fit)
        ω, ρ, score = getnextω(prob, subsolver, grad)
        βi = linesearch(prob, ens.fit, ω)
        βi *= rate
        push!(ens, ω, βi)
        currloss = trainerr(ens)

        if ad
            ens, currloss = backpropωs(prob, ens)
            push!(enses, BBEnsemble(ens))
            llog("ad {ω}")
        end

        abs(1-currloss/prevloss) < 1e-5 && (println("loss converged"); break)
        abs(ens.β[end]) < abs(ens.β[1])*1e-4 && (println("β converged"); break)
        llog(format("{:.2f}%", 100*abs(ens.β[end]) / abs(ens.β[1])))
    end

    llog("\nEND")
    ad ? enses : ens
end


function getInterpolator(A::AbstractArray{T,3}, grid::RegularGridPositions) where T

  tmp = ( size(A,1)==1 ? NoInterp() : BSpline(Linear()),
          size(A,2)==1 ? NoInterp() : BSpline(Linear()),
          size(A,3)==1 ? NoInterp() : BSpline(Linear()) )

  itp = extrapolate(interpolate(A, tmp), 0.0)
  sitp = scale(itp, range(grid,1), range(grid,2), range(grid,3))
  return sitp
end

function interpolate(A::AbstractArray{T,3}, origin::RegularGridPositions,
                        target::RegularGridPositions) where T

  sitp = getInterpolator(A, origin)
  N = target.shape
  AInterp = zeros(eltype(A), N[1], N[2], N[3])
  rx, ry, rz = range(target,1), range(target,2), range(target,3)

  return _interpolate_inner(AInterp,N,sitp,rx,ry,rz)
end

function _interpolate_inner(AInterp,N,sitp,rx,ry,rz)
  for nz=1:N[3]
    for ny=1:N[2]
      for nx=1:N[1]
        AInterp[nx,ny,nz] = sitp(rx[nx],ry[ny],rz[nz])
      end
    end
  end

  return AInterp
end


"""
    SeisSWCGP(in,operator,param,iter,lp,imax)

Robust Stagewise weak Orthognal Matching Pursuit algorithm with the coefficient updated by the conjugate gradient pursuit

# Arguments
- `dn`: Noisy data
- `operator`: Any operator passed the dot test
- `param`: parameters for the operator
- `iter`: Max iterations for the matching pursuit
- `a`: parameter alpha used to calcualte the threshold value
- `lp`: lp-norm space inner product
- `imax`: Max interation for the lp-norm inner product

"""
function SeisSWCGP(dn,operator,param,iter,a,lp,imax)
    m=operator(dn,param,-1,lp,imax);
    r=dn
    n=1
    ind_r=[];
    ind_c=[];
    v=[];
    d=[];
    dr=zeros(size(dn));
    for i=1:iter
        g=zeros(size(m))
        gn=operator(r,param,-1,lp,imax)
        thres=a*maximum(abs.(vec(gn)));
        ind=findall(abs.(gn).>= thres)
        ind_ri=getindex.(ind, 1)
        ind_ci=getindex.(ind, 2)
        push!(ind_r,ind_ri)
        push!(ind_c,ind_ci)
        for j=length(ind_r)
            g[ind_r[j],ind_c[j]]=gn[ind_r[j],ind_c[j]]
        end
        if n==1
            d=g;
            v=operator(d,param,1,lp,imax);
        else
            w=operator(g,param,1,lp,imax)
            u=-LP_norm(vec(v),vec(w),lp,imax,1)
            d=g+u*d
            v=w+u*v
        end
        beta=LP_norm(vec(v),vec(r),lp,imax,1)
        dr=dr+beta*v
        r=r-beta*v
        n=n+1
    end
    return dr
end


#-------------------------------------------------------------------
#* EMSO Model Library (EML) Copyright (C) 2004 - 2007 ALSOC.
#*
#* This LIBRARY is free software; you can distribute it and/or modify
#* it under the therms of the ALSOC FREE LICENSE as available at
#* http://www.enq.ufrgs.br/alsoc.
#*
#* EMSO Copyright (C) 2004 - 2007 ALSOC, original code
#* from http://www.rps.eng.br Copyright (C) 2002-2004.
#* All rights reserved.
#*
#* EMSO is distributed under the therms of the ALSOC LICENSE as
#* available at http://www.enq.ufrgs.br/alsoc.
#*--------------------------------------------------------------------
#* Author: Gerson Balbueno Bicca
#* $Id$
#*--------------------------------------------------------------------
type LMTD_Basic
	LMTD_Basic()=begin
		new(
			temp_delta(Dict{Symbol,Any}(
				:Brief=>"Temperature Difference at Inlet",
				:Lower=>1e-6,
				:Symbol=>"\\Delta T_0",
				:Protected=>true
			)),
			temp_delta(Dict{Symbol,Any}(
				:Brief=>"Temperature Difference at Outlet",
				:Lower=>1e-6,
				:Symbol=>"\\Delta T_L",
				:Protected=>true
			)),
			temp_delta(Dict{Symbol,Any}(
				:Brief=>"Logarithmic Mean Temperature Difference",
				:Lower=>1e-6,
				:Protected=>true
			)),
			positive(Dict{Symbol,Any}(
				:Brief=>"LMTD Correction Factor",
				:Lower=>0.1,
				:Protected=>true
			)),
			[
				:(LMTD = 0.5*(DT0+DTL)),
				:(LMTD= (DT0-DTL)/ln(DT0/DTL)),
				:(LMTD = 0.5*(DT0+DTL)*(1-(DT0-DTL)^2/(DT0*DTL)*(1+(DT0-DTL)^2/(DT0*DTL)/2)/12)),
			],
			[
				"Log Mean Temperature Difference","Log Mean Temperature Difference","Log Mean Temperature Difference",
			],
			[:DT0,:DTL,:LMTD,:Fc,]
		)
	end
	DT0::temp_delta
	DTL::temp_delta
	LMTD::temp_delta
	Fc::positive
	equations::Array{Expr,1}
	equationNames::Array{String,1}
	variables::Array{Symbol,1}
	attributes::Dict{Symbol,Any}
end
export LMTD_Basic
function setEquationFlow(in::LMTD_Basic)
	if DT0*DTL <= 0 * "K^2"
		addEquation(1)
	else
		if abs(DT0 - DTL) > 0.05*max(abs([DT0,DTL]))
			addEquation(2)
		else
			addEquation(3)
		end
	end
end
function atributes(in::LMTD_Basic,_::Dict{Symbol,Any})
	fields::Dict{Symbol,Any}=Dict{Symbol,Any}()
	fields[:Pallete]=false
	fields[:Brief]="Log Mean Temperature Difference Method."
	fields[:Info]="This model should be used as submodel when the LMTD needs to be calculating"
	drive!(fields,_)
	return fields
end
LMTD_Basic(_::Dict{Symbol,Any})=begin
	newModel=LMTD_Basic()
	newModel.attributes=atributes(newModel,_)
	newModel
end