rel_error(a, b) = (a - b) / b
abs_rel_error(a, b) = abs((a - b) / b)

function trimmed_mean(x)
    l, u = quantile(x, [0.2, 0.8])
    mean(x[(x .> l) .& (x .< u)])
end

rng = MersenneTwister(9237429)
parameters = Dict(
    :max_food => 400,
    :food_alpha => 2,
    :food_beta => 10,
    :growth_rate => 1.5,
    :death_rate => 1.05
)

######### testing beverton_holt(x, K, nu, mu)

# If available food is constant, and the population begins under the
# maximum, then it will approach the maximum in the limit but not exceed it.
step_cnt = 200
constant_food = beverton_holt_constant_food(2, step_cnt, parameters)
@test all(constant_food .< 400)
@test abs_rel_error(constant_food[step_cnt], parameters[:max_food]) < 1e-10

# Assert the value is decreasing.
greater_cnt = 0
for diff_idx in 1:(step_cnt - 1)
    if constant_food[diff_idx + 1] < diff_idx
        greater_cnt += 1
    end
end
@test greater_cnt == 0

# If the population begins high, and food is constant, it will fall.
high_pop = 2 * parameters[:max_food]
high_food = beverton_holt_constant_food(high_pop, step_cnt, parameters)
@test all(high_food .> 400)
# Falling slowly because rate is 1.05, so this value is 0.015.
@test abs_rel_error(high_food[step_cnt], parameters[:max_food]) < 0.015

# If the nu and mu are in the right order in the function, then the rise to
# max population will be faster than the fall from high population.
@test abs(constant_food[step_cnt] - 400) < abs(high_food[step_cnt] - 400)

########## testing forecast_population(young, step_cnt, params, rng)

# Increasing the food parameter increases the average value.
lower_food_parameters = copy(parameters)
lower_food_parameters[:max_food] = parameters[:max_food] / 2
high_food = forecast_population(50, 10000, parameters, rng)
low_food = forecast_population(50, 10000, lower_food_parameters, rng)
@test mean(high_food) > mean(low_food)

# Increasing the death rate should lower the average.
# Decreasing the death rate should raise it.
death_rate_trials = collect(0.5:0.1:1.5)
for death_rate in death_rate_trials
    higher_death_parameters = copy(parameters)
    higher_death_parameters[:death_rate] = 1 + (parameters[:death_rate] - 1) * death_rate
    # Copy the rng to get the same draws of random numbers.
    # It's a "variance reduction technique".
    rng2 = copy(rng)
    low_death = forecast_population(50, 10000, parameters, rng)
    high_death = forecast_population(50, 10000, higher_death_parameters, rng2)
    if death_rate < 0.99
        @test trimmed_mean(high_death) > trimmed_mean(low_death)
    elseif death_rate > 1.01
        @test trimmed_mean(high_death) < trimmed_mean(low_death)
    else
        @test abs(trimmed_mean(high_death) - trimmed_mean(low_death)) < 1
    end
end

# The means are near the same value where I last saw them.
long_run_cnt = 100
long_run_max = 10000
long_run_means = zeros(long_run_cnt)
long_run_trimmed = zeros(long_run_cnt)
for long_run_idx in 1:long_run_cnt
    long_run = forecast_population(2, long_run_max, parameters, rng)
    long_run_later = long_run[1000:long_run_max]
    long_run_means[long_run_idx] = mean(long_run)
    long_run_trimmed[long_run_idx] = trimmed_mean(long_run)
end

for lr_arr in [long_run_means, long_run_trimmed]
    @test abs(mean(lr_arr) - 72.5) < 0.5
    @test std(lr_arr) < 0.6
end

# Build an empirical distribution function.
single_sample = forecast_population(50, long_run_max, parameters, rng)
default_cdf = StatsBase.ecdf(single_sample[1000:long_run_max])


module FourierTools

using PaddedViews, ShiftedArrays
using FFTW
FFTW.set_num_threads(4)
using LinearAlgebra
using IndexFunArrays
using ChainRulesCore
using NDTools

include("utils.jl")
include("resampling.jl")
include("custom_fourier_types.jl")
include("fourier_resizing.jl")
include("fourier_shifting.jl")
include("fourier_resample_1D_based.jl")
include("fourier_rotate.jl")
include("fourier_shear.jl")
include("fftshift_alternatives.jl")
include("fft_helpers.jl")
include("convolutions.jl")
include("damping.jl")
include("czt.jl")

end # module


const c = cosmology()

"""
    li_prior_wt(m1, m2, z)

Returns the LALInference prior in m1, m2, and z.

```math
p\\left( m_1, m_2, z \\right) \\propto \\left( 1 + z \\right)^2 d_L^2(z) \\frac{\\partial d_L}{\\partial z}
```
"""
function li_prior_wt(m1, m2, z)
    dL = ustrip(u"Gpc", luminosity_dist(c, z))
    dC = dL / (1+z)
    dH = ustrip(u"Gpc", hubble_dist(c, 0))
    ddLdz = (dC + (1+z)*dH/Cosmology.E(c, z))

    (1 + z)*(1 + z)*dL*dL*ddLdz
end

"""
    md_sfr(z[, lambda, z_p, kappa])

Returns the Madau-Dickinson SFR.

```math
\\frac{\\mathrm{d} N}{\\mathrm{d} V \\mathrm{d} t} \\propto \\frac{\\left( 1 + z \\right)^\\lambda}{1 + \\left( \\frac{1+z}{1+z_p}\\right)^\\kappa}
```

The default values are `(lambda, z_p, kappa) = (2.7, 1.9, 5.6)`.
"""
function md_sfr(z, lambda=2.7, z_p=1.9, kappa=5.6)
    zp1 = 1 + z
    zp1^lambda / (1 + (zp1 / (1 + z_p))^kappa)
end

"""
    md_sfr_zwt(z[, lambda, z_p, kappa])

Returns the weight in `z` corresponding to the Madau-Dickinson SFR.

```math
\\frac{\\mathrm{d} N}{\\mathrm{d} V \\mathrm{d} t} \\propto \\frac{\\left( 1 + z \\right)^\\lambda}{1 + \\left( \\frac{1+z}{1+z_p}\\right)^\\kappa}
```

The default values are `(lambda, z_p, kappa) = (2.7, 1.9, 5.6)`.
"""
function md_sfr_zwt(z, lambda=2.7, z_p=1.9, kappa=5.6)
    zp1 = 1 + z
    wt = zp1^lambda / (1 + (zp1 / (1 + z_p))^kappa)

    4*pi*md_sfr(z, lambda, z_p, kappa) * ustrip(u"Gpc^3", comoving_volume_element(c, z)) / zp1
end

"""
    read_selection(file)

Read the injection set from `file` and return `(m1s, m2s, zs, pdraw, Ndraw)` for sensitivity estimation.
"""
function read_selection(file)
    h5open(file, "r") do f
        i = f["injections"]
        m1s = convert(Vector{Float64}, read(i, "mass1_source"))
        m2s = convert(Vector{Float64}, read(i, "mass2_source"))
        zs = convert(Vector{Float64}, read(i, "redshift"))

        T = read(attributes(f), "analysis_time_s") / (356.25*24.0*3600.0) # yr

        pm1m2z = convert(Vector{Float64}, read(i, "mass1_source_mass2_source_sampling_pdf") .* read(i, "redshift_sampling_pdf"))
        pdraw = pm1m2z / T

        sel_flag = (read(i, "ifar_cwb") .> 1) .| (read(i, "ifar_gstlal") .> 1) .| (read(i, "ifar_mbta") .> 1) .| (read(i, "ifar_pycbc_bbh") .> 1) .| (read(i, "ifar_pycbc_hyperbank") .> 1)

        Ndraw = round(Int, read(attributes(i)["total_generated"]))

        m1s[sel_flag], m2s[sel_flag], zs[sel_flag], pdraw[sel_flag], Ndraw
    end
end

"""
    resample_selection(log_dN, m1s, m2s, pdraws, Ndraw)

Returns re-sampled `(m1s, m2s, pdraws, Ndraw)` as if injections had been drawn
from the given fiducial population.
"""
function resample_selection(log_dN, m1s, m2s, pdraws, Ndraw)
    pop_wts = exp.(log_dN.(m1s, m2s))
    wts = pop_wts ./ pdraws
    sum_wts = sum(wts)
    sum_wts2 = sum(wts.*wts)
    norm = sum_wts / Ndraw
    Neff_norm = round(Int, sum_wts*sum_wts / sum_wts2)

    inds = sample(1:length(m1s), weights(wts), Neff_norm)
    norm_pop_wts = pop_wts ./ norm
    m1s[inds], m2s[inds], norm_pop_wts[inds], Neff_norm
end


using Plots
using Plots.PlotMeasures
using LambertW
import PlotlyBase

plotly()
Plots.PlotlyBackend()

Plots.scalefontsizes()
Plots.scalefontsizes(1.3)

R = 0:0.01:1;

## No-repair reliability
reliability_single = R;
reliability_dr = @. 1 - (1 - R)^2;
reliability_cold = @.  R * (1 - log(R));
reliability_3r = @. 1 - (1 - R)^3;
reliability_tmr = @. 3 * R^2 - 2 * R^3

plot(
    R,
    [reliability_single, reliability_cold, reliability_dr, reliability_tmr, reliability_3r],
    label = ["Single component" "Dual redundancy (cold)" "Dual redundancy (hot)" "Triple modular redundancy (naive)" "Triple modular redundancy (resilient)"],
    # title = "Resilience to permanent failures",
    legend = :outerright,
    xlabel = "Component reliability",
    ylabel = "System reliability",
    lw = 2,
)
savefig("reliability_norepair_en.pdf")

## No-repair reliability with vulnerable voter
Rv = 0:0.01:1;
reliability_tmrv = reliability_tmr * permutedims(Rv)

heatmap(
    R,
    Rv,
    reliability_tmrv,
    xlabel = "Component reliability R",
    ylabel = "Voter reliability Rv",
    colorbar_title = "System reliability",
    aspect_ratio = 1,
    c = :inferno,
)
savefig("reliability_norepair_voter_en.pdf")

## Ρrepair reliability
λ = 10 .^ (LinRange(-11, -2, 1000))

λ_single = λ;
λ_dr = @. -lambertw(-(2λ * exp(-λ) - λ^2 * exp(-2λ)));
λ_tmr = @. -lambertw(-(3(λ * exp(-λ))^2 - 2(λ * exp(-λ))^3));

plot(
    λ,
    [λ_single, λ_single, λ_dr, λ_tmr],
    label = permutedims(
        [
            "Single component"
            "Dual redundancy, cold"
            "Dual redundancy, hot"
            "Triple modular redundancy"
        ],
    ),
    #title = "Resilience to temporary failures",
    legend = (0.6, 0.35),
    xlabel = "Component failure rate",
    ylabel = "System failure rate",
    yaxis = :log,
    xaxis = :log,
    left_margin = -150px,
    lw = 2,
    linecolor = [palette(:tab10)[1] palette(:tab10)[1] palette(:tab10)[2] palette(
        :tab10,
    )[3]],
)
savefig("reliability_repair_en.pdf")


# This file is part of the Julia package ModularForms.jl
#
# Copyright (c) 2018-2019: Lynn Engelberts and Alexandru Ghitza.

"""
    eisenstein_series_qexp(k, prec=10, K=QQ, var="q", normalization="linear")

Return the q-expansion of the normalized weight `k` Eisenstein series
on the modular group to precision `prec` as a power series in the ring
`K` in variable `var`, using the given `normalization`.

Three normalizations are available: "linear" (default), "constant",
and "integral". If the normalization is "linear" then the linear
coefficient is 1. If it is "constant" then the series will be
normalized to have constant term 1. If the normalization is "integral"
then the series will be normalized to have integer coefficients and no
common factors.
Note: To prevent errors, the output will be in the ring QQ if the
normalization is "linear" or "constant".

# Arguments
- `k::Integer`: even positive integer, weight of the Eisenstein series
- `prec::Integer=10`: precision of the output
- `K=ZZ`: base ring of the output
- `var::String="q"`: variable name
- `normalization::String="linear"`: normalization to use

# Examples still missing
"""
function eisenstein_series_qexp(k::Int, prec::Int=10, K=QQ, var::String="q", normalization::String="linear")

   #error handling with regard to input ring
   if normalization != "integral"
      K = QQ
   end

   #initialise
   R, q = PowerSeriesRing(K, prec, var)   	#need to change prec?
   qexp = R(0)

   #error handling
   if k%2 != 0 || k<2
      error("k must be an even positive integer")
   end
   if prec <= 0
      error("prec must be a positive integer")
   end

   poly = eisenstein_series_poly(k, prec, var)

   if normalization == "integral"
      return poly_to_power_series(poly, prec)
   end

   qexp = poly_to_power_series(poly, QQ, prec)
   if normalization == "linear"
      return qexp*1//coeff(qexp, 1)
   elseif normalization == "constant"
      return qexp*1//coeff(qexp, 0)
   else
      error("normalization must be one of 'linear', 'constant', 'integral'")
   end

end

"""
    eisenstein_series_poly(k, prec=10, var="q")

Return the q-expansion of the weight `k` Eisenstein series up to
precision `prec` as a polynomial over ZZ of degree prec-1 in the
variable `var`. The polynomial is normalized to have integer
coefficients and no common factors.

The algorithm is taken from the SageMath implementation of
eisenstein_series_poly.

# Arguments
- `k::Integer`: even positive integer, weight of the Eisenstein series
- `prec::Integer=10`: precision of the output
- `var::String="q"`: variable name

# Examples still missing
"""
function eisenstein_series_poly(k::Int, prec::Int=10, var::String="q")
   a0 = -bernoulli(k) // 2k
   if a0 > 0
      d = ZZ(a0.den)
      n = ZZ(a0.num)
   else
      d = -ZZ(a0.den)
      n = -ZZ(a0.num)
   end
   val = fill(d, prec)
   val[1] = ZZ(n)
   expt = k - 1
   for p in prime_range(prec-1)
      ppow = p
      mult = ZZ(p)^expt
      term = mult * mult
      last = mult
      while ppow < prec
         ind = ppow
         term_m1 = term - 1
         last_m1 = last - 1
         while ind < prec
            val[ind+1] *= term_m1
            val[ind+1], r = fdivrem(val[ind+1], last_m1)
            ind += ppow
         end
         ppow *= p
         last = term
         term *= mult
      end
   end

   R, q = PolynomialRing(ZZ, var)
   return R(val)
end


