module _SyDyEn
export SyDyEn
using Clustering: kmeans, assignments
using StatsBase: Histogram, fit
using Statistics: mean
    """
        SyDy, Zt = SyDyEn(Sig)

    Returns the symbolic dynamic entropy (`SyDy`) and the symbolic sequence
    (`Zt`) of the data sequence (`Sig`) using the default parameters:
    embedding dimension = 2, time delay = 1, symbols = 3, logarithm = natural,
    symbolic partition type = maximum entropy partitioning (`MEP`),
    normalisation = normalises w.r.t # possible vector permutations (c^m)

        SyDy, Zt = SyDyEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1, c::Int=3, Typex::String="MEP", Logx::Real=exp(1), Norm::Bool=true)

    Returns the symbolic dynamic entropy (`SyDy`) and the symbolic sequence
    (`Zt`) of the data sequence (`Sig`) using the specified 'keyword' arguments:

    # Arguments:
    `m`     - Embedding Dimension, a positive integer  \n
    `tau`   - Time Delay, a positive integer  \n
    `c`     - Number of symbols, an integer > 1  \n
    `Typex` - Type of symbolic sequnce partitioning method, one of the following:  \n
              {"linear","uniform","MEP"(default),"kmeans"}
    `Logx`  - Logarithm base, a positive scalar    \n
    `Norm`  - Normalisation of SyDyEn value: \n
              [false]  no normalisation
              [true]   normalises w.r.t # possible vector permutations (c^m+1) - default

    See the EntropyHub guide for more info on these parameters.

    # See also `DispEn`, `PermEn`, `CondEn`, `SampEn`, `MSEn`

    # References:
        [1] Yongbo Li, et al.,
            "A fault diagnosis scheme for planetary gearboxes using
            modified multi-scale symbolic dynamic entropy and mRMR feature
            selection."
            Mechanical Systems and Signal Processing
            91 (2017): 295-312.

        [2] Jian Wang, et al.,
            "Fault feature extraction for multiple electrical faults of
            aviation electro-mechanical actuator based on symbolic dynamics
            entropy."
            IEEE International Conference on Signal Processing,
            Communications and Computing (ICSPCC), 2015.

        [3] Venkatesh Rajagopalan and Asok Ray,
            "Symbolic time series analysis via wavelet-based partitioning."
            Signal processing
            86.11 (2006): 3309-3320.

    """
    function SyDyEn(Sig::AbstractArray{T,1} where T<:Real; m::Int=2, tau::Int=1,
        c::Int=3, Typex::String="MEP", Logx::Real=exp(1), Norm::Bool=true)

    N = size(Sig,1)
    (N > 10) ?  nothing : error("Sig:   must be a numeric vector")
    (m > 0) ?  nothing :  error("m:     must be an integer > 0")
    (tau > 0) ? nothing : error("tau:   must be an integer > 0")
    (c > 1) ? nothing :   error("c:     must be an integer > 1")
    (lowercase(Typex) in ["linear", "kmeans", "uniform","mep"]) ? nothing :
        error("Typex:    must be one of the following strings - 'linear','kmeans','uniform','MEP'")
    (Logx>0) ? nothing :  error("Logx:  must be a positive number > 0")

    Nx = N-((m-1)*tau)
    Zt = zeros(N)
    if lowercase(Typex) ==  "linear"
        Edges = range(minimum(Sig),maximum(Sig),length=c+1)
        Zt = map(x -> sum(Edges[1:c].<=x), Sig)
    elseif lowercase(Typex) == "uniform"
        Ix = sortperm(Sig)
        Edges = range(1,N,length=c+1)
        z = map(x -> sum(Edges[1:c].<=x), 1:N)
        Zt[Ix] .= z

    elseif lowercase(Typex) == "kmeans"
        Tx = kmeans(transpose(Sig), c; maxiter=200)
        z = Int.(assignments(Tx))
        ix = sortperm(Tx.centers[:]);   Zt = zeros(Int,N)
        for k = 1:c
            Zt[z.==ix[k]] .= k;
        end

    else
        Tx = sort(Sig)
        Edges = Tx[vcat(1, Int.(ceil.((1:c-1)*N/c)),N)]
        Zt = map(x -> sum(Edges[1:end-1].<=x), Sig)
    end

    Zm = zeros(Int,Nx,m);
    for n = 1:m
        Zm[:,n] = Zt[(n-1)*tau + 1:Nx+(n-1)*tau]
    end

    T = unique(Zm,dims=1)
    Counter = zeros(size(T,1))
    Counter2 = zeros(size(T,1),c)
    Bins = range(0.5,c+.5,step=1)
    for n = 1:size(T,1)
        Ordx = all(Zm .- transpose(T[n,:]).==0,dims=2)
        Counter[n] = mean(Ordx)
        Temp = Zm[vcat(falses(m*tau), Ordx[1:end-(m*tau)]),1]
        Counter2[n,:] = fit(Histogram,Temp,Bins).weights
    end
    Counter2 ./= sum(Counter2,dims=2)
    Counter2[isnan.(Counter2)] .= 0

    P1 = -sum(Counter.*log.(Logx, Counter))
    P2 = log.(Logx, repeat(Counter,outer=(1,c)).*Counter2)
    P2[isinf.(P2)] .= 0
    SyDy = P1 .- transpose(Counter)*(sum(P2,dims=2))

    if round(sum(Counter),digits=4) != 1 || maximum(round.(sum(Counter2,dims=2),digits=4)) != 1
        print(maximum(sum(Counter2,dims=2)))
        @warn("Potential Error calculating probabilities")
    end
    if Norm
        SyDy = SyDy/(log(Logx, c^(m+1)))
    end

    return SyDy, Zt
    end

end
"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


function reproduce!(s::Simulation, μ)
	#indexarray = []
	nsteps = s.steptotal[1]
	r = .1
	for i in eachindex(s.cells)
		if s.cells[i].d == nsteps
			new_x1 = s.cells[i].p[1] + r*cosd(s.cells[i].o)
			new_y1 = s.cells[i].p[2] + r*sind(s.cells[i].o)
			new_x2 = s.cells[i].p[1] - r*cosd(s.cells[i].o)
			new_y2 = s.cells[i].p[2] - r*sind(s.cells[i].o)

			t_division1 = rand(DiscreteUniform(100+nsteps,μ+nsteps),1)
			t_division2 = rand(DiscreteUniform(100+nsteps,μ+nsteps),1)

			newcell1 = Cell([new_x1,new_y1],s.cells[i].v,t_division1[1],s.cells[i].o,birth=nsteps,motherID=i,sisterID=length(s.cells)+2)
			newcell2 = Cell([new_x2,new_y2],-s.cells[i].v,t_division2[1],s.cells[i].o,birth=nsteps,motherID=i,sisterID=length(s.cells)+1)

			# add the cell that has divided to the array of indices to be removed
			#push!(indexarray, i)
			# add the new cells
			push!(s.cells, newcell1)
			push!(s.cells, newcell2)

			s.cells[i].daughterID[:] .= [length(s.cells)+1,length(s.cells)+2]
		else
			nothing
		end
	end
	# remove the divided cells from array of cells
	#deleteat!(s.cells, indexarray)
end


#
# ### norm²
# function norm²(v::SVector{3,Float64})
#     n2 = muladd(v[1], v[1], 1.0e-66)
#     n2 = muladd(v[2], v[2], n2)
#     return muladd(v[3], v[3], n2)
# end
# function norm²(v::SVector{3,Dual{Type_Tag,Float64,N}}) where {Type_Tag, N}
#     n2 = v[1]^2
#     n2 = muladd(v[2], v[2], n2)
#     return muladd(v[3], v[3], n2)
# end
#
# ### unsafe_norm
# @inline unsafe_norm(v::SVector{3,T}) where {T} = sqrt(norm²(v))
#
# ### safe_norm
# @inline safe_norm(v::SVector{3,Float64}) = unsafe_norm(v)
# function safe_norm(v::SVector{3,Dual{Type_Tag,Float64,N}}) where {Type_Tag, N}
#     n = unsafe_norm(v)
#     return ifelse(value(n) == 0.0, zero(Dual{Type_Tag,Float64,N}), n)
# end
#
# ### unsafe_inv_norm
# @inline unsafe_inv_norm(v::SVector{3,T}) where {T} = 1.0 / unsafe_norm(v)
#
# ### safe_inv_norm
# function safe_inv_norm(v::SVector{3,Float64})
#     n = unsafe_norm(v)
#     return ifelse(n == 0.0, 0.0, 1.0 / n)
# end
# function safe_inv_norm(v::SVector{3,Dual{Type_Tag,Float64,N}}) where {Type_Tag, N}
#     n = unsafe_norm(v)
#     return ifelse(value(n) == 0.0, zero(Dual{Type_Tag,Float64,N}), 1.0 / n)
# end
#
# ### safe_normalize
# safe_normalize(v::SVector{3,T}) where {T} = v * safe_inv_norm(v)
#
# ### unsafe_normalize
# unsafe_normalize(v::SVector{3,T}) where {T} = v * unsafe_inv_norm(v)
#
# ### safe_inv_norm²
# function safe_inv_norm²(v::SVector{3,T}) where {T}
#     n2 = norm²(v)
#     return ifelse(n2 == 0.0, zero(T), 1.0 / n2)
# end
#
# ### safe_scalar_divide
# function safe_scalar_divide(a::T, b::T) where {T}
#     (b != 0.0) && (return a / b)
#     (a == 0.0) && (return zero(T))
#     error("attempted to divide non-zero $a by zero $b you should not do this")
# end


# ## CDPlayer

# ## Model

module cdplayer #jl

using ReachabilityAnalysis, MAT
using ReachabilityModels: @relpath

file = matopen(@relpath "cdplayer.mat")

## system matrix
A = read(file, "A")

## input matrix
B = read(file, "B")

## state domain
X = Universe(120)

## input domain
U = BallInf([0.0, 0.0], 1.0)

