export bissect_secA
function bissect_secA(h :: AbstractNLPModel;
                     t1 :: Float64 = h.meta.x0[1],
                     t2 :: Float64 = 100.0,
                     tol :: Float64 = 1e-7,
                     maxiter :: Int = 50,
                     verbose :: Bool = false)

    (length(h.meta.x0) > 1) && warn("Not a 1-D problem ")
      (t0, t1) = trouve_intervalle(h, t1, 2.5)
      γ = 0.8; t = t1; tp = t0; tqnp = t0
      hk = 0.0; hkm1 = 0.0; gkm1 = 0.0; hplus = 0.0
      iter = 0

      hk = obj(h, [t])[1]; gk = grad(h, [t])[1]
      hkm1 = obj(h, [tqnp])[1]; gkm1 = grad(h, [tqnp])[1]

      verbose &&
        @printf(" iter        tp        t         dN         gk          ")
      verbose &&
        @printf(" gplus        \n")
      verbose &&
        @printf(" %7.2e %7.2e %7.2e  %7.2e  %7.2e  %7.2e\n",
                  iter, tp, t, 0.0, gk, 0.0)

      while ((abs(gk) > tol) && (iter < maxiter)) || (iter == 0)
          s = t - tqnp
          y = gk - gkm1

          Γ = 3 * (gk + gkm1) * s - 6 * (hk - hkm1)
          if y * s + Γ < eps(Float64) * (s^2)
            yt = y
          else
            yt = y + Γ / s
          end
          dN = -gk * s / yt

          if ((tp - t) * dN > 0) && (dN / (tp - t) < γ)
            tplus = t + dN
            hplus = obj(h, [tplus])[1]
            gplus = grad(h, [tplus])[1]
            verbose && println("N")
          else
            tplus = (t + tp) / 2
            hplus = obj(h, [tplus])[1]
            gplus = grad(h, [tplus])[1]
            verbose && println("B")
          end

          if t > tp
            if gplus < 0.0
              tp = t; tqnp = t; t = tplus
            else
              tqnp = t; t = tplus
            end
          else
            if gplus > 0.0
              tp = t; tqnp = t; t = tplus
            else
              tqnp = t; t = tplus
            end
          end

          #mise à jour des valeurs
          hkm1 = hk; gkm1 = gk; hk = hplus; gk = gplus
          iter = iter + 1

          verbose &&
            @printf(" %7.2e %7.2e %7.2e  %7.2e  %7.2e  %7.2e\n",
                    iter, tp, t, dN, gk, gplus)
        end

      topt = t
      status = :NotSolved
      (abs(gk) < tol) && (status = :Optimal)
      (iter >= maxiter) && (status = :Tired)
      tired = iter > maxiter
      optimal = abs(gk) < tol
      return (topt, obj(h, [topt])[1], norm(gk, Inf), iter, optimal, tired, status, h.counters.neval_obj, h.counters.neval_grad, h.counters.neval_hess)
end


module geometry

export chain, triangle, rectangle, square, hexagonal, box, cube, ring, excitation_phases, lhc1, lhc1_p

"""
    geometry.chain(a, N)

Positions of spins on a chain in x-direction.

The chain starts at the origin and continues into positive x-direction.

# Arguments
* `a`: Spin-spin distance.
* `N`: Number of spins
"""
chain(a::T, N::Int) where T<:Real = [[i*a, zero(T), zero(T)] for i=0:N-1]

"""
    geometry.triangle(a)

Positions of spins on a equilateral triangle in the xy-plane with edge length `a`.

The positions are: (0,0,0), (a,0,0), (a/2, h, 0)
"""
triangle(a::T) where T<:Real = Vector{float(T)}[[0, 0, 0], [a, 0, 0], [a/2, a*sqrt(3)/2, 0]]

"""
    geometry.rectangle(a, b; Nx=2, Ny=2)

Positions of spins on a rectangular lattice in the xy-plane.

The lattice starts at the origin and continues into positive x and y direction.

# Arguments
* `a`: Spin-spin distance in x-direction.
* `b`: Spin-spin distance in y-direction.
* `Nx=2`: Number of spins into x direction.
* `Ny=2`: Number of spins into y direction.
"""
rectangle(a::S, b::T; Nx::Int=2, Ny::Int=2) where {S<:Real,T<:Real} = vec([[i*a,zero(S),zero(S)]+[zero(T),j*b,zero(T)] for i=0:Nx-1, j=0:Ny-1])

"""
    geometry.square(a; Nx=2, Ny=2)

Positions of spins on a square lattice in the xy-plane with distance `a`.

The lattice starts at the origin and continues into positive x and y direction.
"""
square(a::Real; Nx::Int=2, Ny::Int=2) = rectangle(a, a; Nx=Nx, Ny=Ny)

"""
    geometry.hexagonal(a; Nr=1)

Positions of spins on a hexagonal lattice in the xy-plane.

The hexagonal lattice consists of `Nr` rings.
"""
function hexagonal(a::T; Nr::Int=1) where T<:Real
    positions = Vector{float(T)}[]
    ax = sqrt(3.0/4)*a
    for ix=-Nr:Nr
        if isodd(ix)
            Ny = div(2*Nr+1-abs(ix),2)
            for iy=-Ny:Ny-1
                push!(positions, [ax*ix, (0.5+iy)*a, 0])
            end
        else
            Ny = div(2*Nr-abs(ix),2)
            for iy=-Ny:Ny
                push!(positions, [ax*ix, iy*a, 0])
            end
        end
    end
    return positions
end

"""
    geometry.box(a, b, c; Nx=2, Ny=2, Nz=2)

Positions of spins on a orthorhombic lattice.

The lattice starts at the origin and continues into
positive x, y and z direction.
"""
box(a::S, b::T, c::U; Nx::Int=2, Ny::Int=2, Nz::Int=2) where {S<:Real,T<:Real,U<:Real}= vec([[i*a,zero(S),zero(S)] + [zero(T),j*b,zero(T)] + [zero(U),zero(U),k*c] for i=0:Nx-1, j=0:Ny-1, k=0:Nz-1])

"""
    geometry.cube(a; Nx=2, Ny=2, Nz=2)

Positions of spins on a cubic lattice with edge length `a`.

The lattice starts at the origin and continues into
positive x, y and z direction.
"""
cube(a::Real; Nx::Int=2, Ny::Int=2, Nz::Int=2) = box(a, a, a, Nx=Nx, Ny=Ny, Nz=Nz)

"""
    geometry.ring(a, N; distance = false)

Ring of N particles with radius a.
If distance is set to true, then `a` gives the distance between particles
and the radius is determined accordingly.
"""
function ring(a::Real, N::Int; distance = false)
    x = distance ? 0.5a/sin(pi/N) : a
    return [[x*cos(2*pi*j/N), x*sin(2*pi*j/N), 0] for j=0:(N-1)]
end

"""

    geometry.ring_rd(a, N)

    Calculate the radius of a ring when the distance between particles is a.

"""
ring_rd(a::Real, N::Int) = 0.5*a/sin(pi/N)

"""
    geometry.ring_p_tangential(N)

    Return tangential polarization vectors for a ring of N atoms in the xy-plane created by ring(a, N).

    Arguments:
    * `N`: Number of emitters.
"""
ring_p_tangential(N::Int) = [[-sin(2*pi*j/N), cos(2*pi*j/N), 0.] for j=0:(N-1)]

"""
    geometry.excitation_phases(k, pos)

    Calculate the excitation phases created in a geometry by an incident plane wave with wave vector k.

    Arguments:
    * `k`: Wave vector of the incident plane wave.
    * `pos`: List of atomic positions.
"""
function excitation_phases(k::Vector{T}, pos::Vector) where T
    @assert length(k) == 3
    k_ = normalize(k)

    PHI = T[]

    for p=pos
        @assert length(p) == 3
        phi = (dot(k_, p) * 2*pi) % (2*pi)
        push!(PHI, phi)
    end

    return PHI
end

"""

    geometry.lhc1()

    Geometry of the LHC-I light harvesting complex in the xy-plane centered around `z=0` with a mean interatomic distance of `1.0`. Positions `1` to `32` constitute the outer ring, while the inner ring comprises positions `33` to `38`.
"""
function lhc1()
    R = [[4.84223, -1.36377, 0.0],[5.10879, -0.398266, 0.0214459],[4.99561, 0.593013, 0.0],[4.87235, 1.58711, 0.0214459],[4.3883, 2.45968, 0.0],[3.89406, 3.33084, 0.0214459],[3.11302, 3.95179, 0.0],[2.32309, 4.56744, 0.0214459],[1.36377, 4.84223, 0.0],[0.398266, 5.10879, 0.0214459],[-0.593013, 4.99561, 0.0],[-1.58711, 4.87235, 0.0214459],[-2.45968, 4.3883, 0.0],[-3.33084, 3.89406, 0.0214459],[-3.95179, 3.11302, 0.0],[-4.56744, 2.32309, 0.0214459],[-4.84223, 1.36377, 0.0],[-5.10879, 0.398266, 0.0214459],[-4.99561, -0.593013, 0.0],[-4.87235, -1.58711, 0.0214459],[-4.3883, -2.45968, 0.0],[-3.89406, -3.33084, 0.0214459],[-3.11302, -3.95179, 0.0],[-2.32309, -4.56744, 0.0214459],[-1.36377, -4.84223, 0.0],[-0.398266, -5.10879, 0.0214459],[0.593013, -4.99561, 0.0],[1.58711, -4.87235, 0.0214459],[2.45968, -4.3883, 0.0],[3.33084, -3.89406, 0.0214459],[3.95179, -3.11302, 0.0],[4.56744, -2.32309, 0.0214459],[0.359274, 0.249228, -0.403357],[-0.36848, -0.221933, -0.376279],[-0.308908, 1.14151, -0.119144],[0.364906, -1.07327, 0.0079068],[0.108506, 1.35747, 1.00961],[0.0429294, -1.1805, 1.16267]]

    return R
end

"""

    geometry.lhc1_p()

    Normalized polarization vectors in an LHC-I light harvesting complex. Entries `1` to `32` pertain to the outer ring, while entries `33` to `38` give the inner ring's dipole orientations.

    Note: This function does not return a geometry, butrather te polarizations of dipoles in the LHC-I complex.

"""
function lhc1_p()
    p = [[0.634, 0.76, 0.147],[0.452, 0.886, 0.098],[0.295, 0.944, 0.147],[0.079, 0.992, 0.098],[-0.089, 0.985, 0.147],[-0.307, 0.947, 0.098],[-0.459, 0.876, 0.147],[-0.646, 0.757, 0.098],[-0.76, 0.634, 0.147],[-0.886, 0.452, 0.098],[-0.944, 0.295, 0.147],[-0.992, 0.079, 0.098],[-0.985, -0.089, 0.147],[-0.947, -0.307, 0.098],[-0.876, -0.459, 0.147],[-0.757, -0.646, 0.098],[-0.634, -0.76, 0.147],[-0.452, -0.886, 0.098],[-0.295, -0.944, 0.147],[-0.079, -0.992, 0.098],[0.089, -0.985, 0.147],[0.307, -0.947, 0.098],[0.459, -0.876, 0.147],[0.646, -0.757, 0.098],[0.76, -0.634, 0.147],[0.886, -0.452, 0.098],[0.944, -0.295, 0.147],[0.992, -0.079, 0.098],[0.985, 0.089, 0.147],[0.947, 0.307, 0.098],[0.876, 0.459, 0.147],[0.757, 0.646, 0.098],[0.371, 0.871, -0.321],[-0.379, -0.86, -0.342],[-0.3, -0.87, -0.391],[0.208, 0.855, -0.476],[0.53, 0.019, 0.848],[-0.367, -0.053, 0.929]]

    return p
end
end # module


# integration segment (a,b), estimated integral I, and estimated error E
struct Segment{TX,TI,TE}
    a::TX
    b::TX
    I::TI
    E::TE
end
Base.@pure Base.promote_rule(::Type{Segment{TX,TI,TE}}, ::Type{Segment{TX′,TI′,TE′}}) where {TX,TI,TE,TX′,TI′,TE′} =
    Segment{promote_type(TX,TX′), promote_type(TI,TI′), promote_type(TE,TE′)}
Base.convert(::Type{T}, s::Segment) where {T<:Segment} = T(s.a,s.b,s.I,s.E)
Base.isless(i::Segment, j::Segment) = isless(i.E, j.E)