@testset "logeqn" begin
    let m = Model()
        m.tol = 1e-15
        @variables m @log(X), Y
        @shocks m Ex Ey
        @parameters m rate = 1.015
        @equations m begin
            @log X[t] = rate * X[t - 1] + Ex[t]
            Y[t] = Y[t - 1] + rate + Ey[t]
        end

        @initialize m
        @steadystate m X = 1.0
        @steadystate m Y = 0.0

        clear_sstate!(m)
        sssolve!(m)
        @test m.sstate.X.level ≈ 1.0
        @test m.sstate.X.slope ≈ m.rate
        @test m.sstate.Y.level + 1 ≈ 0.0 + 1
        @test m.sstate.Y.slope + 1 ≈ m.rate + 1

        p = Plan(m, 1U:10U)

        # make sure the steadystate data is built correctly
        a = steadystatearray(m, p)
        d = steadystatedict(m, p)
        k = steadystatedata(m, p)

        @test a == k
        for s in Symbol.(m.allvars)
            @test k.:($s) == d[string(s)]
        end
        @test k.X[1U] ≈ 1.0
        @test k.Y[1U] + 1 ≈ 1.0
        @test pct(k.X, -1) ≈ TSeries(p.range, (m.rate - 1) * 100)
        @test diff(k.Y) ≈ TSeries(p.range, m.rate)

        # make sure simulations do as expected

        # this model has no final conditions (maxlag == 0)
        ed = SimData(firstdate(p), m.allvars, rand(Float64, (length(p), length(m.allvars))))
        # no shocks
        ed.Ex .= 0.0
        ed.Ey .= 0.0
        # initial
        ed[firstdate(p)] = k[firstdate(p)]

        for fc in instances(FCType)
            @test simulate(m, ed, p, fctype=fc) ≈ k
        end

    end

    let m = Model()
        m.tol = 1e-15
        @variables m @log(X), Y
        @shocks m Ex Ey
        @parameters m rate = 1.015
        @equations m begin
            @log X[t + 1] = rate * X[t] + Ex[t]
            Y[t + 1] = Y[t] + rate + Ey[t]
        end

        @initialize m
        @steadystate m X = 1.0
        @steadystate m Y = 0.0

        clear_sstate!(m)
        sssolve!(m)
        @test m.sstate.X.level + 1 ≈ 1.0 + 1
        @test m.sstate.X.slope + 1 ≈ m.rate + 1
        @test m.sstate.Y.level + 1 ≈ 0.0 + 1
        @test m.sstate.Y.slope + 1 ≈ m.rate + 1

        p = Plan(m, 1U:10U)

        # make sure the steadystate data is built correctly
        a = steadystatearray(m, p)
        d = steadystatedict(m, p)
        k = steadystatedata(m, p)

        @test a == k
        for s in Symbol.(m.allvars)
            @test k.:($s) == d[string(s)]
        end
        @test k.X[1U] + 1 ≈ 1.0 + 1
        @test k.Y[1U] + 1 ≈ 0.0 + 1
        @test pct(k.X, -1) ≈ TSeries(p.range, (m.rate - 1) * 100)
        @test diff(k.Y) ≈ TSeries(p.range, m.rate)

        # make sure simulations do as expected

        # this model has no final conditions (maxlag == 0)
        ed = SimData(firstdate(p), m.allvars, rand(Float64, (length(p), length(m.allvars))))
        # no shocks
        ed.Ex .= 0.0
        ed.Ey .= 0.0
        # Initial conditions don't matter. We set final conditions
        ed[lastdate(p)] = k[lastdate(p)]

        @test simulate(m, ed, p, fctype=fcgiven) ≈ k
        @test simulate(m, ed, p, fctype=fclevel) ≈ k

        # system is inconsistent for fcslope and fcnatural
        # we do some exo-endo juggling to get unique solution

        # we need exogenous variables somewhere
        exogenize!(p, m.variables, firstdate(p))
        # we need endogenous shocks on the last simulation period
        endogenize!(p, m.shocks, lastdate(p) - 1)

        # update exogenous data accordingly
        ed[firstdate(p)] = k[firstdate(p)]
        ed[lastdate(p),1:2] .= rand(2)

        @test simulate(m, ed, p, fctype=fcslope) ≈ k
        @test simulate(m, ed, p, fctype=fcnatural) ≈ k

    end

end


## implement Markov Chain with SEIR
include("dynamics.jl")

struct SIR <: Dynamics
    α::Float64
    β::Float64
end

# default constructor
SIR() = SIR(0.0,0.0)

# change in a day
"""
change(s::Vector{Float64},d::SIR)

Retutn the change in the state of the population in a day

s = state vector (S,I,R)
d = SIR dynamics parameters
"""
function change(s::Vector{Float64},d::SIR)
    # this is done purely for readability of the formula
    st = NamedTuple{(:S,:I,:R)}(s)
    # N = sum(s) # population size
    S = -( d.β*st.I )*st.S
    I =  ( d.β*st.I )*st.S - d.α*st.R
    R = d.α*st.R
    return [S,I,R]
end

function initialize(I::Float64,d::SIR)
    state = zeros(nstates(d))
    state[2] = I
    state[1] = 1-I
    return state
end

function nstates(d::SIR)
    return 3
end

function stateNames(d::SIR)
    return ["S" "I" "R"]
end

function getParams(α::Float64,β::Float64,d::SIR)
    return SIR(α,β)
end