function model(X0)
    S = @system(x' = Ax + Bu, x ∈ X, u ∈ U)
    return IVP(S, X0)
end

end  #jl

# ## References


using BandedMatrices

function example(n)
    t = [ones(n)'*4; ones(n)']
    s = [2ones(n)'; ones(n)']
    tt = Symmetric(BandedMatrix(0=>t[1,:], -1=>t[2,1:end-1]), :L)
    ss = Symmetric(BandedMatrix(0=>s[1,:], -1=>s[2,1:end-1]), :L)
    t, s, tt, ss
end

@testset "bounds($n)" for n = [1,2,3,4,5,7,10,199]
    t, s, = example(n)
    lb00, ub00 = bounds(t, s)
    #lb01, ub01 = bounds(t, -s)
    lb10, ub10 = bounds(-t, s)
    #lb11, ub11 = bounds(-t, -s)
    @test lb00 <= ub00
    #@test lb00 == lb11 && ub00 == ub11
    #@test lb01 == lb10 && ub01 == ub10
    @test lb10 == -ub00 && ub10 == -lb00
end


using IncrementalInference
# using KernelDensityEstimate, Gadfly # for vstack

fg = emptyFactorGraph()

N=100

doors = [-100.0;0.0;100.0;300.0]'
cov = [3.0]

v1 = addNode!(fg,"x1",doors,N=N)
f1  = addFactor!(fg,[v1], Obsv2(doors, cov', [1.0]))

tem = 2.0*randn(1,N)+getVal(v1)+50.0
v2 = addNode!(fg,"x2", tem, N=N)
addFactor!(fg,[v1;v2],Odo([50.0]',[2.0]',[1.0]))

v3=addNode!(fg,"x3",4.0*randn(1,N)+getVal(v2)+50.0, N=N)
addFactor!(fg,[v2;v3],Odo([50.0]',[4.0]',[1.0]))

v4=addNode!(fg,"x4",2.0*randn(1,N)+getVal(v3)+50.0, N=N)
addFactor!(fg,[v3;v4],Odo([50.0]',[2.0]',[1.0]))

v5=addNode!(fg,"x5",2.0*randn(1,N)+getVal(v4)+50.0, N=N)
addFactor!(fg,[v4;v5],Odo([50.0]',[2.0]',[1.0]))

l1=addNode!(fg, "l1", 0.5*randn(1,N)+getVal(v3)+64.0, N=N)
addFactor!(fg, [v3,l1], Ranged([64.0],[0.5],[1.0]))

l2=addNode!(fg, "l2", 0.5*randn(1,N)+getVal(v3)+64.0, N=N)
addFactor!(fg, [v4,l2], Ranged([16.0],[0.5],[1.0]))

l3=addNode!(fg, "l3", 0.5*randn(1,N)+getVal(v3)+64.0, N=N)
addFactor!(fg, [v5,l3], Ranged([16.0],[0.5],[1.0]))

# writeGraphPdf(fg);

tree = prepBatchTree!(fg,drawpdf=true);

# do belief propagation inference over tree once
# inferOverTreeR!(fg, tree, N=100)


#### NullNode ####

type NullRenderer <: AudioRenderer end
typealias NullNode AudioNode{NullRenderer}
export NullNode

function render(node::NullRenderer, device_input::AudioBuf, info::DeviceInfo)
    # TODO: preallocate buffer
    return zeros(info.buf_size)
end

#### SinOsc ####

# Generates a sin tone at the given frequency

type SinOscRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
    freq::T
    phase::Float32
    buf::AudioBuf

    function SinOscRenderer(freq)
        new(freq, 0.0, AudioSample[])
    end
end

typealias SinOsc AudioNode{SinOscRenderer}
SinOsc(freq::Real) = SinOsc(SinOscRenderer{Float32}(freq))
SinOsc(freq::AudioNode) = SinOsc(SinOscRenderer{AudioNode}(freq))
SinOsc() = SinOsc(440)
export SinOsc

function render(node::SinOscRenderer{Float32}, device_input::AudioBuf,
        info::DeviceInfo)
    if length(node.buf) != info.buf_size
        resize!(node.buf, info.buf_size)
    end
    outbuf = node.buf
    phase = node.phase
    freq = node.freq
    # make sure these are Float32s so that we don't allocate doing conversions
    # in the tight loop
    pi2::Float32 = 2pi
    phase_inc::Float32 = 2pi * freq / info.sample_rate
    i::Int = 1
    while i <= info.buf_size
        outbuf[i] = sin(phase)
        phase = (phase + phase_inc) % pi2
        i += 1
    end
    node.phase = phase
    return outbuf
end

function render(node::SinOscRenderer{AudioNode}, device_input::AudioBuf,
        info::DeviceInfo)
    freq = render(node.freq, device_input, info)::AudioBuf
    block_size = min(length(freq), info.buf_size)
    if(length(node.buf) != block_size)
        resize!(node.buf, block_size)
    end
    outbuf = node.buf

    phase::Float32 = node.phase
    pi2::Float32 = 2pi
    phase_step::Float32 = 2pi/(info.sample_rate)
    i::Int = 1
    while i <= block_size
        outbuf[i] = sin(phase)
        phase = (phase + phase_step*freq[i]) % pi2
        i += 1
    end
    node.phase = phase
    return outbuf
end

#### AudioMixer ####

# Mixes a set of inputs equally

type MixRenderer <: AudioRenderer
    inputs::Vector{AudioNode}
    buf::AudioBuf

    MixRenderer(inputs) = new(inputs, AudioSample[])
    MixRenderer() = MixRenderer(AudioNode[])
end

typealias AudioMixer AudioNode{MixRenderer}
export AudioMixer

function render(node::MixRenderer, device_input::AudioBuf, info::DeviceInfo)
    if length(node.buf) != info.buf_size
        resize!(node.buf, info.buf_size)
    end
    mix_buffer = node.buf
    n_inputs = length(node.inputs)
    i = 1
    max_samples = 0
    fill!(mix_buffer, 0)
    while i <= n_inputs
        rendered = render(node.inputs[i], device_input, info)::AudioBuf
        nsamples = length(rendered)
        max_samples = max(max_samples, nsamples)
        j::Int = 1
        while j <= nsamples
            mix_buffer[j] += rendered[j]
            j += 1
        end
        if nsamples < info.buf_size
            deleteat!(node.inputs, i)
            n_inputs -= 1
        else
            i += 1
        end
    end
    if max_samples < length(mix_buffer)
        return mix_buffer[1:max_samples]
    else
        # save the allocate and copy if we don't need to
        return mix_buffer
    end
end

Base.push!(mixer::AudioMixer, node::AudioNode) = push!(mixer.renderer.inputs, node)

#### Gain ####
type GainRenderer{T<:Union(Float32, AudioNode)} <: AudioRenderer
    in1::AudioNode
    in2::T
    buf::AudioBuf

    GainRenderer(in1, in2) = new(in1, in2, AudioSample[])
end

function render(node::GainRenderer{Float32},
                device_input::AudioBuf,
                info::DeviceInfo)
    input = render(node.in1, device_input, info)::AudioBuf
    if length(node.buf) != length(input)
        resize!(node.buf, length(input))
    end
    i = 1
    while i <= length(input)
        node.buf[i] = input[i] * node.in2
        i += 1
    end
    return node.buf
end

function render(node::GainRenderer{AudioNode},
                device_input::AudioBuf,
                info::DeviceInfo)
    in1_data = render(node.in1, device_input, info)::AudioBuf
    in2_data = render(node.in2, device_input, info)::AudioBuf
    block_size = min(length(in1_data), length(in2_data))
    if length(node.buf) != block_size
        resize!(node.buf, block_size)
    end
    i = 1
    while i <= block_size
        node.buf[i] = in1_data[i] * in2_data[i]
        i += 1
    end
    return node.buf
end

typealias Gain AudioNode{GainRenderer}
Gain(in1::AudioNode, in2::Real) = Gain(GainRenderer{Float32}(in1, in2))
Gain(in1::AudioNode, in2::AudioNode) = Gain(GainRenderer{AudioNode}(in1, in2))
export Gain

#### Offset ####
type OffsetRenderer <: AudioRenderer
    in_node::AudioNode
    offset::Float32
    buf::AudioBuf

    OffsetRenderer(in_node, offset) = new(in_node, offset, AudioSample[])
end

function render(node::OffsetRenderer, device_input::AudioBuf, info::DeviceInfo)
    input = render(node.in_node, device_input, info)::AudioBuf
    if length(node.buf) != length(input)
        resize!(node.buf, length(input))
    end
    i = 1
    while i <= length(input)
        node.buf[i] = input[i] + node.offset
        i += 1
    end
    return node.buf
end

typealias Offset AudioNode{OffsetRenderer}
export Offset

#### Array Player ####

# Plays a AudioBuf by rendering it out piece-by-piece

type ArrayRenderer <: AudioRenderer
    arr::AudioBuf
    arr_index::Int
    buf::AudioBuf

    ArrayRenderer(arr::AudioBuf) = new(arr, 1, AudioSample[])
end

typealias ArrayPlayer AudioNode{ArrayRenderer}
export ArrayPlayer

function render(node::ArrayRenderer, device_input::AudioBuf, info::DeviceInfo)
    range_end = min(node.arr_index + info.buf_size-1, length(node.arr))
    block_size = range_end - node.arr_index + 1
    if length(node.buf) != block_size
        resize!(node.buf, block_size)
    end
    copy!(node.buf, 1, node.arr, node.arr_index, block_size)
    node.arr_index = range_end + 1
    return node.buf
end

# Allow users to play a raw array by wrapping it in an ArrayPlayer
function play(arr::AudioBuf, args...)
    player = ArrayPlayer(arr)
    play(player, args...)
end

# If the array is the wrong floating type, convert it
function play{T <: FloatingPoint}(arr::Array{T}, args...)
    arr = convert(AudioBuf, arr)
    play(arr, args...)
end

# If the array is an integer type, scale to [-1, 1] floating point

# integer audio can be slightly (by 1) more negative than positive,
# so we just scale so that +/- typemax(T) becomes +/- 1
function play{T <: Signed}(arr::Array{T}, args...)
    arr = arr / typemax(T)
    play(arr, args...)
end

function play{T <: Unsigned}(arr::Array{T}, args...)
    zero = (typemax(T) + 1) / 2
    range = floor(typemax(T) / 2)
    arr = (arr .- zero) / range
    play(arr, args...)
end

#### Noise ####

type WhiteNoiseRenderer <: AudioRenderer end
typealias WhiteNoise AudioNode{WhiteNoiseRenderer}
export WhiteNoise

function render(node::WhiteNoiseRenderer, device_input::AudioBuf, info::DeviceInfo)
    return rand(AudioSample, info.buf_size) .* 2 .- 1
end

#### AudioInput ####

# Renders incoming audio input from the hardware

type InputRenderer <: AudioRenderer
    channel::Int
    InputRenderer(channel::Integer) = new(channel)
    InputRenderer() = new(1)
end

function render(node::InputRenderer, device_input::AudioBuf, info::DeviceInfo)
    @assert size(device_input, 1) == info.buf_size
    return device_input[:, node.channel]
end

typealias AudioInput AudioNode{InputRenderer}
export AudioInput

#### LinRamp ####

type LinRampRenderer <: AudioRenderer
    key_samples::Array{AudioSample}
    key_durations::Array{Float32}

    duration::Float32
    buf::AudioBuf

    LinRampRenderer(start, finish, dur) = LinRampRenderer([start,finish], [dur])

    LinRampRenderer(key_samples, key_durations) =
        LinRampRenderer(
            [convert(AudioSample,s) for s in key_samples],
            [convert(Float32,d) for d in key_durations]
        )

    function LinRampRenderer(key_samples::Array{AudioSample}, key_durations::Array{Float32})
        @assert length(key_samples) == length(key_durations) + 1
        new(key_samples, key_durations, sum(key_durations), AudioSample[])
    end
end

typealias LinRamp AudioNode{LinRampRenderer}
export LinRamp

function render(node::LinRampRenderer, device_input::AudioBuf, info::DeviceInfo)
    # Resize buffer if (1) it's too small or (2) we've hit the end of the ramp
    ramp_samples::Int = int(node.duration * info.sample_rate)
    block_samples = min(ramp_samples, info.buf_size)
    if length(node.buf) != block_samples
        resize!(node.buf, block_samples)
    end

    # Fill the buffer as long as there are more segments
    dt::Float32 = 1/info.sample_rate
    i::Int = 1
    while i <= length(node.buf) && length(node.key_samples) > 1

        # Fill as much of the buffer as we can with the current segment
        ds::Float32 = (node.key_samples[2] - node.key_samples[1]) / node.key_durations[1] / info.sample_rate
        while i <= length(node.buf)
            node.buf[i] = node.key_samples[1]
            node.key_samples[1] += ds
            node.key_durations[1] -= dt
            node.duration -= dt
            i += 1

            # Discard segment if we're finished
            if node.key_durations[1] <= 0
                if length(node.key_durations) > 1
                    node.key_durations[2] -= node.key_durations[1]
                end
                shift!(node.key_samples)
                shift!(node.key_durations)
                break
            end
        end
    end

    return node.buf
end


#Demo 6: Smith chart for publication (labels etc removed - will be in caption).
#-------------------------------------------------------------------------------
using InspectDR
using Colors

#==Input
===============================================================================#

#Constants
#-------------------------------------------------------------------------------
black = RGB24(0, 0, 0)
white = RGB24(1, 1, 1)
red = RGB24(1, 0, 0)
green = RGB24(0, 1, 0)
blue = RGB24(0, 0, 1)

#Input data
#-------------------------------------------------------------------------------
f = collect(50e6:50e6:10e9)
C1 = 5e-12; GC1 = 100
C2 = .2e-12; RC2 = 10
L = 1.5e-9; RL = 15

#==Equations
===============================================================================#
Γ(Z; Zref::Real=50.0) = (Z - Zref) ./ (Z + Zref)

#Calculations
#-------------------------------------------------------------------------------
ω = 2π*f; j = im
YC1 = (j*ω*C1)+1/GC1
ZC1 = 1./YC1
ZC2 = 1./(j*ω*C2)+RC2
ZL = j*ω*L+RL

#==Generate plot
===============================================================================#
mplot = InspectDR.Multiplot(title="Smith Chart (Publication Layout)")

plot = add(mplot, InspectDR.Plot2D)
	plot.axes = InspectDR.axes(:smith, :Z, ref=50)
	plot.ext_full = InspectDR.PExtents2D(xmin=-1.1,xmax=1.1,ymin=-1.1,ymax=1.1)
	#plot.layout.legend.enabled=true
	plot.layout.legend.width=110
	lyt = plot.layout

#Zero-out title areas, etc:
	lyt.htitle = 0
	lyt.waxlabel = 0
	lyt.haxlabel = 0
	lyt.wnolabels = 0
	lyt.wticklabel = 0
	lyt.hticklabel = 0

#Make data area sqare when saved:
	lyt.wdata = 500
	lyt.hdata = 500

	wfrm = add(plot, f, Γ(ZC1), id="cap: $(C1/1e-12)pF")
	wfrm.line = line(color=blue, width=3)
	wfrm = add(plot, f, Γ(ZC2), id="cap: $(C2/1e-12)pF")
	wfrm.line = line(color=red, width=3)
	wfrm = add(plot, f, Γ(ZL), id="ind: $(L/1e-9)nH")
	wfrm.line = line(color=green, width=3)

gplot = display(InspectDR.GtkDisplay(), mplot)
InspectDR.write_png("export_pubsmith.png", plot)
#InspectDR.write_svg("export_pubsmith.svg", plot)
#InspectDR.write_eps("export_pubsmith.eps", plot)
#InspectDR.write_pdf("export_pubsmith.pdf", plot)

:DONE


@testset "Wshrink: Basic properties" begin

    GammaEqn = CovarianceEstimation.GammaEqn

    # At γ -> ∞ the expression in GammaEq approaches ϵ²
    p = ifelse(endswith(pwd(), "CovarianceEstimation"), "test", "")

    test_mat1 = readdlm(joinpath(p, "test_matrices/20x100.csv"))
    ref_cov1  = readdlm(joinpath(p, "test_matrices/20x100_corpcor.csv"))
    test_mat2 = readdlm(joinpath(p, "test_matrices/100x20.csv"))
    ref_cov2  = readdlm(joinpath(p, "test_matrices/100x20_corpcor.csv"))
    test_mat3 = readdlm(joinpath(p, "test_matrices/50x50.csv"))
    ref_cov3  = readdlm(joinpath(p, "test_matrices/50x50_corpcor.csv"))

    test_matrices = [test_mat1, test_mat2, test_mat3]
    ref_covs = [ref_cov1, ref_cov2, ref_cov3]

    estimators = WassersteinShrinkage.([10., 5., 1., 0.1, 0.01, 0.001, 0.0001, 1e-5])
    for (X, C) ∈ zip(test_matrices, ref_covs)
        estimates = cov.(estimators, [X]);
        norm.(estimates .- [cov(X)])
        norm.(estimates .- [C])
        λ = max.(10e-10, eigen(cov(X)).values)
        @test sqrt(GammaEqn(0.5, λ)([0.], [1e10])/1e10) ≈ 0.5
        @test sqrt(GammaEqn(0.1, λ)([0.], [1e15])/1e15) ≈ 0.1
        @test sqrt(GammaEqn(0.01, λ)([0.], [1e20])/1e20) ≈ 0.01
        n = length(λ)
        @test GammaEqn(1., λ, n)([0.], [0]) == -n
        @test GammaEqn(1., λ, n)([0.], [0]) == -n
        @test all(inv.(prec.(estimators, [X])) .== estimates)
    end
end


#Brownian Bridge path construction
export BrownianBridgeConstruction, transform!, transformToUnit!

mutable struct BrownianBridgeConstruction
    sqrtdt::Vector{Float64}
    stdDev::Vector{Float64}
    bridgeIndex::Vector{Int}
    leftIndex::Vector{Int}
    rightIndex::Vector{Int}
    leftWeight::Vector{Float64}
    rightWeight::Vector{Float64}
    pmap::Vector{Int}
end
#=
This is a port of Quantlib code

 Copyright (C) 2003 Ferdinando Ametrano
 Copyright (C) 2006 StatPro Italia srl
 Copyright (C) 2009 Bojan Nikolic

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
=#
#===========================================================================
 The following copyright notice applies to the original code,

 Copyright (C) 2002 Peter Jäckel "Monte Carlo Methods in Finance".
 All rights reserved.

 Permission to use, copy, modify, and distribute this software is freely
 granted, provided that this notice is preserved.
 ===========================================================================#

#t[1] is first non-zero time, implicitly assumes that first time = 0
function BrownianBridgeConstruction(t::Vector{Float64})
    size = length(t)
    sqrtdt = Vector{Float64}(undef, size)
    sqrtdt[1] = sqrt(t[1])
    @inbounds for i = 2:size
        sqrtdt[i] = sqrt(t[i] - t[i-1])
    end

    # map is used to indicate which points are already constructed.
    # If map[i] is one, path point i is yet unconstructed.
    # map[i]-1 is the index of the variate that constructs
    # the path point i.
    pmap = Vector{Int}(undef, size)
    fill!(pmap, 1)
    bridgeIndex = Vector{Int}(undef, size)
    leftIndex = Vector{Int}(undef, size)
    rightIndex = Vector{Int}(undef, size)
    stdDev = Vector{Float64}(undef, size)
    leftWeight = Vector{Float64}(undef, size)
    rightWeight = Vector{Float64}(undef, size)

    #  The first point in the construction is the global step.
    pmap[size] = 2
    #  The global step is constructed from the first variate.
    bridgeIndex[1] = size
    #  The variance of the global step
    stdDev[1] = sqrt(t[size])
    #  The global step to the last point in time is special.
    leftWeight[1] = 0.0
    rightWeight[1] = 0.0
    j = 1
    for i = 2:size
        # Find the next unpopulated entry in the map.
        while pmap[j] != 1
            j += 1
        end
        k = j
        # Find the next populated entry in the map from there.
        while pmap[k] == 1
            k += 1
        end
        # l-1 is now the index of the point to be constructed next.
        l = j + ((k - 1 - j) >> 1)
        pmap[l] = i
        # The i-th Gaussian variate will be used to set point l-1.
        bridgeIndex[i] = l
        leftIndex[i] = j
        rightIndex[i] = k
        if j != 1
            leftWeight[i] = (t[k] - t[l]) / (t[k] - t[j-1])
            rightWeight[i] = (t[l] - t[j-1]) / (t[k] - t[j-1])
            stdDev[i] = sqrt(((t[l] - t[j-1]) * (t[k] - t[l])) / (t[k] - t[j-1]))
        else
            leftWeight[i] = (t[k] - t[l]) / t[k]
            rightWeight[i] = t[l] / t[k]
            stdDev[i] = sqrt(t[l] * (t[k] - t[l]) / t[k])
        end
        j = k + 1
        if j > size
            j = 1 #  wrap around
        end
    end
    return BrownianBridgeConstruction(
        sqrtdt,
        stdDev,
        bridgeIndex,
        leftIndex,
        rightIndex,
        leftWeight,
        rightWeight,
        pmap,
    )
end

function transformToUnit!(
    bb::BrownianBridgeConstruction,
    in::Vector{Float64},
    out::Vector{Float64},
)
    transform!(bb, in, out)
    size = length(bb.stdDev)
    @. out /= bb.sqrtdt
    return out
end

function transform!(
    bb::BrownianBridgeConstruction,
    in::Vector{Float64},
    out::Vector{Float64},
)
    size = length(bb.stdDev)
    # We use output to store the path...
    @inbounds out[size] = bb.stdDev[1] * in[1]
    @inbounds for i = 2:size
        j = bb.leftIndex[i]
        k = bb.rightIndex[i]
        l = bb.bridgeIndex[i]
        if j != 1
            @inbounds out[l] =
                bb.leftWeight[i] * out[j-1] +
                bb.rightWeight[i] * out[k] +
                bb.stdDev[i] * in[i]
        else
            @inbounds out[l] = bb.rightWeight[i] * out[k] + bb.stdDev[i] * in[i]
        end
    end
    # ...after which, we calculate the variations
    @inbounds for i = size:-1:2
        out[i] -= out[i-1]
    end
    return out
end

function transform!(
    bb::BrownianBridgeConstruction,
    in::Vector{Float64},  #of (d * ntimes). 1,...,d used for first time, d+1,...d+d for second time.
    out::Array{Float64,2}, #size (d,ntimes) retrieve column(ti) = out[:,ti] (column major order)
)
    d = size(out, 1) #number of rows
    bsize = length(bb.stdDev)
    # We use output to store the path...
    @inbounds for di = 1:d
        out[di, bsize] = bb.stdDev[1] * in[di]
    end
    @inbounds for i = 2:bsize
        j = bb.leftIndex[i]
        k = bb.rightIndex[i]
        l = bb.bridgeIndex[i]
        if j != 1
            for di = 1:d
                out[di, l] =
                    bb.leftWeight[i] * out[di, j-1] +
                    bb.rightWeight[i] * out[di, k] +
                    bb.stdDev[i] * in[(i-1)*d+di]
            end
        else
            for di = 1:d
                out[di, l] =
                    bb.rightWeight[i] * (out[di, k]) + bb.stdDev[i] * in[(i-1)*d+di]
            end
        end
    end
    # ...after which, we calculate the variations
    @inbounds for i = bsize:-1:2
        for di = 1:d
            out[di, i] -= out[di, i-1]
        end
    end
    return out
end


using Transformations
using Base.Test

using LearnBase
import LossFunctions: L2DistLoss
using Transformations.TestTransforms
import Transformations.Testing: check_gradient
using Distributions
using StochasticOptimization.Iteration
import MultivariateStats

# for reproducibility
srand(1)

function verify_gradient(err)
    isempty(err) && return
    if maximum(abs(err)) > 1e-3
        @show err extrema(err)
        @test false
    end
end

function test_gradient(t::Transformation, ϵ=1e-5)
    perr, xerr = check_gradient(t, ϵ=ϵ)
    verify_gradient(perr)
    verify_gradient(xerr)
end

@testset "Concat" begin
    n1, n2 = 2, 3
    n = n1 + n2
    cc = Concat(n)

    node1 = Transformations.OutputNode(n1)
    node1.val[:] = rand(n1)
    link_nodes!(node1, input_node(cc))
    node2 = Transformations.OutputNode(n2)
    node2.val[:] = rand(n2)
    link_nodes!(node2, input_node(cc))
    transform!(cc.input)

    @test transform!(cc) == vcat(node1.val, node2.val)

    ∇y = ones(n)
    grad!(cc, ∇y)
    @test input_grad(cc) == ∇y

    test_gradient(cc)
end

@testset "Gate" begin
    n = 3
    g = Gate(n)

    node1 = Transformations.OutputNode(n)
    node1.val[:] = rand(n)
    link_nodes!(node1, input_node(g))
    node2 = Transformations.OutputNode(n)
    node2.val[:] = rand(n)
    link_nodes!(node2, input_node(g))
    transform!(g.input)

    @test transform!(g) ≈ node1.val .* node2.val

    ∇y = ones(n)
    grad!(g, ∇y)
    @test input_grad(g) == ∇y

    # test_gradient(g)
end

@testset "PCA" begin
    nin, nout = 4, 2
    n = 50
    μ = zeros(nin)
    Λ = UpperTriangular(rand(nin,nin))
    Σ = Λ'*Λ
    mv = MultivariateNormal(μ, Σ)
    x = rand(mv, n)

    # learn the whitening by passing over the data a few times
    # note: since we do this online, it should approach the "true"
    # value in the limit
    totn = 1000n
    w = Whiten(nin, nout, lookback=100n, method=:pca)
    for i=1:totn
        learn!(w, x[:,mod1(i,n)])
    end

    # compute the projected output
    y = zeros(nout,n)
    for (xi,yi) in each_obs(x,y)
        yi[:] = transform!(w,xi)
    end

    # do it using MultivariateStats (the reference)
    wref = MultivariateStats.fit(MultivariateStats.PCA, x, maxoutdim=nout)
    yref = MultivariateStats.transform(wref, x)

    # check that we're close enough to the "true" projection
    @test norm(abs(y./yref)-1) < 1

    # check that the covariance matrices are close
    @test maximum(abs(cov(y') - cov(yref'))) < 1e-2

    # check that the means are close
    @test maximum(abs(mean(y,2) - mean(yref,2))) < 1e-2
end

@testset "Whitened PCA" begin
    nin, nout = 4, 2
    n = 50
    μ = zeros(nin)
    Λ = UpperTriangular(rand(nin,nin))
    Σ = Λ'*Λ
    mv = MultivariateNormal(μ, Σ)
    x = rand(mv, n)

    # learn the whitening by passing over the data a few times
    # note: since we do this online, it should approach the "true"
    # value in the limit
    totn = 50n
    w = Whiten(nin, nout, lookback=totn, method=:whitened_pca)
    for i=1:totn
        learn!(w, x[:,mod1(i,n)])
    end

    # compute the projected output
    y = zeros(nout,n)
    for (xi,yi) in each_obs(x,y)
        yi[:] = transform!(w,xi)
    end

    # check that the covariance is close to the identity
    # @show cov(y')
    @test maximum(abs(cov(y')-I)) < 1e-1

    # check that the mean is close to zero
    # @show mean(y,2)
    @test maximum(abs(mean(y,2))) < 1e-1
end

@testset "Whitened ZCA" begin
    nin, nout = 4, 4
    n = 50
    μ = zeros(nin)
    Λ = UpperTriangular(rand(nin,nin))
    Σ = Λ'*Λ
    mv = MultivariateNormal(μ, Σ)
    x = rand(mv, n)

    # learn the whitening by passing over the data a few times
    # note: since we do this online, it should approach the "true"
    # value in the limit
    totn = 100n
    w = Whiten(nin, nout, lookback=10n, method=:zca)
    for i=1:totn
        learn!(w, x[:,mod1(i,n)])
    end

    # compute the projected output
    y = zeros(nout,n)
    for (xi,yi) in each_obs(x,y)
        yi[:] = transform!(w,xi)
    end

    # check that the mean is close to zero
    # @show mean(y,2)
    @test maximum(abs(mean(y,2))) < 1e-1
end

@testset "Distributions" begin
    n = 4
    μ = rand(n)
    σ = rand(n)
    t = MvNormalTransformation(μ, σ)
    @test typeof(t.dist.Σ) <: Distributions.PDiagMat
    @test t.n == n
    @test t.nμ == n
    @test t.nU == n
    @test input_length(t) == 2n
    @test output_length(t) == n
    newx = rand(2n)
    transform!(t, newx)
    grad!(t)
    @test t.dist.Σ.diag ≈ newx[n+1:end] .^ 2
    @test t.dist.Σ.inv_diag ≈ 1 ./ (newx[n+1:end] .^ 2)

    U = rand(n,n)
    t = MvNormalTransformation(μ, U*U')
    @test typeof(t.dist.μ) <: Vector{Float64}
    @test typeof(t.dist.Σ) <: Distributions.PDMat
    @test t.n == n
    @test t.nμ == n
    @test t.nU == div(n*(n+1), 2)
    @test input_length(t) == n + div(n*(n+1), 2)
    @test output_length(t) == n
    newx = rand(input_length(t))
    transform!(t, newx)
    grad!(t)
    @test t.dist.μ == newx[1:n]
    cf = t.dist.Σ.chol[:UL]
    @test cf[1,2] == newx[n+2]
    @test cf[end] == newx[end]

    # test_gradient(t)
end

@testset "Affine" begin
    nin, nout = 2, 3
    input = rand(nin)
    target = rand(nout)
    a = Affine(nin, nout)
    loss = L2DistLoss()
    w, b = a.params.views
    x, y = input_value(a), output_value(a)
    ∇w, ∇b = a.params.∇_views
    ∇x, ∇y = input_grad(a), output_grad(a)
    nparams = nout*(nin+1)

    for i=1:2
        output = transform!(a, input)

        @test y == w * x + b
        @test size(x) == (nin,)
        @test size(w) == (nout,nin)
        @test size(b) == (nout,)
        @test size(y) == (nout,)
        @test size(∇x) == (nin,)
        @test size(∇w) == (nout,nin)
        @test size(∇b) == (nout,)
        @test size(∇y) == (nout,)
        @test size(output) == (nout,)
        @test size(params(a)) == (nparams,)
        @test size(grad(a)) == (nparams,)

        l = value(loss, target, output)
        dl = deriv(loss, target, output)
        grad!(a, dl)

        @test grad(a.output) == dl
        @test ∇w ≈ repmat(input', nout, 1) .* repmat(dl, 1, nin)
        @test ∇b == dl
        @test ∇x ≈ w' * dl
    end

    test_gradient(a)
end

@testset "Linear" begin
    nin, nout = 2, 3
    input = rand(nin)
    target = rand(nout)
    t = Linear(nin, nout)
    loss = L2DistLoss()
    w = t.params.views[1]
    x, y = input_value(t), output_value(t)
    ∇w = t.params.∇_views[1]
    ∇x, ∇y = input_grad(t), output_grad(t)
    nparams = nout*nin

    for i=1:2
        output = transform!(t, input)

        wx = w * x
        @test y ≈ wx
        @test size(x) == (nin,)
        @test size(w) == (nout,nin)
        @test size(y) == (nout,)
        @test size(∇x) == (nin,)
        @test size(∇w) == (nout,nin)
        @test size(∇y) == (nout,)
        @test size(output) == (nout,)
        @test size(params(t)) == (nparams,)
        @test size(grad(t)) == (nparams,)

        l = value(loss, target, output)
        dl = deriv(loss, target, output)
        grad!(t, dl)

        @test grad(t.output) == dl
        @test ∇w ≈ repmat(input', nout, 1) .* repmat(dl, 1, nin)
        @test ∇x ≈ w' * dl
    end

    test_gradient(t, 1e-5)
end

@testset "LayerNorm" begin
    n = 3
    input = rand(n)
    t = LayerNorm(n, α=1.0)
    g, b = t.params.views
    a, y = input_value(t), output_value(t)
    ∇g, ∇b = t.params.∇_views
    ∇a, ∇y = input_grad(t), output_grad(t)
    nparams = 2n

    for i=1:2
        output = transform!(t, input)

        @test y ≈ g .* (a .- mean(a)) ./ std(a) .+ b
        @test size(a) == (n,)
        @test size(g) == (n,)
        @test size(b) == (n,)
        @test size(y) == (n,)
        @test size(∇a) == (n,)
        @test size(∇g) == (n,)
        @test size(∇b) == (n,)
        @test size(∇y) == (n,)
        @test size(output) == (n,)
        @test size(params(t)) == (nparams,)
        @test size(grad(t)) == (nparams,)
    end

    # first test with g=ones, b=zeros, then test with random number
    test_gradient(t, 1e-5)
    g[:] = rand(n)
    b[:] = rand(n)
    test_gradient(t, 1e-5)
end

@testset "Activations" begin
    n = 2
    input = rand(n)
    for s in Transformations.activations
        f = Activation{s,Float64}(n)
        output = transform!(f, input)
        @test output == map(@eval($s), input)

        grad!(f, ones(2))
        @test f.input.∇ == map(@eval($(Symbol(s,"′"))), input)

        test_gradient(f)
    end
end

@testset "Chain" begin
    n1, n2, n3 = 4,5,2
    input = rand(n1)

    T = Float64
    chain = Chain(T,
        Affine(T, n1, n2),
        Activation{:relu,T}(n2),
        Affine(T, n2, n3),
        Activation{:logistic,T}(n3)
    )

    @test length(chain.ts) == 4
    @test chain.ts[1].input.val === chain.input.val
    @test chain.ts[end].output.val === chain.output.val

    x = input
    for t in chain.ts
        x = transform!(t, x)
        @test x ≈ output_value(t)
    end
    y = transform!(chain, input)
    @test y ≈ output_value(chain.ts[end])
    @test y ≈ x

    # first compute the chain of transformations manually,
    manual_output = input
    for t in chain.ts
        manual_output = transform!(t, manual_output)
    end

    # now do it using the chain, and make sure it matches
    output = transform!(chain, input)
    @test manual_output == output

    test_gradient(chain)
end

@testset "ResidualLayer" begin
    n = 3
    f = nnet(3, 3, [4], layernorm=false)
    t = ResidualLayer(n, f)
    @test f === t.f

    x = rand(n)
    x2 = copy(x)
    y = copy(transform!(t, x))
    @test x == x2
    y2 = transform!(t, x)
    @test y == y2
    @test y ≈ transform!(f, x) + x2

    ∇y = rand(n)
    grad!(t, ∇y)
    @test output_grad(f) == ∇y

    test_gradient(t, 1e-8)
end

@testset "resnet" begin
    nin, nout = 3, 2
    nblocks = 2
    t = resnet(nin, nout, nblocks, nh=[2], layernorm=false)
    # @show t

    @test length(t.ts) == nblocks+1
    @test isa(t.ts[end], Affine)
    for i=1:nblocks
        @test isa(t.ts[i], ResidualLayer)
    end

    x = rand(nin)
    y = copy(transform!(t, x))

    x̂ = copy(transform!(t.ts[1], x))
    for i=2:nblocks+1
        x̂ = transform!(t.ts[i])
    end
    @test y ≈ x̂

    ∇y = rand(nout)
    grad!(t, ∇y)
    @test output_grad(t) == ∇y

    test_gradient(t)
end

@testset "ConvFilter" begin
    nin = (3,3)
    nfilter = (2,2)
    stride = (1,1)
    nrf, ncf = nfilter
    x = reshape(linspace(1,9,9), nin...)
    f = ConvFilter(nin, nfilter, stride)

    @test f.sizein == nin
    @test f.sizefilter == nfilter
    @test f.sizeout == (2,2)

    w, b = f.params.views
    b[1] = 0.5
    @test size(w) == nfilter
    @test size(b) == (1,)

    y = transform!(f, x)
    nr,nc = f.sizeout
    for r=1:nr, c=1:nc
        @test y[r,c] ≈ sum(w .* view(x, r:r+nrf-1, c:c+ncf-1)) + b[1]
    end

    ∇y = rand(f.sizeout...)
    grad!(f, ∇y)

    ∇x = input_grad(f)
    ∇w, ∇b = f.params.∇_views
    for i=1:nrf, j=1:ncf
        @test ∇w[i,j] ≈ sum(view(x, i:nr+i-1, j:nc+j-1) .* ∇y)
    end
    @test ∇b[1] == sum(∇y)
    for r=1:nin[1], c=1:nin[2]
        @test ∇x[r,c] ≈ sum(view(w, nrf:-1:1, ncf:-1:1) .*
            Transformations.TileView{Float64,2,typeof(∇y)}(∇y, (r-nrf+1,c-ncf+1), (nrf,ncf)))
    end

    # TODO: once stride is implemented, test that
end

@testset "Differentiable" begin
    f(θ) = sum(100θ.^2)
    df(θ) = map(θi -> 200θi, θ)
    t = tfunc(f, 2, df)
    θ = params(t)
    θ[:] = [1.,2.]
    @test transform!(t) == f(θ)
    grad!(t)
    @test grad(t) == df(θ)

    t = tfunc(rosenbrock, 4, rosenbrock_gradient)
    @test typeof(t) <: OnceDifferentiable{Float64}
    θ = params(t)
    @test size(θ) == (4,)
    θ[:] = linspace(0,3,4)
    @test transform!(t) == rosenbrock(θ)
    grad!(t)
    @test grad(t) == rosenbrock_gradient(θ)

    test_gradient(t)
end


module ImageRegression
import ..AbstractOperator.get_misfit
using ..AbstractOperator
using PyPlot, LinearAlgebra, Statistics
using Random, Images, ImageIO, FileIO, ..CommonToAll

import ..Model, ..Options, ..OptionsStat, ..OptionsNonstat

export Img, get_image_data, calc_image_RMS, get_image_prediction_points,
        plot_image_data, plot_image_posterior, slice_image_posterior

mutable struct Img<:Operator2D

    filename          :: String
    x                 :: StepRangeLen
    y                 :: StepRangeLen
    fractrain         :: Float64
    dec               :: Int
    gausskernelwidth  :: Int
    f                 :: Array{Float64, 2}
    d                 :: Array{Float64, 2}
    useML             :: Bool
    σ                 :: Float64
    select            :: Array{Bool}
    N                 :: Int
    r                 :: Array{Float64, 1}
    xtrain            :: Array{Float64, 2}
end

function Img(;
             filename         = "",
             dx               = 0.01,
             fractrain        = 0.02,
             dec::Int         = 2,
             gausskernelwidth = 7,
             useML            = false,
             σ                = 1.0,
             select           = [true],
             N                = 1,
             r                = [-1.],
             xtrain           = [1 1]
             )

    @assert fractrain > 0 && fractrain < 1
    @assert !(filename == "")
    @assert dec > 1
    @assert σ > 0.0
    f = get_image(filename, gausskernelwidth, dec)
    x = 0:dx:dx*size(f,2)-1
    y = 0:dx:dx*size(f,1)-1
    Img(filename, x, y, fractrain, dec, gausskernelwidth, f, f, useML, σ, select, N, r, xtrain)
end

function get_image(filename::String, gausskernelwidth::Int, dec)
    f = Gray.(load(filename))
    f = convert(Array{Float64, 2}, f)[1:dec:end,:1:dec:end]
    # convert image into something that looks like log resistivity
    f = -1 .+ 3*f
    # smooth image
    imfilter(f,Kernel.gaussian(gausskernelwidth))
end

function get_image_data(img::Img;
                           sdmaxfrac = 0.05,
                           rseed     = 12,
                           ybreak    = 12321.0,
                           takeevery = 4
                          )
    @assert ybreak != 12321.0
    @assert sdmaxfrac > 0 && sdmaxfrac < 1
    Random.seed!(rseed)
    f, x, y = img.f, img.x, img.y
    noisyd = NaN .+ zeros(Float64, size(f))
    ntrain = round(Int, img.fractrain*length(f))
    δtry = sdmaxfrac*max(f...)
    Xtrain = zeros(2,0)
    linidx = randperm(length(f))[1:ntrain]
    for (i,l) in enumerate(linidx)
        row, col = Tuple(CartesianIndices(f)[l])
        if img.y[row] > ybreak
            noisyd[row, col] = f[row, col] + δtry*randn()
            Xtrain = hcat(Xtrain, [x[col]; y[row]])
        else
            if rem(row, takeevery) == 0 && rem(col, takeevery) == 0
                noisyd[row, col] = f[row, col] + δtry*randn()
                Xtrain = hcat(Xtrain, [x[col]; y[row]])
            end
        end
    end
    img.d = noisyd
    img.σ = δtry
    img.select = .!isnan.(noisyd[:])
    img.r = noisyd[img.select]
    img.xtrain = Xtrain
    img.N = sum(img.select)
    nothing
end

function get_image_prediction_points(img::Img)
    f, x, y = img.f, img.x, img.y
    Xall = zeros(2, length(f))
    for i in 1:length(f)
        yid, xid = Tuple(CartesianIndices(f)[i])
        Xall[:,i] = [x[xid]; y[yid]]
    end
    Xall
end

function plot_image_data(img::Img;  s=3, fsize=12)
    f, x, y = img.f, img.x, img.y
    ftrain, Xtrain = img.d[img.select], img.xtrain
    f1, ax1 = plt.subplots(1,2,figsize=(6.7,2.7), sharex=true, sharey=true)
    im1 = ax1[1].imshow(f, extent=[x[1],x[end],y[end],y[1]])
    cb1 = colorbar(im1, ax=ax1[1])
    ax1[2].imshow(f, extent=[x[1],x[end],y[end],y[1]], alpha=0.0)
    im2 = ax1[2].scatter(Xtrain[1,:], Xtrain[2,:], c=ftrain, s=s)
    cb2 = colorbar(im2)
    ax1[2].axis([x[1],x[end],y[end],y[1]])
    nicenup(gcf(), fsize=fsize)

end

function calc_image_RMS(img::Img)
    r = (img.d[img.select] - img.f[img.select])/img.σ
    @info "χ^2 error is $(r'*r) for $(img.N) points RMS: $(sqrt(norm(r)^2/img.N))"
    nothing
end

function get_misfit(m::Model, opt::Options, img::Img)
    chi2by2 = 0.0
    if !opt.debug
        d, r = img.d, img.r
        select = img.select
        r .= m.fstar[select] - d[select]
        if img.useML
            chi2by2 = 0.5*img.N*log(norm(r)^2)
        else
            chi2by2 = norm(r)^2/(2img.σ^2)
        end
    end
    return chi2by2
end

function slice_image_posterior( M::AbstractArray,
                                opt::Options,
                                img::Img, roworcol::Symbol;
                                rowcolnum = 1,
                                nbins = 50,
                                rhomin=Inf,
                                rhomax=-Inf,
                                qp1=0.05,
                                qp2=0.95,
                                islscale = false,
                                pdfnormalize=false,
                                temperaturenum=1)
    @assert temperaturenum == 1
    Mslices = Array{Array{Float64}, 1}(undef, length(M))
    for i in 1:length(M)
        m = reshape(M[i],length(img.y), length(img.x))
        if roworcol == :row
            Mslices[i] = m[rowcolnum,:]
        else
            Mslices[i] = m[:,rowcolnum]
        end
    end
    himage, edges, CI, = gethimage(img, Mslices, opt, burninfrac=0.0, temperaturenum=temperaturenum,
                nbins=nbins, rhomin=rhomin, rhomax=rhomax, qp1=qp1, qp2=qp2, islscale=islscale,
                pdfnormalize=pdfnormalize)
end

function get_image_quantile(M::AbstractArray, q=0.5)
    mall = hcat(M...)
    quantM = zeros(length(M[1]))
    for i = 1:length(M[1])
        quantM[i] = quantile(mall[i,:], q)
    end
    quantM
end

function plot_image_posterior(optns::OptionsNonstat, opts::OptionsStat,
                        img::Img;
                        rownum = 10,
                        colnum = 10,
                        temperaturenum = 1,
                        nbins = 50,
                        burninfrac=0.5,
                        qp1=0.05,
                        qp2=0.95,
                        cmappdf = "magma",
                        cmapmean = "bone",
                        figsizerows=[11,6],
                        figsizecols=[7.5,9.5],
                        pdfnormalize=false,
                        fsize=10,
                        aspect=1.0,
                        domean = false,
                        getquant = 0.5)
    @assert temperaturenum == 1
    @assert 0.0 < getquant < 1.0
    M = assembleTat1(optns, :fstar, burninfrac=burninfrac, temperaturenum=temperaturenum)
    if domean
        mns = mean(M)
    else
        mns = get_image_quantile(M, getquant)
    end
    mns = reshape(mns, length(img.y), length(img.x))
    himage_r_ns, edges_r_ns, CI_r_ns = slice_image_posterior(M, optns, img, :row, rowcolnum=rownum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum)
    himage_c_ns, edges_c_ns, CI_c_ns = slice_image_posterior(M, optns, img, :col, rowcolnum=colnum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum)
    M = assembleTat1(opts, :fstar, burninfrac=burninfrac, temperaturenum=temperaturenum)
    if domean
        ms = 0.5*log10.(mean(M))
    else
        a, b = [m[1,:] for m in M], [m[2,:] for m in M]
        ms = 0.5*log10.([get_image_quantile(a, getquant) get_image_quantile(b, getquant)])'
    end
    msx = reshape(ms[1,:], length(img.y), length(img.x))
    msy = reshape(ms[2,:], length(img.y), length(img.x))
    Mx, My = Array{Array{Float64}, 1}(undef, length(M)), Array{Array{Float64}, 1}(undef, length(M))
    for i = 1:length(M)
        Mx[i] = M[i][1,:]
        My[i] = M[i][2,:]
    end
    himage_x_c, edges_x_c, CI_x_c = slice_image_posterior(Mx, opts, img, :col, rowcolnum=colnum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum, islscale=true)
    himage_y_c, edges_y_c, CI_y_c = slice_image_posterior(My, opts, img, :col, rowcolnum=colnum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum, islscale=true)
    himage_x_r, edges_x_r, CI_x_r = slice_image_posterior(Mx, opts, img, :row, rowcolnum=rownum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum, islscale=true)
    himage_y_r, edges_y_r, CI_y_r = slice_image_posterior(My, opts, img, :row, rowcolnum=rownum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum, islscale=true)
    f = figure(figsize=figsizerows)
    s1 = subplot(231)
    im1 = s1.pcolormesh(img.x, img.y, mns, cmap=cmapmean)
    s1.plot([img.x[1], img.x[end]], [img.y[rownum], img.y[rownum]], "--k", alpha=0.5)
    central_tendency = domean ? ("Mean") : ("Percentile "*"$(round(Int, getquant*100))")
    s1.set_title(central_tendency*" pixel value")
    s1.set_ylabel("distance y")
    # s1.set_aspect(aspect)
    s1.set_xlabel("distance x")
    cb1 = colorbar(im1, ax=s1)
    s2 = subplot(232, sharex=s1, sharey=s1)
    im2 = s2.pcolormesh(img.x, img.y, msx, cmap=cmapmean)
    s2.plot([img.x[1], img.x[end]], [img.y[rownum], img.y[rownum]], "--k", alpha=0.5)
    s2.set_title(central_tendency*" "*L"\log_{10}λ_x")
    s2.set_ylabel("distance y")
    s2.set_xlabel("distance x")
    # s2.set_aspect(aspect)
    s3 = subplot(233, sharex=s1, sharey=s1)
    cb2 = colorbar(im2, ax=s2)
    im3 = s3.pcolormesh(img.x, img.y, msy, cmap=cmapmean)
    s3.plot([img.x[1], img.x[end]], [img.y[rownum], img.y[rownum]], "--k", alpha=0.5)
    # s3.set_aspect(aspect)
    s3.invert_yaxis()
    s3.set_title(central_tendency*" "*L"\log_{10}λ_y")
    s3.set_ylabel("distance y")
    s3.set_xlabel("distance x")
    cb3 = colorbar(im3, ax=s3)
    s4 = subplot(234, sharex=s1)
    im4 = s4.pcolormesh(img.x, edges_r_ns[:], himage_r_ns', cmap=cmappdf)
    s4.plot(img.x, mns[rownum,:][:],"--w", alpha=0.5)
    s4.plot(img.x, CI_r_ns,"--c", alpha=0.5)
    s4.set_title("Pixel value PDF")
    s4.set_ylabel("pixel value")
    s4.set_xlabel("distance x")
    cb4 = colorbar(im4, ax=s4)
    cb4.ax.set_title("pdf")
    s5 = subplot(235, sharex=s1)
    im5 = s5.pcolormesh(img.x, edges_x_r[:], himage_x_r', cmap=cmappdf)
    cb5 = colorbar(im5, ax=s5)
    cb5.ax.set_title("pdf")
    s5.plot(img.x, msx[rownum,:][:],"--w", alpha=0.5)
    s5.plot(img.x, CI_x_r,"--c", alpha=0.5)
    s5.set_title(L"\log_{10}λ_x"*" PDF")
    s5.set_ylabel(L"\log_{10}λ_x")
    s5.set_xlabel("distance x")
    s6 = subplot(236, sharex=s1)
    im6 = s6.pcolormesh(img.x, edges_y_r[:], himage_y_r', cmap=cmappdf)
    cb6 = colorbar(im6, ax=s6)
    cb6.ax.set_title("pdf")
    s6.plot(img.x, msy[rownum,:][:],"--w", alpha=0.5)
    s6.plot(img.x, CI_y_r,"--c", alpha=0.5)
    s6.set_title(L"\log_{10}λ_y"*" PDF")
    s6.set_ylabel(L"\log_{10}λ_y")
    s6.set_xlabel("distance x")
    nicenup(f, fsize=fsize)

    f = figure(figsize=figsizecols)
    s1 = subplot(321)
    im1 = s1.pcolormesh(img.x, img.y, mns, cmap=cmapmean)
    s1.plot([img.y[colnum], img.y[colnum]], [img.y[1], img.y[end]], "--k", alpha=0.5)
    s1.set_title(central_tendency*" pixel value")
    s1.set_ylabel("distance y")
    s1.set_xlabel("distance x")
    # s1.set_aspect(aspect)
    cb1 = colorbar(im1, ax=s1)
    s2 = subplot(323, sharex=s1, sharey=s1)
    im2 = s2.pcolormesh(img.x, img.y, msx, cmap=cmapmean)
    s2.plot([img.y[colnum], img.y[colnum]], [img.y[1], img.y[end]], "--k", alpha=0.5)
    s2.set_title(central_tendency*" "*L"\log_{10}λ_x")
    s2.set_ylabel("distance y")
    s2.set_xlabel("distance x")
    # s2.set_aspect(aspect)
    s3 = subplot(325, sharex=s1, sharey=s1)
    cb2 = colorbar(im2, ax=s2)
    im3 = s3.pcolormesh(img.x, img.y, msy, cmap=cmapmean)
    s3.plot([img.y[colnum], img.y[colnum]], [img.y[1], img.y[end]], "--k", alpha=0.5)
    # s3.set_aspect(aspect)
    s3.invert_yaxis()
    s3.set_title(central_tendency*" "*L"\log_{10}λ_y")
    s3.set_ylabel("distance y")
    s3.set_xlabel("distance x")
    cb3 = colorbar(im3, ax=s3)
    s4 = subplot(322, sharey=s1)
    im4 = s4.pcolormesh(edges_c_ns[:], img.y, himage_c_ns, cmap=cmappdf)
    s4.plot(mns[:,colnum][:], img.y, "--w", alpha=0.5)
    s4.plot(CI_c_ns, img.y, "--c", alpha=0.5)
    s4.set_title("Pixel value PDF")
    s4.set_xlabel("pixel value")
    s4.set_ylabel("distance y")
    cb4 = colorbar(im4, ax=s4)
    cb4.ax.set_title("pdf")
    s5 = subplot(324, sharey=s1)
    im5 = s5.pcolormesh(edges_x_c[:], img.y, himage_x_c, cmap=cmappdf)
    cb5 = colorbar(im5, ax=s5)
    cb5.ax.set_title("pdf")
    s5.plot(msx[:,colnum][:], img.y, "--w", alpha=0.5)
    s5.plot(CI_x_c, img.y, "--c", alpha=0.5)
    s5.set_title(L"\log_{10}λ_x"*" PDF")
    s5.set_xlabel(L"\log_{10}λ_x")
    s5.set_ylabel("distance y")
    s6 = subplot(326, sharey=s1)
    im6 = s6.pcolormesh(edges_y_c[:], img.y, himage_y_c, cmap=cmappdf)
    cb6 = colorbar(im6, ax=s6)
    cb6.ax.set_title("pdf")
    s6.plot(msy[:,colnum][:], img.y, "--w", alpha=0.5)
    s6.plot(CI_y_c, img.y, "--c", alpha=0.5)
    s6.set_title(L"\log_{10}λ_y"*" PDF")
    s6.set_xlabel(L"\log_{10}λ_y")
    s6.set_ylabel("distance y")
    nicenup(f, fsize=fsize)
end

function plot_image_posterior(opt::Options,
                        img::Img;
                        rownum = 10,
                        colnum = 10,
                        temperaturenum = 1,
                        nbins = 50,
                        burninfrac=0.5,
                        qp1=0.05,
                        qp2=0.95,
                        cmappdf = "magma",
                        cmapmean = "bone",
                        figsize=[7.5,5.9],
                        pdfnormalize=false,
                        fsize=10,
                        aspect=1.0,
                        domean = false,
                        getquant = 0.5)
    @assert temperaturenum == 1
    @assert 0.0 < getquant < 1.0
    if typeof(opt) == OptionsStat
        @assert opt.updatenonstat == false
        @assert opt.needλ²fromlog == false
    end
    M = assembleTat1(opt, :fstar, burninfrac=burninfrac, temperaturenum=temperaturenum)
    if domean
        m = mean(M)
    else
        a = [m' for m in M]
        m = get_image_quantile(a, getquant)
    end
    m = reshape(m, length(img.y), length(img.x))
    himage_r, edges_r, CI_r = slice_image_posterior(M, opt, img, :row, rowcolnum=rownum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum)
    himage_c, edges_c, CI_c = slice_image_posterior(M, opt, img, :col, rowcolnum=colnum, nbins = nbins, qp1=qp1, qp2=qp2,
                                pdfnormalize=pdfnormalize, temperaturenum=temperaturenum)
    f = figure(figsize=figsize)
    s1 = subplot(223)
    central_tendency = domean ? ("Mean") : ("Percentile "*"$(round(Int, getquant*100))")
    im1 = s1.pcolormesh(img.x, img.y, m, cmap=cmapmean)
    s1.plot([img.x[1], img.x[end]], [img.y[rownum], img.y[rownum]], "--k", alpha=0.5)
    s1.plot([img.y[colnum], img.y[colnum]], [img.y[1], img.y[end]], "--k", alpha=0.5)
    s1.set_title(central_tendency*" pixel value")
    s1.set_ylabel("distance y")
    s1.invert_yaxis()
    # s1.set_aspect(aspect)
    s1.set_xlabel("distance x")
    cb1 = colorbar(im1, ax=s1)
    s2 = subplot(221, sharex=s1)
    im2 = s2.pcolormesh(img.x, edges_r[:], himage_r', cmap=cmappdf)
    s2.plot(img.x, m[rownum,:][:],"--w", alpha=0.5)
    s2.plot(img.x, CI_r, "--c", alpha=0.5)
    s2.set_title("Pixel value PDF")
    s2.set_ylabel("pixel value")
    s2.set_xlabel("distance x")
    cb2 = colorbar(im2, ax=s2)
    cb2.ax.set_title("pdf")
    s3 = subplot(224, sharey=s1)
    im3 = s3.pcolormesh(edges_c[:], img.y, himage_c, cmap=cmappdf)
    s3.plot(m[:,colnum][:], img.y, "--w", alpha=0.5)
    s3.plot(CI_c, img.y, "--c", alpha=0.5)
    s3.set_title("Pixel value PDF")
    s3.set_xlabel("pixel value")
    s3.set_ylabel("distance y")
    cb3 = colorbar(im3, ax=s3)
    cb3.ax.set_title("pdf")
    nicenup(f, fsize=fsize)
end

end


using Isaac.TestSets
using Isaac.TestSets: precond

println("Testing nsolimod for minimisation")
@testset "nsolidmod-minim" begin

#[1] p-Laplacian
plap(U; n=length(U)) = (n-1) * sum( (0.1 + diff(U).^2).^2 ) - sum(U) / (n-1)
plap1(U; n=length(U), dU = diff(U), dW = 4 * (0.1 + dU.^2) .* dU) =
                        (n-1) * ([0.0; dW] - [dW; 0.0]) - ones(U) / (n-1)
precond_plap(x::Vector) = precond_plap(length(x))
precond_plap(n::Number) = spdiagm( ( -ones(n-1), 2*ones(n), -ones(n-1) ),
                              (-1,0,1), n, n) * (n+1)
E_plap(X) = plap([0;X;0])
dE_plap(X) = plap1([0;X;0])[2:end-1]

if isCI
   N1, N2 = 10, 20
else
   N1, N2 = 10, 50
end

for (n, prec) in [ (N1, false), (N1, true), (N2, false), (N2, true) ]
   x0 = zeros(n)
   P = prec ? precond_plap(n) : I

   println("Testing nkminim, p-Laplacian, n = $n, precon = $prec")
   x, ndE = nkminim(E_plap, dE_plap, x0; P = P)
   println("   num_dE = ", ndE)
   println("   |∇E| = ", norm(dE_plap(x), Inf))
   @test norm(dE_plap(x), Inf) < 1e-5
   xm = x

   println("Testing nsolistab, p-Laplacian, n = $n, precon = $prec")
   x, ndE = nsolistab(dE_plap, x0; P = P)
   println("   num_dE = ", ndE)
   println("   |∇E| = ", norm(dE_plap(x), Inf))
   @test norm(dE_plap(x), Inf) < 1e-5

   @test norm(xm - x, Inf) < 1e-5
end

# [2] 2D LJ Vacancy Test

R1, R2 = 5.1, 12.1

for (R, prec) in [(R1, false), (R1, true), (R2, false), (R2, true)]
   if isCI && R == R2; break; end

   V = LJVacancy2D(R=R, bc = :clamped)
   E, dE = objective(V)
   x0, v0 = ic_dimer(V, :far)
   P, Pprep = prec ? (precond(V, x0),
                      (P, x) -> Isaac.TestSets.precond(V, x)) : (I, (P,x)->P)

   println("Testing nkminim, 2D LJ Vacancy, R = $(round(Int,R)), precon = $(!(P==I))")
   x, ndE = nkminim(E, dE, x0; P = P, precon_prep = Pprep)
   println("   num_dE = ", ndE)
   println("   |∇E| = ", norm(dE(x), Inf))
   @test norm(dE(x), Inf) < 1e-5
   xm = x

   println("Testing nsolistab, 2D LJ Vacancy, R = $(round(Int,R)), precon = $(!(P==I))")
   x, ndE = nsolistab(dE, x0; P = P, precon_prep = Pprep)
   println("   num_dE = ", ndE)
   println("   |∇E| = ", norm(dE(x), Inf))
   @test norm(dE(x), Inf) < 1e-5

   @test norm(xm - x, Inf) < 1e-5
end

# [2] 2D LJ Vacancy Test (IC near saddle but flow to minim)
if notCI
   for (R, prec) in [(R1, false), (R1, true), (R2, false), (R2, true)]
      V = LJVacancy2D(R=R, bc = :clamped)
      E, dE = objective(V)
      x0, v0 = ic_dimer(V, :near)
      P, Pprep = prec ? (precond(V, x0),
               (P, x) -> Isaac.TestSets.precond(V, x)) : (I, (P,x)->P)

      println("Testing nkminim, 2D LJ Vacancy, R = $(round(Int,R)), precon = $(!(P==I))")
      x, ndE = nkminim(E, dE, x0; P = P, precon_prep = Pprep, verbose=0, update_α_old = true)
      println("   num_dE = ", ndE)
      println("   |∇E| = ", norm(dE(x), Inf))
      @test norm(dE(x), Inf) < 1e-5
      xm = x

      println("Testing nsolistab, 2D LJ Vacancy, R = $(round(Int,R)), precon = $(!(P==I))")
      x, ndE = nsolistab(dE, x0; P = P, precon_prep = Pprep, verbose=0)
      println("   num_dE = ", ndE)
      println("   |∇E| = ", norm(dE(x), Inf))
      @test norm(dE(x), Inf) < 1e-5

      @test norm(xm - x, Inf) < 1e-5
   end
end

end  # @testset "nsolidstab-index1"


# # Big Leaf Model

## load packages
using Land.CanopyLayers
using PlotPlants
FT = Float32;
#------------------------------------------------------------------------------

# ## Sunlit and shaded layers
# ### vs. leaf area index
lais   = collect(FT, 0.1:0.1:5.0);
lai_sl = similar(lais);
par_sl = similar(lais); par_sh = similar(lais);
rad_sl = similar(lais); rad_sh = similar(lais);
for i in eachindex(lais)
    lai_sl[i], par_sl[i], par_sh[i], rad_sl[i], rad_sh[i] =
        big_leaf_partition(lais[i], FT(30), FT(800));
end

_fig,_axes = create_canvas("vs LAI"; ncol=3);
_ax1,_ax2,_ax3 = _axes;
_ax1.plot(lais, lai_sl, "k-");
_ax2.plot(lais, par_sl, "k-", label="Sunlit");
_ax2.plot(lais, par_sh, "k:", label="Shaded");
_ax3.plot(lais, rad_sl, "k-", label="Sunlit");
_ax3.plot(lais, rad_sh, "k:", label="Shaded");
_ax2.legend(loc="upper right");
_ax3.legend(loc="upper right");
set_xlabels!(_axes, ["Leaf area index" for i in 1:3]);
set_ylabels!(_axes, ["Sunlit fraction", "PAR (μmol m⁻² s⁻¹)", "Rabs (W m⁻²)"]);
_fig.set_tight_layout(true);
_fig
#------------------------------------------------------------------------------

# ### vs. zenith angle
angles = collect(FT, 5:5:75);
lai_sl = similar(angles);
par_sl = similar(angles); par_sh = similar(angles);
rad_sl = similar(angles); rad_sh = similar(angles);
for i in eachindex(angles)
    lai_sl[i], par_sl[i], par_sh[i], rad_sl[i], rad_sh[i] =
        big_leaf_partition(FT(3), angles[i], FT(800));
end

_fig,_axes = create_canvas("vs Zenith angle"; ncol=3);
_ax1,_ax2,_ax3 = _axes;
_ax1.plot(angles, lai_sl, "k-");
_ax2.plot(angles, par_sl, "k-", label="Sunlit");
_ax2.plot(angles, par_sh, "k:", label="Shaded");
_ax3.plot(angles, rad_sl, "k-", label="Sunlit");
_ax3.plot(angles, rad_sh, "k:", label="Shaded");
_ax2.legend(loc="upper left");
_ax3.legend(loc="upper left");
set_xlabels!(_axes, ["Zenith angle (°)" for i in 1:3]);
set_ylabels!(_axes, ["Sunlit fraction", "PAR (μmol m⁻² s⁻¹)", "Rabs (W m⁻²)"]);
_fig.set_tight_layout(true);
_fig
#------------------------------------------------------------------------------

# ### vs. total radiation
rads   = collect(FT, 50:50:1000);
lai_sl = similar(rads);
par_sl = similar(rads); par_sh = similar(rads);
rad_sl = similar(rads); rad_sh = similar(rads);
for i in eachindex(rads)
    lai_sl[i], par_sl[i], par_sh[i], rad_sl[i], rad_sh[i] =
        big_leaf_partition(FT(3), FT(30), rads[i]);
end

_fig,_axes = create_canvas("vs Total radiation"; ncol=3);
_ax1,_ax2,_ax3 = _axes;
_ax1.plot(rads, lai_sl, "k-");
_ax2.plot(rads, par_sl, "k-", label="Sunlit");
_ax2.plot(rads, par_sh, "k:", label="Shaded");
_ax3.plot(rads, rad_sl, "k-", label="Sunlit");
_ax3.plot(rads, rad_sh, "k:", label="Shaded");
_ax2.legend(loc="upper left");
_ax3.legend(loc="upper left");
set_xlabels!(_axes, ["Total radiation (W m⁻²)" for i in 1:3]);
set_ylabels!(_axes, ["Sunlit fraction", "PAR (μmol m⁻² s⁻¹)", "Rabs (W m⁻²)"]);
_fig.set_tight_layout(true);
_fig
#------------------------------------------------------------------------------

# ### vs. direct light fraction
dirs   = collect(FT, 0.2:0.05:0.8);
lai_sl = similar(dirs);
par_sl = similar(dirs); par_sh = similar(dirs);
rad_sl = similar(dirs); rad_sh = similar(dirs);
for i in eachindex(dirs)
    lai_sl[i], par_sl[i], par_sh[i], rad_sl[i], rad_sh[i] =
        big_leaf_partition(FT(3), FT(30), FT(800), dirs[i]);
end

_fig,_axes = create_canvas("vs Direct light fraction"; ncol=3);
_ax1,_ax2,_ax3 = _axes;
_ax1.plot(dirs, lai_sl, "k-");
_ax2.plot(dirs, par_sl, "k-", label="Sunlit");
_ax2.plot(dirs, par_sh, "k:", label="Shaded");
_ax3.plot(dirs, rad_sl, "k-", label="Sunlit");
_ax3.plot(dirs, rad_sh, "k:", label="Shaded");
_ax2.legend(loc="upper left");
_ax3.legend(loc="upper left");
set_xlabels!(_axes, ["Direct light fraction" for i in 1:3]);
set_ylabels!(_axes, ["Sunlit fraction", "PAR (μmol m⁻² s⁻¹)", "Rabs (W m⁻²)"]);
_fig.set_tight_layout(true);
_fig
#------------------------------------------------------------------------------


module StableArrays

using Base
using LinearAlgebra

export StableArray, stabilize!, stabilize, unstabilize

include("StableNumber.jl")

"""
    StableArray(base::AbstractArray, exponent)
"""
struct StableArray{E, A <: AbstractArray}
    base::A
    exponent::E
end

base(x::StableArray) = x.base
exponent(x::StableArray) = x.exponent

"""
    stabilize!(x::AbstractArray)

Construct a [`StableArray`](@ref)
"""
function stabilize!(x::AbstractArray)
    factor = maximum(abs, x)
    if factor == 0
        StableArray(x, factor)
    else
        x ./= factor
        StableArray(x, log(factor))
    end
end

function stabilize(x::AbstractArray)
    factor = maximum(abs, x)
    if factor == 0
        StableArray(x, factor)
    else
        x /= factor
        StableArray(x, log(factor))
    end
end

function unstabilize(x::StableArray)
    base(x) * exp(exponent(x))
end

function unstabilize(x::StableNumber)
    base(x) * exp(exponent(x))
end

const StableMatrix{E, A} = StableArray{E, A} where A<:AbstractMatrix

function Base.:(*)(x1::StableArray{E}, x2::StableArray{E}) where E
    x = x1.base * x2.base
    factor = maximum(abs, x)
    if factor == 0
        return StableArray(x, factor)
    end
    x ./= factor
    xexponent = x1.exponent + x2.exponent + log(convert(E, factor))
    StableArray(x, xexponent)
end

function Base.:(*)(x1::StableArray, x2)
    x1 * stabilize(x2)
end

function Base.:(*)(x1, x2::StableArray)
    stabilize(x1) * x2
end

function Base.:(^)(x::StableArray, n::Integer)
    prod((x for _ in 1:n))
end

function Base.maximum(x::StableArray)
    StableNumber(maximum(base(x)), exponent(x))
end

function Base.adjoint(x::StableArray)
    StableArray(x.base', x.exponent)
end

function Base.size(x::StableArray, args...)
    size(x.base, args...)
end

function LinearAlgebra.tr(x::StableArray)
    StableNumber(tr(base(x)), exponent(x))
end

end


# function from baggepinnen/ControlSystemIdentification
function wcfft(y, u; n = length(y) ÷ 10, noverlap = n ÷ 2, window = hamming)
    win, norm2 = DSP.Periodograms.compute_window(window, n)
    uw = arraysplit(u, n, noverlap, nextfastfft(n), win)
    yw = arraysplit(y, n, noverlap, nextfastfft(n), win)
    Syy = zeros(length(uw[1]) ÷ 2 + 1)
    Suu = zeros(length(uw[1]) ÷ 2 + 1)
    Syu = zeros(ComplexF64, length(uw[1]) ÷ 2 + 1)
    for i in eachindex(uw)
        xy = rfft(yw[i])
        xu = rfft(uw[i])
        Syu .+= xy .* conj.(xu)
        Syy .+= abs2.(xy)
        Suu .+= abs2.(xu)
    end
    return Syy, Suu, Syu, norm2
end

"""
    tfbrs(RR::Vector{<:Real}, SBP::Vector{<:Real}; ...)

Transfer function based BRS measure as defined by Robbe et al.

# Keyword Arguments

* n: length of hamming window for spectral estimation, defaults to length(RR) ÷ 10
* minCoh: minimal valid coherence, defaults to 0.5
* LF: The frequency range defined as low frequency, defaults to (0.04, 0.15)
"""
function tfbrs(RR::Vector{<:Real}, SBP::Vector{<:Real}; n = length(RR) ÷ 10, minCoh = 0.5, LF::Tuple{Float64, Float64} = (0.04, 0.15))
    0 < minCoh < 1 || throw(DomainError("Coherence is defined between 0 and 1"))
    # detrend
    SBPm = SBP .- mean(SBP)
    RRm = RR .- mean(RR)

    # Spectra estimate
    Syy, Suu, Syu, norma2 = wcfft(RRm, SBPm, n = n)

    # TF
    tf = Syu ./ Suu
    # tfest  = arx(iddata(RRm, SBPm), 30, 30)
    # mag, _ = bode(tfest,  range(0, π, length = length(Syy)))
    # tf = vec(mag)
    freqs = range(0, 0.5, length = length(tf))

    # Coherence κ
    κ = abs2.(Syu) ./ (Suu .* Syy)

    # valid indices
    indsLF = findall(x -> LF[1] <= x <= LF[2], freqs)
    inds = indsLF[findall(x -> κ[x] >= minCoh, indsLF)]
    tfBRSv = mean(abs.(tf[inds]))

    return tfBRS(value = tfBRSv, minCoh = minCoh, κ = κ, tfMag = abs.(tf), inds = inds)
end

"""
Struct that stores all information regarding the tfBRS estimation. The final result is stored in 'value'. It can be plotted for visual inspection.
"""
@with_kw mutable struct tfBRS
    value::Float64 = 0
    minCoh::Float64 = 0
    LF::Tuple{Float64, Float64} = (0.04, 0.15)
    κ::Vector{<:Real} = Float64[]
    tfMag::Vector{<:Real} = Float64[]
    inds::Vector{Int} = Int[]
end

@recipe function f(res::tfBRS)

    layout := (2,1)
    freqs = range(0, 0.5, length = length(res.tfMag))
    @series begin
        subplot := 1
        yguide := "ms/mmHg"
        label := ""
        color := :grey

        freqs, res.tfMag
    end
    @series begin
        subplot := 1
        seriestype := :vline
        yguide := "ms/mmHg"
        label := ""
        color := :red

        [res.LF...]
    end
    @series begin
        subplot := 1
        title := "tfBRS = $(round(res.value, digits = 2)) ms/mmHg"
        seriestype := :scatter
        marker := :d
        yguide := "ms/mmHg"
        label := ""
        color := :red

        freqs[res.inds], res.tfMag[res.inds]
    end

    @series begin
        subplot := 2
        yguide := "Coherence"
        label := ""
        color := :grey

        freqs, res.κ
    end
    @series begin
        subplot := 2
        seriestype := :hline
        yguide := "Coherence"
        label := ""
        color := :grey

        [res.minCoh]
    end
    @series begin
        subplot := 2
        seriestype := :vline
        yguide := "Coherence"
        xguide := "f [c/b]"
        label := ""
        color := :red

        [res.LF...]
    end

end


"""

    Mesh

Abstract type representing a discretisation mesh for a numerical scheme.
"""
abstract type Mesh{T<:AbstractVector{Float64}} end

"""

    n_intervals(m)

Total number of cells for a Mesh or DiscretisedFluidQueue.
"""
n_intervals(mesh::Mesh) = length(mesh.nodes) - 1

"""

    Δ(m)

The width of each cell for a Mesh or DiscretisedFluidQueue.
"""
Δ(mesh::Mesh) = mesh.nodes[2:end] - mesh.nodes[1:end-1]
"""

    Δ(m,k::Int)

The width of cell k for a Mesh or DiscretisedFluidQueue.
"""
Δ(mesh::Mesh,k) = mesh.nodes[k+1] - mesh.nodes[k]
Δ(mesh::Mesh{StepRangeLen{Float64}},k) = mesh.nodes.step.hi

"""

    n_bases_per_phase(m)

Total number of basis functions used to represent the fluid queue for each phase
    of a Mesh or DiscretisedFluidQueue.
"""
n_bases_per_phase(mesh::Mesh) = n_bases_per_cell(mesh) * n_intervals(mesh)


using RvSpectraKitLearn

# Likely need to update this to point to directory containing the spectra you wish to analyze
include(joinpath(Pkg.dir("RvSpectraKitLearn"),"path_to_spectra.jl"))
cd(path_to_spectra)

(datafiles, phases) = get_filenames();
(lambda, obs) = read_filelist(datafiles);

lambda_min = 4500
 lambda_max = 4520
 idx_min = findfirst(x->lambda_min<x<lambda_max,lambda)
 idx_max = findlast(x->lambda_min<x<lambda_max,lambda)
 lambda_range = idx_min:idx_max

Y = reshape(-10.0*cos.(phases*2pi),(length(phases),1))
 X = obs[lambda_range,:]'
 @assert size(X,1) == size(Y,1)
 n = size(X,1)
 p = size(X,2)
 q = size(Y,2)

@time (gamma_list, Z_list)  = fit_arp_slr_2d(X,Y,stepsize=1.01,gamma_init=0.001, max_itterations=1000, min_active=floor(Int64,p*q/10))
(L0,L1,L2,rmsdelta) = compute_alr_path_stats(Z_list)
#(chisq,chisq_refit) = compute_alr_path_chisq!(Z_list,X,Y)  # Refitting via least squares makes coefficients worse, since highly underdetermined
print_alr_path(gamma_list, Z_list,X,Y)

#=
# Plot which wavelengths are being used from the last itteration of the algorithm regularization path algorithm
=#
using PyPlot
Z_idx = size(Z_list,1) # floor(Int64,size(Z_list,1)/2)
  Z = reshape(Z_list[Z_idx,:,:],(size(Z_list,2),size(Z_list,3)))
  find(reshape(Z,(p,q)))
  zmax = maximum(Z)
  ymax = maximum(vec(X[1,:]))
  ymin = minimum(vec(X[1,:]))
  yrange = ymax-ymin
  plot(lambda[lambda_range],vec(X[1,:]),"r.")
  plot(lambda[lambda_range],ymin+yrange/2*(1+1/zmax*(reshape(Z,(p,q)))),"b+")
  plot(lambda[lambda_range],ymin+yrange/2*ones(length(lambda_range)),"-")
#=
=#

#=
# Check that the predictions are good
pred = X*reshape(Z,(p,q))
 plot(phases,Y,"r.")
 plot(phases,Y,"r+")
 plot(phases,pred,"b+")
=#

#=
# Random other diagnostic plots
plot(L0,chisq,"r-")
 plot(L0,chisq,"r.")
 plot(L0,chisq_refit,"b-")
 plot(L0,chisq_refit,"b.")
 #plot(L0,chisq_cv,"g-")
 #plot(L0,chisq_cv,"g.")

plt[:loglog](L0,gamma_list,"g.")
 plot(L0,L1./L0,"g-")
 plot(L0,L1./L0,"g.")

plot(L1,chisq,"r-")
 plot(L1,chisq,"r.")
 plot(L1,chisq_refit,"b-")
 plot(L1,chisq_refit,"b.")

plot(L0,chisq,"r-")
 plot(L0,chisq,"r.")

plt[:loglog](gamma_list,L0,"r-")
 plt[:loglog](gamma_list,L1/(p*q),"b-")
 plt[:loglog](gamma_list,sqrt(L2),"g-")

 plt[:loglog](gamma_list,sqrt(L2/(p*q)),"g+")
 plt[:loglog](gamma_list,sqrt(chisq/(n*q)),"r+")
 plt[:loglog](gamma_list,rmsdelta,"g+")

=#


import ProximalBundleMethod
import Printf
import Convex
import Gurobi
import SCS
using LinearAlgebra
using Random
using SparseArrays

include("utils.jl")
include("csv_exporter.jl")
include("plot_utils.jl")

"""
The following functions are used to process LIBSVM files.
"""
mutable struct LearningData
    feature_matrix::SparseMatrixCSC{Float64,Int64}
    labels::Vector{Float64}
end

function load_libsvm_file(file_name::String)
    open(file_name, "r") do io
        target = Array{Float64,1}()
        row_indicies = Array{Int64,1}()
        col_indicies = Array{Int64,1}()
        matrix_values = Array{Float64,1}()

        row_index = 0
        for line in eachline(io)
            row_index += 1
            split_line = split(line)
            label = parse(Float64, split_line[1])
            # This ensures that labels are 1 or -1. Different dataset use {-1, 1}, {0, 1}, and {1, 2}.
            if abs(label - 1.0) < 1e-05
                label = 1.0
            else
                label = -1.0
            end
            push!(target, label)
            for i = 2:length(split_line)
                push!(row_indicies, row_index)
                matrix_coef = split(split_line[i], ":")
                push!(col_indicies, parse(Int64, matrix_coef[1]))
                push!(matrix_values, parse(Float64, matrix_coef[2]))
            end
        end
        feature_matrix = sparse(row_indicies, col_indicies, matrix_values)
        return LearningData(feature_matrix, target)
    end
end

function normalize_columns(feature_matrix::SparseMatrixCSC{Float64,Int64})
    m = size(feature_matrix, 2)
    normalize_columns_by = ones(m)
    for j = 1:m
        col_vals = feature_matrix[:, j].nzval
        if length(col_vals) > 0
            normalize_columns_by[j] = 1.0 / norm(col_vals, 2)
        end
    end
    return feature_matrix * sparse(1:m, 1:m, normalize_columns_by)
end

function remove_empty_columns(feature_matrix::SparseMatrixCSC{Float64,Int64})
    keep_cols = Array{Int64,1}()
    for j = 1:size(feature_matrix, 2)
        if length(feature_matrix[:, j].nzind) > 0
            push!(keep_cols, j)
        end
    end
    return feature_matrix[:, keep_cols]
end

function add_intercept(feature_matrix::SparseMatrixCSC{Float64,Int64})
    return [sparse(ones(size(feature_matrix, 1))) feature_matrix]
end

function preprocess_learning_data(result::LearningData)
    result.feature_matrix = remove_empty_columns(result.feature_matrix)
    result.feature_matrix = add_intercept(result.feature_matrix)
    result.feature_matrix = normalize_columns(result.feature_matrix)
    return result
end

function svm_objective(w, Xp, y, coeff)
    n, _ = size(Xp)
    soft_margin = map(x -> max(0, x), ones(length(y)) - y .* (Xp * w))
    return sum(soft_margin) / n + coeff / 2 * LinearAlgebra.norm(w)^2
end

function svm_subgradient(w, Xp, y, coeff)
    n, m = size(Xp)
    soft_margin = map(x -> max(0, x), ones(length(y)) - y .* (Xp * w))
    result = zeros(length(w))
    for i = 1:length(soft_margin)
        if soft_margin[i] > 0.0
            result += -y[i] * Xp[i, :] / n
        end
    end
    return result + coeff * w
end

function compute_minimum_with_cvx(Xp, y, reg_coeff)
    n, m = size(Xp)
    w = Convex.Variable(m)
    problem = Convex.minimize(
        (reg_coeff / 2) * Convex.sumsquares(w) +
        Convex.sum(Convex.pos((1 - y .* (Xp * w)) / n)),
    )
    Convex.solve!(
        problem,
        () -> Gurobi.Optimizer(BarConvTol = 1e-10, BarQCPConvTol = 1e-10),
    ) #() -> SCS.Optimizer(verbose=false), verbose=false)
    return problem
end

function main()
    Random.seed!(2625)
    instance_names = ["colon-cancer", "duke", "leu"]
    coefficients = [0.001, 0.01, 0.1, 0.5, 1.0, 1.5, 2.0, 10.0]

    step_sizes = [1e-15 * 100^j for j = 0:10]

    instances = Dict()
    for name in instance_names
        instances[name] = preprocess_learning_data(load_libsvm_file("data/" * name))
    end

    errors = DataFrame(
        instance = String[],
        coeff = Float64[],
        error_subg = Float64[],
        error_bundle = Float64[],
    )

    params_subgradient = create_subgradient_method_parameters(
        iteration_limit = 2000,
        verbose = true,
        printing_frequency = 100,
    )

    params_bundle = create_bundle_method_parameters(
        iteration_limit = 2000,
        verbose = true,
        printing_frequency = 100,
        full_memory = false,
    )

    for (name, instance) in instances
        Xp = (instance.feature_matrix)
        y = instance.labels
        _, m = size(Xp)
        x_init = randn(m) / m
        for coeff in coefficients
            print("\n------------------------------------\n")
            Printf.@printf("Instance %s, coeff %12g\n", name, coeff)
            println("Solving with Convex.jl first")
            problem = compute_minimum_with_cvx(Xp, y, coeff)
            Printf.@printf("Obj=%12g\n", problem.optval,)

            objective = (w -> svm_objective(w, Xp, y, coeff) - problem.optval)
            gradient = (w -> svm_subgradient(w, Xp, y, coeff))
            poly_step_size = ((_, _, t) -> 1 / (coeff * t))

            println("\nAbout to solve a random SVM problem using subgradient method.")
            sol_subgradient = ProximalBundleMethod.solve(
                objective,
                gradient,
                params_subgradient,
                poly_step_size,
                x_init,
            )

            println("\nAbout to solve a random SVM problem using adaptive parallel method.")
            sol_bundle, iter_info_agents = ProximalBundleMethod.solve_adaptive(
                objective,
                gradient,
                params_bundle,
                step_sizes,
                x_init,
            )

            push!(
                errors,
                [
                    name,
                    coeff,
                    objective(sol_subgradient.solution),
                    objective(sol_bundle.solution),
                ],
            )

            # Uncomment this block of code if you want to save stats about each run.

            # csv_path_sol =
            #     "results/svm/results_subgradient_" * name * "_" * string(coeff) * "_svm.csv"
            # output_stepsize_plot =
            #     "results/svm/results_subgradient_" * name * "_" *
            #     string(coeff) *
            #     "_stepsize_svm.pdf"
            # output_objective_plot =
            #     "results/svm/results_subgradient_" * name * "_" *
            #     string(coeff) *
            #     "_objective_svm.pdf"

            # export_statistics(sol_subgradient, csv_path_sol)
            # plot_step_sizes(csv_path_sol, output_stepsize_plot)
            # plot_objective(csv_path_sol, output_objective_plot)

            # csv_path_sol =
            #     "results/svm/results_bundle_" * name * "_" * string(coeff) * "_svm.csv"
            # output_stepsize_plot =
            #     "results/svm/results_bundle_" * name * "_" *
            #     string(coeff) *
            #     "_stepsize_svm.pdf"
            # output_objective_plot =
            #     "results/svm/results_bundle_" * name * "_" *
            #     string(coeff) *
            #     "_objective_svm.pdf"

            # csv_path =
            #     "results/svm/results_" * name * "_" * string(coeff) * "_agents_svm.csv"
            # output_path =
            #     "results/svm/results_" * name * "_" * string(coeff) * "_agents_svm.pdf"

            # export_statistics(sol_bundle, csv_path_sol)
            # plot_step_sizes(csv_path_sol, output_stepsize_plot)
            # plot_objective(csv_path_sol, output_objective_plot)

            # export_losses_agents(iter_info_agents, step_sizes, csv_path)
            # plot_agent_losses(csv_path, output_path)

        end
    end
    print(errors)
    CSV.write("results/svm/results_svm.csv", errors)
end

main()


export getLinearOperatorsSystemsFaces,getInjectionOperatorsSystemsFaces,MGsetupSystems,getFacesStaggeredIndicesOfCell
## n is always in cells in this file

function getInjectionOperatorsSystemsFaces(n::Array{Int64},withCellsBlock::Bool)
	if length(n) == 2
		R1, = getRestrictionFacesInjectionUj(n,1);
		R2, = getRestrictionFacesInjectionUj(n,2);

		if withCellsBlock
			R3, = getRestrictionCellCentered(n);
			Rinj = blkdiag(R1,R2,R3);
		else
			Rinj = blkdiag(R1,R2);
		end
	else
		R1, = getRestrictionFacesInjectionUj(n,1);
		R2, = getRestrictionFacesInjectionUj(n,2);
		R3, = getRestrictionFacesInjectionUj(n,3);
		if withCellsBlock
			R4, = getRestrictionCellCentered(n);
			Rinj = blkdiag(R1,R2,R3,R4);
		else
			Rinj = blkdiag(R1,R2,R3);
		end
	end
	return Rinj;
end

function getLinearOperatorsSystemsFaces(n::Array{Int64},withCellsBlock::Bool)
	if length(n) == 2
		(P1,nc) = getLinearInterpolationFacesUj(n,1);
		P2, = getLinearInterpolationFacesUj(n,2);
		if withCellsBlock
			P3, = getLinearInterpolationCellCentered(n);
			P  = blkdiag(P1,P2,P3);
		else
			P  = blkdiag(P1,P2);
		end
		R1, = getRestrictionFacesFullWeightUj(n,1);
		R2, = getRestrictionFacesFullWeightUj(n,2);
		if withCellsBlock
			R3, = getRestrictionCellCentered(n);
			R  = blkdiag(R1,R2,R3);
		else
			R  = blkdiag(R1,R2);
		end
	else
		(P1,nc) = getLinearInterpolationFacesUj(n,1);
		P2, = getLinearInterpolationFacesUj(n,2);
		P3, = getLinearInterpolationFacesUj(n,3);

		if withCellsBlock
			P4, = getLinearInterpolationCellCentered(n);
			P  = blkdiag(P1,P2,P3,P4);
		else
			P  = blkdiag(P1,P2,P3);
		end
		R1, = getRestrictionFacesFullWeightUj(n,1);
		R2, = getRestrictionFacesFullWeightUj(n,2);
		R3, = getRestrictionFacesFullWeightUj(n,3);
		if withCellsBlock
			R4, = getRestrictionCellCentered(n);
			R  = blkdiag(R1,R2,R3,R4);
		else
			R  = blkdiag(R1,R2,R3);
		end
	end
	return (P,R,nc);
end

function get1DRestrictionCells(n::Int64)
	## R is an 2X1 aggregation restriction.
	if n < 16
		return speye(n),n;
	end
	nc = div(n,2);
	if 2*nc != n
		error("Err: get1DRestrictionCells(): size should be a multiplication of 2");
	end
	R = 1/2*spdiagm((ones(n-1),ones(n-1)),[0,1],n-1,n);
	R = R[1:2:n,:];
	return R,nc;
end

function get1DNodeInjection(n_cells::Int64)
	## R is a node injection operator - C,F,C,F,...,C
	n = n_cells;
	if n < 16
		return speye(n+1),n;
	end
	nc = div(n,2);
	if 2*nc != n
		error("Err: get1DNodeInjection(): size should be a multiplication of 2");
	end
	R = speye(n+1);
	R = R[1:2:n+1,:];
	return R,nc;
end

function get1DNodeFullWeightRestriction(n_cells::Int64)
	## R is a full weighting X2 restriction: 0.25 , 0.5 , 0.25 operates on nodes.
	## B.C: Here we need to choose what happens at boundaries - we choose injection
	n = n_cells;
	if n < 16
		return speye(n+1),n;
	end
	nc = div(n,2);
	if 2*nc != n
		error("Err: get1DNodeFullWeightRestriction(): size should be a multiplication of 2");
	end
	Qtr = 0.25*ones(n);
	R = spdiagm((Qtr,0.5*ones(n+1),Qtr),[-1,0,1],n+1,n+1);
    R = R[:,1:2:end]';
	R[1,1:2] = [0.75 0.25];
	R[end,end-1:end] = [0.25 0.75];
	return R,nc;
end

function get1DProlongationCellCentered(ncells_fine::Int64)
	## P takes a two cells [C,C] into [F,F,F,F]
	n = ncells_fine;
	if n < 16
		return speye(n),n;
	end
	nc = div(n,2);
	if 2*nc != n
		error("Err: get1DProlongationCellCentered(): size should be a multiplication of 2");
	end
	P  = spdiagm(((1/4)*ones(n-2),(3/4)*ones(n-1),(3/4)*ones(n),(1/4)*ones(n-1)),[-2,-1,0,1],n,n);
	P = P[:,1:2:end];
	P[1,1:2] = [5/4,-1/4];
	P[end,end-1:end] = [-1/4,5/4];
	return P,nc;
end

function get1DProlongationNodes(ncells_fine::Int64)
	## P takes a two cells [C,C] into [F,F,F,F]
	n = ncells_fine;
	if n < 16
		return speye(n+1),n;
	end
	nc = div(n,2);
	if 2*nc != n
		error("Err: get1DProlongationNodes(): size should be a multiplication of 2");
	end
	Half = 0.5*ones(n);
	P = spdiagm((Half,ones(n+1),Half),[-1,0,1],n+1,n+1);
    P = P[:,1:2:end];
	return P,nc;
end

function getRestrictionCellCentered(n::Array{Int64})
	if length(n) == 3
		(R1,nc1) = get1DRestrictionCells(n[1]);
		(R2,nc2) = get1DRestrictionCells(n[2]);
		(R3,nc3) = get1DRestrictionCells(n[3]);
		R = kron(R3,kron(R2,R1));
		nc = [nc1;nc2;nc3];
	elseif length(n) == 2
		(R1,nc1) = get1DRestrictionCells(n[1]);
		(R2,nc2) = get1DRestrictionCells(n[2]);
		R = kron(R2,R1);
		nc = [nc1;nc2];
	else
		error("getRestrictionCells() : Dimension not supported!");
	end
	return R,nc
end

function getRestrictionFacesInjectionUj(n::Array{Int64},j::Int64)
    # n here is number of cells.
	R = Array(SparseMatrixCSC,length(n));
	nc = zeros(Int64,length(n));
	for kk = 1:length(n)
		if kk == j
			(R[kk],nc[kk]) = get1DNodeInjection(n[kk]);
		else
			(R[kk],nc[kk]) = get1DRestrictionCells(n[kk]);
		end
	end
	if length(n) == 3
		R = kron(R[3],kron(R[2],R[1]));
	elseif length(n) == 2
		R = kron(R[2],R[1]);
	else
		error("getRestrictionFacesInjectionUj() : Dimension not supported!");
	end
	return R,nc;
end

function getRestrictionFacesFullWeightUj(n::Array{Int64},j::Int64)
    # n here is number of cells.
	R = Array(SparseMatrixCSC,length(n));
	nc = zeros(Int64,length(n));
	for kk = 1:length(n)
		if kk == j
			(R[kk],nc[kk]) = get1DNodeFullWeightRestriction(n[kk]);
		else
			(R[kk],nc[kk]) = get1DRestrictionCells(n[kk]);
		end
	end
	if length(n) == 3
		R = kron(R[3],kron(R[2],R[1]));
	elseif length(n) == 2
		R = kron(R[2],R[1]);
	else
		error("getRestrictionFacesFullWeightUj() : Dimension not supported!");
	end
	return R,nc;
end

function getLinearInterpolationFacesUj(n::Array{Int64},j::Int64)
	P = Array(SparseMatrixCSC,length(n));
	nc = zeros(Int64,length(n));
	for kk = 1:length(n)
		if kk == j
			(P[kk],nc[kk]) = get1DProlongationNodes(n[kk]);
		else
			(P[kk],nc[kk]) = get1DProlongationCellCentered(n[kk]);
		end
	end
	if length(n) == 3
		P = kron(P[3],kron(P[2],P[1]));
	elseif length(n) == 2
		P = kron(P[2],P[1]);
	else
		error("getLinearInterpolationFacesUj() : Dimension not supported!");
	end
	return P,nc;
end

function getLinearInterpolationCellCentered(n::Array{Int64})
	nc = zeros(Int64,length(n));
	if length(n) == 3
		(P1,nc1) = get1DProlongationCellCentered(n[1]);
		(P2,nc2) = get1DProlongationCellCentered(n[2]);
		(P3,nc3) = get1DProlongationCellCentered(n[3]);
		P = kron(P3,kron(P2,P1));
		nc = [nc1;nc2;nc3];
	elseif length(n) == 2
		(P1,nc1) = get1DProlongationCellCentered(n[1]);
		(P2,nc2) = get1DProlongationCellCentered(n[2]);
		P = kron(P2,P1);
		nc = [nc1;nc2];
	else
		error("getLinearInterpolationCellCentered() : Dimension not supported!");
	end
	return P,nc;
end