# Internal routine: approximately integrate f(x) over the interval (a,b)
# by evaluating the integration rule (x,w,gw). Return a Segment.
function evalrule(f, a,b, x,w,gw, nrm)
    # Ik and Ig are integrals via Kronrod and Gauss rules, respectively
    s = convert(eltype(x), 0.5) * (b-a)
    n1 = 1 - (length(x) & 1) # 0 if even order, 1 if odd order
    # unroll first iterationof loop to get correct type of Ik and Ig
    fg = f(a + (1+x[2])*s) + f(a + (1-x[2])*s)
    fk = f(a + (1+x[1])*s) + f(a + (1-x[1])*s)
    Ig = fg * gw[1]
    Ik = fg * w[2] + fk * w[1]
    for i = 2:length(gw)-n1
        fg = f(a + (1+x[2i])*s) + f(a + (1-x[2i])*s)
        fk = f(a + (1+x[2i-1])*s) + f(a + (1-x[2i-1])*s)
        Ig += fg * gw[i]
        Ik += fg * w[2i] + fk * w[2i-1]
    end
    if n1 == 0 # even: Gauss rule does not include x == 0
        Ik += f(a + s) * w[end]
    else # odd: don't count x==0 twice in Gauss rule
        f0 = f(a + s)
        Ig += f0 * gw[end]
        Ik += f0 * w[end] +
            (f(a + (1+x[end-1])*s) + f(a + (1-x[end-1])*s)) * w[end-1]
    end
    Ik_s, Ig_s = Ik * s, Ig * s # new variable since this may change the type
    E = nrm(Ik_s - Ig_s)
    if isnan(E) || isinf(E)
        throw(DomainError(a+s, "integrand produced $E in the interval ($a, $b)"))
    end
    return Segment(oftype(s, a), oftype(s, b), Ik_s, E)
end

# compute result = f(x1) + f(x2) in-place
function eval2x!(result, f::InplaceIntegrand{F}, x1, x2) where {F}
    f.f!(result, x1)
    f.f!(f.fx, x2)
    result .+= f.fx
end

# as above, but call assume a mutable result type (e.g. an array) and
# act in-place using `f!(result, x)`.
function evalrule(f::InplaceIntegrand{F}, a,b, x,w,gw, nrm) where {F}
    # Ik and Ig are integrals via Kronrod and Gauss rules, respectively
    s = convert(eltype(x), 0.5) * (b-a)
    n1 = 1 - (length(x) & 1) # 0 if even order, 1 if odd order
    fg, fk, Ig, Ik = f.fg, f.fk, f.Ig, f.Ik # pre-allocated temporary arrays
    # unroll first iterationof loop to get correct type of Ik and Ig
    eval2x!(fg, f, a + (1+x[2])*s, a + (1-x[2])*s)
    eval2x!(fk, f, a + (1+x[1])*s, a + (1-x[1])*s)
    Ig .= fg .* gw[1]
    Ik .= fg .* w[2] .+ fk .* w[1]
    for i = 2:length(gw)-n1
        eval2x!(fg, f, a + (1+x[2i])*s, a + (1-x[2i])*s)
        eval2x!(fk, f, a + (1+x[2i-1])*s, a + (1-x[2i-1])*s)
        Ig .+= fg .* gw[i]
        Ik .+= fg .* w[2i] .+ fk .* w[2i-1]
    end
    f.f!(f.fx, a + s)
    if n1 == 0 # even: Gauss rule does not include x == 0
        Ik .+= f.fx .* w[end]
    else # odd: don't count x==0 twice in Gauss rule
        Ig .+= f.fx .* gw[end]
        f.f!(f.fg, a + (1+x[end-1])*s)
        f.f!(f.fk, a + (1-x[end-1])*s)
        Ik .+= f.fx .* w[end] .+ (f.fg + f.fk) .* w[end-1]
    end
    Ik_s = Ik * s # new variable since this may change the type
    f.Idiff .= Ik_s .- Ig .* s
    E = nrm(f.Idiff)
    if isnan(E) || isinf(E)
        throw(DomainError(a+s, "integrand produced $E in the interval ($a, $b)"))
    end
    return Segment(oftype(s, a), oftype(s, b), Ik_s, E)
end


# verify examples from README.md run
using Interpolations, Test

@testset "Readme Examples" begin

    ## Bsplines
    a = randn(6)
    A = randn(5, 5)

    # Nearest-neighbor interpolation
    itp = interpolate(a, BSpline(Constant()))
    v = itp(5.4)   # returns a[5]
    @test v ≈ a[5]

    # (Multi)linear interpolation
    itp = interpolate(A, BSpline(Linear()))
    v = itp(3.2, 4.1)  # returns 0.9*(0.8*A[3,4]+0.2*A[4,4]) + 0.1*(0.8*A[3,5]+0.2*A[4,5])
    @test v ≈ (0.9*(0.8*A[3,4]+0.2*A[4,4]) + 0.1*(0.8*A[3,5]+0.2*A[4,5]))

    # Quadratic interpolation with reflecting boundary conditions
    # Quadratic is the lowest order that has continuous gradient
    itp = interpolate(A, BSpline(Quadratic(Reflect(OnCell()))))

    # Linear interpolation in the first dimension, and no interpolation (just lookup) in the second
    itp = interpolate(A, (BSpline(Linear()), NoInterp()))
    v = itp(3.65, 5)  # returns  0.35*A[3,5] + 0.65*A[4,5]
    @test v ≈ (0.35*A[3,5] + 0.65*A[4,5])

    ## Scaled Bsplines
    A_x = 1.:2.:40.
    A = [log(x) for x in A_x]
    itp = interpolate(A, BSpline(Cubic(Line(OnGrid()))))
    sitp = scale(itp, A_x)
    @test sitp(3.) ≈ log(3.) # exactly log(3.)
    @test sitp(3.5) ≈ log(3.5) atol=.1 # approximately log(3.5)

    # For multidimensional uniformly spaced grids
    A_x1 = 1:.1:10
    A_x2 = 1:.5:20
    f(x1, x2) = log(x1+x2)
    A = [f(x1,x2) for x1 in A_x1, x2 in A_x2]
    itp = interpolate(A, BSpline(Cubic(Line(OnGrid()))))
    sitp = scale(itp, A_x1, A_x2)
    @test sitp(5., 10.) ≈ log(5 + 10) # exactly log(5 + 10)
    @test sitp(5.6, 7.1) ≈ log(5.6 + 7.1) atol=.1 # approximately log(5.6 + 7.1)

    ## Gridded interpolation
    A = rand(8,20)
    knots = ([x^2 for x = 1:8], [0.2y for y = 1:20])
    itp = interpolate(knots, A, Gridded(Linear()))
    @test itp(4,1.2) ≈ A[2,6] atol=.1  # approximately A[2,6]

    ## extrapolate
    itp = interpolate(1:7, BSpline(Linear()))
    etpf = extrapolate(itp, Flat())
    @test etpf(0) == 1
    @test etpf(1) == 1
    @test etpf(7) == 7
    @test etpf(7.8) == 7
    etp0 = extrapolate(itp, 0)
    @test etp0(0) === 0.0
    @test etp0(1) === 1.0
    @test etp0(7) === 7.0
    @test etp0(7.8) === 0.0

    f(x) = log(x)
    xs = 1:0.2:5
    A = [f(x) for x in xs]
    extrap = LinearInterpolation(xs, A, extrapolation_bc = Line())
    @test extrap(1 - 0.2) ≈ f(1) - (f(1.2) - f(1))
    @test extrap(5 + 0.2) ≈ f(5) + (f(5) - f(4.8))
    extrap = LinearInterpolation(xs, A, extrapolation_bc = NaN)
    @test isnan(extrap(5.2))

end


using LogFixPoint16s, Lorenz63, PyPlot

LogFixPoint16s.set_nfrac(10)

Δt = 0.005
N = 200000

XYZ0 = L63(Float64;N,Δt)
XYZ1 = L63(Float16;N,Δt)
XYZ2 = L63(LogFixPoint16;N,Δt)

## PLOTTING
pygui(true)
fig,(ax1,ax2,ax3) = subplots(1,3,sharex=true,sharey=true,figsize=(10,3.5))

ax1.plot(XYZ0[1,:],XYZ0[3,:],"k",lw=0.05)
ax2.plot(XYZ1[1,:],XYZ1[3,:],"k",lw=0.05)
ax3.plot(XYZ2[1,:],XYZ2[3,:],"k",lw=0.05)

ax1.set_xticks([])
ax1.set_yticks([])
ax1.set_xlabel("x")
ax2.set_xlabel("x")
ax3.set_xlabel("x")
ax1.set_ylabel("y")

ax1.set_title("Float64",loc="left")
ax2.set_title("Float16",loc="left")
ax3.set_title("LogFixPoint16",loc="left")
ax1.set_title("a",loc="right",fontweight="bold")
ax2.set_title("b",loc="right",fontweight="bold")
ax3.set_title("c",loc="right",fontweight="bold")

tight_layout()
savefig("lorenz_attractor.png",dpi=200)


using PyPlot, transD_GP, Statistics, LinearAlgebra

## model
ρbg        = 100
zfixed     = [-1e5,   0,   (20:20:200)...]
rho        = [1e12,   ρbg, ρbg*ones(10)...]
ianom      = 5
rho[ianom] = 10.
δ          = 1e-4  # fd step size in log10

## Gauss Newton settings
use_fd          = false # in the one step for Gauss Newton
startfromcloser = false  # whether to start midway between anomaly and bg or plain bg in G-N step
λ²              = 1e-11 # model update damping

##  geometry and modeling parameters
rRx = 13.
zRx = -42.0
zTx = -40.0
freqlow = 1e-3
include("electronics_halt.jl")
calcjacobian = true

Flm = transD_GP.AEM_VMD_HMD.HFieldDHT(
                      lowpassfcs = lowpassfcs,
                      nmax   = length(zfixed),
                      times  = LM_times,
                      ramp   = LM_ramp,
                      zTx    = zTx,
                      rRx    = rRx,
                      rTx    = rTx,
                      zRx    = zRx,
                      freqlow = freqlow,
                      calcjacobian = calcjacobian,
                      )
transD_GP.AEM_VMD_HMD.getfieldTD!(Flm, zfixed, rho)

## plot HM
f, ax = plt.subplots(1,3, sharey=true, figsize=(8,6))
delz, delt = diff(zfixed[2:end]), diff(log10.(Flm.times))
tlabel = [(log10.(Flm.times) .- delt[1]/2)..., log10.(Flm.times)[end]+delt[end]/2]
zlab = [zfixed[2:end]..., zfixed[end]+delz[end]]
ax[1].step(log10.(rho[2:end]), zfixed[2:end])
im2 = ax[2].pcolormesh(tlabel, zlab, Flm.dBzdt_J[2:end,:], shading="auto")
cb2 = colorbar(im2, ax=ax[2])
im3 = ax[3].pcolormesh(tlabel, zlab, log10.(abs.(Flm.dBzdt_J[2:end,:])))
cb2 = colorbar(im3, ax=ax[3])
ax[1].set_xlabel("log10 Ω-m")
ax[1].invert_xaxis()
ax[2].invert_yaxis()
ax[2].set_xlabel("log10 s")
ax[3].set_xlabel("log10 s")
ax[1].set_ylabel("Depth m")
ax[2].set_title("∂f/∂(log10σ)")
ax[3].set_title("∂f/∂(log10σ)")

## approximate partials with central differences and compare
J = copy(Flm.dBzdt_J)

function getJ_TD!(Fin, zin, ρ, Δ)
    # sensitivities computed in log10σ
    # same as in AEM_VMD_HMD code
    Jout = similar(Fin.dBzdt_J)
    modelfd = copy(ρ)
    jflag = Fin.calcjacobian
    Fin.calcjacobian = false
    for i = 2:length(zin)
        modelfd[:] = ρ
        modelfd[i] = 10 .^-(-log10(ρ[i]) .+ Δ/2)
        transD_GP.AEM_VMD_HMD.getfieldTD!(Fin, zin, modelfd)
        HTD_z1 = copy(Fin.dBzdt)
        modelfd[i] = 10 .^-(-log10(ρ[i]) .- Δ/2)
        transD_GP.AEM_VMD_HMD.getfieldTD!(Fin, zin, modelfd)
        Jout[i,:] = (HTD_z1 - Fin.dBzdt) / Δ
    end
    Fin.calcjacobian = jflag
    Jout
end
J_fd = getJ_TD!(Flm, zfixed, rho, δ)

## comparison plots
f, ax = plt.subplots(1,3, sharey=true, figsize=(10,6))
ax[1].step(log10.(rho[2:end]), zfixed[2:end])
im2 = ax[2].pcolormesh(tlabel, zlab, J[2:end,:])
cb2 = colorbar(im2, ax=ax[2])
im3 = ax[3].pcolormesh(tlabel, zlab, J_fd[2:end,:])
cb2 = colorbar(im3, ax=ax[3])
ax[1].set_xlabel("log10 Ω-m")
ax[1].invert_xaxis()
ax[2].invert_yaxis()
ax[2].set_xlabel("log10 s")
ax[3].set_xlabel("log10 s")
ax[1].set_ylabel("Depth m")
ax[2].set_title("∂f/∂(log10σ) analytic")
ax[3].set_title("∂f/∂(log10σ) FD")