"""
estimatedStates( nt::Int64, N::Int64, I0::Float64, d::SIR)

nt: time points
N: population size
I0: proportion infected at start
d: SIR parameters
"""
function estimatedStates(nt::Int64,N::Int64,I0::Float64,d::SIR)
    s0 = initialize(I0,d) # initial state
    (d,ds) = evolve(N*1.0,s0,d,nt) # evolve over time
    s1 = DataFrame(N.*d',[:S,:I,:R]) # data frame with states over time
    return s1
end

## betachange is how the β1 parameter is changed on the logit scale
function estimatedStates( N::Int64, E0::Float64,  betachange::Vector{Float64}, d::SEI3R)
    s0 = initialize(E0,d)
    (d,ds) = evolve(N*1.0,s0,betachange,d)
    s1 = DataFrame(N.*d',[:S,:I,:R])
    return s1
end


using Printf
using LinearAlgebra

include("../common/PWGrid_v01.jl")
include("../common/wrappers_fft.jl")
include("../common/gen_lattice_pwscf.jl")
include("structure_factor.jl")
include("write_xsf.jl")

function test_main()
    Ns = [50,50,50]
    LatVecs = gen_lattice_sc(10.0)
    pwgrid = PWGrid(Ns,LatVecs)

    Npoints = prod(Ns)
    Ω = pwgrid.Ω
    G2 = pwgrid.G2

    # Atomic positions and nuclear charge
    Xpos = reshape( [5.0, 5.0, 5.0,
                     6.5, 5.0, 5.0], (3,2) )
    #Xpos = reshape( [4.0, 4.0, 4.0], (3,1) )

    # Calculate structure factor
    Sf = structure_factor( Xpos, pwgrid.G )

    Ng = Npoints
    Vg = zeros(ComplexF64, Ng)
    prefact = -4.0*pi/Ω
    for ig = 2:Ng
        Vg[ig] = prefact/G2[ig]
    end
    Vg = Vg .* Sf
    Vr = real( G_to_R(Ns, Vg) )*Npoints

    @printf("sum(Vr) = %18.10e\n", sum(Vr))

    write_xsf( "H2.xsf", LatVecs, Xpos )
    write_xsf_3d_crystal( "H2.xsf", Ns, LatVecs, Vr )
end

@time test_main()


# Functions supporting trendline identification (support/resistance, zigzag, elliot waves, etc.)

"""
```
maxima(x::Array{T}; threshold::T=0.0, order::Int=1) where {T<:Real}
```

Estimate local maxima of a time series
"""
function maxima(x::Array{T}; threshold::T=0.0, order::Int=1) where {T<:Real}
    @assert threshold >= 0.0 "threshold must be positive"
    @assert order > 0 "order must be a positive integer"
    n = size(x,1)
    crit = falses(n)
    @inbounds for i=2:n-1
        if (x[i]-x[i-1] >= threshold) && (x[i]-x[i+1] >= threshold)
            crit[i] = true
        end
    end
    while order > 1
        idx = findall(crit)
        crit[idx[!maxima(x[crit], threshold=threshold)]] .= false
        order -= 1
    end
    return crit
end

"""
```
minima(x::Array{T}; threshold::T=0.0, order::Int=1) where {T<:Real}
```

Estimate local minima of a time series
"""
function minima(x::Array{T}; threshold::T=0.0, order::Int=1) where {T<:Real}
    @assert threshold <= 0.0 "threshold must be negative"
    @assert order > 0 "order must be a positive integer"
    n = size(x,1)
    crit = falses(n)
    @inbounds for i=2:n-1
        if (x[i]-x[i-1] <= threshold) && (x[i]-x[i+1] <= threshold)
            crit[i] = true
        end
    end
    while order > 1
        idx = findall(crit)
        crit[idx[!minima(x[crit], threshold=threshold)]] .= false
        order -= 1
    end
    return crit
end

function interpolate(x1::Int, x2::Int, y1::T, y2::T) where {T<:Real}
	m = (y2-y1)/(x2-x1)
	b = y1 - m*x1
	x = collect(x1:1.0:x2)
	y = m*x .+ b
	return y
end

"""
```
resistance(x::Array{T}; order::Int=1, threshold::T=0.0) where {T<:Real}
```

Estimate resistance lines of a financial time series
"""
function resistance(x::Array{T}; order::Int=1, threshold::T=0.0) where {T<:Real}
    out = zeros(size(x))
    crit = maxima(x, threshold=threshold, order=order)
    out[.!crit] .= NaN
    idx = findall(crit)
    @inbounds for i=2:length(idx)
        out[idx[i-1]:idx[i]] .= interpolate(idx[i-1], idx[i], x[i-1], x[i])
    end
    return out
end

"""
```
support(x::Array{T}; order::Int=1, threshold::T=0.0) where {T<:Real}
```

Estimate support lines of a financial time series
"""
function support(x::Array{T}; order::Int=1, threshold::T=0.0) where {T<:Real}
    out = zeros(size(x))
    crit = minima(x, threshold=threshold, order=order)
    out[.!crit] .= NaN
    idx = findall(crit)
    @inbounds for i=2:length(idx)
        out[idx[i-1]:idx[i]] .= interpolate(idx[i-1], idx[i], x[i-1], x[i])
    end
    return out
end


module TwoDNavierStokes

export
  Problem,
  set_ζ!,
  updatevars!,

  energy,
  energy_dissipation_hyperviscosity,
  energy_dissipation_hypoviscosity,
  energy_work,
  enstrophy,
  enstrophy_dissipation_hyperviscosity,
  enstrophy_dissipation_hypoviscosity,
  enstrophy_work

using
  CUDA,
  Reexport,
  DocStringExtensions

@reexport using FourierFlows

using LinearAlgebra: mul!, ldiv!
using FourierFlows: parsevalsum

nothingfunction(args...) = nothing

"""
    Problem(dev::Device=CPU();
                nx = 256,
                ny = nx,
                Lx = 2π,
                Ly = Lx,
                 ν = 0,
                nν = 1,
                 μ = 0,
                nμ = 0,
                dt = 0.01,
           stepper = "RK4",
             calcF = nothingfunction,
        stochastic = false,
  aliased_fraction = 1/3,
                 T = Float64)

Construct a two-dimensional Navier-Stokes `problem` on device `dev`.

Keyword arguments
=================
  - `dev`: (required) `CPU()` or `GPU()`; computer architecture used to time-step `problem`.
  - `nx`: Number of grid points in ``x``-domain.
  - `ny`: Number of grid points in ``y``-domain.
  - `Lx`: Extent of the ``x``-domain.
  - `Ly`: Extent of the ``y``-domain.
  - `ν`: Small-scale (hyper)-viscosity coefficient.
  - `nν`: (Hyper)-viscosity order, `nν```≥ 1``.
  - `μ`: Large-scale (hypo)-viscosity coefficient.
  - `nμ`: (Hypo)-viscosity order, `nμ```≤ 0``.
  - `dt`: Time-step.
  - `stepper`: Time-stepping method.
  - `calcF`: Function that calculates the Fourier transform of the forcing, ``F̂``.
  - `stochastic`: `true` or `false`; boolean denoting whether `calcF` is temporally stochastic.
  - `aliased_fraction`: the fraction of high-wavenumbers that are zero-ed out by `dealias!()`.
  - `T`: `Float32` or `Float64`; floating point type used for `problem` data.
"""
function Problem(dev::Device=CPU();
  # Numerical parameters
                nx = 256,
                ny = nx,
                Lx = 2π,
                Ly = Lx,
  # Drag and/or hyper-/hypo-viscosity
                 ν = 0,
                nν = 1,
                 μ = 0,
                nμ = 0,
  # Timestepper and equation options
                dt = 0.01,
           stepper = "RK4",
             calcF = nothingfunction,
        stochastic = false,
  # Float type and dealiasing
  aliased_fraction = 1/3,
                 T = Float64)

  grid = TwoDGrid(dev, nx, Lx, ny, Ly; aliased_fraction=aliased_fraction, T=T)

  params = Params{T}(ν, nν, μ, nμ, calcF)

  vars = calcF == nothingfunction ? DecayingVars(dev, grid) : (stochastic ? StochasticForcedVars(dev, grid) : ForcedVars(dev, grid))

  equation = Equation(params, grid)

  return FourierFlows.Problem(equation, stepper, dt, grid, vars, params, dev)
end

# ----------
# Parameters
# ----------

"""
    Params{T}(ν, nν, μ, nμ, calcF!)

A struct containing the parameters for the two-dimensional Navier-Stokes. Included are:

$(TYPEDFIELDS)
"""
struct Params{T} <: AbstractParams
    "small-scale (hyper)-viscosity coefficient"
       ν :: T
    "(hyper)-viscosity order, `nν```≥ 1``"
      nν :: Int
    "large-scale (hypo)-viscosity coefficient"
       μ :: T
    "(hypo)-viscosity order, `nμ```≤ 0``"
      nμ :: Int
    "function that calculates the Fourier transform of the forcing, ``F̂``"
  calcF! :: Function
end

Params(ν, nν) = Params(ν, nν, typeof(ν)(0), 0, nothingfunction)

# ---------
# Equations
# ---------

"""
    Equation(params, grid)

Return the `equation` for two-dimensional Navier-Stokes with `params` and `grid`. The linear
operator ``L`` includes (hyper)-viscosity of order ``n_ν`` with coefficient ``ν`` and
hypo-viscocity of order ``n_μ`` with coefficient ``μ``,

```math
L = - ν |𝐤|^{2 n_ν} - μ |𝐤|^{2 n_μ} .
```

Plain old viscocity corresponds to ``n_ν=1`` while ``n_μ=0`` corresponds to linear drag.

The nonlinear term is computed via the function `calcN!`.
"""
function Equation(params::Params, grid::AbstractGrid)
  L = @. - params.ν * grid.Krsq^params.nν - params.μ * grid.Krsq^params.nμ
  CUDA.@allowscalar L[1, 1] = 0

  return FourierFlows.Equation(L, calcN!, grid)
end

# ----
# Vars
# ----

abstract type TwoDNavierStokesVars <: AbstractVars end

"""
    Vars{Aphys, Atrans, F, P}(ζ, u, v, ζh, uh, vh, Fh, prevsol)

The variables for two-dimensional Navier-Stokes:

$(FIELDS)
"""
struct Vars{Aphys, Atrans, F, P} <: TwoDNavierStokesVars
    "relative vorticity"
        ζ :: Aphys
    "x-component of velocity"
        u :: Aphys
    "y-component of velocity"
        v :: Aphys
    "Fourier transform of relative vorticity"
       ζh :: Atrans
    "Fourier transform of x-component of velocity"
       uh :: Atrans
    "Fourier transform of y-component of velocity"
       vh :: Atrans
    "Fourier transform of forcing"
       Fh :: F
    "`sol` at previous time-step"
  prevsol :: P
end

const DecayingVars = Vars{<:AbstractArray, <:AbstractArray, Nothing, Nothing}
const ForcedVars = Vars{<:AbstractArray, <:AbstractArray, <:AbstractArray, Nothing}
const StochasticForcedVars = Vars{<:AbstractArray, <:AbstractArray, <:AbstractArray, <:AbstractArray}

"""
    DecayingVars(dev, grid)

Return the `vars` for unforced two-dimensional Navier-Stokes problem on device `dev` and
with `grid`.
"""
function DecayingVars(::Dev, grid::AbstractGrid) where Dev
  T = eltype(grid)

  @devzeros Dev T (grid.nx, grid.ny) ζ u v
  @devzeros Dev Complex{T} (grid.nkr, grid.nl) ζh uh vh

  return Vars(ζ, u, v, ζh, uh, vh, nothing, nothing)
end

"""
    ForcedVars(dev, grid)

Return the `vars` for forced two-dimensional Navier-Stokes on device `dev` and with `grid`.
"""
function ForcedVars(dev::Dev, grid::AbstractGrid) where Dev
  T = eltype(grid)

  @devzeros Dev T (grid.nx, grid.ny) ζ u v
  @devzeros Dev Complex{T} (grid.nkr, grid.nl) ζh uh vh Fh

  return Vars(ζ, u, v, ζh, uh, vh, Fh, nothing)
end

"""
    StochasticForcedVars(dev, grid)

Return the `vars` for stochastically forced two-dimensional Navier-Stokes on device `dev` and
with `grid`.
"""
function StochasticForcedVars(dev::Dev, grid::AbstractGrid) where Dev
  T = eltype(grid)

  @devzeros Dev T (grid.nx, grid.ny) ζ u v
  @devzeros Dev Complex{T} (grid.nkr, grid.nl) ζh uh vh Fh prevsol

  return Vars(ζ, u, v, ζh, uh, vh, Fh, prevsol)
end

# -------
# Solvers
# -------

"""
    calcN_advection!(N, sol, t, clock, vars, params, grid)

Calculate the Fourier transform of the advection term, ``- 𝖩(ψ, ζ)`` in conservative form,
i.e., ``- ∂_x[(∂_y ψ)ζ] - ∂_y[(∂_x ψ)ζ]`` and store it in `N`:

```math
N = - \\widehat{𝖩(ψ, ζ)} = - i k_x \\widehat{u ζ} - i k_y \\widehat{v ζ} .
```
"""
function calcN_advection!(N, sol, t, clock, vars, params, grid)
  @. vars.uh =   im * grid.l  * grid.invKrsq * sol
  @. vars.vh = - im * grid.kr * grid.invKrsq * sol
  @. vars.ζh = sol

  ldiv!(vars.u, grid.rfftplan, vars.uh)
  ldiv!(vars.v, grid.rfftplan, vars.vh)
  ldiv!(vars.ζ, grid.rfftplan, vars.ζh)

  uζ = vars.u                  # use vars.u as scratch variable
  @. uζ *= vars.ζ              # u*ζ
  vζ = vars.v                  # use vars.v as scratch variable
  @. vζ *= vars.ζ              # v*ζ

  uζh = vars.uh                # use vars.uh as scratch variable
  mul!(uζh, grid.rfftplan, uζ) # \hat{u*ζ}
  vζh = vars.vh                # use vars.vh as scratch variable
  mul!(vζh, grid.rfftplan, vζ) # \hat{v*ζ}

  @. N = - im * grid.kr * uζh - im * grid.l * vζh

  return nothing
end

"""
    calcN!(N, sol, t, clock, vars, params, grid)

Calculate the nonlinear term, that is the advection term and the forcing,

```math
N = - \\widehat{𝖩(ψ, ζ)} + F̂ .
```
"""
function calcN!(N, sol, t, clock, vars, params, grid)
  dealias!(sol, grid)

  calcN_advection!(N, sol, t, clock, vars, params, grid)

  addforcing!(N, sol, t, clock, vars, params, grid)

  return nothing
end

"""
    addforcing!(N, sol, t, clock, vars, params, grid)

When the problem includes forcing, calculate the forcing term ``F̂`` and add it to the
nonlinear term ``N``.
"""
addforcing!(N, sol, t, clock, vars::DecayingVars, params, grid) = nothing

function addforcing!(N, sol, t, clock, vars::ForcedVars, params, grid)
  params.calcF!(vars.Fh, sol, t, clock, vars, params, grid)

  @. N += vars.Fh

  return nothing
end
function addforcing!(N, sol, t, clock, vars::StochasticForcedVars, params, grid)
  if t == clock.t # not a substep
    @. vars.prevsol = sol # sol at previous time-step is needed to compute budgets for stochastic forcing
    params.calcF!(vars.Fh, sol, t, clock, vars, params, grid)
  end
  @. N += vars.Fh

  return nothing
end

# ----------------
# Helper functions
# ----------------

"""
    updatevars!(prob)

Update variables in `vars` with solution in `sol`.
"""
function updatevars!(prob)
  vars, grid, sol = prob.vars, prob.grid, prob.sol

  dealias!(sol, grid)

  @. vars.ζh = sol
  @. vars.uh =   im * grid.l  * grid.invKrsq * sol
  @. vars.vh = - im * grid.kr * grid.invKrsq * sol

  ldiv!(vars.ζ, grid.rfftplan, deepcopy(vars.ζh)) # deepcopy() since inverse real-fft destroys its input
  ldiv!(vars.u, grid.rfftplan, deepcopy(vars.uh)) # deepcopy() since inverse real-fft destroys its input
  ldiv!(vars.v, grid.rfftplan, deepcopy(vars.vh)) # deepcopy() since inverse real-fft destroys its input

  return nothing
end

"""
    set_ζ!(prob, ζ)

Set the solution `sol` as the transform of `ζ` and then update variables in `vars`.
"""
function set_ζ!(prob, ζ)
  mul!(prob.sol, prob.grid.rfftplan, ζ)

  CUDA.@allowscalar prob.sol[1, 1] = 0 # enforce zero domain average

  updatevars!(prob)

  return nothing
end

"""
    energy(prob)

Return the domain-averaged kinetic energy. Since ``u² + v² = |{\\bf ∇} ψ|²``, the domain-averaged
kinetic energy is

```math
\\int \\frac1{2} |{\\bf ∇} ψ|² \\frac{𝖽x 𝖽y}{L_x L_y} = \\sum_{𝐤} \\frac1{2} |𝐤|² |ψ̂|² .
```
"""
@inline function energy(prob)
  sol, vars, grid = prob.sol, prob.vars, prob.grid
  energyh = vars.uh # use vars.uh as scratch variable

  @. energyh = 1 / 2 * grid.invKrsq * abs2(sol)
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(energyh, grid)
end

"""
    enstrophy(prob)

Returns the domain-averaged enstrophy,

```math
\\int \\frac1{2} ζ² \\frac{𝖽x 𝖽y}{L_x L_y} = \\sum_{𝐤} \\frac1{2} |ζ̂|² .
```
"""
@inline function enstrophy(prob)
  sol, grid = prob.sol, prob.grid
  return 1 / (2 * grid.Lx * grid.Ly) * parsevalsum(abs2.(sol), grid)
end

"""
    energy_dissipation(prob, ξ, νξ)

Return the domain-averaged energy dissipation rate done by the viscous term,

```math
- ξ (-1)^{n_ξ+1} \\int ψ ∇^{2n_ξ} ζ \\frac{𝖽x 𝖽y}{L_x L_y} = - ξ \\sum_{𝐤} |𝐤|^{2(n_ξ-1)} |ζ̂|² ,
```
where ``ξ`` and ``nξ`` could be either the (hyper)-viscosity coefficient ``ν`` and its order
``n_ν``, or the hypo-viscocity coefficient ``μ`` and its order ``n_μ``.
"""
@inline function energy_dissipation(prob, ξ, nξ)
  sol, vars, grid = prob.sol, prob.vars, prob.grid
  energy_dissipationh = vars.uh # use vars.uh as scratch variable

  @. energy_dissipationh = - ξ * grid.Krsq^(nξ - 1) * abs2(sol)
  CUDA.@allowscalar energy_dissipationh[1, 1] = 0
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(energy_dissipationh, grid)
end

"""
    energy_dissipation_hyperviscosity(prob, ξ, νξ)

Return the domain-averaged energy dissipation rate done by the ``ν`` (hyper)-viscosity.
"""
energy_dissipation_hyperviscosity(prob) = energy_dissipation(prob, prob.params.ν, prob.params.nν)

"""
    energy_dissipation_hypoviscosity(prob, ξ, νξ)

Return the domain-averaged energy dissipation rate done by the ``μ`` (hypo)-viscosity.
"""
energy_dissipation_hypoviscosity(prob) = energy_dissipation(prob, prob.params.μ, prob.params.nμ)

"""
    enstrophy_dissipation(prob, ξ, νξ)

Return the domain-averaged enstrophy dissipation rate done by the viscous term,

```math
ξ (-1)^{n_ξ+1} \\int ζ ∇^{2n_ξ} ζ \\frac{𝖽x 𝖽y}{L_x L_y} = - ξ \\sum_{𝐤} |𝐤|^{2n_ξ} |ζ̂|² ,
```

where ``ξ`` and ``nξ`` could be either the (hyper)-viscosity coefficient ``ν`` and its order
``n_ν``, or the hypo-viscocity coefficient ``μ`` and its order ``n_μ``.
"""
@inline function enstrophy_dissipation(prob, ξ, nξ)
  sol, vars, grid = prob.sol, prob.vars, prob.grid
  enstrophy_dissipationh = vars.uh # use vars.uh as scratch variable

  @. enstrophy_dissipationh = - ξ * grid.Krsq^nξ * abs2(sol)
  CUDA.@allowscalar enstrophy_dissipationh[1, 1] = 0
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(enstrophy_dissipationh, grid)
end

"""
    enstrophy_dissipation_hyperviscosity(prob, ξ, νξ)

Return the domain-averaged enstrophy dissipation rate done by the ``ν`` (hyper)-viscosity.
"""
enstrophy_dissipation_hyperviscosity(prob) = enstrophy_dissipation(prob, prob.params.ν, prob.params.nν)

"""
    enstrophy_dissipation_hypoviscosity(prob, ξ, νξ)

Return the domain-averaged enstrophy dissipation rate done by the ``μ`` (hypo)-viscosity.
"""
enstrophy_dissipation_hypoviscosity(prob) = enstrophy_dissipation(prob, prob.params.μ, prob.params.nμ)

"""
    energy_work(prob)
    energy_work(sol, vars, grid)

Return the domain-averaged rate of work of energy by the forcing ``F``,

```math
- \\int ψ F \\frac{𝖽x 𝖽y}{L_x L_y} = - \\sum_{𝐤} ψ̂ F̂^* .
```
"""
@inline function energy_work(sol, vars::ForcedVars, grid)
  energy_workh = vars.uh # use vars.uh as scratch variable

  @. energy_workh = grid.invKrsq * sol * conj(vars.Fh)
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(energy_workh, grid)
end

@inline function energy_work(sol, vars::StochasticForcedVars, grid)
  energy_workh = vars.uh # use vars.uh as scratch variable

  @. energy_workh = grid.invKrsq * (vars.prevsol + sol) / 2 * conj(vars.Fh)
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(energy_workh, grid)
end

@inline energy_work(prob) = energy_work(prob.sol, prob.vars, prob.grid)

"""
    enstrophy_work(prob)
    enstrophy_work(sol, vars, grid)

Return the domain-averaged rate of work of enstrophy by the forcing ``F``,

```math
\\int ζ F \\frac{𝖽x 𝖽y}{L_x L_y} = \\sum_{𝐤} ζ̂ F̂^* .
```
"""
@inline function enstrophy_work(sol, vars::ForcedVars, grid)
  enstrophy_workh = vars.uh # use vars.uh as scratch variable

  @. enstrophy_workh = sol * conj(vars.Fh)
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(enstrophy_workh, grid)
end

@inline function enstrophy_work(sol, vars::StochasticForcedVars, grid)
  enstrophy_workh = vars.uh # use vars.uh as scratch variable

  @. enstrophy_workh = (vars.prevsol + sol) / 2 * conj(vars.Fh)
  return 1 / (grid.Lx * grid.Ly) * parsevalsum(enstrophy_workh, grid)
end

@inline enstrophy_work(prob) = enstrophy_work(prob.sol, prob.vars, prob.grid)

end # module


import DFT, FFTW
import DFT: fft_kernel_sizes
using Test

# kernel test
@testset "FFT kernel codelet" begin
    for i in fft_kernel_sizes
        data = rand(i)
        @test DFT.fft(data) ≈ FFTW.fft(data)
    end
end


# Michelle Medina
# June 20, 2020
# Tests Linear Interpolation Code

#TODO comment
x = [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
u=sin.(x)

@test line(3.0,5.0,1.0,3.0,1.5) == 3.5

@test find_interval(x,3.5) == 3

@test interpolate(x,u,1.5) == 0.8753842058167891

# interpolateX(0.0, 1.0, 0.0, 1.0, 2.0, 3.0, 0.5)

interpolateY(0.0, 1.0, 1.0, 2.0, 0.5)

p1 = (0.0, 0.0, 0.0)
p2 = (0.0, 1.0, 1.0)
p3 = (1.0, 0.0, 2.0)
p4 = (1.0, 1.0, 3.0)
p5 = (0.5, 0.5)

p6 = (0.0, 0.0)
p7 = (0.0, 1.0)
p8 = (1.0, 0.0)
p9 = (1.0, 1.0)

@test interpolate(p1, p2, p3, p4, p5) == 1.5

# Test at corner points
@test interpolate(p1, p2, p3, p4, p6) == 0.0
@test interpolate(p1, p2, p3, p4, p7) == 1.0
@test interpolate(p1, p2, p3, p4, p8) == 2.0
@test interpolate(p1, p2, p3, p4, p9) == 3.0


using AutoGrad

# === building blocks for NN ===

function init_weights(;n_in=1, n_hidden=10, n_out=1)
    W1 = randn(n_hidden, n_in) # for left multiply W1*x
    b1 = zeros(n_hidden)
    W2 = randn(n_out, n_hidden)
    b2 = zeros(n_out)
    params = [W1, b1, W2, b2]
    return params
end

function sizes_and_length(params)
    sizes = map(size, params)
    lengths = map(length, params)
    total_l = sum(lengths)
    return sizes, lengths, total_l
end

function predict(params, t, y0, t0; act=tanh)
    W1, b1, W2, b2 = params

    # normal NN calculation
    a = act.(W1*t .+ b1)
    out = W2*a .+ b2

    # force intial condition
    y = y0 .+ (t .- t0) .* out
    return y
end

# gradient w.r.t t
# get diagonal of jacobian to vectorize over t
predict_sum(params, t, y0, t0) = sum(predict(params, t, y0, t0))
predict_dt = grad(predict_sum, 2)

# === flatten & unflatten ===

flat_opt = p->collect(Iterators.flatten(p)) # flatten operation for a single NN params

function unflatten(params_flat, sizes, lengths)
    params = []
    i1 = 1
    for j in 1:length(sizes)
        s = sizes[j]
        l = lengths[j]
        i2 = i1+l
        #p = reshape(params_flat[i1:i2-1], s)
        p = reshape(view(params_flat,i1:i2-1), s)
        push!(params, p)
        i1 = i2
    end
    return params
end

function unflatten_all(all_flattened_params, Nvar, total_l, sizes, lengths)
    params_list = []
    all_params_reshape = reshape(all_flattened_params, total_l, Nvar)
    for i in 1:Nvar
        params = unflatten(all_params_reshape[:,i], sizes, lengths)
        push!(params_list, params)
    end
    return params_list
end


function gradcheck(net;showgrad=false,returngrad=false)
    #ONLY WORKS ON THE CPU AT THE MOMENT
    # (c) David Barber, University College London 2015
    println("gradcheck...")
    value=net.value
    node=net.node
    net2=deepcopy(net) ## CAREFUL -- DEEPCOPY IS BUGGY FOR CUDAARRAY

    ADforward!(net)
    ADbackward!(net)
    g=net.gradient

    epsilon=1e-7;
    tol=1e-6
    gemp=deepcopy(g)
    for par=1:length(net.node) # loop over the nodes in the network
        if node[par].returnderivative
            println("------------------------------------------------------------------------------")
            println("node $par:")
            for linearidx in 1:prod(size(value[par]))
                net2.value[par][linearidx]=value[par][linearidx]+epsilon
                fplus=ADforward!(net2,returnf=true)
                net2.value[par][linearidx]=value[par][linearidx]-epsilon
                fminus=ADforward!(net2,returnf=true)
                net2.value[par][linearidx]=value[par][linearidx]
                gemp[par][linearidx]=0.5*(fplus-fminus)/epsilon
            end
            diff=mean(abs(g[par]-gemp[par]))
            reldiff=mean(abs((g[par]-gemp[par])./(realmin()+g[par])))
            println("absolute difference between analytic and empirical gradient=$diff")
            println("relative difference between analytic and empirical gradient=$reldiff")

                #if showgrad
                #println("analytic gradient:"); println(g[par])
                #println("empirical gradient:"); println(gemp[par])
                #end

            if diff>tol
                if showgrad
                println("analytic gradient:"); println(g[par])
                println("empirical gradient:"); println(gemp[par])
                end
                print_with_color(:red,"failed: analytic and empiricial gradient mismatch more than $tol\n")
            else
                print_with_color(:green,"passed\n")
            end
        end
    end
    if returngrad
        return gemp
    end
end


# ---
# title: 714. Best Time to Buy and Sell Stock with Transaction Fee
# id: problem714
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array, Dynamic Programming, Greedy
# link: <https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/>
# hidden: true
# ---
#
# Your are given an array of integers `prices`, for which the `i`-th element is
# the price of a given stock on day `i`; and a non-negative integer `fee`
# representing a transaction fee.
#
# You may complete as many transactions as you like, but you need to pay the
# transaction fee for each transaction. You may not buy more than 1 share of a
# stock at a time (ie. you must sell the stock share before you buy again.)
#
# Return the maximum profit you can make.
#
# **Example 1:**
#
#
#
#     Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
#     Output: 8
#     Explanation: The maximum profit can be achieved by:
#
#     * Buying at prices[0] = 1
#     * Selling at prices[3] = 8
#     * Buying at prices[4] = 4
#     * Selling at prices[5] = 9
#     The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
#
#
# **Note:**
#
# * `0 < prices.length <= 50000`.
# * `0 < prices[i] < 50000`.
# * `0 <= fee < 50000`.
#
#
## @lc code=start
using LeetCode
function max_profit714(price::Vector{Int}, fee::Int)::Int
    dp1, dp2 = 0, -50000
    for p in price
        dp1, dp2 = max(dp1, dp2 + p - fee), max(dp2, dp1 - p)
    end
    return dp1
end
## @lc code=end


# HIRES (chemical reaction, mildly stiff)
#
# http://www.dm.uniba.it/~testset/problems/hires.php
# http://www.unige.ch/~hairer/testset/testset.html
#
## Copyright (C) 2014, Mauro Werder <mauro_lc@runbox.com>
#
# Adapted from OdePkg, original author:
## Copyright (C) 2007-2012, Thomas Treichl <treichl@users.sourceforge.net>
## OdePkg - A package for solving ordinary differential equations and more
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## 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
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; If not, see <http://www.gnu.org/licenses/>.

export hires

hires = let
    tcname = :hires
    T = Float64
    Tarr = Matrix
    dae = 0
    dof = 8

    # Computes the results for the HIRES problem
    function fn!(t, y, out)
        out[1] = -1.71 * y[1] + 0.43 * y[2] + 8.32 * y[3] + 0.0007
        out[2] =  1.71 * y[1] - 8.75 * y[2]
        out[3] = -10.03 * y[3] + 0.43 * y[4] + 0.035 * y[5]
        out[4] =  8.32 * y[2] + 1.71 * y[3] - 1.12 * y[4]
        out[5] = -1.745 * y[5] + 0.43 * (y[6] + y[7])
        out[6] = -280 * y[6] * y[8] + 0.69 * y[4] + 1.71 * y[5] - 0.43 * y[6] + 0.69 * y[7]
        out[7] =  280 * y[6] * y[8] - 1.81 * y[7]
        out[8] = -out[7]
        return nothing
    end
    # initializes storage for y:
    fn!(;T_::Type=T, dof_=dof) = zeros(T_,dof_)

    # Computes the JACOBIAN matrix for the HIRES problem
    function jac!(t, y, dfdy)
        dfdy[1,1] = -1.71
        dfdy[1,2] =  0.43
        dfdy[1,3] =  8.32
        dfdy[2,1] =  1.71
        dfdy[2,2] = -8.75
        dfdy[3,3] = -10.03
        dfdy[3,4] =  0.43
        dfdy[3,5] =  0.035
        dfdy[4,2] =  8.32
        dfdy[4,3] =  1.71
        dfdy[4,4] = -1.12
        dfdy[5,5] = -1.745
        dfdy[5,6] =  0.43
        dfdy[5,7] =  0.43
        dfdy[6,4] =  0.69
        dfdy[6,5] =  1.71
        dfdy[6,6] = -280 * y[8] - 0.43
        dfdy[6,7] =  0.69
        dfdy[6,8] = -280 * y[6]
        dfdy[7,6] =  280 * y[8]
        dfdy[7,7] = -1.81
        dfdy[7,8] =  280 * y[6]
        dfdy[8,6] = -280 * y[8]
        dfdy[8,7] =  1.81
        dfdy[8,8] = -280 * y[6]
        return nothing
    end
    # Returns a matrix which can hold the Jacobian of selected
    # type, initialized to zero.  Can also be used to make a
    # Matlab-style JPattern matrix with jac!(Bool)
    jac!(;T_::Type=T, dof_=dof) = zeros(T_,dof_,dof_)

    mass! = nothing

    ic = T[1, 0, 0, 0, 0, 0, 0, 0.0057]
    tspan = T[0.0, 321.8122]

    refsol = zeros(T, dof)
    refsol[1] = 0.73713125733256e-3
    refsol[2] = 0.14424857263161e-3
    refsol[3] = 0.58887297409675e-4
    refsol[4] = 0.11756513432831e-2
    refsol[5] = 0.23863561988313e-2
    refsol[6] = 0.62389682527427e-2
    refsol[7] = 0.28499983951857e-2
    refsol[8] = 0.28500016048142e-2

    refsolinds = trues(dof)

    scd_absinds = Int[]

    tc = TestCaseExplicit{tcname, T, Tarr}(
                             mildlystiff,
                             dae,
                             dof,
                             fn!,
                             jac!,
                             mass!,
                             ic,
                             tspan,
                             refsol,
                             refsolinds,
                             scd_absinds)

    tc_all[tcname] = tc
    tc_stiff[tcname] = tc
end


# ---
# title: 497. Random Point in Non-overlapping Rectangles
# id: problem497
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Binary Search, Random
# link: <https://leetcode.com/problems/random-point-in-non-overlapping-rectangles/description/>
# hidden: true
# ---
#
# Given a list of **non-overlapping**  axis-aligned rectangles `rects`, write a
# function `pick` which randomly and uniformily picks an **integer point** in
# the space covered by the rectangles.
#
# Note:
#
#   1. An **integer point**  is a point that has integer coordinates.
#   2. A point on the perimeter of a rectangle is  **included** in the space covered by the rectangles.
#   3. `i`th rectangle = `rects[i]` = `[x1,y1,x2,y2]`, where `[x1, y1]` are the integer coordinates of the bottom-left corner, and `[x2, y2]` are the integer coordinates of the top-right corner.
#   4. length and width of each rectangle does not exceed `2000`.
#   5. `1 <= rects.length <= 100`
#   6. `pick` return a point as an array of integer coordinates `[p_x, p_y]`
#   7. `pick` is called at most `10000` times.
#
# **Example 1:**
#
#
#
#     Input: ["Solution","pick","pick","pick"]
#     [[[[1,1,5,5]]],[],[],[]]
#     Output: [null,[4,1],[4,1],[3,3]]
#
#
# **Example 2:**
#
#
#
#     Input: ["Solution","pick","pick","pick","pick","pick"]
#     [[[[-2,-2,-1,-1],[1,0,3,0]]],[],[],[],[],[]]
#     Output: [null,[-1,-2],[2,0],[-2,-1],[3,0],[-2,-2]]
#
# **Explanation of Input Syntax:**
#
# The input is two lists: the subroutines called and their arguments.
# `Solution`'s constructor has one argument, the array of rectangles `rects`.
# `pick` has no arguments. Arguments are always wrapped with a list, even if
# there aren't any.
#
#
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


"""
    _check_signs(pa, pb, values = ())

Check the signs of `pa`, `pb` and `values`. Returns `1` if all of them
definitely have the same sign, `-1` if there are at least two who
definitely have different signs and `0` otherwise.
"""
function _check_signs(pa, pb, values = ())
    has_negative = false
    has_positive = false
    has_zero = false

    for v in Base.Iterators.flatten(((pa, pb), values))
        sgn = Arblib.sgn_nonzero(v)

        has_negative |= sgn == -1
        has_positive |= sgn == 1
        has_zero |= sgn == 0

        has_negative && has_positive && return -1
    end

    if has_zero
        return 0
    else
        return 1
    end
end

"""
    extrema_polynomial(p::ArbPoly, a::Arf, b::Arf; abs_value = false, verbose = false)

Compute an enclosure of both the minimum and maximum of the polynomial
`p` on the interval `[a, b]` and return them as a 2-tuple.

The extrema are computed by finding the zeros of the derivative of `p`
using [`isolate_roots`](@ref) and refining them using
[`refine_root`](@ref), then evaluating `p` on the potential zeros as
well as the endpoints `a`, `b` of the interval.

If `abs_value = true` the compute the extrema of `abs(p(x))` on the
interval `[a, b]`. For the computation of the maximum this is mostly
the same, only difference is that we have to take the absolute value
of the evaluations. For the minimum we have to take into account that
the polynomial might cross zero, in which case the minimum is zero. To
see if `p` crosses zero we compare the sign of `p` at the endpoints
and all zeros of its derivative. If any of these have different signs
we are guaranteed that `p` crosses zero and the minimum is zero, if
all of them have the same sign we are instead guaranteed that the
polynomial doesn't cross zero.

If `verbose = true` then output information about the process.

TODO: Allow passing arguments to `isolate_roots`?
"""
function extrema_polynomial(p::ArbPoly, a::Arf, b::Arf; abs_value = false, verbose = false)
    check_interval(a, b)

    maybe_abs = abs_value ? abs : identity

    if a == b
        pa = maybe_abs(p(a))
        return pa, pa
    end

    # Compute values at endpoints
    pa, pb = p(a), p(b)

    # Flag to determine if the minimum value has already been
    # determined in the case when computing the absolute value.
    min_done = false

    # Handle the signs not being the same when computing the absolute
    # value
    if abs_value
        sgn = _check_signs(pa, pb)
        if sgn == -1
            # The sign differs - the minimum is zero
            verbose && @info "sign of endpoints differ - minimum is zero"

            min_value = zero(pa)
            min_done = true
        elseif sgn == 0
            # At least one of the enclosures contain zero, can't get a
            # better enclosure than the minimum of these two
            min_value = min(maybe_abs(pa), maybe_abs(pb))
            Arblib.nonnegative_part!(min_value, min_value)

            verbose &&
                @info "sign of endpoints undetermined - minimum on endpoints: $min_value"
            min_done = true
        end
    end

    # Compute the extrema on the endpoints
    min_endpoints, max_endpoints = minmax(maybe_abs(pa), maybe_abs(pb))

    verbose && @info "extrema on endpoints: $((min_endpoints, max_endpoints))"

    # Short circuit non-finite values and return NaN
    !isfinite(min_endpoints) ||
        !isfinite(max_endpoints) && return (
            Arb(NaN, prec = precision(min_endpoints)),
            Arb(NaN, prec = precision(min_endpoints)),
        )

    # Short circuit on constant or linear polynomial where extrema is
    # attained at endpoints (or possibly is zero if computing absolute
    # value)
    if Arblib.degree(p) <= 1
        if min_done
            return min_value, max_endpoints
        else
            return min_endpoints, max_endpoints
        end
    end

    # Compute the roots of the derivative
    dp = Arblib.derivative(p)
    roots, flags = isolate_roots(dp, a, b)
    roots = Arb.(roots) # We want the roots as balls rather than tuples

    verbose &&
        @info "found $(length(roots)) intervals with possible roots of which $(sum(flags)) unique"

    # Evaluate the polynomial on all the roots
    values = p.(roots)

    # Handle sign crossings when computing the absolute value
    if abs_value && !min_done && _check_signs(pa, pb, values) == -1
        verbose && @info "polynomial crosses zero - minimum is zero"
        min_value = zero(pa)
        min_done = true
    end

    if !min_done
        min_value = copy(min_endpoints)
    end
    max_value = copy(max_endpoints)
    for v in values
        v = maybe_abs(v)
        min_done || Arblib.min!(min_value, min_value, v)
        Arblib.max!(max_value, max_value, v)
    end

    abs_value && Arblib.nonnegative_part!(min_value, min_value)
    abs_value && Arblib.nonnegative_part!(max_value, max_value)

    verbose && @info "extrema with unrefined roots: $((min_value, max_value))"

    # Refine the unique roots where the extrema could occur
    count = 0
    for i in eachindex(roots)
        !(maybe_abs(values[i]) < max_value)

        if flags[i] && (
            !(min_done || maybe_abs(values[i]) > min_value) ||
            !(maybe_abs(values[i]) < max_value)
        )
            roots[i] = refine_root(dp, roots[i], strict = false)
            values[i] = p(roots[i])
            count += 1
        end
    end

    verbose && @info "refined $count roots"

    if abs_value && !min_done && _check_signs(pa, pb, values) == -1
        verbose && @info "polynomial crosses zero - minimum is zero"
        min_value = zero(pa)
        min_done = true
    end

    # Recompute extrema on the refined roots
    if !min_done
        min_value = copy(min_endpoints)
    end
    max_value = copy(max_endpoints)
    for v in values
        v = maybe_abs(v)
        min_done || Arblib.min!(min_value, min_value, v)
        Arblib.max!(max_value, max_value, v)
    end

    abs_value && Arblib.nonnegative_part!(min_value, min_value)
    abs_value && Arblib.nonnegative_part!(max_value, max_value)

    verbose && @info "extrema with refined roots: $((min_value, max_value))"

    return min_value, max_value
end

"""
    minimum_polynomial(p::ArbPoly, a::Arf, b::Arf; abs_value = false, verbose = false)

Compute an enclosure of the minimum of the polynomial `p` on the
interval `[a, b]`.

Takes the same arguments as [`extrema_polynomial`](@ref). The
algorithm is also the same except that it only looks for the minimum.
"""
function minimum_polynomial(p::ArbPoly, a::Arf, b::Arf; abs_value = false, verbose = false)
    check_interval(a, b)

    maybe_abs = abs_value ? abs : identity

    if a == b
        return maybe_abs(p(a))
    end

    # Compute values at endpoints
    pa, pb = p(a), p(b)

    # Handle the signs not being the same when computing the absolute
    # value
    if abs_value
        sgn = _check_signs(pa, pb)
        if sgn == -1
            # The sign differs return zero
            verbose && @info "sign of endpoints differ - minimum is zero"

            return zero(pa)
        elseif sgn == 0
            # At least one of the enclosures contain zero, return the
            # minimum of the two enclosures
            m = min(maybe_abs(pa), maybe_abs(pb))
            Arblib.nonnegative_part!(m, m)

            verbose && @info "sign of endpoints undetermined - minimum on endpoints: $m"

            return m
        end
    end

    # Compute the minimum on the endpoints
    m_endpoints = min(maybe_abs(pa), maybe_abs(pb))

    abs_value && Arblib.nonnegative_part!(m_endpoints, m_endpoints)

    verbose && @info "minimum on endpoints: $m_endpoints"

    # Short circuit non-finite values and return NaN
    !isfinite(m_endpoints) && return Arb(NaN, prec = precision(m_endpoints))

    # Short circuit on constant or linear polynomial where minimum is
    # attained at endpoints
    Arblib.degree(p) <= 1 && return m_endpoints

    # Compute the roots of the derivative
    dp = Arblib.derivative(p)
    roots, flags = isolate_roots(dp, a, b)
    roots = Arb.(roots) # We want the roots as balls rather than tuples

    verbose &&
        @info "found $(length(roots)) intervals with possible roots of which $(sum(flags)) unique"

    # Evaluate the polynomial on all the roots and find the minimum
    values = p.(roots)

    # Handle sign crossings when computing the absolute value
    if abs_value && _check_signs(pa, pb, values) == -1
        verbose && @info "polynomial crosses zero - minimum is zero"
        return zero(m_endpoints)
    end

    m = copy(m_endpoints)
    for v in values
        Arblib.min!(m, m, maybe_abs(v))
    end

    # If computing with absolute value keep only non-negative part
    abs_value && Arblib.nonnegative_part!(m, m)

    verbose && @info "minimum with unrefined roots: $m"

    # Refine the unique roots where the minimum could occur
    count = 0
    for i in eachindex(roots)
        if flags[i] && !(values[i] > m)
            roots[i] = refine_root(dp, roots[i], strict = false)
            values[i] = p(roots[i])
            count += 1
        end
    end

    verbose && @info "refined $count roots"

    # Handle sign crossings when computing the absolute value
    if abs_value && _check_signs(pa, pb, values) == -1
        verbose && @info "polynomial crosses zero - minimum is zero"
        return zero(m_endpoints)
    end

    # Recompute minimum on the refined roots
    m = copy(m_endpoints)
    for v in values
        Arblib.min!(m, m, maybe_abs(v))
    end

    # If computing with absolute value keep only non-negative part
    abs_value && Arblib.nonnegative_part!(m, m)

    verbose && @info "minimum with refined roots: $m"

    return m
end

"""
    maximum_polynomial(p::ArbPoly, a::Arf, b::Arf; abs_value = false, verbose = false)

Compute an enclosure of the maximum of the polynomial `p` on the
interval `[a, b]`.

Takes the same arguments as [`extrema_polynomial`](@ref). The
algorithm is also the same except that it only looks for the maximum.
"""
function maximum_polynomial(p::ArbPoly, a::Arf, b::Arf; abs_value = false, verbose = false)
    check_interval(a, b)

    maybe_abs = abs_value ? abs : identity

    if a == b
        return maybe_abs(p(a))
    end

    # Compute the maximum on the endpoints
    m_endpoints = max(maybe_abs(p(a)), maybe_abs(p(b)))

    # If computing with absolute value keep only non-negative part
    abs_value && Arblib.nonnegative_part!(m_endpoints, m_endpoints)

    verbose && @info "maximum on endpoints: $m_endpoints"

    # Short circuit non-finite values and return NaN
    !isfinite(m_endpoints) && return Arb(NaN, prec = precision(m_endpoints))

    # Short circuit on constant or linear polynomial where maximum is
    # attained at endpoints
    Arblib.degree(p) <= 1 && return m_endpoints

    # Compute the roots of the derivative
    dp = Arblib.derivative(p)
    roots, flags = isolate_roots(dp, a, b)
    roots = Arb.(roots) # We want the roots as balls rather than tuples

    verbose &&
        @info "found $(length(roots)) intervals with possible roots of which $(sum(flags)) unique"

    # Evaluate the polynomial on all the roots and find the maximum
    values = p.(roots)

    m = copy(m_endpoints)
    for v in values
        Arblib.max!(m, m, maybe_abs(v))
    end

    # If computing with absolute value keep only non-negative part
    abs_value && Arblib.nonnegative_part!(m, m)

    verbose && @info "maximum with unrefined roots: $m"

    # Refine the unique roots where the maximum could occur
    count = 0
    for i in eachindex(roots)
        if flags[i] && !(maybe_abs(values[i]) < m)
            roots[i] = refine_root(dp, roots[i], strict = false)
            values[i] = p(roots[i])
            count += 1
        end
    end

    verbose && @info "refined $count roots"

    # Recompute maximum on the refined roots
    m = copy(m_endpoints)
    for v in values
        Arblib.max!(m, m, maybe_abs(v))
    end

    # If computing with absolute value keep only non-negative part
    abs_value && Arblib.nonnegative_part!(m, m)

    verbose && @info "maximum with refined roots: $m"

    return m
end


#!/usr/local/bin/julia

function bubbleSort(array::Array{Int64, 1})
    array_length = length(array)
    for iteration in 1:array_length
        for item in 1:array_length-iteration-1
            if array[item] > array[item+1]
                array[item], array[item+1] = array[item+1], array[item]
            end
        end
    end
    return array
end

# julia> a = [12,24,48,11,23,45,9,7,5,78]
# julia> bubbleSort(a)
# 10-element Array{Int64,1}:
#   5
#   7
#   9
#  11
#  12
#  23
#  24
#  45
#  48
#  78


# ---
# title: 209. Minimum Size Subarray Sum
# id: problem209
# author: Indigo
# date: 2021-01-20
# difficulty: Medium
# categories: Array, Two Pointers, Binary Search
# link: <https://leetcode.com/problems/minimum-size-subarray-sum/description/>
# hidden: true
# ---
#
# Given an array of **n** positive integers and a positive integer **s** , find
# the minimal length of a **contiguous** subarray of which the sum ≥ **s**. If
# there isn't one, return 0 instead.
#
# **Example:  **
#
#
#
#     Input: s = 7, nums = [2,3,1,2,4,3]
#     Output: 2
#     Explanation: the subarray [4,3] has the minimal length under the problem constraint.
#
# **Follow up:**
#
# If you have figured out the _O_ ( _n_ ) solution, try coding another solution
# of which the time complexity is _O_ ( _n_ log _n_ ).
#
#
## @lc code=start
using LeetCode

function min_subarray_len(s::Int, nums::Vector{Int})
    left = right = 1
    res = typemax(Int)
    len = length(nums)
    Σ = nums[1]
    while right <= len
        if Σ == s
            res = min(res, right - left + 1)
            Σ -= nums[left]
            left += 1
            right += 1
            (right <= len) && (Σ += nums[right])
        elseif Σ < s
            right += 1
            (right <= len) && (Σ += nums[right])
        else
            Σ -= nums[left]
            left += 1
        end
    end
    res
end
## @lc code=end


### A Pluto.jl notebook ###
# v0.14.4

using Markdown
using InteractiveUtils

# ╔═╡ bfe42bab-98e8-4460-a8ba-12968297d227
begin
	using PlutoUI, LinearAlgebra
	PlutoUI.TableOfContents(aside=true)
end

# ╔═╡ 43d24d37-df93-4ee6-8e48-89f9ca6f0f92
md"""
# Singular Value Decomposition - Algorithms and Error Analysis

We study only algorithms for real matrices, which are most commonly used in the applications described in this course.

For more details, see
[A. Kaylor Cline and I. Dhillon, Computation of the Singular Value Decomposition, pp. 58.1-58.13](https://www.routledge.com/Handbook-of-Linear-Algebra/Hogben/p/book/9781138199897) and the references therein.

__Prerequisites__

The reader should be familiar with facts about the singular value decomposition and perturbation theory and algorithms for the symmetric eigenvalue decomposition.

__Competences__

The reader should be able to apply an adequate algorithm to a given problem, and to assess the accuracy of the solution.
"""

# ╔═╡ 64ca893a-a1ee-4172-bd20-cf4aca1c3d41
md"""
# Basics

## Definitions

The __singular value decomposition__ (SVD) of $A\in\mathbb{R}^{m\times n}$ is $A=U\Sigma V^T$, where $U\in\mathbb{R}^{m\times m}$ is orthogonal, $U^TU=UU^T=I_m$, $V\in\mathbb{R}^{n\times n}$ is orthogonal, $V^TV=VV^T=I_n$, and $\Sigma \in\mathbb{R}^{m\times n}$ is diagonal with singular values $\sigma_1,\ldots,\sigma_{\min\{m,n\}}$ on the diagonal.

If $m>n$, the __thin SVD__ of $A$ is $A=U_{1:m,1:n} \Sigma_{1:n,1:n} V^T$.

## Facts

1. Algorithms for computing SVD of $A$ are modifications of algorithms for the symmetric eigenvalue decomposition of the matrices $AA^T$, $A^TA$ and $\begin{bmatrix} 0 & A\\ A^T & 0 \end{bmatrix}$.

2. Most commonly used approach is the three-step algorithm:
    1. Reduce $A$ to bidiagonal matrix $B$ by orthogonal transformations, $X^TAY=B$.
    2. Compute the SVD of $B$ with QR iterations, $B=W\Sigma Z^T$.
    3. Multiply $U=XW$ and $V=YZ$.

3. If $m\geq n$, the overall operation count for this algorithm is $O(mn^2)$ operations.

4. __Error bounds.__ Let $U\Sigma V^T$ and $\tilde U \tilde \Sigma \tilde V^T$ be the exact and the computed SVDs of $A$, respectively. The algorithms generally compute the SVD with errors bounded by

$$
|\sigma_i-\tilde \sigma_i|\leq \phi \epsilon\|A\|_2,
\qquad
\|u_i-\tilde u_i\|_2, \| v_i-\tilde v_i\|_2 \leq \psi\epsilon \frac{\|A\|_2}
{\min_{j\neq i}
|\sigma_i-\tilde \sigma_j|},$$

where $\epsilon$ is machine precision and $\phi$ and $\psi$ are slowly growing polynomial functions of $n$ which depend upon the algorithm used (typically $O(n)$ or $O(n^2)$). These bounds are obtained by combining perturbation bounds with the floating-point error analysis of the algorithms.
"""

# ╔═╡ 38b90f14-4c98-4a9c-a54a-5fabacb252ea
md"""
#  Bidiagonalization

## Facts

1. The reduction of $A$ to bidiagonal matrix can be performed by applying $\min\{m-1,n\}$ Householder reflections $H_L$ from the left and $n-2$ Householder reflections $H_R$ from the right. In the first step, $H_L$ is chosen to annihilate all elements of the first column below the diagonal, and $H_R$ is chosen to annihilate all elements of the first row right of the first super-diagonal. Applying this procedure recursively yields the bidiagonal matrix.

2.  $H_L$ and $H_R$ do not depend on the normalization of the respective Householder vectors $v_L$ and $v_R$. With the normalization $[v_L]_1=[V_R]_1=1$, the vectors $v_L$ are stored in the lower-triangular part of $A$, and the vectors $v_R$ are stored in the upper-triangular part of $A$ above the super-diagonal.

3. The matrices $H_L$ and $H_R$ are not formed explicitly - given $v_L$ and $v_R$, $A$ is overwritten with $H_L A H_R$ in $O(mn)$ operations by using matrix-vector multiplication and rank-one updates.

4. Instead of performing rank-one updates, $p$ transformations can be accumulated, and then applied. This __block algorithm__ is rich in matrix-matrix multiplications (roughly one half of the operations is performed using BLAS 3 routines), but it requires extra workspace.

5. If the matrices $X$ and $Y$ are needed explicitly, they can be computed from the stored Householder vectors. In order to minimize the operation count, the computation starts from the smallest matrix and the size is gradually increased.

6. The backward error bounds for the bidiagonalization are as follows: The computed matrix $\tilde B$ is equal to the matrix which would be obtained by exact bidiagonalization of some perturbed matrix $A+\Delta A$, where $\|\Delta A\|_2 \leq \psi \varepsilon \|A\|_2$ and $\psi$ is a slowly increasing function of $n$. The computed matrices $\tilde X$ and $\tilde Y$ satisfy $\tilde X=X+\Delta X$ and $\tilde Y=Y+\Delta Y$, where $\|\Delta X \|_2,\|\Delta Y\|_2\leq \phi \varepsilon$ and $\phi$ is a slowly increasing function of $n$.

7. The bidiagonal reduction is implemented in the [LAPACK](http://www.netlib.org/lapack) subroutine [DGEBRD](http://www.netlib.org/lapack/explore-html/dd/d9a/group__double_g_ecomputational.html#ga9c735b94f840f927f8085fd23f3ee2e6). The computation of $X$ and $Y$ is implemented in the subroutine [DORGBR](http://www.netlib.org/lapack/lapack-3.1.1/html/dorgtr.f.html), which is not yet wrapped in Julia.

8. Bidiagonalization can also be performed using Givens rotations. Givens rotations act more selectively than Householder reflectors, and are useful if $A$ has some special structure, for example, if $A$ is a banded matrix. Error bounds for function `BidiagG()` are the same as above, but with slightly different functions $\psi$ and $\phi$.
"""

# ╔═╡ 875fce0a-1b1b-4332-a150-6ec5f20aa7dc
md"
## Example
"

# ╔═╡ 3c8c847e-978d-4d43-99ea-773d915e03ea
begin
	m=8
	n=5
	import Random
	Random.seed!(421)
	A=rand(-9.0:9,m,n)
end

# ╔═╡ fa7b3f29-d9bc-4a64-90ae-16a6a3f8e3a6
# Householder matrix from vector
function H(x)
    v=copy(x)
    v[1]+=sign(x[1])*norm(x)
    # display(v/v[1])
    I-(2/(v⋅v))*v*v'
end

# ╔═╡ 78d6a3a1-9fd5-4eec-94dd-2baf31cf312e
H₁=H(A[:,1])

# ╔═╡ 2447bb48-334b-4554-b7ca-5b4f74f68be9
# Orthogonality
norm(H₁'*H₁-I)

# ╔═╡ ed9bfaff-ed9f-442d-b5a5-c0435e22d2ef
B₁=H₁*A

# ╔═╡ 8856d77a-f2fb-4d7e-aca3-bde649508c8a
H¹=cat(1,H(B₁[1,2:5]),dims=(1,2))

# ╔═╡ 5d742283-df40-416b-8067-1a21f89f6e71
H₁*A*H¹

# ╔═╡ fd73c193-a5bb-4eb9-a0ce-aca44b7ef7ee
# ?LAPACK.gebrd!

# ╔═╡ 783deb44-2d8f-4a40-80d5-cc65bcf9018c
# We need copy()
O=LAPACK.gebrd!(copy(A))

# ╔═╡ cfba6cc8-451c-4716-95a2-9a3f49009253
O[4]

# ╔═╡ 2f92ce80-72d0-4424-949e-a4f07065102c
B=Bidiagonal(O[2],O[3][1:end-1],'U')

# ╔═╡ e132f765-d232-40e3-9d41-1ec07528dbae
[svdvals(A) svdvals(B)]

# ╔═╡ 583548a3-b3ad-493f-8061-fae21138208a
# Extract X
function BidiagX(H::Matrix)
    m,n=size(H)
    T=typeof(H[1,1])
    X = Matrix{T}(I,m,n)
    v = Array{T}(undef,m)
    for j = n : -1 : 1
        v[j] = one(T)
        v[j+1:m] = H[j+1:m, j]
        γ = -2 / (v[j:m]⋅v[j:m])
        w = γ * X[j:m, j:n]'*v[j:m]
        X[j:m, j:n] = X[j:m, j:n] + v[j:m]*w'
    end
    X
end

# ╔═╡ 75d77c3b-649e-4d20-a1cf-5cc4d1335896
# Extract Y
function BidiagY(H::AbstractMatrix)
    n,m=size(H)
    T=typeof(H[1,1])
    Y = Matrix{T}(I,n,n)
    v = Array{T}(undef,n)
    for j = n-2 : -1 : 1
        v[j+1] = one(T)
        v[j+2:n] = H[j+2:n, j]
        γ = -2 / (v[j+1:n]⋅v[j+1:n])
        w = γ * Y[j+1:n, j+1:n]'*v[j+1:n]
        Y[j+1:n, j+1:n] = Y[j+1:n, j+1:n] + v[j+1:n]*w'
    end
    Y
end

# ╔═╡ 28a70c10-3900-4a22-9c93-41abe15b5ce8
X=BidiagX(O[1])

# ╔═╡ a0065053-51a6-4351-b7e8-da5e37b3eb94
Y=BidiagY(O[1]')

# ╔═╡ 583eec81-0a99-4ccc-9e7e-0625a942afbc
# Orthogonality
norm(X'*X-I), norm(Y'*Y-I)

# ╔═╡ 0b4010cc-5956-4a52-879f-dbc3dd6cc519
# Residual error
norm(A*Y-X*B)

# ╔═╡ 7d6da96f-c272-46e8-979c-14cc2a92d071
# Bidiagonalization using Givens rotations
function BidiagG(A1::Matrix)
    A=deepcopy(A1)
    m,n=size(A)
    T=typeof(A[1,1])
    X=Matrix{T}(I,m,m)
    Y=Matrix{T}(I,n,n)
    for j = 1 : n
        for i = j+1 : m
            G,r=givens(A,j,i,j)
            # Use the faster in-place variant
            # A=G*A
            lmul!(G,A)
            # X=G*X
            lmul!(G,X)
            # display(A)
        end
        for i=j+2:n
            G,r=givens(A',j+1,i,j)
            # A*=adjoint(G)
            rmul!(A,adjoint(G))
            # Y*=adjoint(G)
            rmul!(Y,adjoint(G))
            # display(A)
        end
    end
    X',Bidiagonal(diag(A),diag(A,1),'U'), Y
end

# ╔═╡ 5496e8da-6899-4792-8c20-46a5f8843354
X₁, B¹, Y₁ = BidiagG(A)

# ╔═╡ 1caedf0a-23d5-460e-8e61-f533f7f731a0
# Orthogonality
norm(X₁'*X₁-I), norm(Y₁'*Y₁-I)

# ╔═╡ e5ccbecf-5bf3-4c0e-a036-2f8fab7d0a3f
# Diagonalization
X₁'*A*Y₁

# ╔═╡ c5820b28-6e62-4b87-af13-6cbf3a3369a5
# X, Y and B are not unique
B

# ╔═╡ c2552861-ffc9-45e1-aac4-e44b30f28ee3
B¹

# ╔═╡ 28ee391d-2ae4-4d35-9832-973071e55681
md"""
# Bidiagonal QR method

Let $B$ be a real upper-bidiagonal matrix of order $n$ and let $B=W\Sigma Z^T$ be its SVD.

All metods for computing the SVD of bidiagonal matrix are derived from the methods for computing the EVD of the tridiagonal matrix $T=B^T B$.

## Facts

1. The shift $\mu$ is the eigenvalue of the $2\times 2$ matrix $T_{n-1:n,n-1:n}$ which is closer to $T_{n,n}$. The first Givens rotation from the right is the one which annihilates the element $(1,2)$ of the shifted $2\times 2$ matrix $T_{1:2,1:2}-\mu I$. Applying this rotation to $B$ creates the bulge at the element $B_{2,1}$. This bulge is subsequently chased out by applying adequate Givens rotations alternating from the left and from the right. This is the __Golub-Kahan algorithm__.

2. The computed SVD satisfes error bounds from the Fact 4 above.

3. The special variant of zero-shift QR algorithm (the __Demmel-Kahan algorithm__) computes the singular values with high relative accuracy.

4. The tridiagonal divide-and-conquer method, bisection and inverse iteration, and MRRR method can also be adapted for bidiagonal matrices.

5. Zero shift QR algorithm for bidiagonal matrices is implemented in the LAPACK routine [DBDSQR](http://www.netlib.org/lapack/explore-html/db/dcc/dbdsqr_8f.html). It is also used in the function `svdvals()`. Divide-and-conquer algorithm for bidiagonal matrices is implemented in the LAPACK routine [DBDSDC](http://www.netlib.org/lapack/explore-html/d9/d08/dbdsdc_8f.html). However, this algorithm also calls zero-shift QR to compute singular values.
"""

# ╔═╡ e64fc85e-2861-4e42-9789-ee12b6f9950f
md"
## Examples
"

# ╔═╡ a0d540ea-d116-499a-bcad-d6bfe3e52470
W,σ,Z=svd(B)

# ╔═╡ 2b900980-6ccb-441c-9ef5-e7dc68ff843e
@which svd(B)

# ╔═╡ 90970845-d921-4b88-9681-d344451d4c3c
σ₁=svdvals(B)

# ╔═╡ 860a3476-b257-4384-b255-4a8bd68728c9
@which svdvals!(B)

# ╔═╡ 2c1943df-95d8-43bb-bf6a-8fe13424a2c9
σ-σ₁

# ╔═╡ 1a79b153-6560-45d7-8113-e39101d17460
# ?LAPACK.bdsqr!

# ╔═╡ 7fafeb92-568a-4270-b4fb-4d44c655bcfb
B.dv

# ╔═╡ 99109bd8-e2ca-435c-a242-5772ea4432c0
one(B)

# ╔═╡ ef17e375-670b-4b7d-878a-bc2c06760651
begin
	V₀=Matrix(one(B))
	U₀=Matrix(one(B))
	C₀=Matrix(one(B))
	σ₂,Z₂,W₂,C = LAPACK.bdsqr!('U',copy(B.dv),copy(B.ev),V₀,U₀,C₀)
end

# ╔═╡ 613c0e7b-7288-4dac-a6be-731cf4f8be63
W₂'*B*Z₂'

# ╔═╡ e8119506-bd02-47e2-890c-c088fd883532
#?LAPACK.bdsdc!

# ╔═╡ ecf74ee7-97e3-4749-b63d-2cf200e4451e
σ₃,ee,W₃,Z₃,rest=LAPACK.bdsdc!('U','I',copy(B.dv),copy(B.ev))

# ╔═╡ cbed834b-4935-431c-96f6-3630534ba2eb
W₃'*B*Z₃'

# ╔═╡ ffa18a0d-c576-473a-9851-f7bd70a843ba
md"""
Functions `svd()`, `LAPACK.bdsqr!()` and `LAPACK.bdsdc!()` use the same algorithm to compute singular values.
"""

# ╔═╡ beec8bad-9621-4c66-88d3-4f48dbcf5566
[σ₃-σ₂ σ₃-σ]

# ╔═╡ 569d2241-b9a8-4501-8b17-9527e1777cdc
md"
Let us compute some timings. We observe $O(n^2)$ operations.
"

# ╔═╡ cfd21881-5cfe-430e-b60a-a783feac2cdb
begin
	n₀=1000
	Bₙ=Bidiagonal(rand(n₀),rand(n₀-1),'U')
	B₂ₙ=Bidiagonal(rand(2*n₀),rand(2*n₀-1),'U')
	println(" ")
	@time svdvals(Bₙ)
	@time svdvals(B₂ₙ)
	@time LAPACK.bdsdc!('U','N',copy(Bₙ.dv),copy(Bₙ.ev))
	@time svd(Bₙ)
	@time svd(B₂ₙ)
	1
end

# ╔═╡ 5ef6758d-51f9-49c5-a736-413f8be3e734
md"""
# QR method

Final algorithm is obtained by combining bidiagonalization and bidiagonal SVD methods. Standard method is implemented in the LAPACK routine [DGESVD](http://www.netlib.org/lapack/explore-html/d8/d2d/dgesvd_8f.html). Divide-and-conquer method is implemented in the LAPACK routine [DGESDD](http://www.netlib.org/lapack/explore-html/db/db4/dgesdd_8f.html).

The functions `svd()`, `svdvals()`, and `svdvecs()`  use `DGESDD`. Wrappers for `DGESVD` and `DGESDD` give more control about output of eigenvectors.
"""

# ╔═╡ 416c8bca-215c-491d-b1f1-1abf0793b65b
# The built-in algorithm
U,σA,V=svd(A)

# ╔═╡ 17d90d71-0776-498d-8c09-9d651ee3ea1f
Sv=svd(A)

# ╔═╡ c8e602f7-9ccf-4753-9aa5-a07df141cf40
Sv.Vt

# ╔═╡ 4ef07f50-e9aa-4075-960a-dce2ee9f9d9e
begin
	# With our building blocks
	U₁=X*W
	V₁=Y*Z
	U₁'*A*V₁
end

# ╔═╡ bb4e96f8-e61a-486c-b385-72dd2542e7fc
#?LAPACK.gesvd!

# ╔═╡ 7c5cea63-5273-46df-ae5e-58a7b4312360
# DGESVD
LAPACK.gesvd!('A','A',copy(A))

# ╔═╡ dd07def6-6ee3-4f48-ba11-340674fbc22d
#?LAPACK.gesdd!

# ╔═╡ 7e8aebb8-7621-4fd2-afeb-bdc71b091cd1
LAPACK.gesdd!('N',copy(A))

# ╔═╡ 08f390ca-5122-4a38-b7c0-631e7d9ea726
md"""
Let us perform some timings. We observe $O(n^3)$ operations.
"""

# ╔═╡ f425bd0a-ebde-42a5-8585-270b7e86f27f
begin
	n₁=1000
	Aₙ=rand(n₁,n₁)
	A₂ₙ=rand(2*n₁,2*n₁)
	println(" ")
	@time Uₙ,σₙ,Vₙ=svd(Aₙ)
	@time svd(A₂ₙ)
	@time LAPACK.gesvd!('A','A',copy(Aₙ))
	@time LAPACK.gesdd!('A',copy(Aₙ))
	@time LAPACK.gesdd!('A',copy(A₂ₙ))
	1
end

# ╔═╡ ed98fe9c-1ee8-4333-8ace-958223df6552
# Residual
norm(Aₙ*Vₙ-Uₙ*Diagonal(σₙ))

# ╔═╡ Cell order:
# ╟─bfe42bab-98e8-4460-a8ba-12968297d227
# ╟─43d24d37-df93-4ee6-8e48-89f9ca6f0f92
# ╟─64ca893a-a1ee-4172-bd20-cf4aca1c3d41
# ╟─38b90f14-4c98-4a9c-a54a-5fabacb252ea
# ╟─875fce0a-1b1b-4332-a150-6ec5f20aa7dc
# ╠═3c8c847e-978d-4d43-99ea-773d915e03ea
# ╠═fa7b3f29-d9bc-4a64-90ae-16a6a3f8e3a6
# ╠═78d6a3a1-9fd5-4eec-94dd-2baf31cf312e
# ╠═2447bb48-334b-4554-b7ca-5b4f74f68be9
# ╠═ed9bfaff-ed9f-442d-b5a5-c0435e22d2ef
# ╠═8856d77a-f2fb-4d7e-aca3-bde649508c8a
# ╠═5d742283-df40-416b-8067-1a21f89f6e71
# ╠═fd73c193-a5bb-4eb9-a0ce-aca44b7ef7ee
# ╠═783deb44-2d8f-4a40-80d5-cc65bcf9018c
# ╠═cfba6cc8-451c-4716-95a2-9a3f49009253
# ╠═2f92ce80-72d0-4424-949e-a4f07065102c
# ╠═e132f765-d232-40e3-9d41-1ec07528dbae
# ╠═583548a3-b3ad-493f-8061-fae21138208a
# ╠═75d77c3b-649e-4d20-a1cf-5cc4d1335896
# ╠═28a70c10-3900-4a22-9c93-41abe15b5ce8
# ╠═a0065053-51a6-4351-b7e8-da5e37b3eb94
# ╠═583eec81-0a99-4ccc-9e7e-0625a942afbc
# ╠═0b4010cc-5956-4a52-879f-dbc3dd6cc519
# ╠═7d6da96f-c272-46e8-979c-14cc2a92d071
# ╠═5496e8da-6899-4792-8c20-46a5f8843354
# ╠═1caedf0a-23d5-460e-8e61-f533f7f731a0
# ╠═e5ccbecf-5bf3-4c0e-a036-2f8fab7d0a3f
# ╠═c5820b28-6e62-4b87-af13-6cbf3a3369a5
# ╠═c2552861-ffc9-45e1-aac4-e44b30f28ee3
# ╟─28ee391d-2ae4-4d35-9832-973071e55681
# ╟─e64fc85e-2861-4e42-9789-ee12b6f9950f
# ╠═a0d540ea-d116-499a-bcad-d6bfe3e52470
# ╠═2b900980-6ccb-441c-9ef5-e7dc68ff843e
# ╠═90970845-d921-4b88-9681-d344451d4c3c
# ╠═860a3476-b257-4384-b255-4a8bd68728c9
# ╠═2c1943df-95d8-43bb-bf6a-8fe13424a2c9
# ╠═1a79b153-6560-45d7-8113-e39101d17460
# ╠═7fafeb92-568a-4270-b4fb-4d44c655bcfb
# ╠═99109bd8-e2ca-435c-a242-5772ea4432c0
# ╠═ef17e375-670b-4b7d-878a-bc2c06760651
# ╠═613c0e7b-7288-4dac-a6be-731cf4f8be63
# ╠═e8119506-bd02-47e2-890c-c088fd883532
# ╠═ecf74ee7-97e3-4749-b63d-2cf200e4451e
# ╠═cbed834b-4935-431c-96f6-3630534ba2eb
# ╟─ffa18a0d-c576-473a-9851-f7bd70a843ba
# ╠═beec8bad-9621-4c66-88d3-4f48dbcf5566
# ╟─569d2241-b9a8-4501-8b17-9527e1777cdc
# ╠═cfd21881-5cfe-430e-b60a-a783feac2cdb
# ╟─5ef6758d-51f9-49c5-a736-413f8be3e734
# ╠═416c8bca-215c-491d-b1f1-1abf0793b65b
# ╠═17d90d71-0776-498d-8c09-9d651ee3ea1f
# ╠═c8e602f7-9ccf-4753-9aa5-a07df141cf40
# ╠═4ef07f50-e9aa-4075-960a-dce2ee9f9d9e
# ╠═bb4e96f8-e61a-486c-b385-72dd2542e7fc
# ╠═7c5cea63-5273-46df-ae5e-58a7b4312360
# ╠═dd07def6-6ee3-4f48-ba11-340674fbc22d
# ╠═7e8aebb8-7621-4fd2-afeb-bdc71b091cd1
# ╟─08f390ca-5122-4a38-b7c0-631e7d9ea726
# ╠═f425bd0a-ebde-42a5-8585-270b7e86f27f
# ╠═ed98fe9c-1ee8-4333-8ace-958223df6552

src/Module B - Eigenvalue and Singular Value Decompositions/L6a Singular Value Decomposition - Algorithms and Error Analysis.jl


"""

```
wrap_to_cell_fraction(x,unit_cell_matrix)
```

Obtaint the coordinates of `x` as a fraction of unit cell vectors, first
positive cell. `x` is a vector of dimension `N` and `cell` a matrix of
dimension `NxN`

### Example

```
julia> unit_cell_matrix = [ 10 0
                            0 10 ];

julia> x = [ 15, 13 ];

julia> wrap_to_cell_fraction(x,unit_cell_matrix)
2-element Vector{Float64}:
 0.5
 0.3
```

"""
@inline function wrap_cell_fraction(x,unit_cell_matrix)
  p = mod.(unit_cell_matrix\x,1)
  p = @. ifelse(p < 0, p + 1, p)
  return p
end

"""

```
wrap_to_first(x,unit_cell_matrix)
```

Wraps the coordinates of point `x` such that the returning coordinates are in the
first unit cell with all-positive coordinates.

### Example

```
julia> unit_cell_matrix = [ 10 0
                            0 10 ];

julia> x = [ 15, 13 ];

julia> wrap_to_first(x,unit_cell_matrix)
2-element Vector{Float64}:
 5.0
 3.0000000000000004
 ```

"""
@inline function wrap_to_first(x,unit_cell_matrix)
  p = wrap_cell_fraction(x,unit_cell_matrix)
  return unit_cell_matrix*p
end

"""

```
wrap_to_first(x,box::Box)
```

Wraps the coordinates of point `x` such that the returning coordinates are in the
first unit cell with all-positive coordinates, given the `Box` structure.

"""
@inline wrap_to_first(x,box::Box) = wrap_to_first(x,box.unit_cell.matrix)

"""

```
wrap_to_first(x,box::Box{OrthorhombicCell,N,T}) where {N,T}
```

Wraps the coordinates of point `x` such that the returning coordinates are in the
first unit cell with all-positive coordinates, given an Orthorhombic cell.
This is slightly cheaper than for general cells.

"""
@inline function wrap_to_first(x,box::Box{OrthorhombicCell,N,T}) where {N,T}
  sides = SVector{N,T}(ntuple(i->box.unit_cell.matrix[i,i],N))
  x = mod.(x,sides)
  x = @. ifelse(x < 0, x + sides, x)
  return x
end

"""

```
wrap_relative_to(x, xref, unit_cell_matrix::SMatrix{N,N,T}) where {N,T}
```

Wraps the coordinates of point `x` such that it is the minimum image relative to `xref`.

"""
@inline function wrap_relative_to(x, xref, unit_cell_matrix::SMatrix{N,N,T}) where {N,T}
  x_f = wrap_cell_fraction(x,unit_cell_matrix)
  xref_f = wrap_cell_fraction(xref,unit_cell_matrix)
  xw = wrap_relative_to(x_f,xref_f,SVector{N,T}(ntuple(i->1,N)))
  return unit_cell_matrix * (xw - xref_f) + xref
end

"""

```
wrap_relative_to(x,xref,box::Box{UnitCellType,N,T}) where {UnitCellType,N,T}
```

Wraps the coordinates of point `x` such that it is the minimum image relative to `xref`,
given a general `Box` structure.

"""
@inline wrap_relative_to(x,xref,box::Box{UnitCellType,N,T}) where {UnitCellType,N,T} =
  wrap_relative_to(x,xref,box.unit_cell.matrix)

"""

```
wrap_relative_to(x,xref,box::Box{OrthorhombicCell,N,T}) where {N,T}
```

Wraps the coordinates of point `x` such that it is the minimum image relative to `xref`,
given an Orthorhombic cell. This is slightly cheaper than for general cells.

"""
@inline function wrap_relative_to(x,xref,box::Box{OrthorhombicCell,N,T}) where {N,T}
  sides = SVector{N,T}(ntuple(i->box.unit_cell.matrix[i,i],N))
  return wrap_relative_to(x,xref,sides)
end

"""

```
wrap_relative_to(x,xref,sides::AbstractVector)
```

Wraps the coordinates of point `x` such that it is the minimum image relative to `xref`,
for an Orthorhombic cell of which only the side lengths are provided.

"""
@inline function wrap_relative_to(x,xref,sides::AbstractVector)
  xw = mod.(x-xref,sides)
  xw = _wrap_single_coordinate.(xw,sides)
  return xw + xref
end

#
# Wrap a single coordinate
#
@inline function _wrap_single_coordinate(x,s)
  if x >= s/2
    x = x - s
  elseif x < -s/2
    x = x + s
  end
  return x
end

"""

```
translation_image(x::SVector{N,T},unit_cell_matrix,indices) where {N,T}
```

Translate vector `x` according to the `unit_cell_matrix` lattice vectors and the `indices`
provided.

"""
@inline translation_image(x::SVector{N,T},unit_cell_matrix,indices) where {N,T} =
  x + unit_cell_matrix*SVector{N,T}(ntuple(i -> indices[i],N))

"""

```
neighbour_cells(box::Box{UnitCellType,N}) where UnitCellType
```

Function that returns the iterator of the cartesian indices of the cells that must be
evaluated (forward, i. e. to avoid repeated interactions)
if the cells have sides of length `box.cell_size`. `N` can be
`2` or `3`, for two- or three-dimensional systems.

"""
function neighbour_cells(box::Box{UnitCellType,3}) where UnitCellType
  @unpack lcell = box
  nb = Iterators.flatten((
    CartesianIndices((1:lcell,-lcell:lcell,-lcell:lcell)),
    CartesianIndices((0:0,1:lcell,-lcell:lcell)),
    CartesianIndices((0:0,0:0,1:lcell))
  ))
  return nb
end

function neighbour_cells(box::Box{UnitCellType,2}) where UnitCellType
  @unpack lcell = box
  nb = Iterators.flatten((
    CartesianIndices((1:lcell,-lcell:lcell)),
    CartesianIndices((0:0,1:lcell))
  ))
  return nb
end

"""

```
neighbour_cells_all(box::Box{UnitCellType,N}) where N
```

Function that returns the iterator of the cartesian indices of all neighbouring
cells of a cell if the cells have sides of `box.cell_size`. `N` can be
`2` or `3`, for two- or three-dimensional systems.

"""
function neighbour_cells_all(box::Box{UnitCellType,3}) where UnitCellType
  @unpack lcell = box
  return CartesianIndices((-lcell:lcell,-lcell:lcell,-lcell:lcell))
end

function neighbour_cells_all(box::Box{UnitCellType,2}) where UnitCellType
  @unpack lcell = box
  return CartesianIndices((-lcell:lcell,-lcell:lcell))
end

"""

```
particle_cell(x::SVector{N,T}, box::Box) where {N,T}
```

Returns the coordinates of the *computing cell* to which a particle belongs, given its coordinates
and the `cell_size` vector. The computing box is always Orthorhombic, and the first
computing box with positive coordinates has indexes `Box.lcell + 1`.

"""
@inline particle_cell(x::SVector{N}, box::Box) where N =
  CartesianIndex(ntuple(i -> floor(Int,x[i]/box.cell_size[i]) + box.lcell + 1, N))

"""

```
cell_center(c::CartesianIndex{N},box::Box{UnitCellType,N,T}) where {UnitCellType,N,T}
```

Computes the geometric center of a computing cell, to be used in the projection
of points. Returns a `SVector{N,T}`

"""
@inline cell_center(c::CartesianIndex{N},box::Box{UnitCellType,N,T}) where {UnitCellType,N,T} =
  SVector{N,T}(ntuple(i -> box.cell_size[i]*(c[i] - 0.5 - box.lcell), N))

"""

```
cell_cartesian_indices(nc::SVector{N,Int}, i1D) where {N}
```

Given the linear index of the cell in the cell list, returns the cartesian indices
of the cell (for arbitrary dimension N).

"""
@inline cell_cartesian_indices(nc::SVector{N,Int}, i1D) where N =
  CartesianIndices(ntuple(i -> nc[i],N))[i1D]

"""

```
cell_linear_index(nc::SVector{N,Int}, indices) where N
```

Returns the index of the cell, in the 1D representation, from its cartesian coordinates.

"""
@inline cell_linear_index(nc::SVector{N,Int}, indices) where N =
  LinearIndices(ntuple(i -> nc[i],N))[ntuple(i->indices[i],N)...]

"""

```
out_of_bounding_box(x::SVector{N},box::Box) where N
```

Function that evaluates if a particle is outside the computing bounding box,
defined by the maximum and minimum unit cell coordinates.

"""
function out_of_bounding_box(x::SVector{N},box::Box) where N
  @unpack cutoff, unit_cell_max = box
  for i in 1:N
    (x[i] < -cutoff) && return true
    (x[i] >= unit_cell_max[i]+cutoff) && return true
  end
  return false
end
out_of_bounding_box(p::ParticleWithIndex,box::Box) =
  out_of_bounding_box(p.coordinates,box)

"""

```
replicate_particle!(ip,p::SVector{N},box,cl) where N
```

Replicates the particle as many times as necessary to fill the computing box.

"""
function replicate_particle!(ip,p::SVector{N},box,cl) where N
  itr = Iterators.product(ntuple(i->box.ranges[i],N)...)
  for indexes in itr
    (count(isequal(0),indexes) == N) && continue
    x = translation_image(p,box.unit_cell.matrix,indexes)
    if ! out_of_bounding_box(x,box)
      cl = add_particle_to_celllist!(ip,x,box,cl;real_particle=false)
    end
  end
  return cl
end

"""

```
check_unit_cell(box::Box)
```

Checks if the unit cell satisfies the conditions for using the minimum-image
convention, with the same criteria used gy Gromacs.

"""
check_unit_cell(box::Box) = check_unit_cell(box.unit_cell.matrix,box.cutoff)

function check_unit_cell(unit_cell_matrix::SMatrix{3},cutoff;printerr=true)
  a = @view(unit_cell_matrix[:,1])
  b = @view(unit_cell_matrix[:,2])
  c = @view(unit_cell_matrix[:,3])
  check = true

  if size(unit_cell_matrix) != (3,3)
    printerr && println("UNIT CELL CHECK FAILED: unit cell matrix must have dimenions (3,3).")
    check = false
  end

  if count(el -> el < 0, unit_cell_matrix) != 0
    printerr && println("UNIT CELL CHECK FAILED: unit cell matrix components be strictly positive.")
    check = false
  end

  bc = cross(b,c)
  bc = bc / norm(bc)
  aproj = dot(a,bc)

  ab = cross(a,b)
  ab = ab / norm(ab)
  cproj = dot(c,ab)

  ca = cross(c,a)
  ca = ca / norm(ca)
  bproj = dot(b,ca)

  if (aproj < 2*cutoff) || (bproj < 2*cutoff) || (cproj < 2*cutoff)
    printerr && println("UNIT CELL CHECK FAILED: distance between cell planes too small relative to cutoff.")
    check = false
  end

  return check
end

function check_unit_cell(unit_cell_matrix::SMatrix{2},cutoff;printerr=true)
  a = @view(unit_cell_matrix[:,1])
  b = @view(unit_cell_matrix[:,2])
  check = true

  if size(unit_cell_matrix) != (2,2)
    printerr && println("UNIT CELL CHECK FAILED: unit cell matrix must have dimenions (2,2).")
    check = false
  end

  if count(el -> el < 0, unit_cell_matrix) != 0
    printerr && println("UNIT CELL CHECK FAILED: unit cell matrix components must be strictly positive.")
    check = false
  end

  i = a / norm(a)
  bproj = sqrt(norm_sqr(b) - dot(b,i)^2)

  j = b / norm(b)
  aproj = sqrt(norm_sqr(a) - dot(a,j)^2)

  if (aproj < 2*cutoff) || (bproj < 2*cutoff)
    printerr && println("UNIT CELL CHECK FAILED: distance between cell planes too small relative to cutoff.")
    check = false
  end

  return check
end

#
# The following functions are not being used anymore in CellListMap, but may
# be useful if this is split in into a sepparate package for playing with
# periodic systems.
#
"""

```
ranges_of_replicas(cell_size, lcell, nc, unit_cell_matrix::SMatrix{3,3,T}) where T
```

Function that sets which is the range of periodic images necessary to fill
the computing box, in 3D.

"""
function ranges_of_replicas(cell_size, lcell, nc, unit_cell_matrix::SMatrix{3,3,T}) where T
  V = SVector{3,T}
  cmin = -lcell*cell_size
  cmax = (nc .- lcell) .* cell_size
  cell_vertices = SVector{8,V}(
    V(   cmin[1],   cmin[2],   cmin[3] ),
    V(   cmin[1],   cmin[2],   cmax[3] ),
    V(   cmin[1],   cmax[2],   cmin[3] ),
    V(   cmin[1],   cmax[2],   cmax[3] ),
    V(   cmax[1],   cmin[2],   cmin[3] ),
    V(   cmax[1],   cmin[2],   cmax[3] ),
    V(   cmax[1],   cmax[2],   cmin[3] ),
    V(   cmax[1],   cmax[2],   cmax[3] )
  )
  r_min, r_max = _ranges_of_replicas(
    SVector{3,Int}(10^6,10^6,10^6), #min
    SVector{3,Int}(-1,-1,-1),       #max
    unit_cell_matrix,
    cell_vertices
  )
  ranges = SVector{3,UnitRange{Int}}(
    r_min[1]:r_max[1],
    r_min[2]:r_max[2],
    r_min[3]:r_max[3]
  )
  return ranges
end

"""

```
ranges_of_replicas(cell_size, lcell, nc, unit_cell_matrix::SMatrix{3,3,T}) where T
```

Function that sets which is the range of periodic images necessary to fill
the computing box, in 2D.

"""
function ranges_of_replicas(cell_size, lcell, nc,unit_cell_matrix::SMatrix{2,2,T}) where T
  V = SVector{2,T}
  cmin = -lcell*cell_size
  cmax = (nc .- lcell) .* cell_size
  cell_vertices = SVector{4,V}(
    V(   cmin[1],   cmin[2] ),
    V(   cmin[1],   cmax[2] ),
    V(   cmax[1],   cmin[2] ),
    V(   cmax[1],   cmax[2] ),
  )
  r_min, r_max = _ranges_of_replicas(
    SVector{2,Int}(10^6,10^6), #min
    SVector{2,Int}(-1,-1),     #max
    unit_cell_matrix,
    cell_vertices
  )
  ranges = SVector{2,UnitRange{Int}}(
    r_min[1]:r_max[1],
    r_min[2]:r_max[2]
  )
  return ranges
end

function _ranges_of_replicas(r_min,r_max,unit_cell,cell_vertices)
  for vert in cell_vertices
    r = unit_cell \ vert
    ri = @. ceil(Int,abs(r))
    for (i,el) in pairs(r)
      if el < 0
        @set! ri[i] = -ri[i]
      end
    end
    r_min = min.(ri,r_min)
    r_max = max.(ri,r_max)
  end
  return r_min, r_max
end


#
using SpectralElemenets, Plots
import FastGaussQuadrature

nr = 8
np = 100

zr,_ = FastGaussQuadrature.gausslobatto(nr)
zp   = range(start=-1,stop=1,length=np) |> Array

J = interpMat(zp,zr)

## basis

plt = plot()
plt = plot!(title="Gauss Lobbato Legendre Polynomials, ψᵢ(x)")
#plt = plot!(xlims=(-1,1))

fr = @. 2 + sin(pi*zr)

for i=1:nr
    global plt = plot!(zp,J[:,i]*fr[i],width=2,label=:none)
end

plt = plot!(zp,J*fr,width=2,color=:black,label=:none)
savefig(plt,"basis.png")

## elem

#plt = plot()
#plt = plot!(xlims=(-1,3))
#
#for i=1:nr
#    global plt = plot!(0 .+zp,J[:,i],width=2,label=:none)
#end
#savefig(plt,"elem1.png")
#
#plt = plot()
#plt = plot!(xlims=(-1,3))
#
#for i=1:nr
#    global plt = plot!(2 .+zp,J[:,i],width=2,label=:none)
#end
#savefig(plt,"elem2.png")

## indicator func
#zp = range(start=-1,stop=3,length=1000) |> Array
#id1 = -1 .<= zp .<= 1
#id2 =  1 .<= zp .<= 3
#plt = plot()
#plt = plot!(xlims=(-1,3),ylims=(-0.2,1.2))
#plt = plot!(zp,id1,width=2,label=:none,color=:black)
#savefig(plt,"id1.png")
#plt = plot()
#plt = plot!(xlims=(-1,3),ylims=(-0.2,1.2))
#plt = plot!(zp,id2,width=2,label=:none,color=:black)
#savefig(plt,"id2.png")


#! /usr/bin/julia

# Rosetta Code, Twelve statements

function showflaggedbits{T<:BitArray{1}}(a::T, f::T)
    tf = map(x->x ? "T" : "F", a)
    flg = map(x->x ? "*" : " ", f)
    join(tf .* flg, " ")
end

const props = [s -> length(s) == 12,
               s -> sum(s[7:12]) == 3,
               s -> sum(s[2:2:end]) == 2,
               s -> !s[5] || (s[6] & s[7]),
               s -> !any(s[2:4]),
               s -> sum(s[1:2:end]) == 4,
               s -> s[2] $ s[3],
               s -> !s[7] || (s[5] & s[6]),
               s -> sum(s[1:6]) == 3,
               s -> s[11] & s[12],
               s -> sum(s[7:9]) == 1,
               s -> sum(s[1:end-1]) == 4]

const NDIG = length(props)
NDIG < WORD_SIZE || println("WARNING, too many propositions!")

mhist = zeros(Int, NDIG+1)

println("Checking the ", NDIG, " statements against all possibilities.\n")
print(" "^15)
for i in 1:NDIG
    print(@sprintf "%3d" i)
end
println()

for i in 0:(2^NDIG-1)
    s = bitpack(digits(i, 2, NDIG))
    t = bitpack([p(s) for p in props])
    misses = s$t
    mcnt = sum(misses)
    mhist[NDIG-mcnt+1] += 1
    mcnt < 2 || mcnt == NDIG || continue
    if mcnt == 0
        print("    Exact Match: ")
    elseif mcnt == NDIG
        print("     Total Miss: ")
    else
        print("      Near Miss: ")
    end
    println(showflaggedbits(t, misses))
end

println()
println("Distribution of matches")
println(" Matches  Cases")
for i in (NDIG+1):-1:1
    println(@sprintf "    %2d => %4d" i-1 mhist[i])
end


"""
    destination(origin::Position, distance::Real, bearing::Real, units::String="kilometers")

Take a Point or a Position and calculate the location of a destination point given a distance in
degrees, radians, miles, or kilometers; and bearing in degrees.
The destination is calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature.

# Examples
```jldoctest
julia> point = Point([-75, 38])
Point([-75.0, 38.0])

julia> destination(point, 100, 0)
Point([-75.0, 38.8993])

julia> destination(point, 100, 45)
Point([-74.1859, 38.6331])

julia> destination(point, 50, 0, "miles")
Point([-75.0, 38.7237])
```
"""
function destination(origin::Position, distance::Real, bearing::Real, units::String="kilometers")
    lon1 = deg2rad(origin[1])
    lat1 = deg2rad(origin[2])
    bearingRad = deg2rad(bearing)
    radians = length_to_radians(distance, units)

    lat2 = asin(sin(lat1) * cos(radians) + cos(lat1) * sin(radians) * cos(bearingRad))
    lon2 = lon1 + atan(sin(bearingRad) * sin(radians) * cos(lat1), cos(radians) - sin(lat1) * sin(lat2))

    return Point([rad2deg(lon2), rad2deg(lat2)])
end

destination(origin::Point, distance::Real, bearing::Real, units::String="kilometers") = destination(origin.coordinates, distance, bearing, units)

"""
    rhumb_destination(origin::Position, distance::Real, bearing::Real, units::String="kilometers")

Take a Point or a Position and return the destination Point having travelled the given distance along a Rhumb line from the
origin Point with the (varant) given bearing.

# Examples
```jldoctest
julia> point = Point([-75, 38])
Point([-75.0, 38.0])

julia> rhumb_destination(point, 100, 0)
Point([-75.0, 38.8993])

julia> rhumb_destination(point, 100, 45)
Point([-74.1895, 38.6359])

julia> rhumb_destination(point, 50, 0, "miles")
Point([-75.0, 38.7237])
```
"""
function rhumb_destination(origin::Position, distance::Real, bearing::Real, units::String="kilometers")
    negative::Bool = distance < 0
    distanceinMeters = convert_length(abs(distance), units, "meters")
    if negative
        distanceinMeters = - abs(distanceinMeters)
    end

    dest = calculateRhumbDestination(origin, distanceinMeters, bearing)

    dest[1] += (dest[1] - origin[1] > 180) ? -360 : (origin[1] - dest[1] > 180) ? 360 : 0

    return Point(dest)
end

rhumb_destination(origin::Point, distance::Real, bearing::Real, units::String="kilometers") = rhumb_destination(origin.coordinates, distance, bearing, units)

function calculateRhumbDestination(origin::Position, distance::Real, bearing::Real)
    Δ = distance / earth_radius
    λ1 = origin[1] * pi /180
    ϕ1 = deg2rad(origin[2])
    θ = deg2rad(bearing)

    Δϕ = Δ * cos(θ)
    ϕ2 = ϕ1 + Δϕ

    if abs(ϕ2 > pi / 2)
        ϕ2 = ϕ2 > 0 ? pi - ϕ2 : -pi - ϕ2
    end

    Δψ = log(tan(ϕ2 / 2 + pi / 4) / tan(ϕ1 / 2 + pi / 4))
    q = abs(Δψ) > 10e-12 ? Δϕ / Δψ : cos(ϕ1)

    Δλ = Δ * sin(θ) / q
    λ2 = λ1 + Δλ

    return [((λ2 * 180 / pi) + 540) % 360 - 180, ϕ2 * 180 / pi]
end


using BasisFunctions
using MoreBasisFunctions
using DomainSets
using FrameFun

f(x) = cos(π*x)

L = LagrangeGaussLegendre(21)
FL = Fun(f, L, support(L))
println(FL(0.0))

B = Bernstein(21)
FB = Fun(f, B, support(B))
println(FB(0.5))


function solve()
    x = parse(Int, readline())
    sol = 0
    deposit = 100
    while deposit < x
        sol += 1
        deposit += deposit ÷ 100
    end
    sol
end

println(solve())


### Some prime group generation algorithms. References:
# + https://crypto.stackexchange.com/questions/820/how-does-one-calculate-a-primitive-root-for-diffie-hellman
# + https://math.stackexchange.com/questions/124408/finding-a-primitive-root-of-a-prime-number

# Eventually will need to revisit this implmentation to comply with CryptoGroups standart

using Primes: isprime, nextprime
using Random: AbstractRNG

function n_bit_random_number(rng::AbstractRNG, len::Integer)
    max_n = ( BigInt(1) << len ) - 1
    if len > 2
        min_n = BigInt(1) << (len - 1)
        return rand(rng, min_n:max_n)
    end
    return rand(rng, 1:max_n)
end

function nbit_prime_of_size(rng::AbstractRNG, n_bits::Integer)
    # generate a random nbit number
    r = n_bit_random_number(rng, n_bits)
    return nextprime(r)
end

rngprime(rng::AbstractRNG, N) = nbit_prime_of_size(rng, N)
rngint(rng::AbstractRNG, N) = n_bit_random_number(rng, N)

### I could manage to get 512 security
# Something wrong with this one
function sophie_germain_group(rng::AbstractRNG, g::Integer, t::Int)
    @assert g!=1
    while true
        q = rngprime(rng, 2*t)
        p = 2*q + 1
        if g!=p-1 && isprime(p)
            return MODP(; p, g, q)
        end
    end
end

function generate_qp(rng::AbstractRNG, t)
    while true
        q = rngprime(rng, t)
        r = rngint(rng, t)

        p = q*r + 1
        if isprime(p)
            return q, p
        end
    end
end

function dsa_standart_group(rng::AbstractRNG, tp::Int, tg::Int)
    q, p = generate_qp(rng, 2*tp)
    while true
        u = mod(rngint(rng, tg), p)
        g = powermod(u, div((p-1), q), p)

        if g!=1 || g!=0
            return MODP(; p, g, q)
        end
    end
end


@testset "Power series integration" begin

    T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model=:capped_absolute)

    @test ps_integrate(zero(T)) == zero(T)

    @test ps_integrate(one(T)) == gen(T)

    @test ps_integrate(gen(T)) == gen(T)^2 * (1 // 2)

    @test ps_integrate(t^3 - t^4 * (1 // 8) + 5 * t^10) == t^4 * (1 // 4) - t^5 * (1 // 40) + t^11 * (5 // 11)

    @test ps_integrate( (1 + t)^1000 ) == truncate((1 + t)^1001 * (1 // 1001) - 1 // 1001, 300)

end


using GridapFSI
using DifferentialEquations

function get_cylinder_motion()
  # Parameters
  A₀ = 1.5
  A₁ = 8.5
  A₂ = -11.1
  A₃ = 2.6
  B₀ = 4.2
  B₁ = 11.3
  B₂ = -68.7
  B₃ = 50.5

  # Physics Properties
  k = 1.0
  m = 1.0
  D = 0.1
  L = 1.0
  ρ = 1.0e3
  b = 1.0
  V = 1.0

  # Parameters
  q₁ = 2
  St = 0.2
  Cx0 = 2.0
  Cy0 = 0.3
  A = 12
  ε = 0.3

  # Auxiliar variables
  mₐ = π*ρ*D^2*L/4
  mʳ = m/mₐ
  ωₙ = sqrt(k/(m+mₐ))
  ωₛ = St*V/(2*π*D)
  Ωₙ = ωₙ/ωₛ
  ζ = b/(2*sqrt((m+mₐ)*k))

  function oscillator(x,p,t)
    y, q, dy, dq = x
    y_dot = dy
    q_dot = dq
    Cvy= (-2^π*St*dy*Cx0 + Cy0/q₁*q) * sqrt(1+4*π^2*St^2*dy^2)
    dy_dot = ρ*D^2*L/(m+mₐ)*1.0/(8*π^2*St^2)*Cvy - 2*ζ*Ωₙ*dy - Ωₙ^2*y
    dq_dot = A*dy_dot - ε*(q^2-1)*dq - q
    dx = [y_dot; q_dot; dy_dot; dq_dot]
  end

  prob = ODEProblem(oscillator,[0.0;2.0;0.0;0.0],(0.0,100.0))
  sol=DifferentialEquations.solve(prob)
  y(t) = sol(t)[1]
  dy(t) = sol(t)[3]

  return y,dy
end

# Define cylinder motion
u, du = get_cylinder_motion()

output = main(problemName="oscillator",dt=0.025,tf=10.0,u_y=u, du_y=du)