## let's try this in an optimisation framework to see if the gradient makes sense
log₁₀σ₀ = -2
mtrue = copy(rho)
m = copy(mtrue)
m[ianom] = startfromcloser ? ρbg+(rho[ianom]-ρbg)/2 : ρbg
transD_GP.AEM_VMD_HMD.getfieldTD!(Flm, zfixed, mtrue)
d = copy(Flm.dBzdt)
transD_GP.AEM_VMD_HMD.getfieldTD!(Flm, zfixed, m)
f = copy(Flm.dBzdt)
r = (f-d)
J = use_fd ? getJ_td!(Flm, zfixed, m, δ)[2:end,:] : Flm.dBzdt_J[2:end,:]
Δm = -real((J*J' + λ²*I)\(J*r + λ²*I*(-log10.(m[2:end]) .- log₁₀σ₀)))
m2 = vcat(mtrue[1], 10 .^-(-log10.(m[2:end]) + Δm))

## plot everything
figure()
step(log10.(m[2:end]), zfixed[2:end], linewidth=2, label="start model")
step(log10.(m2[2:end]), zfixed[2:end], label="1 step")
step(log10.(mtrue[2:end]), zfixed[2:end], linestyle="--", color="k", label="true model")
gca().invert_yaxis()
gca().invert_xaxis()
legend()
xlabel("log10 ρ")
ylabel("Depth m")


export AbstractParameter, Parameter, value

abstract type AbstractParameter{T} end
const AP{T} = AbstractParameter{T}

# Sometime when setup our models, we require it's parameters to follow
# certain constrains, e.g. the support for scaling factor `σ` & length scale `l`
# of a GP kernel should be ℜ⁺, however, adding those constrains make the optimization
# process less convenient. `Parameter` is used to handle this, it's bijector is used
# to map constrained variable to unconstrained one, which is used during the optimization,
# and the inverse of the bijector does the reverse, which will provide us with the constrained
# variables we need.
struct Parameter{T, N, fT<:Bijector{N}} <: AP{T}
    x::AbstractArray{T}
    f::fT

    function Parameter{T}(y, f::fT) where {T<:Real, fT}
        N = ndims(y)
        x = inv(f)(y)
        if N == 0
            return new{T, N, fT}(T[x], f)
        else
            return new{T, N, fT}(x, f)
        end
    end
end

Parameter(y, f) = Parameter{eltype(y)}(y, f)

# interface
value(y::Parameter{T, 0}) where {T} = y.f(first(y.x))
value(y::Parameter) = y.f(y.x)

# if no constrain is added, it will use `Identity` bijector by default
Parameter(y) = Parameter(y, Identity{ndims(y)}())

# if we want positivity constrain, then use `Exp` bijector
Parameter(y, ::Val{:pos}) = Parameter(y, Bijectors.Exp{ndims(y)}())


#=
AdvancedPrim:
- Julia version:
- Author: samuel
- Date: 2021-09-10
=#

using SuiteSparseGraphBLAS
using SparseArrays
using LinearAlgebra

function stoppingCondition(m, n)

infinito = true

for i = 1:n
        if(m[i] == 0.0)
            return false
    end
end
    return infinito
end

#A is the input matrix, n is the number of nodes, m is the source matrix. Output: minimum spanning tree and minimum spanning tree cost
function a_prim(A, n, m)

    d = GBVector{Float64}(n)

    weight = 0.0

    d = A[1,:]
    mst = GBVector{Float64}(n) #minimum spanning tree

    print("INITIAL WEIGHT: ")
    print(weight)
    print("\n\n")
    while(stoppingCondition(m, n) == false)

        u = argmin(m'+d)
        m[u[2]] = Inf
        push!(mst, d[u[2]])
        weight = weight + d[u[2]]
        print("WEIGHT: ")
        print(weight)
        d = emul(d, A[u[1],:],  BinaryOps.MIN)
        print("  ITERATION FINISHED")
        print("\n\n\n")
    end

    print("COMPLETED")
    print(" ALL THE NODES HAVE BEEN PROCESSED (INF)")
    print("\n\n\n")
    print(m')
    print("\n\n\n")
    print("MINIMUM SPANNING TREE EDGES")
    print("\n\n\n")
    print(mst)
    return weight, mst

end


## Julia program for Box-Muller algorithm
## author: weiya <szcfweiya@gmail.com>
## date: 2018-08-19

function boxmuller()
    x = ones(2)
    u = rand(2)
    logu = sqrt(-2*log(u[1]))
    x[1] = logu * cos(2*pi*u[2])
    x[2] = logu * sin(2*pi*u[2])
    return(x)
end

## example
boxmuller()


"""
    contract(A, idxA, B, idxB)

contract the symmetric tensor A, B. The to-be-contracted
indexes are shown with negative integers while the remaining
indexes for the result are shown with positive integers. For
example if there are l negative numbers in both A and B (should
be numbered from -1 to -l) then the rest of the indexes in A and B
combined should be 1:N+M-2n.
"""
function contract(A     :: AbstractSymTensor{S,T1,N},
                  idxA  :: NTuple{N, Int},
                  B     :: AbstractSymTensor{S,T2,M},
                  idxB  :: NTuple{M, Int}) where{S,T1,T2, N, M}

    remsA, consA, tofinalsA = _contract_index_perm(idxA)
    remsB, consB, tofinalsB = _contract_index_perm(idxB)

    ## TODO: techniqually here the contraction vector space should be made
    ## once! But now is made twice, so can we do better!

    # TODO: check
    # compatibility println(remsA, consA, remsB, consB)
    if length(remsA) == 0
        _A = SymVector(A, consA, false)
        if length(remsB) == 0
            _B = SymVector(B, consB, true)
            return _A * _B
        else
            _B = SymMatrix(B, consB, remsB)
            return permutelegs(splitleg(_A*_B, 2, B.legs[remsB]),
                               invperm(tofinalsB))
        end
    end

    _A = SymMatrix(A, remsA, consA)

    if length(remsB) == 0
        _B = SymVector(B, consB, true)
        return permutelegs(splitleg(_A*_B, 1, A.legs[remsA]),
                           invperm(tofinalsA))
    end

    _B = SymMatrix(B, consB, remsB)
    return permutelegs(SymTensor(_A * _B, A.space[remsA], B.space[remsB]),
                       invperm([tofinalsA; tofinalsB]))
end

function contract(A     :: AbstractArray{T1,N},
                  idxA  :: NTuple{N, Int},
                  B     :: AbstractArray{T2,M},
                  idxB  :: NTuple{M, Int}) where{S,T1,T2, N, M}
    remsA, consA, tofinalsA = _contract_index_perm(idxA)
    remsB, consB, tofinalsB = _contract_index_perm(idxB)
    permA = [remsA; consA]
    permB = [consB; remsB]

    if length(remsA) == 0
        _A = reshape(permutedims(A, permA), prod(size(A)[permA]))
        if length(remsB) == 0
            _B = reshape(permutedims(B, permB), prod(size(B)[permB]))
            return sum(_A .* _B)
        else
            _B = reshape(permutedims(B, permB), prod(size(B)[consB]), prod(size(B)[remsB]))
            return permutedims(reshape(transpose(_A)*_B, size(B)[remsB]...),
                               invperm(tofinalsB))
        end
    end

    _A = reshape(permutedims(A, permA), prod(size(A)[remsA]), prod(size(A)[consA]))
    if length(remsB) == 0
        _B = reshape(permutedims(B, permB), prod(size(B)[permB]))
        return permutedims(reshape(_A*_B, size(A)[remsA]...),
                           invperm(tofinalsA))
    end
    _B = reshape(permutedims(B, permB), prod(size(B)[consB]), prod(size(B)[remsB]))
    permutedims(reshape(_A*_B, size(A)[remsA]..., size(B)[remsB]...),
                invperm([tofinalsA; tofinalsB]))
end

# auxillary function to find indexes for contract
function _contract_index_perm(indexset; mode::Symbol=:MINUS)
    rems = Int[]
    cons = Int[]
    cons_order = Int[]
    tofinals = Int[]
    for i in eachindex(indexset)
        idx = indexset[i]
        if idx < 0
            push!(cons, i)
            push!(cons_order, idx)
        else
            push!(rems, i)
            push!(tofinals, idx)
        end
    end
    perm = sortperm(tofinals)
    rems[perm], cons[sortperm(cons_order, by=x->abs(x))], tofinals[perm]
end

function SymVector(A::AbstractSymTensor,
                   perm::Vector{Int},
                   _dual::Bool)
    N = rank(A)
    sort(perm) == collect(1:N) ||
        error("Incorrect index set for conversion to SymVector!")

    pA = permutelegs(A, perm)
    fuselegs(pA, 1, N, _dual)
end

function SymMatrix(A::AbstractSymTensor,
                   rowidxs::Vector{Int},
                   colidxs::Vector{Int})

    N = rank(A)
    idxperm = [rowidxs; colidxs]
    sort(idxperm) == collect(1:N) ||
        error("Incorrect index set for conversion to SymMatrix!")
    length(rowidxs) == 0 && error("empty row for matrix not allowed!")
    length(colidxs) == 0 && error("empty col for matrix not allowed!")
    n = length(rowidxs)

    T = eltype(A)
    S = vtype(A)

    rowdualinfo = isdual.(A.space[rowidxs])
    coldualinfo = isdual.(A.space[colidxs])
    csects = [Sector(x[idxperm]) for x in sectors(A)]
    sperm1 = sortperm(csects)
    fsects = Vector{Sector{S, 2}}(undef, length(csects))
    for i in eachindex(csects)
        sector = csects[sperm1][i]
        c1 = sum(Sector(sector[1:n]), rowdualinfo)
        c2 = inv(sum(Sector(sector[n+1:N]), coldualinfo))
        fsects[i] = Sector(c1, c2)
    end
    sperm2 = sortperm(fsects)
    semits = collect(onlysemitokens(A.blocks))[sperm1][sperm2]

    space = (fuse(false, A.space[rowidxs]), fuse(true, A.space[colidxs]))

    sects, sizes = _allsectorsandsizes(A.charge, space)

    blocks = SortedDict{Sector{S, 2}, Matrix{T}}()
    pointer = 1
    for index in 1:length(sects)
        blk = Matrix{T}(undef, sizes[index])

        rlim, clim = sizes[index]
        pc = 1
        while pc <= clim
            pr = 1
            fdc = 0
            while pr <= rlim
                Ablk = permutedims(A.blocks[semits[pointer]], idxperm)
                s = size(Ablk)
                fdr = prod(s[1:n])
                fdc = prod(s[n+1:N])
                blk[pr:pr+fdr-1, pc:pc+fdc-1] =
                    reshape(Ablk, fdr, fdc)
                pr += fdr
                pointer += 1
            end
            pc += fdc
        end
        sector = sects[index]
        blocks[sector] = blk
    end
    SymMatrix{S,T}(A.charge, space, blocks)
end

function SymMatrix(A::Array,
                   rowidxs::Vector{Int},
                   colidxs::Vector{Int})

    N = ndims(A)
    idxperm = [rowidxs; colidxs]
    sort(idxperm) == collect(1:N) ||
        error("Incorrect index set for conversion to SymMatrix!")
    length(rowidxs) == 0 && error("empty row for matrix not allowed!")
    length(colidxs) == 0 && error("empty col for matrix not allowed!")
    T = eltype(A)
    reshape(permutedims(A, idxperm), prod(size(A)[rowidxs]), prod(size(A)[colidxs]))
end

function SymTensor(A     :: SymMatrix,
                   rlegs :: NTuple{N, VectorSpace{S}},
                   clegs :: NTuple{M, VectorSpace{S}}) where {S, N, M}
    T = eltype(A)

    blocks = SortedDict{Sector{S, N+M}, Array{T, N+M}}()
    csects = sectors(A)

    rpats, rsizes = Vector{Sector{S, M}}(), Vector{NTuple{M, Int}}()
    cpats, csizes = Vector{Sector{S, M}}(), Vector{NTuple{M, Int}}()
    isduals = isdual.(A.space)
    for (s,blk) in A.blocks
        c1, c2 = s.charges
        c1 = isdual(A.space[1]) ? inv(c1) : c1
        c2 = isdual(A.space[2]) ? inv(c2) : c2
        rpats, rsizes = _allsectorsandsizes(c1, rlegs)
        cpats, csizes = _allsectorsandsizes(c2, clegs)

        pc = 0
        for cpatidx in eachindex(cpats)
            csl = prod(csizes[cpatidx])
            pr = 0
            for rpatidx in eachindex(rpats)
                rsl = prod(rsizes[rpatidx])
                blocks[Sector(rpats[rpatidx]..., cpats[cpatidx]...)] =
                    reshape(blk[pr+1:pr+rsl, pc+1:pc+csl],
                            rsizes[rpatidx]..., csizes[cpatidx]...)
                pr += rsl
            end
            pc += csl
        end
    end

    #TODO: now check to see if the new legs can fuse into the original leg
    space = (rlegs...,clegs...)

    SymTensor{S,T,N+M}(A.charge, space, blocks)
end

function *(A::SymVector, B::SymVector)
    isdual(A.space[1], B.space[1]) ||
        error("not contractible!", A.space[1], " and ", B.space[1])

    semits = collect(onlysemitokens(A.blocks))
    return sum(A.blocks[semits[1]] .* B.blocks[semits[1]])
end

function *(A::AbstractSymMatrix,
           B::AbstractSymMatrix)

    isdual(A.space[2], B.space[1]) ||
        error("not contractible! ", A.space[2], " and ", B.space[1])

    S = vtype(A)
    T = promote_type(eltype(A), eltype(B))
    T == Union{} &&
        error("can't promote_type! $(eltype(A)) , $(eltype(B))")
    ## NOTE: assume the set of charges for the vector space to be
    ## contracted is {c1,..,ci,...,cn} then each sector in A is given
    ## by (CA+ci, -ci) and each sector in B by (ci, -ci+CB) and since
    ## both are sorted based on the last charge in sector, then they
    ## are sorted the same. So we simply need to multiply them here
    ## but take into account that it is possible for the matrices to
    ## have different number of sectors allowed due to the other
    ## vector space they map from/to.

    ## NOTE: After contraction it is possible that some new sectors
    ## (that weren't allowed with the contraction indexes) are now
    ## allowed. So they have to be manually added and initialized to
    ## zero. I am actually not very happy with this; originally I
    ## decided to not include these new sectors at all but then
    ## realized at some instances the existance of them is necessary,
    ## well they are allowed sectors anyways, right! Still don't know
    ## what is the best approach.

    ## TODO: Also it would be nice if there is the option of dropping
    ## the charges that only show up in complete zero sectors! This is
    ## particularly useful is Gutzwiller projection thing

    charge = A.charge + B.charge
    space = (A.space[1], B.space[2])

    sects, sizes = _allsectorsandsizes(charge, space)
    n_sectors = length(sects)
    blocks = SortedDict{Sector{S, 2}, Matrix{T}}()

    semitsA = collect(onlysemitokens(A.blocks))
    semitsB = collect(onlysemitokens(B.blocks))
    bmax = length(semitsB)
    amax = length(semitsA)
    a, b = 1, 1

    for i=1:n_sectors
        c1, c2 = sects[i]
        _c1 = isdual(A.space[1]) ? inv(c1) : c1
        _c2 = isdual(B.space[2]) ? inv(c2) : c2
        if isdual(A.space[2])
            sectorA = Sector(c1, inv(A.charge)+_c1)
            sectorB = Sector(B.charge-_c2, c2)
        else
            sectorA = Sector(c1, A.charge-_c1)
            sectorB = Sector(inv(B.charge)+_c2, c2)
        end
        if haskey(A.blocks, sectorA) && haskey(B.blocks, sectorB)
            blocks[sects[i]] = A.blocks[sectorA] * B.blocks[sectorB]
        else
            blocks[sects[i]] = zeros(T, sizes[i])
        end
    end
    #SymMatrix(charge, space, blocks)
    SymMatrix{S,T}(charge, space, blocks)
end

###TODO: This is the above function with a version of fuselegs (that fuses both of them at once!)
function SymMatrix2(A::AbstractSymTensor{T, N},
                    rowidxs::Vector{Int},
                    colidxs::Vector{Int}) where {T<:Number, N}

    idxperm = [rowidxs; colidxs]
    sort(idxperm) == collect(1:N) || error("Can't convert to SymMatrix!")
    length(rowidxs) == 0 && error("Zero row for matrix not allowed!")
    length(colidxs) == 0 && error("Zero col for matrix not allowed!")

    pA = permutelegs(A, idxperm)

    fusedsectors = NTuple{Int, Int}[]
    patranges = NTuple{UnitRange{Int}, UnitRange{Int}}[]

    #     csects = sten.sects
    #     fchrdict = Dict{Int, FusedCharge{n}}()
    #     signs = Tuple(sten.legs[c].sign for c in l:l+n-1)
    #     for i in eachindex(csects)
    #         sect = [csects[i]...]

    #         pat = Tuple(sect[l:l+n-1])
    #         spat = sign .* signs .* pat
    #         fchr = sign * sum(signs .* [sect[c] for c in l:l+n-1])
    #         patrange = UnitRange{Int}(1,1)
    #         if haskey(fchrdict, fchr)
    #             if haskey(fchrdict[fchr].pats, spat)
    #                 patrange = fchrdict[fchr].pats[spat]
    #             else
    #                 fdim = prod([getdim(sten.legs[c+l-1], pat[c]) for c in 1:n])
    #                 dim = fchrdict[fchr].dim
    #                 fchrdict[fchr].dim += fdim
    #                 patrange = dim+1:dim+fdim
    #                 fchrdict[fchr].pats[spat] = patrange
    #             end
    #         else
    #             fdim = prod([getdim(sten.legs[c+l-1], pat[c]) for c in 1:n])
    #             patrange = 1:fdim
    #             fchrdict[fchr] = FusedCharge(fchr, fdim, Dict(spat => patrange))
    #         end

    #         fsect = vcat( sect[1:l-1], fchr, sect[l+n:end])
    #         push!(fusedsectors, Tuple(fsect))
    #         push!(patranges, patrange)
    #     end

    #     fusedsectperm = sortperm(fusedsectors)
    #     new_sectors, refs = uniquesorted(fusedsectors[fusedsectperm])
    #     refs = refs[invperm(fusedsectperm)]

    #     # the infos are: new_sectors, refs, patranges

    #     # make the new leg
    #     fleg = U1Space(sign, unzip([(k, fchrdict[k].dim) for k in sort(collect(keys(fchrdict)))])...)

    #     new_legs = Tuple(vcat([sten.legs[1:l-1]...], fleg, [sten.legs[l+n:end]...]))

    #     new_nzblks = Array{T, N-n+1}[]
    #     for sect in new_sectors
    #         push!(new_nzblks,Array{T, N-n+1}(undef, [getdim(new_legs[c], sect[c]) for c in 1:N-n+1]...))
    #     end
    #     #indexing = [1:end for i=1:N-n+1]...
    #     for i in eachindex(sten.nzblks)
    #         s = size(sten.nzblks[i])
    #         new_nzblks[refs[i]][[1:s[i] for i=1:l-1]..., patranges[i],[1:s[i] for i=l+n:N]...] =
    #             reshape(sten.nzblks[i], s[1:l-1]..., prod(s[l:l+n-1]), s[l+n:end]...)
    #     end

    #     SymTensor(sten.charge, new_legs, new_sectors, new_nzblks)

    fuselegs(
        fuselegs(psten, -1, length(rowidxs)+1, length(colidxs)),
        +1, 1, length(rowidxs))

end


#---------------------------------------------------------
# # A Phosphorus Cycling Model
#---------------------------------------------------------

#md # !!! tip
#md #     This example is also available as a Jupyter notebook:
#md #     [`p_cycle_DIP_DOP_POP.ipynb`](@__NBVIEWER_ROOT_URL__/examples/generated/p_cycle_DIP_DOP_POP.ipynb)

#---------------------------------------------------------
# ## Tracer equations
#---------------------------------------------------------

# We consider a simple model for the cycling of phosphorus with 3 state variables consisting of phosphate (PO₄) AKA dissolved inorganic phosphorus (DIP), dissolved organic phosphorus (DOP), and particulate organic phosphorus (POP).
#
# The dissolved phases are transported by advection and diffusion whereas the particulate phase sinks rapidly down the water column without any appreciable transport by the circulation.
#
# The governing equations are:
#
# $$\frac{\partial}{\partial t} DIP + \nabla \cdot \left[\boldsymbol{u} + \mathbf{K}\cdot\nabla \right] DIP = -\gamma(DIP) + \kappa_\mathsf{D} \, DOP,$$
#
# $$\frac{\partial}{\partial t} DOP + \nabla \cdot \left[\boldsymbol{u} + \mathbf{K}\cdot \nabla \right] DOP = \sigma \, \gamma(DIP) + \kappa_\mathsf{P} \, POP - \kappa_\mathsf{D} \, DOP,$$
#
# and
#
# $$\frac{\partial}{\partial t} POP + \frac{\partial}{\partial z} \left[w_\mathsf{P} \, POP\right] = (1-\sigma) \, \gamma(DIP) - \kappa_\mathsf{P} \, POP,$$
#
# where $\boldsymbol{u}$ is the fluid velocity and $\mathbf{K}$ is the eddy-diffusion tensor.
# Thus, $\nabla \cdot \left[ \boldsymbol{u} - \mathbf{K} \cdot \nabla \right]$ is a differential operator that represents the transport by the ocean circulation.
# The function $\gamma(DIP)$ represents the biological uptake of DIP by phytoplankton.

# Oxygen participates to this cycle too and satisfies its own tracer equation
#
# $$\frac{\partial}{\partial t} DO_2 + \nabla \cdot \left[\boldsymbol{u} + \mathbf{K}\cdot\nabla \right] DO_2 = -r_{\mathsf{O}_2:\mathsf{P}} \, \kappa_\mathsf{D} \, DOP + \Lambda(DO_2 - [O_2]_{\mathsf{sat}})$$
#
# where $\Lambda$ is the air-sea gas exchange operator.

# These tracer equations depend on a number of scalars, that we list below
#
# | Symbol                        | Definition                                                                  |
# |:------------------------------|:----------------------------------------------------------------------------|
# | $w_\mathsf{P}$                | depth dependent particle sinking speed                                      |
# | $\sigma$                      | fraction of the organic matter production allocated to the dissolved phase  |
# | $\kappa_\mathsf{D}$           | respiration rate for dissolved organic matter (DOP → DIP)                   |
# | $\kappa_\mathsf{P}$           | dissolution rate for particulate organic matter (POP → DOP)                 |
# | $r_{\mathsf{O}_2:\mathsf{P}}$ | number of moles of O₂ needed to respire 1 mole of DOP                       |

using AIBECS

# Load the circulation and grid

wet3D, grd, T_Circulation = OCIM0.load() ;

# Define useful constants and arrays

iwet = indices_of_wet_boxes(wet3D)
nb = length(iwet)
z = ustrip.(vector_of_depths(wet3D, grd))
ztop = vector_of_top_depths(wet3D, grd) ;

# And matrices

DIV = buildDIV(wet3D, iwet, grd)
Iabove = buildIabove(wet3D, iwet) ;

# ### Transport matrices

T_DIP(p) = T_Circulation
T_DOP(p) = T_Circulation
T_DO2(p) = T_Circulation
S₀ = buildPFD(ones(nb), DIV, Iabove)
S′ = buildPFD(ztop, DIV, Iabove)
function T_POP(p)
    w₀, w′ = p.w₀, p.w′
    return w₀ * S₀ + w′ * S′
end
T_all = (T_DIP, T_DOP, T_POP, T_DO2) ;

# Because AIBECS will solve for the steady state solution directly without time-stepping the goverining equations to equilibrium, we don't have any opportunity to specify any intial conditions.
# Initial conditions are how the total amount of conserved elements get specified in most global biogeochemical modelels.
# Thus to specify the total inventory of P in AIBECS we add a very weak resporing term to the DIP equation.
# The time-scale for this restoring term is chosen to be very long compared to the timescale with which the ocean circulation homogenizes a tracer.
# Because of this long timescale we call it the geological restoring term, but geochemists who work on geological processes don't like that name!
# In any event the long timescale allows us to prescribe the total inventory of P in a way that yields the same solution we would have gotten had we time-stepped the model to steady-state with the total inventory prescribed by the initial condition.

# ### Sources minus sinks

# ##### Geological Restoring

function geores(x, p)
    τg, xgeo = p.τg, p.xgeo
    return (xgeo .- x) / τg
end

# ##### Uptake of phosphate (DIP)

relu(x) = (x .≥ 0) .* x
function uptake(DIP, p)
    τu, ku, z₀ = p.τu, p.ku, p.z₀
    DIP⁺ = relu(DIP)
    return 1/τu * DIP⁺.^2 ./ (DIP⁺ .+ ku) .* (z .≤ z₀)
end

# ##### Remineralization DOP into DIP

function respiration(DOP, p)
    κDOP = p.κDOP
    return κDOP * DOP
end

# ##### Dissolution of POP into DOP

function dissolution(POP, p)
    κPOP = p.κPOP
    return κPOP * POP
end

# ##### Air-sea gas exchange

h = ustrip(grd.δdepth[1])             # thickness of the top layer
z = grd.depth_3D[iwet]        # depth of the gridbox centers
using WorldOceanAtlasTools
WOA = WorldOceanAtlasTools
μDO2sat , σ²DO2sat = WOA.fit_to_grid(grd,2018,"O2sat","annual","1°","an") ;
DO2sat = vec(μDO2sat)[iwet]
function airsea(DO2, p)
    κDO2 = p.κDO2
    return κDO2 * (z .< 20u"m") .* (DO2sat .- DO2) / h
end

# Add them up into sms functions (Sources Minus Sinks)

function sms_DIP(DIP, DOP, POP, DO2, p)
    return -uptake(DIP, p) + respiration(DOP, p) + geores(DIP, p)
end
function sms_DOP(DIP, DOP, POP, DO2, p)
    σ = p.σ
    return σ * uptake(DIP, p) - respiration(DOP, p) + dissolution(POP, p)
end
function sms_POP(DIP, DOP, POP, DO2, p)
    σ = p.σ
    return (1 - σ) * uptake(DIP, p) - dissolution(POP, p)
end
function sms_DO2(DIP, DOP, POP, DO2, p)
    rO2P = p.rO2P
    return airsea(DO2,p) + rO2P * (uptake(DIP,p) - respiration(DOP,p))
end
sms_all = (sms_DIP, sms_DOP, sms_POP, sms_DO2,) # bundles all the source-sink functions in a tuple

# ### Parameters
#
# Build the parameters type and p₀

t = empty_parameter_table()    # initialize table of parameters
add_parameter!(t, :xgeo, 2.17u"mmol/m^3")
add_parameter!(t, :τg, 1.0u"Myr")
add_parameter!(t, :ku, 10.0u"μmol/m^3")
add_parameter!(t, :z₀, 80.0u"m")
add_parameter!(t, :w₀, 1.0u"m/d")
add_parameter!(t, :w′, 1/4.4625u"d")
add_parameter!(t, :κDOP, 1/0.25u"yr")
add_parameter!(t, :κPOP, 1/5.25u"d")
add_parameter!(t, :σ, 0.3u"1")
add_parameter!(t, :τu, 30.0u"d")
add_parameter!(t, :κDO2, 50u"m" / 30u"d")
add_parameter!(t, :rO2P, 175.0u"mol/mol")
initialize_Parameters_type(t, "DIP_DOP_POP_O₂_Parameters")   # Generate the parameter type

# ### Generate state function and Jacobian

nt = length(T_all)    # number of tracers
n = nt * nb           # total dimension of the state vector
p = DIP_DOP_POP_O₂_Parameters() # parameters
x = p.xgeo * ones(n) # initial iterate
F, ∇ₓF = state_function_and_Jacobian(T_all, sms_all, nb)

# and solve

prob = SteadyStateProblem(F, ∇ₓF, x, p)
nothing # s = solve(prob, CTKAlg()) # Not working yet

# unpack state

DIP, DOP, POP, DO2 = state_to_tracers(x, nb, nt) # remove when line below works
nothing # DIP, DOP, POP, DO2 = state_to_tracers(s.u, nb, nt)

# We will plot the concentration of DIP at a given depth horizon

depth = grd.depth
iz = findfirst(depth .> 200u"m")
iz, depth[iz]

#-

DIP_3D = rearrange_into_3Darray(DIP, wet3D)
DIP_2D = DIP_3D[:,:,iz] * ustrip(1.0u"mol/m^3" |> u"mmol/m^3")
lat, lon = ustrip.(grd.lat), ustrip.(grd.lon)

# and plot

ENV["MPLBACKEND"]="qt5agg"
using PyPlot, PyCall
clf()
ccrs = pyimport("cartopy.crs")
ax = subplot(projection = ccrs.EqualEarth(central_longitude=-155.0))
ax.coastlines()
# making it cyclic for Cartopy
lon_cyc = [lon; 360+lon[1]]
DIP_2D_cyc = hcat(DIP_2D, DIP_2D[:,1])
# And plot
p = contourf(lon_cyc, lat, DIP_2D_cyc, levels=0:0.2:3.6, transform=ccrs.PlateCarree(), zorder=-1)
colorbar(p, orientation="horizontal");
gcf()


include("../src/general_driver.jl")
using PyPlot
using Printf
using LinearAlgebra
using JLD
function plot_hess_eigs(w, η, s=1.0)
	npts = 50000
	max_hess_eigs = zeros(npts)
	min_hess_eigs = zeros(npts)
	for i = 1:npts
		d2L = hess_loss(w, s)
		x = eigvals(d2L)
		max_hess_eigs[i] = maximum(x)
		min_hess_eigs[i] = minimum(x)
		w = next(w, η, s)
	end
	fig, ax = subplots()
	ax.plot(max_hess_eigs, ".", ms=10)
	ax.xaxis.set_tick_params(labelsize=30)
	ax.yaxis.set_tick_params(labelsize=30)
    ax.set_xlabel("step number", fontsize=30)
	ax.set_ylabel(L"$ \||\nabla^2 L \|| $", fontsize=30)
    ax.grid(true)
    ax.set_title("η = $η", fontsize=30)
	fig, ax = subplots()
	ax.plot(min_hess_eigs, ".", ms=10)
	ax.xaxis.set_tick_params(labelsize=30)
	ax.yaxis.set_tick_params(labelsize=30)
    ax.set_xlabel("step number", fontsize=30)
	ax.set_ylabel(L"$ \||(\nabla^2 L)^{-1}\|| $", fontsize=30)
    ax.grid(true)

    return max_hess_eigs, min_hess_eigs

end
function plot_lyapunov_exponents()
	npts = 10
	w = rand(npts)
	les = rand(npts)
	η = [0.001,0.01,0.02,0.025,0.05]
	fig, ax = subplots()
	ax.xaxis.set_tick_params(labelsize=30)
	ax.yaxis.set_tick_params(labelsize=30)

	for (i, ηi) = enumerate(η)
		w .= rand(npts)
		@show i, ηi
		for j = 1:npts
			les[j] = lyap_exp(w[j], ηi)
		end
		ax.plot(w, les, label=latexstring(L"\eta = ",@sprintf("%0.3f", ηi)))
	end
end
function fixed_point(w, η, s)
	n = 20000
	for i = 1:n
		w = next(w, η, s)
	end
	return w
end
function plot_bifurcation()
	nSteps = 20000
	η = LinRange(1.e-3, 2.0, 50)
	npts_η = size(η)[1]
	npts = 100
	s = 1.0
	fig, ax = subplots()
	fig1, ax1 = subplots()
    wstar = randn(3)
	w = zeros(3)
	wplot = zeros(3, npts)
   	i = 1
	while i <= npts_η
		@show i
		ηi = η[i]
		w .= wstar
		w = run(w, ηi, nSteps, s)
		wplot[:,1] = w
		for n = 1:npts-1
				wplot[:,n+1] = next(wplot[:,n], ηi, s)
		end
		@show ηi, wplot[:,end]
		if maximum(wplot) != NaN && minimum(wplot) != NaN
			i += 1
		    ax.plot(ηi*ones(npts), wplot[1,:], "bo", ms=3.0)
		    ax1.plot(ηi*ones(npts), wplot[2,:], "ko", ms=3.0)

		end
	end
	ax.grid(true)
	ax.set_xlabel(L"$ \eta $", fontsize=30)
	ax.set_ylabel(L"$ w^*_1 $", fontsize=30)
    ax1.grid(true)
	ax1.set_xlabel(L"$ \eta $", fontsize=30)
	ax1.set_ylabel(L"$ w^*_2 $", fontsize=30)
	ax.xaxis.set_tick_params(labelsize=30)
	ax.yaxis.set_tick_params(labelsize=30)
    ax1.xaxis.set_tick_params(labelsize=30)
	ax1.yaxis.set_tick_params(labelsize=30)
    return wplot, wstar
end
X = load("goodIC-deep.jld")
w0 = X["w0"]
η = 0.7
max_hess_eigs, min_hess_eigs = plot_hess_eigs(w0, η)


s = open("input.txt") do file
    read(file, String);
end

parts = split(s, "\n\n");

rules = split(parts[1],"\n")[2:end];
myticket = parts[2];
otherTickets = split(parts[3],"\n")[2:end];

function GetValidRanges(rules)
    validRanges = [];

    for rule in rules
        m = match(r": (\d+)-(\d+) or (\d+)-(\d+)", rule);
        nums = map(x->parse(Int,x), m.captures);
        push!(validRanges, nums[1:2])
        push!(validRanges, nums[3:4])
    end

    return validRanges;
end

function CheckTickets(ranges, tickets)
    scanningError = 0;
    for ticket in tickets
        if length(ticket) == 0
            continue
        end

        vals = map(x->parse(Int,x), split(ticket,","));

        for val in vals
            valid = false;
            for range in ranges
                if range[1] <= val <= range[2]
                    valid = true;
                    break;
                end
            end

            if !valid
                scanningError += val;
            end
        end
    end

    println(scanningError);
    return scanningError;
end

validRanges = GetValidRanges(rules);

ans = CheckTickets(validRanges, otherTickets)


function EuropeanCallEFD(K, T, S, sig, r, div, N, Nj, dx)

  dt = T/N
  nu = r - div - 0.5 * sig^2
  edx = exp(dx)
  pu = 0.5 * dt * ((sig/dx)^2 + nu/dx)
  pm = 1 - dt * (sig/dx)^2 - r * dt
  pd = 0.5 * dt * ((sig/dx)^2 - nu/dx)

  ## Initialise asset prices at maturity

  St = zeros(Float64, 2N+1)

  ## creating a shifted ranged for addressing the Array in the usual i,j notation

  J = sub(1:(2N+1), (1+(N+1):((2N+1)+(N+1))))

  ## Asset price at node N,-N
  St[J[-Nj]] = S * exp(-Nj * dx)

  ## Asset prices from bottom -N, to N, at time step N
  for j = (-Nj+1):Nj
    St[J[j]] = St[J[j]-1] * edx
  end

  ## initialise Array for the tree

  C = zeros(Float64, (2N+1, N+1))

  ## Option Values at expiry, time step = N, in rows -N to N

  for j = -Nj:Nj
    C[J[j], Nj+1] = max(0, St[J[j]] - K)
  end

  ## Stepping back through the lattice

  for i in range(N-1, -1, N)
    for j = -Nj+1:Nj-1
      C[J[j], i+1] = pu * C[J[j]+1, i+2] + pm * C[J[j], i+2] + pd * C[J[j]-1, i+2]
    end
    C[J[-Nj], i+1] = C[J[-Nj]+1, i+1]
    C[J[Nj], i+1] = C[J[Nj]-1, i+1] + (St[J[Nj]]-St[J[Nj]-1])

  end

C
end


"""
    hamming(x, X)
Compute the hamming distance of `x` over a collection of solutions `X`, i.e. the minimal number of variables to switch in `x`to reach a solution.
"""
hamming(x, X) = mapreduce(y -> Distances.hamming(x,y), min, X)

"""
    minkowski(x, X, p)
"""
minkowski(x, X, p) = mapreduce(Distances.minkowski(x, y, p), min, X)

"""
    manhattan(x, X)
"""
manhattan(x, X) = mapreduce(y -> Distances.cityblock(x, y), min, X)

"""
    weigths_bias(x)
A metric that bias `x` towards operations with a lower bit. Do not affect the main metric.
"""
weigths_bias(x) = sum(p -> p[1] * log2(1. + p[2]), enumerate(x)) / length(x)^4


#Reconstruction

function minmod(a,b)
    if a*b < 0.0
        return 0.0
    elseif abs(a) < abs(b)
        return a
    else
        return b
    end
end

function tvd_minmod_reconstruction(n, g, f, x, xi)
    fp = zeros(n)
    fm = zeros(n)

    for i = g:(n-g+2)
        dx_up = x[i] - x[i-1]
        dx_down = x[i+1] - x[i]
        dx_m = x[i] - xi[i]
        dx_p = xi[i+1] - x[i]
        df_up = (f[i] - f[i-1])/dx_up
        df_down = (f[i+1]-f[i])/dx_down
        delta = minmod(df_up, df_down)
        fp[i] = f[i] + delta*dx_p
        fm[i] = f[i] - delta*dx_m
    end

    return fp, fm
end

signum(x,y) = y >= 0.0 ? abs(x) : -abs(x)

function tvd_mc_reconstruction(n, g, f, x, xi)
    fp = zeros(n)
    fm = zeros(n)

    for i = g:(n-g+2)
        dx_up = x[i] - x[i-1]
        dx_down = x[i+1] - x[i]
        dx_m = x[i] -xi[i]
        dx_p = xi[i+1] - x[i]
        df_up = (f[i]-f[i-1]) / dx_up
        df_down = (f[i+1]-f[i]) / dx_down

        if df_up*df_down < 0.0
            delta = 0.0
        else
            delta = signum(min(2.0abs(df_up),
                               2.0abs(df_down),
                               0.5(abs(df_up)+abs(df_down))),
                           df_up + df_down)
        end

        fp[i] = f[i] + delta*dx_p
        fm[i] = f[i] - delta*dx_m
    end

    return fp, fm
end

#2-dim reconstruction
function reconstruct(hyd::data2d)

  #x-pencils
  for j = (hyd.g-1):(hyd.ny-hyd.g+2)
    rhop, rhom = tvd_minmod_reconstruction(hyd.nx,
                                       hyd.g,
                                       vec(hyd.rho[j,:]),
                                       hyd.x,
                                       hyd.xi)

    hyd.rhop[j,:,1] = rhop'
    hyd.rhom[j,:,1] = rhom'

    epsp, epsm = tvd_minmod_reconstruction(hyd.nx,
                                       hyd.g,
                                       vec(hyd.eps[j,:]),
                                       hyd.x,
                                       hyd.xi)

    hyd.epsp[j,:,1] = epsp'
    hyd.epsm[j,:,1] = epsm'

    velxp, velxm = tvd_minmod_reconstruction(hyd.nx,
                                         hyd.g,
                                         vec(hyd.velx[j,:]),
                                         hyd.x,
                                         hyd.xi)

    hyd.velxp[j,:,1] = velxp'
    hyd.velxm[j,:,1] = velxm'

    velyp, velym = tvd_minmod_reconstruction(hyd.nx,
                                         hyd.g,
                                         vec(hyd.vely[j,:]),
                                         hyd.x,
                                         hyd.xi)

    hyd.velyp[j,:,1] = velyp'
    hyd.velym[j,:,1] = velym'
  end

  #y-pencils
  for i = (hyd.g-1):(hyd.nx-hyd.g+2)
    hyd.rhop[:,i,2], hyd.rhom[:,i,2] = tvd_minmod_reconstruction(hyd.ny,
                                                             hyd.g,
                                                             hyd.rho[:,i],
                                                             hyd.y,
                                                             hyd.yi)

    hyd.epsp[:,i,2], hyd.epsm[:,i,2] = tvd_minmod_reconstruction(hyd.ny,
                                                             hyd.g,
                                                             hyd.eps[:,i,1],
                                                             hyd.y,
                                                             hyd.yi)

    hyd.velxp[:,i,2], hyd.velxm[:,i,2] = tvd_minmod_reconstruction(hyd.ny,
                                                               hyd.g,
                                                               hyd.velx[:,i],
                                                               hyd.y,
                                                               hyd.yi)

    hyd.velyp[:,i,2], hyd.velym[:,i,2] = tvd_minmod_reconstruction(hyd.ny,
                                                               hyd.g,
                                                               hyd.vely[:,i],
                                                               hyd.y,
                                                               hyd.yi)
  end

  hyd.pressp[:,:,1] = eos_press(hyd.rhop[:,:,1], hyd.epsp[:,:,1], gamma)
  hyd.pressm[:,:,1] = eos_press(hyd.rhom[:,:,1], hyd.epsm[:,:,1], gamma)
  hyd.pressp[:,:,2] = eos_press(hyd.rhop[:,:,2], hyd.epsp[:,:,2], gamma)
  hyd.pressm[:,:,2] = eos_press(hyd.rhom[:,:,2], hyd.epsm[:,:,2], gamma)

  hyd.qp[:,:,:,1] = prim2con(hyd.rhop[:,:,1], hyd.velxp[:,:,1], hyd.velyp[:,:,1], hyd.epsp[:,:,1])
  hyd.qm[:,:,:,1] = prim2con(hyd.rhom[:,:,1], hyd.velxm[:,:,1], hyd.velym[:,:,1], hyd.epsm[:,:,1])
  hyd.qp[:,:,:,2] = prim2con(hyd.rhop[:,:,2], hyd.velxp[:,:,2], hyd.velyp[:,:,2], hyd.epsp[:,:,2])
  hyd.qm[:,:,:,2] = prim2con(hyd.rhom[:,:,2], hyd.velxm[:,:,2], hyd.velym[:,:,2], hyd.epsm[:,:,2])

  return hyd
end


using Test
using WaveProp.Geometry
using WaveProp.Integration
using WaveProp.Mesh
using LinearAlgebra

@testset "Lagrange elements" begin
    @testset "LagrangeLine" begin
        ## line in 3d
        vtx = ((0.,0.,0.),(1.,1.,1.))
        l   = LagrangeLine(vtx)
        @test domain(l) == ReferenceLine()
        @test ambient_dimension(l) == 3
        @test geometric_dimension(l) == 1
        # makes no sense to ask for normal here. Make sure error is thrown.
        @test_throws AssertionError normal(l,SVector(0.5)) == SVector(-1.,1.)/√2
        ## line in 2d
        a = (0.,0.)
        b = (1.,1.)
        l   = LagrangeLine((a,b))
        @test domain(l) == ReferenceLine()
        @test ambient_dimension(l) == 2
        @test geometric_dimension(l) == 1
        @test normal(l,SVector(0.5)) == SVector(1.,-1.)/√2
    end
    @testset "LagrangeTriangle" begin
        # triangle in 2d
        vtx = SVector(SVector(0.,0.),SVector(0.,1.),SVector(-1.,0))
        t   = LagrangeTriangle(vtx)
        @test ambient_dimension(t) == 2
        @test geometric_dimension(t) == 2
        # triangle in 3d
        vtx = SVector(SVector(0.,0.,0.),SVector(0.,1.,0.),SVector(-1.,0,0.))
        t   = LagrangeTriangle(vtx)
        @test ambient_dimension(t) == 3
        @test geometric_dimension(t) == 2
        @test normal(t,SVector(0.1,0.1)) == SVector(0,0,1.)
    end
    @testset "Tetrahedron" begin
        # TODO: add tests
    end
end

@testset "Line quadrature" begin
    el     = LagrangeLine((1.,1.),(5.,4.))
    qrule  = GaussLegendre{1}()
    x,w    = qrule(el)
    @test sum(w) ≈ 5
end

@testset "Triangle quadrature" begin
    qrule = Gauss{ReferenceTriangle,1}()
    F     = LagrangeTriangle((0.,0.),(1.,0),(0.,1.))
    x,w   = qrule(F)
    @test sum(w) ≈ 1/2
    ## equilateral triangle
    F   = LagrangeTriangle((-1.,0),(1.,0),(0.,1.))
    x,w = qrule(F)
    @test sum(w) ≈ 1
end

@testset "Triangle surface quadrature" begin
    qrule = Gauss{ReferenceTriangle,1}()
    F   = LagrangeTriangle((0.,0.,0.),(1.,0.,0.),(0.,1.,0.))
    x,w = qrule(F)
    @test sum(w) ≈ 1/2
    ## equilateral triangle
    F   = LagrangeTriangle((-1.,0.,1.),(1.,0.,1.),(0.,1.,1.))
    x,w = qrule(F)
    @test sum(w) ≈ 1
end

@testset "Tetrahedron quadrature" begin
    D     = ReferenceTetrahedron
    qrule = Gauss{D,1}()
    F   = LagrangeTetrahedron((0,0,0.),(1.,0,0),(0,1,0),(0.,0.,1.))
    x,w = qrule(F)
    @test sum(w) ≈ 1/6
    # dilate by 2x and translate by 1 along  the tetrahedron
    F   = LagrangeTetrahedron((1,0,0),(3,0,0),(1,2,0),(1,0,2))
    x,w = qrule(F)
    @test sum(w) ≈ 1/6*2^3
end

@testset "Curved lines" begin
    qstd = GaussLegendre(20)
    el   = LagrangeLine((0,0),(1,1),(1/2,1/4))
    # simple test on smooth integrand
    f     = x->cos(x[1])
    Ie     = integrate(f,el)
    for shand in [IMT(), Kress()]
        q     = SingularQuadratureRule(qstd,shand)
        Ia    = integrate(f,q,el)
        @debug Ia - Ie
        @test isapprox(Ia,Ie,rtol=1e-6)
    end
    # non-smooth integrand
    f     = x -> log(abs(x[1])*cos(x[1]))
    Ie     = integrate(f,el)
    for shand in [IMT(), Kress()]
        q     = SingularQuadratureRule(qstd,shand)
        Ia    = integrate(f,q,el)
        @debug Ia - Ie
        @test isapprox(Ia,Ie,rtol=1e-5)
        # check that the `naive` integration woudl have failed the test
        Istd    = integrate(f,qstd,el)
        @debug Istd - Ie
        @test !isapprox(Istd,Ie,rtol=1e-5)
    end
end

@testset "Curved line with singularity inside" begin
    qstd = GaussLegendre(20)
    el   = LagrangeLine((0,0),(1,1),(1/2,1/4))
    vs   = 1/3
    xs   = el(vs)
    f    = x -> x == xs ? 0.0 : log(norm(x-xs)*cos(x[1]))
    Ie    = integrate(f,el)
    for shand in [IMT(), Kress()]
        q     = SingularQuadratureRule(qstd,shand)
        x,w   = q(el,vs)
        Ia    = integrate(f,x,w)
        @debug Ie-Ia
        @test isapprox(Ie,Ia;rtol=1e-5)
        Ia    = integrate(f,q,el,vs)
        @test isapprox(Ie,Ia;rtol=1e-5)
        # check that the `naive` integration woudl have failed the test
        Istd    = integrate(f,qstd,el)
        @test !isapprox(Istd,Ie,rtol=1e-5)
    end
end


@testset "Example 2" begin
    Mod.@polyvar x[1:3]
    p = sum(x .* x)
    @test p == x[1]^2 + x[2]^2 + x[3]^2
    @test subs(p, x[1]=>2, x[3]=>3) == x[2]^2 + 13
    A = [1.0 2 3; 4 5 6; 7 8 9]
    @test p(x=>A*vec(x)) == (x[1] + 2x[2] + 3x[3])^2 + (4x[1] + 5x[2] + 6x[3])^2 + (7x[1] + 8x[2] + 9x[3])^2
end


# Test file

using FractalTools
using Plots
pyplot()
# default(:size, (1900, 1000))

# Construct interpolation data
xi, dx, xf = 0, 0.1, 1
x = collect(xi : dx : xf)
y = sin.(2π * x)
z = cos.(2π * x)
u = 0.9
d = u * ones(length(x) - 1)
h = u * ones(length(x) - 1)
l = u * ones(length(x) - 1)
m = u * ones(length(x) - 1)

plt = plot(layout=(5,1))
for niter in 1 : 5
    interp = interpolate(x, y, d, niter=1)
    # interp = hiddenfif(x, y, z, d, h, l, m)
    xd = collect(xi : 0.1dx : xf)
    plot!(xd, getindex.(interp.(xd), 1), subplot=niter)
    scatter!(x, y, subplot=niter)
end
display(plt)


module Decoder

using DSP, FFTW
using Match

export decode

# Returns two vectors containing
# the indexes where samples with
# information (aka: different from silences)
# start and end
#
function find_data_samples(x, window, get_threshold)

	if isempty(x)
		return [], []
	end

	# Normalize samples
	x = x ./ maximum(abs.(x))

	# Calculate the envolvent
	y = conv(window, x)

	# Keep the samples beginning in N/2
	# because the convolution shift it in that amount
	y1 = y[div(length(window), 2):end]

	# Threshold
	thres = ones(length(y1)) .* get_threshold(y1)

	# Detecting tones and silences
	pulses = map(x -> Int(x), y1 .> thres)

	# To detect where the
	# start and end of each pulse are
	differ = diff(pulses)

	# Indexes where the pulses start
	pulses_start = map(x -> x + 1, findall(x -> x == 1, differ))

	# Indexes where the pulses end
	pulses_end = findall(x -> x == -1, differ)

	return pulses_start, pulses_end
end

function get_time_window(fs)

	# The samples per period
	# The longest period is for the smallest freq
	# In this case: 697 Hz ~ 500 Hz
	samples_period = Int(0.005 * fs)

	# Window size is 3 periods
	N = samples_period * 3

	h = ones(N) ./ N

	return h
end

function get_pulse_window()

	N = 120
	h = tukey(N, 0.5) ./ (N/4)

	return h
end

function get_freq_window()

	N = 10
	h = tukey(N, 0.5) ./ (N/2)

	return h
end

function time_threshold(y)
	return 0.0125
end

function pulse_threshold(y)
	return 0.85 * maximum(y)
end

function freq_threshold(y)
	return 0.15 * maximum(y)
end

# Given a frequency it matches to a
# (start, end) interval it returns
# the frequency that belongs to it
get_freq(f) = @match f begin
	600:733 => 697
	744:811 => 770
	812:896 => 852
	897:1020 => 941
	1100:1272 => 1209
	1273:1406 => 1336
	1407:1555 => 1477
	1556:1700 => 1633
	_ => 0
end

get_symbol(f1, f2) = @match (f1, f2) begin

	(941, 1336) || (1336, 941) => "0"
	(697, 1209) || (1209, 697) => "1"
	(697, 1336) || (1336, 697) => "2"
	(697, 1477) || (1477, 697) => "3"
	(770, 1209) || (1209, 770) => "4"
	(770, 1336) || (1336, 770) => "5"
	(770, 1477) || (1477, 770) => "6"
	(852, 1209) || (1209, 852) => "7"
	(852, 1336) || (1336, 852) => "8"
	(852, 1477) || (1477, 852) => "9"
	(941, 1209) || (1209, 941) => "*"
	(941, 1477) || (1477, 941) => "#"
	(697, 1633) || (1633, 697) => "A"
	(770, 1633) || (1633, 770) => "B"
	(852, 1633) || (1633, 852) => "C"
	(941, 1633) || (1633, 941) => "D"
	_ => ""
end

# Receives a list of tuples containing
# the start and end of the interval
# that corresponds to a given frequency
#
# Returns the symbol it belongs to
function decode_symbol(freqs)

	if isempty(freqs)
		return ""
	end

	freqs = map(f -> get_freq(div(f[1] + f[2], 2)), freqs)

	if length(freqs) < 2
		return ""
	end

	return get_symbol(freqs[1], freqs[2])
end

# Find frequency peaks and filter
# only the ones that are not in the range
# of dialing tones
function analyze_freqs(X, fs)

	freqs = []

	if isempty(X)
		return freqs
	end

	starts, ends = find_data_samples(X,
					 get_freq_window(),
					 freq_threshold)

	samples_per_freq = Int(floor(fs/(2*length(X))))

	starts = starts .* samples_per_freq
	ends = ends .* samples_per_freq

	for i in range(1, length(starts))

		# Filter frequencies only in the
		# range of (500 Hz, 1700 Hz)
		if (500 <= starts[i] <= 1700 &&
			ends[i] <= 1800 &&
			1 <= ends[i] - starts[i] < 250)

			push!(freqs, (starts[i], ends[i]))
		end
	end

	return freqs
end

function analyze_pulse(pulse, fs)

	e = pulse .^ 2

	starts, ends = find_data_samples(e,
					 get_pulse_window(),
					 pulse_threshold)

	symbols = []

	for i in range(1, length(starts))

		# Because of the convolution with
		# the window, in some cases the 'ends'
		# indexes may be outside the range of
		# the pulse.
		if ends[i] > length(pulse)
			p = pulse[starts[i]:end]
		else
			p = pulse[starts[i]:ends[i]]
		end

		X = abs.(fft(p))
		X = X[1:div(end,2)]

		freqs = analyze_freqs(X, fs)
		sym = decode_symbol(freqs)

		append!(symbols, sym)
	end

	return symbols
end

function decode(s, fs)

	s = vec(s)

	e = s .^ 2

	symbols = []

	starts, ends = find_data_samples(e,
					 get_time_window(fs),
					 time_threshold)

	# Analyze all data chunks looking for
	# the "dialing" frequencies
	for i in range(1, length(starts))

		pulse = s[starts[i]:ends[i]]

		syms = analyze_pulse(pulse, fs)

		append!(symbols, syms)
	end

	return symbols
end

end # module


using Bridge, Distributions
using Test
#import Bridge: b, σ, a, transitionprob
qu(x) = x*x'
S = SVector{2,Float64}
B = SMatrix{2,2,Float64}([-1 0.1; -0.2 -1])
mu = S([0.2, 0.3])
sigma = SMatrix{2,2,Float64}([-0.212887  0.0687025
  0.193157  0.388997 ])
a = qu(sigma)
@test supnorm(qu(cholupper(a)') - a) < eps()

@test norm(cov(Bridge.mat(S[ cholupper(a)'*randn(S) for i in 1:100])') - Matrix(a)) < 0.1

x = [0.289639+0.649503im, 0.724532+0.963363im]

@test dot(x,x) == Bridge.inner(x)
@test dot(x, 1 .- x) == Bridge.inner(x, 1 .- x)
@test x*x' == Bridge.outer(x)
@test x*(1 .- x)' == Bridge.outer(x, 1 .- x)

A = reshape(collect(1:5*4*3), 5,4,3)
@test A[.., 1] == reshape(1:5*4, 5,4)
A[.., 1] = A[.., 3]
@test A[.., 1] == reshape((3-1) * 5 * 4 .+ (1:5*4), 5, 4)


using DataFrames
using LinearAlgebra
using Statistics: std, var, mean, cov
using HighFrequencyCovariance
using Random

brownian_corr_matrix = Hermitian([1.0 0.75 0.5 0.0;
                                  0.0 1.0 0.5 0.25;
                                  0.0 0.0 1.0 0.25;
                                  0.0 0.0 0.0 1.0])
assets = [:BARC, :HSBC, :VODL, :RYAL]
assets1 = [:BARC]
assets2 = [:BARC,  :VODL, :RYAL]
assets3 = [:HSBC,  :VODL, :RYAL]
twister = MersenneTwister(1)

ts1, true_covar, micro_noise, update_rates = generate_random_path(4, 2000; brownian_corr_matrix = brownian_corr_matrix, assets = assets, vols = [0.02,0.03,0.04,0.05], twister = deepcopy(twister), syncronous = true)
ts2, true_covar, micro_noise, update_rates = generate_random_path(4, 2000; brownian_corr_matrix = brownian_corr_matrix, assets = assets, vols = [0.02,0.03,0.04,0.05], twister = deepcopy(twister), syncronous = true)
ts3, true_covar, micro_noise, update_rates = generate_random_path(4, 2000; assets = assets, vols = [0.02,0.03,0.04,0.05], twister = deepcopy(twister), syncronous = true)

iscloser(a,b) = (a.Correlation_error + a.Volatility_error < b.Correlation_error + b.Volatility_error)

# Simple Volatility
simple_vol = simple_volatility(ts1)
all(values(simple_vol) .< 0.1)

# Preav Convergence
preav_estimate1 = preaveraged_covariance(ts1, assets1)
preav_estimate2 = preaveraged_covariance(ts2, assets2)
preav_estimate3 = preaveraged_covariance(ts3, assets3)

# Testing the combination of multiple covariance matrices.
vector_of_covars = [preav_estimate1, preav_estimate2, preav_estimate3]
combined = combine_covariance_matrices(vector_of_covars, [1,2,3], [3,2,1])
isnan(get_correlation(combined, :BARC, :HSBC)) # As they never appear together.

# Testing order does not matter
preav_estimate4 = preaveraged_covariance(ts3, assets)
vector_of_covars = [preav_estimate1, preav_estimate2, preav_estimate4]
combined = combine_covariance_matrices(vector_of_covars, [1,2,3], [3,2,1])
vector_of_covars = [preav_estimate4, preav_estimate2, preav_estimate1]
combined2 = combine_covariance_matrices(vector_of_covars, [3,2,1], [1,2,3])
calculate_mean_abs_distance(combined, combined2).Correlation_error .< 10*eps()
calculate_mean_abs_distance(combined, combined2).Volatility_error .< 10*eps()


#using SparseArrays

mutable struct woodbury_identity
    U::AbstractMatrix
    V::AbstractMatrix
    invA_U::AbstractMatrix
    factored_capacitance_matrix
    factored_A::abstract_linear_system_solver

    # (A + U*V')^-1
    function woodbury_identity(factored_A::abstract_linear_system_solver, U::AbstractArray, V::AbstractArray)
        invA_U = false
        capacitance_matrix = false
        try
          invA_U = ls_solve(factored_A, U)
          n = size(U,1);
          k = size(U,2);

          capacitance_matrix = (eye(k) + V * invA_U);
          factored_capacitance_matrix = lufact(capacitance_matrix);

          return new(U, V, invA_U, factored_capacitance_matrix, factored_A);
        catch e
            println("ERROR woodbury_identity")
            #@show full(factored_A._SparseMatrix)
            #@show capacitance_matrix
            #@show invA_U'
            throw(e)
        end
    end
end

function evaluate(W::woodbury_identity, sol::Array{Float64,1})
    return W.factored_A._SparseMatrix * sol + W.U * (W.V * sol)
end

function rel_error(W::woodbury_identity, rhs::Array{Float64,1}, sol::Array{Float64,1})
    return LinearAlgebra.norm(evaluate(W, sol) - rhs,1)/LinearAlgebra.norm(rhs,1);
end

function ls_solve(W::woodbury_identity, rhs::Array{Float64,1})
  try
    rhs_err = rhs
    sol = zeros(length(rhs))
    for i = 1:1
        # iterative refinement
        my_temp = ls_solve(W.factored_A, rhs_err)
        Δd = (my_temp - W.invA_U * (W.factored_capacitance_matrix \ (W.V * my_temp)))
        sol = sol + Δd[:]
        rhs_err = rhs - evaluate(W,sol)
        #@show i, LinearAlgebra.norm(rhs_err,1)
    end

    # catch errors
    tol = 1e-8;
    err = rel_error(W, rhs, sol) # why is this rel error?
    if err > tol
        my_warn("numerical instability using Woodbury, using direct factorization instead of woodbury.")
        sol = ls_solve_direct(W, rhs)
        err = rel_error(W, rhs, sol)

        if (err > tol)
            error("numerical stability using direct factorization, computation skipped")
        end
    end

    return sol
  catch e
      println("ERROR ls_solve")
      throw(e)
  end
end

function ls_solve_direct(W::woodbury_identity, rhs::Array{Float64,1}) # if there is numerical errors
    mat = W.factored_A._SparseMatrix + SparseArrays.sparse(W.U) * SparseArrays.sparse(W.V)
    return lufact(mat) \ rhs;
end

function numerical_error(A, sol, rhs)
    return LinearAlgebra.norm(A * sol - rhs,1);
end

function e_(i::Int64,n::Int64)
    @assert(i <= n)
    vec = zeros(n);
    vec[i] = 1.0;
    return vec;
end


using NonconvexCore, LinearAlgebra, Test, Zygote

f(x::AbstractVector) = sqrt(x[2])
g(x::AbstractVector, a, b) = (a*x[1] + b)^3 - x[2]

m = Model(f)
addvar!(m, [0.0, 0.0], [10.0, 10.0])
add_ineq_constraint!(m, x -> g(x, 2, 0))
add_ineq_constraint!(m, x -> g(x, -1, 1))

@test getmin(m) == fill(0, 2)
@test getmax(m) == fill(10, 2)
@test getmin(m, 1) == 0.0
@test getmax(m, 1) == 10.0
@test getmin(m, 2) == 0.0
@test getmax(m, 2) == 10.0

p = [1.234, 2.345]

val0, grad0 = NonconvexCore.value_gradient(NonconvexCore.getobjective(m), p)
@test val0 == f(p)
@test grad0 == Zygote.gradient(f, p)[1]

val1, grad1 = NonconvexCore.value_gradient(NonconvexCore.getineqconstraint(m, 1), p)
@test val1 == g(p, 2, 0)
@test grad1 == Zygote.gradient(p -> g(p, 2, 0), p)[1]

val2, grad2 = NonconvexCore.value_gradient(NonconvexCore.getineqconstraint(m, 2), p)
@test val2 == g(p, -1, 1)
@test grad2 == Zygote.gradient(p -> g(p, -1, 1), p)[1]

vals, jac = NonconvexCore.value_jacobian(NonconvexCore.getineqconstraints(m), p)
@test [val1, val2] == vals
@test [grad1 grad2]' == jac

vals, jac = NonconvexCore.value_jacobian(NonconvexCore.getobjectiveconstraints(m), p)
@test [val0, val1, val2] == vals
@test [grad0 grad1 grad2]' == jac


using WoodburyMatrices
using LinearAlgebra, SparseArrays, Test
using Random: seed!

include("woodbury.jl")
include("symwoodbury.jl")


"""
An instance of the budgeted m-set problem. The budget is a minimum total
weight that must be taken for a solution to be feasible.

All weights are supposed to be positive integers, the budget too (this
hypothesis is crucial to get efficient algorithms). Values are not restricted.

It can be formalised as follows:

``\\max \\sum_i \\mathrm{values}_i x_i``
``\\mathrm{s.t.} \\sum_i x_i \\leq m, \\quad \\sum_i \\mathrm{weights}_i x_i \\geq \\mathrm{budget}, \\qquad x \\in \\{0, 1\\}^d``

`max_weight` is a parameter that may be used by the algorithm and corresponds
to the largest value a single weight may take. If not present, it is computed
as `max(weights)`.

`budget` can be ignored; in that case, it will be computed as the maximum
budget that may have a feasible solution, i.e. `d * max_weight`, `d` being
the dimension of the problem (the number of objects). If a `budget` is given
and the algorithm computes all solutions for lower budgets, then the algorithm
is allowed to stop at the given budget (i.e. it will not be possible to
retrieve a solution for a larger budget).
"""
struct BudgetedMSetInstance
  values::Vector{Float64}
  weights::Vector{Int}
  m::Int
  budget::Int # weights * solution >= budget
  max_weight::Int

  function BudgetedMSetInstance(values::Vector{Float64}, weights::Vector{Int}, m::Int;
                                budget::Union{Nothing, Int}=nothing,
                                max_weight::Union{Nothing, Int}=nothing)
    # Error checking.
    if m < 0
      error("m is less than zero: there is no solution.")
    end

    if m == 0
      error("m is zero: the only solution is to take no items, if the budget constraint is satisfied.")
    end

    if ! isnothing(budget) && budget < 0
      error("Budget is present and negative.")
    end

    if any(weights .< 0)
      error("At least a weight is negative.")
    end

    if length(values) != length(weights)
      error("Not the same number of values and weights; these two vectors must have the same size.")
    end

    # Complete the optional parameters.
    if isnothing(max_weight)
      max_weight = maximum(weights)
    end

    if isnothing(budget)
      d = length(values) # Dimension of the problem.
      budget = d * max_weight
    end

    # Return a new instance.
    new(values, weights, m, budget, max_weight)
  end
end

values(i::BudgetedMSetInstance) = i.values
weights(i::BudgetedMSetInstance) = i.weights
m(i::BudgetedMSetInstance) = i.m
budget(i::BudgetedMSetInstance) = i.budget
max_weight(i::BudgetedMSetInstance) = i.max_weight

value(i::BudgetedMSetInstance, o::Int) = values(i)[o]
values(i::BudgetedMSetInstance, o) = values(i)[o]
weight(i::BudgetedMSetInstance, o::Int) = weights(i)[o]
weights(i::BudgetedMSetInstance, o) = weights(i)[o]
dimension(i::BudgetedMSetInstance) = length(values(i))

struct BudgetedMSetSolution
  instance::BudgetedMSetInstance
  items::Vector{Int} # Indices to the chosen items.
  state::Array{Float64, 3} # Data structure built by the dynamic-programming recursion.
  solutions::Dict{Tuple{Int, Int, Int}, Vector{Int}} # From the indices of state to the corresponding solution.
end

function value(s::BudgetedMSetSolution)
  return sum(s.instance.values[i] for i in s.items)
end

function items(s::BudgetedMSetSolution, budget::Int)
  return s.solutions[s.instance.m, 0, budget]
end

function items_all_budgets(s::BudgetedMSetSolution, max_budget::Int)
  sol = Dict{Int, Vector{Int}}()
  m = s.instance.m
  for budget in 0:max_budget
    sol[budget] = s.solutions[m, 0, budget]
  end
  return sol
end

function value(s::BudgetedMSetSolution, budget::Int)
  its = items(s, budget)
  if -1 in its
    return -Inf
  end
  return sum(s.instance.values[i] for i in its)
end


# Adapted from https://ccrma.stanford.edu/~jos/filters/Partial_Fraction_Expansion_residued_m.html

using DSP # requires DSP module for filt, equivalent Julia function to filter

function residued(b, a, toler)
if nargin<3, toler=0.001; end
NUM = vec(b);
DEN = vec(a);
nb = length(NUM);
na = length(DEN);
f = [];
if na<=nb
  f = filt(NUM,DEN,[1,zeros(nb-na)]);
  NUM = NUM - conv(DEN,f);
  NUM = NUM(nb-na+2:end);
end
[r,p,f2,e] = residuez(NUM,DEN,toler);

return [r, p, f, e]


export _At_mul_B,
       _At_ldiv_B,
       DEFAULT_COND_TOL,
       hasfullrowrank,
       issquare,
       isinvertible,
       cross_product,
       nonzero_columns,
       extend,
       projection_matrix,
       remove_zero_columns

# default tolerance for matrix condition number (see 'isinvertible')
const DEFAULT_COND_TOL = 1e6

# matrix-matrix multiplication
@inline _At_mul_B(A, B) = transpose(A) * B

# matrix-matrix division
@inline _At_ldiv_B(A, B) = transpose(A) \ B

# rank of sparse submatrix (see #1497)
LinearAlgebra.rank(M::SubArray{N, 2, <:SparseMatrixCSC}) where {N} = rank(sparse(M))

"""
    issquare(M::AbstractMatrix)

Check whether a matrix is square.

### Input

- `M` -- matrix

### Output

`true` iff the matrix is square.
"""
function issquare(M::AbstractMatrix)
    m, n = size(M)
    return m == n
end

"""
    hasfullrowrank(M::AbstractMatrix)

Check whether a matrix has full row rank.

### Input

- `M` -- matrix

### Output

`true` iff the matrix has full row rank.
"""
function hasfullrowrank(M::AbstractMatrix)
    return rank(M) == size(M, 1)
end

"""
    isinvertible(M::Matrix; [cond_tol]::Number=DEFAULT_COND_TOL)

A sufficient check of a matrix being invertible (or nonsingular).

### Input

- `M`        -- matrix
- `cond_tol` -- (optional, default: `DEFAULT_COND_TOL`) tolerance of matrix
                condition

### Output

If the result is `true`, `M` is invertible.
If the result is `false`, the matrix is non-square or this function could not
conclude.

### Algorithm

We check whether the matrix is square and whether the
[matrix condition number](https://en.wikipedia.org/wiki/Condition_number#Matrices)
`cond(M)` is below some prescribed tolerance.
"""
function isinvertible(M::Matrix; cond_tol::Number=DEFAULT_COND_TOL)
    return issquare(M) && cond(M) < cond_tol
end

# cond is not available for sparse matrices; see JuliaLang#6485 and related issues
function isinvertible(M::AbstractSparseMatrix;
                      cond_tol::Number=DEFAULT_COND_TOL)
    return issquare(M) && isinvertible(Matrix(M), cond_tol=cond_tol)
end

function isinvertible(M::Diagonal; cond_tol=nothing)
    return !any(iszero, diag(M))
end

"""
    cross_product(M::AbstractMatrix{N}) where {N<:Real}

Compute the high-dimensional cross product of ``n-1`` ``n``-dimensional vectors.

### Input

- `M` -- ``n × n - 1``-dimensional matrix

### Output

A vector.

### Algorithm

The cross product is defined as follows:

```math
\\left[ \\dots, (-1)^{n+1} \\det(M^{[i]}), \\dots \\right]^T
```
where ``M^{[i]}`` is defined as ``M`` with the ``i``-th row removed.
See *Althoff, Stursberg, Buss: Computing Reachable Sets of Hybrid Systems Using
a Combination of Zonotopes and Polytopes. 2009.*
"""
function cross_product(M::AbstractMatrix{N}) where {N<:Real}
    n = size(M, 1)
    @assert 1 < n == size(M, 2) + 1 "the matrix must be n x (n-1) dimensional"

    v = Vector{N}(undef, n)
    minus = false
    for i in 1:n
        Mi = view(M, 1:n .!= i, :)  # remove i-th row
        d = det(Mi)
        if minus
            v[i] = -d
            minus = false
        else
            v[i] = d
            minus = true
        end
    end
    return v
end

# det cannot handle sparse matrices in some cases
cross_product(M::AbstractSparseMatrix) = cross_product(Matrix(M))
cross_product(M::SubArray{N, 2, <:AbstractSparseMatrix}) where {N} = cross_product(Matrix(M))

"""
    nonzero_columns(A::AbstractMatrix)

Return all columns that have at least one non-zero entry.

### Input

- `A` -- matrix

### Output

A vector of indices.
"""
function nonzero_columns(A::AbstractMatrix)
    n = size(A, 2)
    nzcol = Vector{Int}()
    sizehint!(nzcol, n)
    for j in 1:n
        if !iszero(view(A, :, j))
            push!(nzcol, j)
        end
    end
    return nzcol
end

function nonzero_columns(A::SparseMatrixCSC)
    dropzeros!(A)
    return collect(j for j in 1:A.n if A.colptr[j] < A.colptr[j+1])
end

"""
    extend(M::AbstractMatrix; check_rank=true)

Return an invertible extension of `M` whose first `n` columns span the column
space of `M`, assuming that `size(M) = (m, n)`, `m > n` and the rank of `M` is `n`.

### Input

- `M`          -- rectangular `m × n` matrix with `m > n` and full rank (i.e. its
                  rank is `n`)
- `check_rank` -- (optional, default: `true`) if `true`, check the rank assumption,
                  otherwise do not perform this check

### Output

The tuple `(Mext, inv_Mext)`, where `Mext` is a square `m × m` invertible matrix
that extends `M`, i.e. in the sense that `Mext = [M | Q2]`, and the rank of `Mext`
is `m`. Here, `inv_Mext` is the inverse of `Mext`.

### Algorithm

First we compute the QR decomposition of `M` to extract a suitable subspace of
column vectors (`Q2`) that are orthogonal to the column span of `M`. Then we observe
that the inverse of the extended matrix `Mext = [M | Q2]` is `[R⁻¹Qᵀ; Q2ᵀ]`.
"""
function extend(M::AbstractMatrix; check_rank=true)
    m, n = size(M)

    m <= n && throw(ArgumentError("this function requires that the number " *
    "of rows is greater than the number of columns, but they are of size $m and " *
    "$n respectively"))

    if check_rank
        r = rank(M)
        r != n && throw(ArgumentError("the rank of the given matrix is " *
        "$r, but this function assumes that it is $n"))
    end

    # compute QR decomposition of M
    Q, R = qr(M)

    # Q2 spans the null space of M
    Q2 = Q[:, (n + 1):end]

    # extend M by appending the columns orthogonal to the column span of M
    Mext = hcat(M, Q2)

    # since the inverse is easy to compute, return it
    inv_Mext = vcat(inv(R) * Q', Q2')

    return Mext, inv_Mext
end

"""
    projection_matrix(block::AbstractVector{Int}, n::Int, [N]::Type{<:Number}=Float64)

Return the projection matrix associated to the given block of variables.

### Input

- `block` -- integer vector with the variables of interest
- `n`     -- integer representing the ambient dimension
- `N`     -- (optional, default: `Float64`) number type

### Output

A sparse matrix that corresponds to the projection onto the variables in `block`.

### Examples

```jldoctest projection_matrix
julia> using LazySets: projection_matrix

julia> projection_matrix([1, 3], 4)
2×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 2 stored entries:
 1.0   ⋅    ⋅    ⋅
  ⋅    ⋅   1.0   ⋅

julia> Matrix(ans)
2×4 Matrix{Float64}:
 1.0  0.0  0.0  0.0
 0.0  0.0  1.0  0.0
```
"""
function projection_matrix(block::AbstractVector{Int}, n::Int, N::Type{<:Number}=Float64)
    m = length(block)
    return sparse(1:m, block, ones(N, m), m, n)
end

# fallback: represent the projection matrix as a sparse array
function projection_matrix(block::AbstractVector{Int}, n::Int, VN::Type{<:AbstractVector{N}}) where {N}
    return projection_matrix(block, n, N)
end

function load_projection_matrix_static()

return quote
    # represent the projection matrix with a static array
    function projection_matrix(block::AbstractVector{Int}, n::Int, VN::Type{<:SVector{L, N}}) where {L, N}
        mat = projection_matrix(block, n, N)
        m = size(mat, 1)
        return SMatrix{m, n}(mat)
    end
end # quote
end # end load_projection_matrix_static

"""
    remove_zero_columns(A::AbstractMatrix)

Return a matrix with all columns containing only zero entries removed.

### Input

- `A` -- matrix

### Output

The original matrix `A` if it contains no zero columns or otherwise a new matrix
where those columns have been removed.
"""
function remove_zero_columns(A::AbstractMatrix)
    nzcol = nonzero_columns(A)
    if length(nzcol) == size(A, 2)
        return A
    else
        return A[:, nzcol]
    end
end


using ProximalOperators
using Test

@testset "PointwiseMinimum" begin

T = Float64

f = PointwiseMinimum(IndPoint(T[-1.0]), IndPoint(T[1.0]))
x = T[0.1]

predicates_test(f)
@test ProximalOperators.is_set(f) == true
@test ProximalOperators.is_cone(f) == false

y, fy = prox_test(f, x)
@test all(y .== T[1.0])
@test fy == T(0)

end