export AdjointOperator

"""
`AdjointOperator(A::AbstractOperator)`

Shorthand constructor:

`'(A::AbstractOperator)`

Returns the adjoint operator of `A`.

```julia
julia> AdjointOperator(DFT(10))
ℱᵃ  ℂ^10 -> ℝ^10

julia> [DFT(10); DCT(10)]'
[ℱ;ℱc]ᵃ  ℂ^10  ℝ^10 -> ℝ^10
```
"""
struct AdjointOperator{T <: AbstractOperator} <: AbstractOperator
	A::T
	function AdjointOperator(A::T) where {T<:AbstractOperator}
		is_linear(A) == false && error("Cannot transpose a nonlinear operator. You might use `jacobian`")
		new{T}(A)
	end
end

# Constructors

AdjointOperator(L::AdjointOperator) = L.A

# Properties

size(L::AdjointOperator) = size(L.A,2), size(L.A,1)

domainType(L::AdjointOperator) = codomainType(L.A)
codomainType(L::AdjointOperator) = domainType(L.A)

fun_name(L::AdjointOperator)  = fun_name(L.A)*"ᵃ"

is_linear(L::AdjointOperator) = is_linear(L.A)
is_null(L::AdjointOperator) = is_null(L.A)
is_eye(L::AdjointOperator) = is_eye(L.A)
is_diagonal(L::AdjointOperator) = is_diagonal(L.A)
is_AcA_diagonal(L::AdjointOperator) = is_AAc_diagonal(L.A)
is_AAc_diagonal(L::AdjointOperator) = is_AcA_diagonal(L.A)
is_orthogonal(L::AdjointOperator) = is_orthogonal(L.A)
is_invertible(L::AdjointOperator) = is_invertible(L.A)
is_full_row_rank(L::AdjointOperator) = is_full_column_rank(L.A)
is_full_column_rank(L::AdjointOperator) = is_full_row_rank(L.A)

diag(L::AdjointOperator) = diag(L.A)
diag_AcA(L::AdjointOperator) = diag_AAc(L.A)
diag_AAc(L::AdjointOperator) = diag_AcA(L.A)


using myJuliaUtils
using Test
using LinearAlgebra
using Random
using Distributions
# using BenchmarkTools
using DelimitedFiles

# comparing the procedure to matlab

path = dirname(@__FILE__)
# path = "/Users/zymantas/TVPVARPkg/test/TestWish"

T = readdlm("$path/T.txt", ',');
T = Int(T[1])
if T > 80
    error("matlab uses a different procedure for high degrees of freedom")
end
draw = readdlm("$path/draw.txt", ',');
aa = readdlm("$path/WW.txt", ',');
MatlabInvWish = readdlm("$path/matlabinvwish.txt", ',');
MatlabWish = readdlm("$path/matlabwish.txt", ',');

A = draw * cholesky(aa).U
JuliaWish = A'A

B = copy(draw)
A = cholesky(aa).L / qr!(B).R
JuliaInvWish = A * A'

# C2 = inv(Symmetric(aa));
# C3= draw * cholesky(C2).U
# C3=C3'C3
# D2 = inv(Symmetric(C3))
# @test D2 ≈ MatlabInvWish

@testset "Compared to Matlab" begin
    @test JuliaWish ≈ MatlabWish
    @test JuliaInvWish ≈ MatlabInvWish
end

# testing wishart and inverse wishart for univariate compared to multivariate
WW = randn(1, 1)
WW = WW'WW
T = rand(1:1000)

SomeSeed = rand(1:10000)

Random.seed!(SomeSeed);
C = wish(WW, T)

Random.seed!(SomeSeed);
C1 = wish(vec(WW), T)

Random.seed!(SomeSeed);
D = iwish(WW, T)

Random.seed!(SomeSeed);
D1 = iwish(vec(WW), T)

@testset "matching univariate to multivariate" begin
    @test C ≈ [C1]
    @test D ≈ [D1]
end

# testing wishart and inverse wishart for univariate
WW = randn(1, 1)
WW = WW'WW
T = rand(1:1000)

Random.seed!(SomeSeed);
A = rand(Wishart(T, WW))
Random.seed!(SomeSeed);
C = wish(WW, T)

Random.seed!(SomeSeed);
A = rand(InverseWishart(T, WW))

Random.seed!(SomeSeed);
B = iwish(WW, T)

Random.seed!(SomeSeed);
C2 = inv(WW);
C3 = wish(C2, T);
C = inv(C3)

# for univariate case where WW is a vector
WW = [1 + rand() * 10000]
T = rand(1:1000)

SomeSeed = rand(1:10000)

Random.seed!(SomeSeed);
A_v = rand(InverseWishart(T, reshape(WW, 1, 1)))

Random.seed!(SomeSeed);
B_v = iwish(WW, T)

Random.seed!(SomeSeed);
C2 = inv.(WW);
C3 = wish(C2, T);
C_v = inv.(C3)

@testset "univariate" begin
    # @test A ≈ B
    @test B ≈ C

    # @test A_v ≈ B_v
    @test B_v ≈ C_v
end

# # testing for multivariate wishart

# K = round(Int, 1 + rand() * 6)

# WW = randn(K, K)
# WW = WW'WW
# T = round(Int, 1 + rand() * 1000)

# # testing the equality between wish and iwish
# Random.seed!(SomeSeed);
# A = rand(InverseWishart(T, WW))
# Random.seed!(SomeSeed);
# B = iwish(WW, T)
# Random.seed!(SomeSeed);
# C2 = inv(Symmetric(WW));
# C3 = wish(C2, T);
# C = inv(C3)

# @testset "multivariate" begin
#     @test_broken A ≈ B
#     @test_broken B ≈ C
# end

# using Law of large numbers

reps = 2000000
K = rand(1:5)

WW = randn(K, K)
WW = WW'WW
T = rand(K+1:300)

SomeSeed = rand(1:10000)
Random.seed!(SomeSeed)
a1 = wish(WW, T)

Random.seed!(SomeSeed);
a2 = rand(Wishart(T, WW))

cont1 = zeros(K, K);
cont2 = copy(cont1);
cont3 = copy(cont1);
cont4 = copy(cont1);

wD=Wishart(T, WW)
iwD=InverseWishart(T, WW)

for i = 1:reps
    global cont1 .+= wish(WW, T);
    global cont2 .+= rand(wD);

    global cont3 .+= iwish(WW, T);
    global cont4 .+= rand(iwD);
end
cont1 ./= reps;
cont2 ./= reps;
cont3 ./= reps;
cont4 ./= reps;

@testset "based on LLN" begin

    # @test maximum(abs.(round.(cont1 - cont2; digits=0)))==0.0
    @test maximum(abs.(round.(cont1 - mean(Wishart(T, WW)); digits=0)))==0.0
    @test maximum(abs.(round.(mean(Wishart(T, WW)) - cont2; digits=0)))==0.0

    # @test maximum(abs.(round.(cont3 - cont4; digits=4)))==0.0
    @test maximum(abs.(round.(cont3 - mean(InverseWishart(T, WW)); digits=3)))==0.0
    @test maximum(abs.(round.(mean(InverseWishart(T, WW)) - cont4; digits=3)))==0.0

end


using SimpleDiffEq, Test, StaticArrays

# dX_t = 2u dt + dW_t
f(u,p,t) = 2u
g(u,p,t) = 1
u0 = 0.5
tspan = (0.0,1.0)
prob = SDEProblem(f,g,u0,tspan)

sol = solve(prob,SimpleEM(),dt=0.25)

@test sol.t == collect(0:0.25:1.0)
@test length(sol.u) == 5
@test typeof(sol) <: DESolution

u0 = @SVector [0.1,0.2]
prob = SDEProblem(f,g,u0,tspan)
sol = solve(prob,SimpleEM(),dt=0.25)

@test typeof(sol.u) <: Vector{SVector{2,Float64}}

f(du,u,p,t) = du .= 2.0 * u
g(du,u,p,t) = du .= 1
u0 = 0.5ones(4)
tspan = (0.0,1.0)
prob = SDEProblem(f,g,u0,tspan)
sol = solve(prob,SimpleEM(),dt=0.25)


"""
    c

Speed of light = `299,792,458m/s`
"""
const c = 299792458  # m/s

"""
    k

Boltzman constant = `1.38×10⁻²³ J/K`
"""
const k = 1.380649e-23  # J/K

"""
    Rₑ

Rₑ (Volumetric Mean Radius of the Earth) = `6371.000e3m`

From [NASA Earth Fact Sheet](https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html)
"""
const Rₑ = 6371.000e3  # meters

"""
    L1_freq

L1 carrier frequency = `1575.42e6Hz`
"""
const L1_freq = 1575.42e6  # Hz

"""
    L2_freq

L2 carrier frequency = `1227.60e6Hz`
"""
const L2_freq = 1227.60e6  # Hz

"""
    L5_freq

L5 Frequency `1176.45e6Hz`
"""
const L5_freq = 1176.45e6  # Hz

"""
    h_parms_tcxo

TCXXO typical oscillator h-parameters. Taken from Table 1 in
*Digital GNSS PLL DesignConditioned on Thermal andOscillator Phase Noise* by
Curran & Murphy located [https://ieeexplore.ieee.org/document/6129629](https://ieeexplore.ieee.org/document/6129629)

Order: [h₋₂, h₋₁, h₀, h₁, h₂]
"""
const h_parms_tcxo = [[2.0e-20, 7.0e-21, 2.0e-19,       0,       0],
                      [2.0E-20, 1.0E-20, 1.0e-21,       0,       0],
                      [3.8e-21, 1.8e-19, 9.4e-20,       0,       0],
                      [2.4e-22, 2.4e-21, 3.9e-22, 2.5e-23, 2.0e-26],
                      [8.5e-22, 4.5e-20, 3.5e-20, 6.0e-23,       0],
                      [2.5e-23, 3.5e-21, 1.9e-21, 6.0e-23, 2.8e-24]]

"""
    h_parms_ocxo

OCXO typical oscillator h-parameters. Taken from Table 1 in
*Digital GNSS PLL DesignConditioned on Thermal andOscillator Phase Noise* by
Curran & Murphy located [https://ieeexplore.ieee.org/document/6129629](https://ieeexplore.ieee.org/document/6129629)

Order: [h₋₂, h₋₁, h₀, h₁, h₂]
"""
const h_parms_ocxo = [[4.0e-26, 3.6e-25, 2.6e-22, 2.0e-23, 2.2e-26],
                      [4.0e-23, 2.0e-21, 8.0e-20,       0,       0]]

"""
    h_parms_cesium

Cesium typical oscillator h-parameters. Taken from Table 1 in
*Digital GNSS PLL DesignConditioned on Thermal andOscillator Phase Noise* by
Curran & Murphy located [https://ieeexplore.ieee.org/document/6129629](https://ieeexplore.ieee.org/document/6129629)

Order: [h₋₂, h₋₁, h₀, h₁, h₂]
"""
const h_parms_cesium = [4.0e-29, 7.0e-23, 2.0e-20,      0,       0]

"""
    g1b1()

Data whose filenames contain the `g1b1` string are collected at 25 Msps,
with the center frequency set to 1.57GHz which may contain the following
signals:

- GPS L1
- Galileo E1bc
- Beidou B1i

Arguments: `None`

Returns:

- `(f_s, f_if, f_center, L1_freq, sigtype)::Tuple`: where
    * `f_s::Float64`: sampling rate of data `(25MHz)`
    * `f_if::Float64`: absolute value of the difference between the signal
                       carrier frequency and receiver center frequency `(5.42MHz)`
    * `f_center::Float64`: receiver center frequency `(1.57GHz)`
    * `L1_freq::Float64`: GPS L1 carrier frequency `(1.57542GHz)`
    * `sigtype::Val{:l1ca}`: `[DEPRICATED]` used in previous implementation of
                             GNSSTools and will be removed soon
"""
function g1b1()
    f_s = 25e6  # Hz
    f_center = 1.57e9  # Hz
    f_if = abs(L1_freq-f_center)  # Hz
    sigtype = Val(:l1ca)
    return (f_s, f_if, f_center, L1_freq, sigtype)
end

"""
    g5()

Data whose filenames contain the `g5` string are collected at 25 Msps,
with the center frequency set to 1.17645GHz which may contain the following
signals:

- GPS L5
- Galileo E5a

Arguments: `None`

Returns:

- `(f_s, f_if, f_center, L5_freq, sigtype)::Tuple`: where
    * `f_s::Float64`: sampling rate of data `(25MHz)`
    * `f_if::Float64`: absolute value of the difference between the signal
                       carrier frequency and receiver center frequency `(0Hz)`
    * `f_center::Float64`: receiver center frequency `(1.17645GHz)`
    * `L1_freq::Float64`: GPS L1 carrier frequency `(1.17645GHz)`
    * `sigtype::Val{:l5q}`: `[DEPRICATED]` used in previous implementation of
                            GNSSTools and will be removed soon
"""
function g5()
    f_s = 25e6  # Hz
    f_center = 1.17645e9  # Hz
    f_if = abs(L5_freq-f_center)  # Hz
    sigtype = Val(:l5q)
    return (f_s, f_if, f_center, L5_freq, sigtype)
end

"""
    WGS84

Struct type that hols the WGS84 constants.

Fields:

- `a::Float64`: semi-major axis of Earth `(6378137.0m)`
- `f::Float64`: flattening factor `(1/298.257223563)`
- `ω::Float64`: Earth's angular velocity `(7.2921151467e-5rad/s)`
- `μ::Float64`: also known as `GM`, is Earth's gravitational constant
                `(3.986005e14m³/s²)`
"""
struct WGS84
    a::Float64
    f::Float64
    ω::Float64
    μ::Float64
end

"""
    initWGS84()

Initializes the WGS84 constants and stores them in a `WGS84` struct.

Arguments: `None`

Returns:

- `WGS84` struct
"""
function initWGS84()
    return WGS84(6378137.0,  # meters
                 1/298.257223563,
                 7.2921151467e-5,  # rad/s
                 3.986005e14)  # m³/s²
end

"""
    wgs84

Initialized constant struct that contains the WGS84 constants.
"""
const wgs84 = initWGS84()

"""
    parseGPSData(data_file)

Parse the GPS data file from AGI. See function `getCurrentGPSNORADIDS` below.

Required Arguments:

- `data_file::String`: file name of data file

Returns:

- `GPSData::Dict`: multi-layer `Dict` containing the history of SVs for each PRN
                   see `getCurrentGPSNORADIDS` for more detailed information
"""
function parseGPSData(data_file)
    file = open(data_file, "r")
    lines = readlines(file)
    close(file)
    N = size(lines)[1]
    GPSData = Dict{Int,Dict{Int,Dict{String,Any}}}()
    for i in 23:N
        line = lines[i]
        if length(line) > 1
            prn = parse(Int, line[3:4])
            sv = parse(Int, line[7:8])
            satnum = parse(Int, line[11:15])
            block = line[18:20]
            start_week = parse(Int, line[27:30])
            start_toa = parse(Int, line[32:37])
            start_year = parse(Int, line[39:42])
            start_month = parse(Int, line[44:45])
            start_day = parse(Int, line[47:48])
            start_date = (start_year, start_month, start_day)
            start_julian_date = DatetoJD(start_date..., 0, 0, 0)
            active = occursin("Active", line)
            notes = line[77:end]
            if active
                end_week = missing
                end_toa = missing
                end_date = missing
                end_julian_date = missing
            else
                end_week = parse(Int, line[53:56])
                end_toa = parse(Int, line[58:63])
                end_year = parse(Int, line[65:68])
                end_month = parse(Int, line[70:71])
                end_day = parse(Int, line[73:74])
                end_date = (end_year, end_month, end_day)
                end_julian_date = DatetoJD(end_date..., 0, 0, 0)
            end
            sv_info = Dict("satnum"=>satnum,
                           "block"=>block,
                           "start_week"=>start_week,
                           "start_toa"=>start_toa,
                           "start_date"=>start_date,
                           "start_julian_date"=>start_julian_date,
                           "end_week"=>end_week,
                           "end_toa"=>end_toa,
                           "end_date"=>end_date,
                           "end_julian_date"=>end_julian_date,
                           "active"=>active,
                           "status"=>notes)
            try
                GPSData[prn][sv] = sv_info
            catch
                GPSData[prn] = Dict(sv=>sv_info)
            end
        end
    end
    return GPSData
end

"""
    getCurrentGPSNORADIDS(file_url)

Download GPS SV data from AGI and parse. Returns a dictionary with historical
SVs for each PRN number.

Required Arguments:

- `file_url::String`: the file URL `(Default = GPS_data_file_url)
    * See `const GPS_data_file_url` below

Returns:

- `GPSData::Dict`: multi-layer `Dict` containing the history of SVs for each PRN
    * top level is sorted by PRN number
    * `GPSData[prn]` yields another set of dictionaries, sorted by SV
    * `GPSData[prn][sv]` yields a `Dict` containing attributes for that specific
      SV such as
        + `"active"`: `Bool` value if set to `true`, indicates that SV is still
                      in service and the fields `"end_week"`, `"end_toa"`,
                      `"end_date"`, and `"end_julian_date"` are all set to
                      `misssing`
        + `"status"`: `String` that either says `"active"`, if `"active"` is
                      `true` or `"Removed from almanac"` if it is `false`
        + `"satnum"`: the SV catalog number for Space-Track
        + `"block"`: the satellite version number
        + `"start_week"`: GPS week when SV goes into service
        + `"start_toa"`: GPS seconds in the week when SV goes into service
        + `"start_date"`: `Tuple` in the format (year, month, day) representing
                          the date when the SV goes into service
        + `"start_julian_date"`: `"start_date"` above converted to the Julian
                                 Date when the SV goes into service
        + `"end_week"`: GPS week when SV goes out of service
        + `"end_toa"`: GPS seconds in the week when SV goes out of service
        + `"end_date"`: `Tuple` in the format (year, month, day) representing
                        the date when the SV goes out of service
        + `"end_julian_date"`: `"start_date"` above converted to the Julian
                               Date when the SV goes out of service
"""
function getCurrentGPSNORADIDS(file_url)
    # Search for existing `.GNSSTools` folder in user home directory and create
    # one if it does not already exist
    directory = string(homedir(), "/.GNSSTools")
    if ~isdir(directory)
        mkpath(directory)
    end
    # Check for `GPSData.txt` file to parse and if it does not exist, download
    # it from the url, `file_url`
    ids_file = string(directory, "/GPSData.txt")
    if ~isfile(ids_file)
        run(`curl -o $(ids_file) $(file_url)`)
    end
    # Parse GPSData file and return the `Dict` containing the history of
    # Sv usage of each PRN number
    GPSData = parseGPSData(ids_file)
    return GPSData
end

"""
    GPS_data_file_url

URL location of the historical and current GPS PRN data file which contains info
on each SV that used each PRN number throughout the U.S. GPS constellation's
history.

url = `ftp://ftp.agi.com/pub/Catalog/Almanacs/SEM/GPSData.txt`
"""
const GPS_data_file_url = "ftp://ftp.agi.com/pub/Catalog/Almanacs/SEM/GPSData.txt"

"""
    GPSData

Dictionary containing information about each SV that used each PRN numbers in
the U.S. GPS constellation throughout its history. The format of the dictionary
is `GPSData[prn][sv]["[parameter]"]`. See `getCurrentGPSNORADIDS` for more
detail.
"""
const GPSData = getCurrentGPSNORADIDS(GPS_data_file_url)

"""
    updateCurrentGPSNORADIDS()

Redownload the historical GPS PRN data file. It will be reparsed when
`GNSSTools` is imported in a new Julia session.
"""
function updateCurrentGPSNORADIDS()
    directory = string(homedir(), "/.GNSSTools")
    ids_file = string(directory, "/GPSData.txt")
    run(`curl -o $(ids_file) $(file_url)`)
end


bootstrapsample(xs::AbstractVector) = xs[rand(1:length(xs), length(xs))]

function twotailedquantile(quantile::AbstractFloat)
    0. ≤ quantile ≤ 1. || throw(DomainError(quantile))
    lq = (1-quantile)/2
    uq = quantile+lq
    lq, uq
end

"""
    AbstractConfidenceInterval{T<:Real}

A type representing a confidence interval.
"""
abstract type AbstractConfidenceInterval{T<:Real} end

"""
    lower(ci::AbstractConfidenceInterval)

Returns the lower bound of a confidence interval.
"""
lower(ci::AbstractConfidenceInterval) = ci.lower

"""
    upper(ci::AbstractConfidenceInterval)

Returns the upper bound of a confidence interval.
"""
upper(ci::AbstractConfidenceInterval) = ci.upper

"""
    confint(ci::AbstractConfidenceInterval)

Returns the lower and upper bounds of a confidence interval.
"""
HypothesisTests.confint(ci::AbstractConfidenceInterval) = lower(ci), upper(ci)

"""
    quantile(ci::AbstractConfidenceInterval)

Returns the quantile of a confidence interval.
"""
Distributions.quantile(ci::AbstractConfidenceInterval) = ci.quantile

function Base.show(io::IO, ci::AbstractConfidenceInterval)
    print(io, quantile(ci), "CI (", round(lower(ci), digits=PRECISION), ", ",
          round(upper(ci), digits=PRECISION), ")")
end

"""
    ConfidenceInterval(lower, upper, quantile)

A type representing the `lower` lower and `upper` upper bounds of an effect size confidence
interval at the `quantile` quantile.

    ConfidenceInterval(xs, ys, es; quantile)

Calculates a confidence interval for the effect size `es` between two vectors `xs` and `ys`
at the `quantile` quantile.
"""
struct ConfidenceInterval{T<:Real} <: AbstractConfidenceInterval{T}
    lower::T
    upper::T
    quantile::Float64

    function ConfidenceInterval(l::T,u::T,q::Float64) where T<:Real
        l ≤ u || throw(DomainError((l,u), "l > u"))
        0. ≤ q ≤ 1. || throw(DomainError(q))
        new{T}(l,u,q)
    end
end

function ConfidenceInterval(xs::AbstractVector{T}, ys::AbstractVector{T}, es::T;
                            quantile::AbstractFloat) where T<:Real
    0. ≤ quantile ≤ 1. || throw(DomainError(quantile))
    nx = length(xs)
    ny = length(ys)
    σ² = √(((nx+ny)/(nx*ny)) + (es^2 / 2(nx+ny)))
    _, uq = twotailedquantile(quantile)
    z = Distributions.quantile(Normal(), uq)
    ci = z*√σ²
    ConfidenceInterval(es-ci, es+ci, quantile)
end

"""
    BootstrapConfidenceInterval(lower, upper, quantile, bootstrap)

A type representing the `lower` lower and `upper` upper bounds of an effect size confidence
interval at the `quantile` quantile with `bootstrap` boostrap resamples.

    BootstrapConfidenceInterval(xs, ys, f, bootstrap; quantile)

Calculates the effect size confidence interval between two vectors `xs` and `ys` at the
`quantile` quantile by applying `f` to `bootstrap` bootstrap resamples of `xs` and `ys`.
"""
struct BootstrapConfidenceInterval{T<:Real} <: AbstractConfidenceInterval{T}
    lower::T
    upper::T
    quantile::Float64
    bootstrap::Int64

    function BootstrapConfidenceInterval(l::T,u::T,q::Float64,b::Int64) where T<:Real
        l ≤ u || throw(DomainError((l,u), "l > u"))
        0. ≤ q ≤ 1. || throw(DomainError(q))
        b > 1 || throw(DomainError(b))
        new{T}(l,u,q,b)
    end
end

function BootstrapConfidenceInterval(xs::AbstractVector{T}, ys::AbstractVector{T},
                                     f::Function, bootstrap::Integer=1000;
                                     quantile::AbstractFloat) where T<:Real
    0. ≤ quantile ≤ 1. || throw(DomainError(quantile))
    bootstrap > 1 || throw(DomainError(bootstrap))
    es = map(_->f(bootstrapsample(xs), bootstrapsample(ys)), 1:bootstrap)
    # es = @distributed vcat for _ = 1:bootstrap
    #     f(bootstrapsample(xs), bootstrapsample(ys))
    # end
    lq, uq = twotailedquantile(quantile)
    BootstrapConfidenceInterval(Distributions.quantile(es, lq),
        Distributions.quantile(es, uq), quantile, bootstrap)
end


# ---
# title: 282. Expression Add Operators
# id: problem282
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Divide and Conquer
# link: <https://leetcode.com/problems/expression-add-operators/description/>
# hidden: true
# ---
#
# Given a string that contains only digits `0-9` and a target value, return all
# possibilities to add **binary** operators (not unary) `+`, `-`, or `*` between
# the digits so they evaluate to the target value.
#
# **Example 1:**
#
#
#
#     Input: _num_ = "123", _target_ = 6
#     Output: ["1+2+3", "1*2*3"]
#
#
# **Example 2:**
#
#
#
#     Input: _num_ = "232", _target_ = 8
#     Output: ["2*3+2", "2+3*2"]
#
# **Example 3:**
#
#
#
#     Input: _num_ = "105", _target_ = 5
#     Output: ["1*0+5","10-5"]
#
# **Example 4:**
#
#
#
#     Input: _num_ = "00", _target_ = 0
#     Output: ["0+0", "0-0", "0*0"]
#
#
# **Example 5:**
#
#
#
#     Input: _num_ = "3456237490", _target_ = 9191
#     Output: []
#
#
#
#
# **Constraints:**
#
#   * `0 <= num.length <= 10`
#   * `num` only contain digits.
#
#
## @lc code=start
using LeetCode

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


# using Revise
using PiecewiseDeterministicMarkovProcesses, LinearAlgebra, Random, DifferentialEquations, Sundials
const r = 10.

function AnalyticalSample(xc0,xd0,ti,nj::Int64)
	xch = [xc0[1]]
	xdh = [xd0[1]]
	th  = [ti]
	list_rng = Float64[]
	t = ti
	while length(th)<nj
		xc = xch[end]
		xd = xdh[end]
		push!(list_rng, rand())
		S = -log(list_rng[end])
		a = -r * (2mod(xd,2)-1)
		dt = log(a*S/xc+1)/a
		t += dt
		push!(th, t)
		push!(xch,xc + a * S )
		push!(xdh,xd .+ 1 )
		push!(list_rng, rand())
		S = -log(list_rng[end])
	end
	return th, xch, xdh, list_rng
end

function F!(ẋ, xc, xd, parms, t)
	ẋ[1] = -r * (2mod(xd[1],2)-1) * xc[1]
end

R(x) = x

function R!(rate, xc, xd, parms, t, issum::Bool)
	# rate fonction
	if issum==false
		rate[1] = R(xc[1])
		rate[2] = 0.0
		return R(xc[1]),40.
	else
		return R(xc[1]),40.
	end
end

xc0 = [1.0]
xd0 = [0,0]

nu = [[1 0];[0 -1]]
parms = [0.0]
ti = 0.332
tf = 100000.
nj = 50

Random.seed!(8)
	res_a = AnalyticalSample(xc0,xd0,ti,nj)

errors = Float64[]
# state of the random generator
rnd_state = 0.

println("\n\nComparison of solvers")
	for ode in [(:lsoda,"lsoda"),(:cvode,"cvode"),(CVODE_BDF(),"CVODEBDF"),(CVODE_Adams(),"CVODEAdams"),(Tsit5(),"tsit5"),(Rodas4P(autodiff=false),"rodas4P-noAutoDiff"),(Rodas5(),"rodas5"),(Rosenbrock23(),"RS23"),(AutoTsit5(Rosenbrock23()),"AutoTsit5-RS23")]
		Random.seed!(8)

		problem = PDMP.PDMPProblem(F!, R!, nu, xc0, xd0, parms, (ti, tf))
		res =  PDMP.solve(problem, CHV(ode[1]); n_jumps = nj)
		# this is to check the state of the random generator at the end of the simulation
		if ode[1] == :lsoda
			global rnd_state = rand()
		else
			@assert rnd_state == rand()
		end

		println("--> norm difference = ", norm(res.time - res_a[1],Inf64), "  - solver = ",ode[2])
		push!(errors,norm(res.time - res_a[1],Inf64))
	end


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#   Functions related to the conversion from Euler angle and axis to quaternion.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

export angleaxis_to_quat

"""
    angleaxis_to_quat(θ::Number, v::AbstractVector)
    angleaxis_to_quat(angleaxis::EulerAngleAxis)

Convert the Euler angle `θ` [rad] and Euler axis `v` to a quaternion.

Those values can also be passed inside the structure `ea` (see
[`EulerAngleAxis`](@ref)).

!!! warning
    It is expected that the vector `v` is unitary. However, no verification is
    performed inside the function. The user must handle this situation.

# Example

```jldoctest
julia> v = [1, 1, 1];

julia> v /= norm(v);

julia> angleaxis_to_quat(pi/2,v)
Quaternion{Float64}:
  + 0.707107 + 0.408248⋅i + 0.408248⋅j + 0.408248⋅k
```
"""
@inline function angleaxis_to_quat(θ::Number, v::AbstractVector)
    # Check the arguments.
    (length(v) ≠ 3) && throw(ArgumentError("The provided vector for the Euler axis must have 3 elements."))

    sθo2, cθo2 = sincos(θ / 2)

    # Keep `q0` positive.
    s = (cθo2 < 0) ? -1 : +1

    # Create the quaternion.
    return Quaternion(s * cθo2, s * sθo2 * v)
end

@inline angleaxis_to_quat(av::EulerAngleAxis) = angleaxis_to_quat(av.a, av.v)


# using Plots
using Omega
# using Parameters
# include(joinpath(dirname(pathof(Omega)), "viz.jl"))

function prob1()
  x = uniform(-1.0, 1.0)
  y = uniform(-1.0, 1.0)
  (x = x, y = y, c = x ==ₛ y, xlims = (-1, 1), ylims = (-1, 1))
end

function prob2()
  x = uniform(-1.0, 1.0)
  y = uniform(-1.0, 1.0)
  (x = x, y = y, c = x >ₛ y, xlims = (-1, 1), ylims = (-1, 1))
end

function prob3()
  x = uniform(-1.0, 1.0)
  y = uniform(-1.0, 1.0)
  (x = x, y = y, c = abs(x) >ₛ abs(y), xlims = (-1, 1), ylims = (-1, 1))
end

function prob4()
  x = uniform(-1.0, 1.0)
  y = uniform(-1.0, 1.0)
  (x = x, y = y, c = x^2 ==ₛ y^2, xlims = (-1, 1), ylims = (-1, 1))
end

function prob5(k = 3, thresh = 0.9999)
  x = uniform(0.0, 1.0)
  y = uniform(0.0, 1.0)
  c = (sin(2π * x * k) * cos(2π * y * k)) >ₛ thresh
  (x = x, y = y, c = c, xlims = (0, 1), ylims = (0, 1))
end

function prob6(k = 3, thresh = 0.8)
  x = uniform(0, 1.0)
  y = uniform(0, 1.0)
  c = (sin(2π * x * k) * cos(2π * y * k)) >ₛ thresh
  (x = x, y = y, c = c, xlims = (0, 1), ylims = (0, 1))
end

function prob7(k = 3, thresh = 0.8)
  x = uniform(-1.0, 1.0)
  y = uniform(-100.0, 100.0)
  c = (x ==ₛ 0.0) & (y ==ₛ 0.0)
  (x = x, y = y, c = c, xlims = (-1, 1), ylims = (-100, 100))
end

# function allsamples(prob, n; algkwargs...)
#   # ωsamples = rand(defΩ(), logerr(prob.c), n, alg, kwargs...)
#   # xy = randtuple((prob.x, prob.yy))
#   # samples = map(ω -> applynotrackerr(x, ω), ωsamples)

#   # ωsamples = rand((prob.x, prob.y), prob.c, n; alg = alg, kwargs...)
#   # (ωsamples = ωsamples, samples = samples)
#   samples = rand((prob.x, prob.y), prob.c, n; algkwargs...)
#   (samples = samples, ωsamples = nothing)
# end

# function scatterxy(samples;
#                    label = nothing, legend = nothing, xlims = (-1, 1),
#                    ylims = (-1, 1), kwargs...)
#   xs, ys = ntranspose(samples)
#   scatter(xs, ys, label = label, legend = legend, xlims = xlims, ylims = ylims)
# end

# function vizall(probs, algs, n)
#   plots = []
#   for prob in probs
#     @unpack x, y, c, xlims, ylims = prob
#     push!(plots, ωcontour(err(c); label = nothing, legend = nothing,
#                                   colorbar = nothing))
#     for alg in algs
#       try
#         @unpack samples, ωsamples = allsamples(prob, n; alg...)
#         push!(plots, scatterxy(samples, xlims = xlims, ylims = ylims))
#       catch e
#         println("Failed")
#         display(e)
#         # rethrow(e)
#         push!(plots, plot())
#       end
#     end
#   end
#   plots
#   # plt = plot(plots..., layout = (length(probs), length(algs) + 1))
# end

# probs = [prob1(), prob2(), prob3(), prob4(), prob5(), prob6()]
# algs = [(alg = SSMH,),
#         (alg = NUTS,),
#         (alg = Replica, nreplicas = 4, inneralg = SSMH)
#         (alg = Replica, nreplicas = 4, inneralg = NUTS)
#         ]

# plots = vizall(probs, algs, 1000)

# function makeplots()
#   plt = plot(plots..., layout = (length(probs), length(algs) + 1),
#              markersize=0.01, tickfontsize=4)
# end

# # n = 8
# # temps = Omega.Inference.logtemps(n)
# # temps = [1e-9, 10000]
# # withkernel(kseα(temps[end])) do
# #   @unpack x, y, c = prob6()
# #   # p1 = ωcontour(err(c); label = nothing, legend = nothing);
# #   p2 = ωcontour(logerr(c); label = nothing, legend = nothing)
# #   # samples = rand((x, y), c, 100000; alg = Replica, nreplicas = 8, temps = temps)
# #   # p3 = scatterxy(samples, xlims = (0, 1), ylims = (0, 1))
# #   # plot(p1, p3, markersize = 0.01, aspectratio = 1)
# # end

x = normal(0, 1)
y = normal(0, 1)
# rand((x, y), x ==ₛ y, alg = Replica, inneralg = HMCFAST)
rand((x, y), x ==ₛ y, 1000, alg = HMCFAST)


# Author: Luaksz Bratos

include("diff.jl")
using Test

# dane testowe do zadania 1
x1 = [3.0, 1.0, 5.0, 6.0]
f1 = [1.0, -3.0, 2.0, 4.0]
ans1 = [1.0, 2.0, -3.0 / 8.0, 7.0 / 40.0]

x2 = [-1.0, 0.0, 1.0, 2.0, 3.0]
f2 = [2.0, 1.0, 2.0, -7.0, 10.0]
ans2 = [2.0, -1.0, 1.0, -2.0, 2.0]

# dane testowe do zadania 2
fx2 = Diff.ilorazyRoznicowe(x2, f2)

# dane testowe do zadania 3
ans3 = [1.0, 6.0, -1.0, -6.0, 2.0]

@testset "Ilorazy roznicowe" begin
    @test isapprox(Diff.ilorazyRoznicowe(x1, f1), ans1)
    @test isapprox(Diff.ilorazyRoznicowe(x2, f2), ans2)
end

@testset "Wartosc wielomianu Newtona" begin
    @test isapprox(Diff.warNewton(x2, fx2, 2.0), -7.0)
    @test isapprox(Diff.warNewton(x2, fx2, 10.0), 13961.0)
    @test isapprox(Diff.warNewton(x2, fx2, 5.0), 506.0)
    @test isapprox(Diff.warNewton(x2, fx2, -3.0), 298.0)
end

@testset "Wspolczynniki w postaci normalnej z postaci Newtona" begin
    @test isapprox(Diff.naturalna(x2, fx2), ans3)
end


# ---
# title: 785. Is Graph Bipartite?
# id: problem785
# author: Indigo
# date: 2021-01-28
# difficulty: Medium
# categories: Depth-first Search, Breadth-first Search, Graph
# link: <https://leetcode.com/problems/is-graph-bipartite/description/>
# hidden: true
# ---
#
# Given an undirected `graph`, return `true` if and only if it is bipartite.
#
# Recall that a graph is _bipartite_ if we can split its set of nodes into two
# independent subsets A and B, such that every edge in the graph has one node in
# A and another node in B.
#
# The graph is given in the following form: `graph[i]` is a list of indexes `j`
# for which the edge between nodes `i` and `j` exists.  Each node is an integer
# between `0` and `graph.length - 1`.  There are no self edges or parallel
# edges: `graph[i]` does not contain `i`, and it doesn't contain any element
# twice.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/10/21/bi1.jpg)
#
#
#
#     Input: graph = [[1,3],[0,2],[1,3],[0,2]]
#     Output: true
#     Explanation: We can divide the vertices into two groups: {0, 2} and {1, 3}.
#
#
#
# **Example 2:**
#
# ![](https://assets.leetcode.com/uploads/2020/10/21/bi2.jpg)
#
#
#
#     Input: graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
#     Output: false
#     Explanation: We cannot find a way to divide the set of nodes into two independent subsets.
#
#
#
#
#
# **Constraints:**
#
#   * `1 <= graph.length <= 100`
#   * `0 <= graph[i].length < 100`
#   * `0 <= graph[i][j] <= graph.length - 1`
#   * `graph[i][j] != i`
#   * All the values of `graph[i]` are **unique**.
#   * The graph is **guaranteed** to be **undirected**.
#
#
## @lc code=start
using LeetCode

function is_bipartite(graph::Vector{Vector{Int}})
    for edge in graph
        edge .+= 1
    end
    color = fill(0, length(graph))
    q = Queue{Int}()
    for i in 1:length(graph)
        if color[i] != 0
            continue
        end
        color[i] = 1
        enqueue!(q, i)
        while !isempty(q)
            root = dequeue!(q)
            for neighbor in graph[root]
                if color[neighbor] == 0
                    color[neighbor] = -color[root]
                    enqueue!(q, neighbor)
                elseif color[neighbor] == color[root]
                    return false
                end
            end
        end
    end
    return true
end
## @lc code=end


# # [Bending of an Initially Curved Beam](@id curved)
#
# This example is a common benchmark problem for the geometrically exact bending of
# nonlinear beams.
#
# ![](../assets/curved-drawing.svg)
#
#-
#md # !!! tip
#md #     This example is also available as a Jupyter notebook:
#md #     [`curved.ipynb`](@__NBVIEWER_ROOT_URL__/examples/curved.ipynb).
#-

using GXBeam, LinearAlgebra

## problem constants
R = 100
L = R*pi/4 # inches
h = w = 1 # inches
E = 1e7 # psi Young's Modulus
ν = 0.0
G = E/(2*(1+ν))

## beam starting point, frame, and curvature
start = [0, 0, 0]
frame = [0 -1 0; 1 0 0; 0 0 1]
curvature = [0, 0, -1/R]

## cross section properties
A = h*w
Ay = A
Az = A
Iyy = w*h^3/12
Izz = w^3*h/12
J = Iyy + Izz

## discretize the beam
nelem = 16
ΔL, xp, xm, Cab = discretize_beam(L, start, nelem;
    frame = frame,
    curvature = curvature)

## force
P = 600 # lbs

## index of left and right endpoints of each beam element
pt1 = 1:nelem
pt2 = 2:nelem+1

## compliance matrix for each beam element
compliance = fill(Diagonal([1/(E*A), 1/(G*Ay), 1/(G*Az), 1/(G*J), 1/(E*Iyy),
    1/(E*Izz)]), nelem)

## create assembly of interconnected nonlinear beams
assembly = Assembly(xp, pt1, pt2, compliance=compliance, frames=Cab,
    lengths=ΔL, midpoints=xm)

## create dictionary of prescribed conditions
prescribed_conditions = Dict(
    ## fixed left endpoint
    1 => PrescribedConditions(ux=0, uy=0, uz=0, theta_x=0, theta_y=0, theta_z=0),
    ## force on right endpoint
    nelem+1 => PrescribedConditions(Fz = P)
)

## perform static analysis
system, converged = static_analysis(assembly;
    prescribed_conditions = prescribed_conditions)

## post-process results
state = AssemblyState(system, assembly;
    prescribed_conditions = prescribed_conditions)

println("Tip Displacement: ", state.points[end].u)
println("Tip Displacement (Bathe and Bolourch): [-13.4, -23.5, 53.4]")

# The calculated tip displacements match those reported by Bathe and Bolourch in "Large
# Displacement Analysis of Three-Dimensional Beam Structures" closely, thus verifying our
# implementation of geometrically exact beam theory.
#
# We can visualize the deformed geometry and inspect the associated point and element data
# using ParaView.

write_vtk("curved-visualization", assembly, state)

# ![](../assets/curved-visualization.png)


# ---
# title: 1363. Largest Multiple of Three
# id: problem1363
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Math, Dynamic Programming
# link: <https://leetcode.com/problems/largest-multiple-of-three/description/>
# hidden: true
# ---
#
# Given an integer array of `digits`, return the largest multiple of **three**
# that can be formed by concatenating some of the given digits in any order.
#
# Since the answer may not fit in an integer data type, return the answer as a
# string.
#
# If there is no answer return an empty string.
#
#
#
# **Example 1:**
#
#
#
#     Input: digits = [8,1,9]
#     Output: "981"
#
#
# **Example 2:**
#
#
#
#     Input: digits = [8,6,7,1,0]
#     Output: "8760"
#
#
# **Example 3:**
#
#
#
#     Input: digits = [1]
#     Output: ""
#
#
# **Example 4:**
#
#
#
#     Input: digits = [0,0,0,0,0,0]
#     Output: "0"
#
#
#
#
# **Constraints:**
#
#   * `1 <= digits.length <= 10^4`
#   * `0 <= digits[i] <= 9`
#   * The returning answer must not contain unnecessary leading zeros.
#
#
## @lc code=start
using LeetCode

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


using DReal
import DReal: opensmt_context, opensmt_set_verbosity, opensmt_assert, opensmt_check, opensmt_print_expr, opensmt_mk_context,
              opensmt_mk_bool_var, opensmt_mk_int_var, opensmt_mk_minus, opensmt_mk_leq, opensmt_mk_num_from_string,
              opensmt_push, opensmt_pop, opensmt_reset, opensmt_del_context
using Base.Test
using Compat

begin
# Blank slate
reset_ctx!()

print( "Creating context\n" )
ctx = opensmt_mk_context(@compat UInt32(1))

 # Creating boolean variables
print( "Creating some boolean variables\n" )
a = opensmt_mk_bool_var( ctx, "a" )
b = opensmt_mk_bool_var( ctx, "b" )
c = opensmt_mk_bool_var( ctx, "c" )

 # Creating integer variables
print( "Creating some integer variables\n" )
x = opensmt_mk_int_var( ctx, "x" , -100, 100)
y = opensmt_mk_int_var( ctx, "y" , -100, 100)
z = opensmt_mk_int_var( ctx, "z" , -100, 100)
 # Creating inequality
print( "Creating x - y <= 0\n" )

print( "  Creating x - y\n" )
minus = opensmt_mk_minus( ctx, x, y )
print( "  Expression created: " )
opensmt_print_expr( minus )
print( "\n" )

print( "  Creating 0\n" )
zero = opensmt_mk_num_from_string( ctx, "0" )
print( "  Expression created: " )
opensmt_print_expr( zero )
print( "\n" )

print( "  Creating x - y <= 0\n" )
leq = opensmt_mk_leq( ctx, minus, zero )
print( "  Expression created: " )
opensmt_print_expr( leq )
print( "\n" )

 # Creating inequality 2
print( "Creating y - z <= 0\n" )
minus2 = opensmt_mk_minus( ctx, y, z )
leq2 = opensmt_mk_leq( ctx, minus2, zero )
print( "  Expression created: " )
opensmt_print_expr( leq2 )
print( "\n" )

 # Creating inequality 3
print( "Creating z - x <= -1\n" )
minus3 = opensmt_mk_minus( ctx, z, x )
mone = opensmt_mk_num_from_string( ctx, "-1" )
leq3 = opensmt_mk_leq( ctx, minus3, mone )
print( "  Expression created: " )
opensmt_print_expr( leq3 )
print( "\n" )

 # Creating inequality 4
print( "Creating z - x <= 0\n" )
minus4 = opensmt_mk_minus( ctx, z, x )
leq4 = opensmt_mk_leq( ctx, minus4, zero )
print( "  Expression created: " )
opensmt_print_expr( leq4 )
print( "\n" )

 # Asserting first inequality
print( "Asserting ")
opensmt_print_expr( leq )
print( "\n" )
opensmt_assert( ctx, leq )
opensmt_push( ctx )

println("Expression is")
opensmt_print_expr( leq )

# Checking for consistency
println( "\nChecking for consistency: " )
res = opensmt_check( ctx )
println( "res = $(res == -1 ? "unsat" : "sat")")
@test res == 1

# Asserting second inequality
print( "Asserting ")
opensmt_print_expr( leq2 )
print( "\n" )
opensmt_assert( ctx, leq2 )
opensmt_push( ctx )

 # Checking for consistency
print( "\nChecking for consistency: " )
res = opensmt_check( ctx )
println( "res = $(res == -1 ? "unsat" : "sat")")
@test res == 1

# Asserting third inequality
print( "\nAsserting ")
opensmt_print_expr( leq3 )
print( "\n" )
opensmt_push( ctx )
opensmt_assert( ctx, leq3 )

# Checking for consistency - it is UNSAT
print( "\nChecking for consistency: " )
res = opensmt_check( ctx )
@test res == -1

 # Backtracking one step - so the state is SAT again
opensmt_pop( ctx )
print( "Backtracking one step\n\n" )

# Asserting fourth inequality
print( "Asserting ")
opensmt_print_expr( leq4 )
print( "\n" )
opensmt_assert( ctx, leq4 )

 # Checking for consistency
print( "\nChecking for consistency: " )
res = opensmt_check( ctx )
println( "res = $(res == -1 ? "unsat" : "sat")")
@test res == 1

# Resetting context
print( "Resetting context\n" )
opensmt_reset( ctx )

# # Deleting context
# print( "Deleting context\n" )
# opensmt_del_context( ctx )
end


"""
    int(x) = trunc(Int, x)
"""
int(x) = trunc(Int, x)

"""
    dropmean(X, d) = dropdims(mean(X, dims=d), dims=d)
"""
function dropmean(X, d)
    if ndims(X) == 1
        mean(X; dims = d)
    else
        dropdims(mean(X; dims = d); dims = d)
    end
end

"""
    squeeze(X::AbstractArray)

removing singleton dimensions
"""
function squeeze(X::AbstractArray)
    keepd = Tuple(d for d in size(X) if d != 1)
    return reshape(X, keepd)
end

"""
    detrend!(data, n)

(in place) Linear detrend of signals along first axis removing the n-th order polynomial.
This detrend function is limited to linear orders (0th and 1st order).

### Arguments

  - `data::AbstractArray`: N-dim array where signal is in column-major order
  - `n::Integer`: `n = 0` subtracts mean from data, `n = 1` removes linear trend

**Note**: shape of data must be (signal length, ...)
"""
function detrend!(data::AbstractArray, n::Integer)
    original_shape = size(data)
    nsamp = size(data, 1)  # number of samples

    A = Array{Float64}([ones(nsamp) Array(1:nsamp)])

    data = reshape(data, (nsamp, :))  # reshaping data
    if n == 0
        data .-= mean(data; dims = 1)
    elseif n == 1
        data .-= A * (A \ data)
    end
    return reshape(data, original_shape)
end

"""
    window = hanning_fun(N)

Hanning window similar to MATLAB `hanning` implementation
"""
function hanning_fun(N::Integer)
    x = [range(0.0, 1.0; length = N + 2);]
    window = 0.5 .* (1 .- cospi.(2 .* x))
    window = (window + window[end:-1:1]) ./ 2  # forcing symmetry
    return window[2:(end-1)]  # excluding the zero values
end

"""
    review(X, dim)

Reshape + View
"""
review(X, dim) = reshape(view(X, :, dim), :, 1)

"""
    pink_noise(n, c)

Pink noise with `n` samples and pinkness of:
```math
P_s \\propto \\frac{1}{f^c} .
```

### Arguments

  - `n::Integer`: Number of samples
  - `c::Float64`: pinkness order (frequency power)

"""
function pink_noise(n::Integer, c::Float64)
    nf = int(n / 2)  # Nyquist frequency
    x = reshape(range(start = 0.0, stop = 1.0, length = n), 1, :)
    f = reshape(range(start = 1, stop = nf), :, 1)
    phi = rand(nf)
    y = f * x .+ phi
    z = sin.(2 * pi * y) ./ (f .^ c)
    return sum(z; dims = 1)[1, :]
end


# Bubble Sort is the simplest sorting algorithm that works by
# repeatedly swapping the adjacent elements if they are in wrong order.

function swap!{T}(x::AbstractArray{T}, a::T, b::T)

  tmp = x[a]
  x[a] = x[b]
  x[b] = tmp
  return x

end

function bubblesort!{T}(x::AbstractArray{T})

  for i in 2:length(x)
    for j in 1:length(x)-1
      if x[j] > x[j+1]
        swap!(x, j, j+1)
      end
    end
  end

  return x
end

x = [10,2,5,1,3,0]

println(bubblesort!(x))


#=

  The BUGS Book

  Example 3.3.3 Trihalomethanes in tap water

  Cf ~/jags/bugs_book_3_3_3.R
  """
  model {
    for (i in 1:n) {
      y[i] ~ dnorm(mu, inv.sigma.squared)
    }
    mu ~ dnorm(gamma, inv.omega.squared)
    inv.omega.squared <- n0/sigma.squared
    inv.sigma.squared <- 1/sigma.squared
    y.pred ~ dnorm(mu, inv.sigma.squared)
    P.crit <- step(y.pred - y.crit)
  }

  Data: list(n=2, y=c(128, 132), gamma=120, n0=0.25,sigma.squared=25, y.crit=145)

  Output:
            Mean      SD  Naive SE Time-series SE
P.crit 3.213e-03 0.05659 0.0002001      0.0002034
mu     1.289e+02 3.33096 0.0117767      0.0117332
y.pred 1.289e+02 5.95716 0.0210618      0.0209726
  """

  Summary Statistics
  parameters       mean       std   naive_se      mcse          ess      rhat   ess_per_sec
      Symbol    Float64   Float64    Float64   Float64      Float64   Float64       Float64

          mu   128.9489    3.3703     0.0337    0.0502    4816.4635    0.9999     2519.0709
      y_pred   128.9964    6.0306     0.0603    0.0779    7496.1334    1.0000     3920.5719
      p_crit     0.0046    0.0677     0.0007    0.0006   10096.6867    0.9999     5280.6939

  Quantiles
  parameters       2.5%      25.0%      50.0%      75.0%      97.5%
      Symbol    Float64    Float64    Float64    Float64    Float64

          mu   122.2672   126.7344   128.9249   131.2583   135.4088
      y_pred   117.2568   124.8223   128.9742   133.1282   140.7995
      p_crit     0.0000     0.0000     0.0000     0.0000     0.0000

  Cf ~/webppl/bugs_book_3_3_3.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function bugs_book_3_3_3(y=[128.0,132.0])
    n = 2
    gamma = 120
    n0 = 0.25
    sigma_squared = 25
    y_crit = 145.0

    omega_squared = sigma_squared/n0
    mu ~ Normal(gamma, sqrt(omega_squared))
    #  random Real inv_omega_squared ~ n0/sigma_squared;
    y ~ filldist(Normal(mu, sqrt(sigma_squared)),2)

    #  random Real inv_sigma_squared ~ 1/sigma_squared;
    y_pred ~ Normal(mu, sqrt(sigma_squared));
    p_crit ~ Dirac(y_pred > y_crit)

 end

model = bugs_book_3_3_3()

num_chains = 4

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 100_000)
# chns = sample(model, PG(15), 10_000)
chns = sample(model, SMC(10_000), 10_000)
# chns = sample(model, IS(), 10_000)

# chns = sample(model, NUTS(), 10_000)
# chns = sample(model, HMC(0.1,5), 10_000)

display(chns)
# display(plot(chns))


"""
    catbyrow(arr)

Convert an array of array to a 2-dimenional matrix, the inner vector
is transposed as concatenated as rows.
"""
catbyrow(arr) = vcat(transpose(arr)...);

"""
    toparameter(parameters)

Convert a parameter vector of length 1 to a single parameter.
"""
function toparameter(parameters::Vector{Symbol})
    length(parameters) == 1 || error("Number of parameters should be 1")
    return parameters[1]
end

effsamplesize(n1, n2) = 1/(1/n1 + 1/n2)

dofpooled(n1, n2) = n1 + n2 - 2

function dofwelch(sd1, sd2, n1, n2)
    se1_sq = sd1^2/n1
    se2_sq = sd2^2/n2
    (se1_sq + se2_sq)^2 / (se1_sq^2/(n1-1) + se2_sq^2/(n2-1))
end

pooledsd(sd1, sd2, n1, n2) = sqrt(((n1-1)*sd1^2 + (n2-1)*sd2^2) / (n1 + n2 - 2))

zstat(x̄, μ0, σ, n) = sqrt(n)*(x̄ - μ0)/σ


using DiffEqFlux, Optim, OrdinaryDiffEq, DiffEqSensitivity, Zygote, Test

function f(du,u,p,t)
  du[1] = u[2]
  du[2] = -p[1]
end

function condition(u,t,integrator) # Event when event_f(u,t) == 0
  u[1]
end

function affect!(integrator)
  integrator.u[2] = -integrator.p[2]*integrator.u[2]
end

cb = ContinuousCallback(condition,affect!)
u0 = [50.0,0.0]
tspan = (0.0,15.0)
p = [9.8, 0.8]
prob = ODEProblem(f,u0,tspan,p)
sol = solve(prob,Tsit5(),callback=cb)

function loss1(θ)
  sol = solve(prob,Tsit5(),p=[9.8,θ[1]],saveat=0.1,callback=cb,sensealg=ForwardDiffSensitivity())
  target = 20.0
  abs2(sol[end][1] - target)
end

function loss2(θ)
  sol = solve(prob,Tsit5(),p=[9.8,θ[1]],saveat=0.1,callback=cb,sensealg=ReverseDiffAdjoint())
  target = 20.0
  abs2(sol[end][1] - target)
end

#Zygote.gradient(loss1,[0.8])

#Zygote.gradient(loss2,[0.8])

#res = DiffEqFlux.sciml_train(loss1,[0.8],BFGS())
#@test loss1(res.minimizer) < 1

#res = DiffEqFlux.sciml_train(loss2,[0.8],BFGS())
#@test loss2(res.minimizer) < 1


#Comparing Gini Index to L0 as a measure of sparsity

using PyPlot
using CompressedSensing
srand(0)

#Create 2 data sets, one without noise, and one with 5% gaussian noise
example_data_no_noise=zeros(100)
example_data_noise=randn(100)*.05

#Create arrays that will contain the measures of sparsity for the two data sets
calculated_GI_no_noise = []
calculated_GI_noise = []
calculated_L0_no_noise = []
calculated_L0_noise = []

#for both data sets, set an increasing number of points to 1, IE: reduce sparsity on element at a time
for i=1:length(example_data_no_noise)

    example_data_no_noise[i]+=1
    example_data_noise[i]+=1

    #calculate the Gini Index for both data sets and save the value
    calculated_GI_no_noise = [calculated_GI_no_noise,GI(example_data_no_noise)]
    calculated_GI_noise = [calculated_GI_noise,GI(example_data_noise)]

    #calculate the L0 sparsity measure for each data set and save the value
    calculated_L0_no_noise = [calculated_L0_no_noise,sum(abs(example_data_no_noise).>0)]
    calculated_L0_noise = [calculated_L0_noise,sum(abs(example_data_noise).>0.)]
end

#Plot the results
figure(figsize=(6,5))
title("Comparison of Gini Index to L0 as measures of Sparsity")
plot([1:length(example_data_no_noise)],calculated_GI_no_noise,label="Gini Index",color="red")
plot([1:length(example_data_noise)],calculated_GI_noise,label="Gini Index w/ Noise",color="purple")
xlabel("Number of Non-Zero Elements in Data")
ylabel("Gini Index")
ylim([0,1.05])
grid()
#put the L0 on a different axes, as it goes from 0-100 not 0-1
twinx()
plot([1:length(example_data_no_noise)],calculated_L0_no_noise,label="L0 Sparsity")
plot([1:length(example_data_noise)],calculated_L0_noise,label="L0 w/ Noise")
ylabel("L0 Sparsity Measure")
ylim([0,105])
annotate("L0 w/ Noise", xy=[70, 100], xytext=(65, 89),arrowprops=["width"=>.1,"headwidth"=>5]);
annotate("L0", xy=[80, 80], xytext=(85, 69),arrowprops=["width"=>.1,"headwidth"=>5]);
annotate("GI", xy=[20, 80], xytext=(25, 90),arrowprops=["width"=>.1,"headwidth"=>5]);
annotate("GI w/ Noise", xy=[20, 71], xytext=(10, 50),arrowprops=["width"=>.1,"headwidth"=>5]);
savefig("Example_3_Fig_1.png")


using Winston
using DASSL

# test the convergence of dasslSolve.  dasslTestConvergence returns a
# tuple of relative and absolute L^Inf norms of a difference of
# analytic and numerical solutions.  The third element in the returned
# touple is the time it took to obtain the numerical solution.
function dasslTestConvergence{T<:Number}(F          :: Function,  # equation to solve
                                         y0         :: Vector{T}, # initial data
                                         tspan      :: Vector{T}, # time span of a solution
                                         sol        :: Function,  # analytic solution, for comparison with numerical solution
                                         rtol_range :: Vector{T}, # vector of relative tolerances
                                         atol_range :: Vector{T}) # vector of absolute tolerances

    if length(rtol_range) != length(atol_range)
        error("The table of relative errors and absolute errors should be of the same size.")
    end

    n = length(rtol_range)

    norms_abs = zeros(T,n)
    norms_rel = zeros(T,n)
    times     = zeros(T,n)

    for i = 1:n
        times[i] = @elapsed (tn,yn)=dasslSolve(F,y0,tspan,
                                               rtol = rtol_range[i],
                                               atol = atol_range[i])
        k = length(tn)
        delta_rel = zeros(T,k)
        delta_abs = zeros(T,k)

        for j = 1:k
            t = tn[j]
            delta_abs[j] = norm(sol(t)-yn[:,j], Inf)
            delta_rel[j] = norm(sol(t)-yn[:,j], Inf)/norm(sol(t), Inf)
        end

        norms_abs[i]=norm(delta_abs,Inf)
        norms_rel[i]=norm(delta_rel,Inf)

    end

    return norms_rel, norms_abs, times
end

function dasslDrawConvergence{T<:Number}(F     :: Function,  # equation to solve
                                         y0    :: Vector{T}, # initial data
                                         tspan :: Vector{T}, # time span of a solution
                                         sol   :: Function,  # analytic solution, for comparison with numerical solution
                                         rtol  :: Vector{T}, # vector of relative tolerances
                                         atol  :: Vector{T}) # vector of absolute tolerances

    # run the dasslSolve to trigger the compilation
    dasslSolve(F,y0,tspan)

    # run the convergence tests
    (rerr,aerr,time) = dasslTestConvergence(F,y0,tspan,sol,rtol,atol)

    tbl = Table(1,3)

    tbl[1,1] = FramedPlot(title  = "Absolute error",
                          xlabel = "Absolute tolerance",
                          ylabel = "Absolute error",
                          xlog   = true,
                          ylog   = true)
    tbl[1,2] = FramedPlot(title  = "Relative error",
                          xlabel = "Relative tolerance",
                          ylabel = "Relative error",
                          xlog   = true,
                          ylog   = true)
    tbl[1,3] = FramedPlot(title  = "Execution time",
                          xlabel = "Relative error",
                          ylabel = "Elapsed time",
                          xlog   = true,
                          ylog   = true)

    add(tbl[1,1], Points(atol,aerr))
    add(tbl[1,2], Points(rtol,rerr))
    add(tbl[1,3], Points(rerr,time))

    setattr(tbl, aspect_ratio=0.3)

    return tbl

end

#------------------------------------------------------------
# index-1 example
#------------------------------------------------------------

function F1{T<:Number}(t::T,y::Array{T,1},dy::Array{T,1})
    a=10.0
    [-dy[1]+(a-1/(2-t))*y[1]+(2-t)*a*y[3]+exp(t)*(3-t)/(2-t),
     -dy[2]+(1-a)/(t-2)*y[1]-y[2]+(a-1)*y[3]+2*exp(t),
     (t+2)*y[1]+(t^2-4)*y[2]-(t^2+t-2)*exp(t)]
end

function sol1{T<:Number}(t::T)
    a=10.0
    [exp(t),
     exp(t),
     -exp(t)/(2-t)]
end

const y1     = sol1(0.0)
const tspan1 = [0.0, 1.0]

const rtol=10.0.^-[1.0:0.1:5.0]
const atol=0.01*rtol

tbl=dasslDrawConvergence(F1,y1,tspan1,sol1,rtol,atol)
display(tbl)


# # Particle Cloud
#
#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/notebooks/particle_cloud.ipynb)
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/notebooks/particle_cloud.ipynb)
#
# Using the same setup as `detailed_look.jl` or `example2()`, here we simulate
# a point cloud getting advected by the flow field.
# For additional documentation e.g. see :
# [1](https://JuliaClimate.github.io/IndividualDisplacements.jl/dev/),
# [2](https://JuliaClimate.github.io/MeshArrays.jl/dev/),
# [3](https://docs.juliadiffeq.org/latest/solvers/ode_solve.html),
# [4](https://en.wikipedia.org/wiki/Displacement_(vector))

# ## 1. Import Software

using IndividualDisplacements, OrdinaryDiffEq, Statistics
p=dirname(pathof(IndividualDisplacements))
include(joinpath(p,"../examples/recipes_plots.jl"))
include(joinpath(p,"../examples/example123.jl"));

# ## 2. Setup Problem

𝑃,Γ=example2_setup()

ii1=5:5:40; ii2=5:5:25
x=vec([x-0.5 for x in ii1, y in ii2])
y=vec([y-0.5 for x in ii1, y in ii2])
xy = permutedims([[x[i];y[i];1.0] for i in eachindex(x)])

solv(prob) = solve(prob,Tsit5(),reltol=1e-6,abstol=1e-6)
tr = DataFrame(ID=Int[], x=Float64[], y=Float64[], t=Float64[])

#𝐼 = Individuals{Float64,2}(📌=xy[:,:], 🔴=tr, 🆔=collect(1:size(xy,2)),
#                         🚄 = dxdt!, ∫ = solv, 🔧 = postprocess_xy, 𝑃=𝑃);

I=(position=xy,record=deepcopy(tr),velocity=dxdt!,
   integration=solv,postprocessing=postprocess_xy,parameters=𝑃)
𝐼=Individuals(I)

# ## 3. Compute Trajectories

𝑇 = (0.0,2998*3600.0)
∫!(𝐼,𝑇)

# ## 4. Display results

#md 𝐼.🔴.lon=5000*𝐼.🔴.x
#md 𝐼.🔴.lat=5000*𝐼.🔴.y
#md plt=plot_paths(𝐼.🔴,size(xy,2),100000.0)

# Compare with trajectory output from `MITgcm`

#IndividualDisplacements.flt_example_download()
#df=read_flt(IndividualDisplacements.flt_example_path,Float32)
#ref=plot_paths(df,size(xy,2),100000.0)


using Dates

mutable struct InterpolatedDiscountCurve{DC <: DayCount, P <: Interpolation, B <: BusinessCalendar} <: InterpolatedCurve{P}
  settlementDays::Int
  referenceDate::Date
  dc::DC
  interp::P
  cal::B
  dates::Vector{Date}
  times::Vector{Float64}
  data::Vector{Float64}
end

function InterpolatedDiscountCurve(dates::Vector{Date},
                                  discounts::Vector{Float64},
                                  dc::DC,
                                  interpolator::P) where {DC <: DayCount, P <: Interpolation}
  idc = InterpolatedDiscountCurve{DC, P, NullCalendar}(0, dates[1], dc, interpolator, NullCalendar(), dates, zeros(length(dates)), discounts)

  initialize!(idc)

  return idc
end

function initialize!(idc::InterpolatedDiscountCurve)
  length(idc.dates) == length(idc.data) || error("dates / data mismatch")
  idc.times[1] = 0.0
  @simd for i = 2:length(idc.dates)
    @inbounds idc.times[i] = year_fraction(idc.dc, idc.dates[1], idc.dates[i])
  end

  # initialize interpolator
  Math.initialize!(idc.interp, idc.times, idc.data)

  Math.update!(idc.interp)

  return idc
end

function discount_impl(idc::InterpolatedDiscountCurve, t::Float64)
  if t <= idc.times[end]
    return Math.value(idc.interp, t)
  end

  # flat fwd extrapolation
  tMax = idc.times[end]
  dMax = idc.data[end]
  instFwdMax = Math.derivative(idc.interp, tMax) / dMax
  return dMax * exp(-instFwdMax * (t - tMax))
end


using FinancialToolbox, Test, FinancialFFT, FinancialMonteCarlo, ForwardDiff

A = 600.0;
N = 18;

S0 = 100.0;
K = 100.0;
r = 0.02;
T = 1.0;
d = 0.01
sigma = 0.2;
lam = 15.0;
mu1 = 0.03;
sigma1 = 0.02;
zero_rate = ZeroRate(r);

Model = BlackScholesProcess(sigma, Underlying(S0, d));

EUData = EuropeanOption(T, K)
Nsim = 100000;
Nstep = 30;
mc = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());
analytic_result = blsprice(S0, K, r, T, sigma, d);
function test_forward_diff(x, y, toll)
    @test abs(sum(x .- y)) < toll
    nothing
end
x = Float64[sigma, S0, r]
method_cm = CarrMadanMethod(A, N);
method_lewis = LewisMethod(700.0, 200000);
method_cm_lewis = CarrMadanLewisMethod(A, N);
f(x::Vector) = blsprice(x[2], K, x[3], T, x[1], d);
f_cm(x::Vector) = pricer(BlackScholesProcess(x[1], Underlying(x[2], d)), ZeroRate(x[3]), method_cm, EUData);
f_lewis(x::Vector) = pricer(BlackScholesProcess(x[1], Underlying(x[2], d)), ZeroRate(x[3]), method_lewis, EUData);
f_cm_lewis(x::Vector) = pricer(BlackScholesProcess(x[1], Underlying(x[2], d)), ZeroRate(x[3]), method_cm_lewis, EUData);
analytic_result = ForwardDiff.gradient(f, x);
@show result_cm = ForwardDiff.gradient(f_cm, x);
@show result_lewis = ForwardDiff.gradient(f_lewis, x);
@show result_cm_lewis = ForwardDiff.gradient(f_cm_lewis, x);
toll = 1e-2;
@testset "ForwardDiff test" begin
    # test_forward_diff(analytic_result, result_cm, toll)
    test_forward_diff(analytic_result, result_lewis, toll)
    test_forward_diff(analytic_result, result_cm_lewis, toll)
end


#From http://home.online.no/~pjacklam/notes/invnorm/

function CTΦInv(p::Float64)::Float64

    a1::Float64 = -39.6968302866538
    a2::Float64 = 220.946098424521
    a3::Float64 = -275.928510446969
    a4::Float64 = 138.357751867269
    a5::Float64 = -30.6647980661472
    a6::Float64 = 2.50662827745924

    b1::Float64 = -54.4760987982241
    b2::Float64 = 161.585836858041
    b3::Float64 = -155.698979859887
    b4::Float64 = 66.8013118877197
    b5::Float64 = -13.2806815528857

    c1::Float64 = -7.78489400243029E-03
    c2::Float64 = -0.322396458041136
    c3::Float64 = -2.40075827716184
    c4::Float64 = -2.54973253934373
    c5::Float64 = 4.37466414146497
    c6::Float64 = 2.93816398269878

    d1::Float64 = 7.78469570904146E-03
    d2::Float64 = 0.32246712907004
    d3::Float64 = 2.445134137143
    d4::Float64 = 3.75440866190742

    #Rational approximation for lower region.

    if p < 0.02425
        q::Float64 = √(-2.0 * log(p))
        ans::Float64 = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1.0)
    elseif p < 0.97575
        q = (p - 0.5) * (p - 0.5)
        ans = (((((a1 * q + a2) * q + a3) * q + a4) * q + a5) * q + a6) * (p - 0.5) / (((((b1 * q + b2) * q + b3) * q + b4) * q + b5) * q + 1)
    else
        q = √(-2 * log(1 - p))
        ans = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1)
    end

    return ans
end

CTΦ(z::Real)::Real = 0.5+0.5*erf(z*sqrt2Inv)
CTϕ(z::Real)::Real = sqrt2PiInv*exp(-z^2.0/2.0)


#=
@authors: Shunsuke Hori

Markov Perfect Equilibrium for the simple duopoly example.

See the lecture at http://quant-econ.net/jl/markov_perf.html for a
description of the model.
=#

using QuantEcon

# == Parameters == #
a0    = 10.0
a1    = 2.0
beta  = 0.96
gamma = 12.0

# == In LQ form == #

A  = eye(3)

B1 = [0.0, 1.0, 0.0]
B2 = [0.0, 0.0, 1.0]

R1 = [0.0      -a0/2.0 0.0;
      -a0/2.0  a1      a1/2.0;
      0.0      a1/2.0  0.0]

R2 = [0.0     0.0   -a0/2.0;
      0.0     0.0    a1/2.0;
      -a0/2.0 a1/2.0 a1]

Q1 = Q2 = gamma

S1 = S2 = W1 = W2 = M1 = M2 = 0.0

# == Solve using QE's nnash function == #
F1, F2, P1, P2 = nnash(A, B1, B2, R1, R2, Q1, Q2, S1, S2, W1, W2, M1, M2,
                          beta=beta)

# == Display policies == #
println("Computed policies for firm 1 and firm 2:")
println("F1 = $F1")
println("F2 = $F2")


using LAR
include("src/inters.jl")

lineArray = randomLines(400,0.3);
W,EW = lines2lar(lineArray);
model = W,EW

printLar("test/csv/test1", model)
V,EV = readLar("test/csv/test1")
larview(V,EV)
viewexploded(V,EV)

EW = biconnectedComponents(V,EV)
viewexploded(V,EW)


using LinearAlgebraicRepresentation
Lar = LinearAlgebraicRepresentation

#Tetrahemihexahedron
"""
Return the Lar.Struct of tetrahemihexahedron
"""
function tetrahemihexahedron()
	V, (VV,EV,FV,CV) = Lar.simplex(3, true)
	tetra = V, EV,FV,CV
	twotetra = Lar.Struct([ tetra, Lar.r(0,π,0),tetra])
	V,EV,FV,CV = Lar.struct2lar(Lar.Struct([twotetra,Lar.r(0,0,π),twotetra]))
	return Lar.Struct([(V,collect(Set(EV)),FV,CV)])
end


function prime_factors(number)
    factors = []
    divisor = 2
    while divisor <= number
        quotient, reminder = divrem(number, divisor)

        if iszero(reminder)
            push!(factors, divisor)
            number = quotient
            divisor = 1
        end

        divisor += 1
    end
    return factors
end


using Pkg, Revise, FourierGPE
gr(titlefontsize=12,size=(500,300),transpose=true,colorbar=false)

## set simulation parameters
L = (25.0,25.0)
N = (256,256)
μ = 25.0

## Initialize simulation
sim = Sim(L,N)
@pack! sim = μ
@unpack_Sim sim

## Two ways to set potential:

# Time dependent potential function (here trivial t dep)
import FourierGPE.V
V(x,y,t) = 0.5*(x^2 + y^2)
ψ0(x,y,μ,g) = sqrt(μ/g)*sqrt(max(1.0-V(x,y,0.0)/μ,0.0)+im*0.0)

# As a static Potential
# x,y = X
# Vs(x,y) = 0.5*(x^2 + y^2)
# ψ0(x,y,μ,g) = sqrt(μ/g)*sqrt(max(1.0-Vs(x,y)/μ,0.0)+im*0.0)
# sim = Sim(sim,V0=Vs.(x,y'))

## make initial state
x,y = X
ψi = ψ0.(x,y',μ,g)
ϕi = kspace(ψi,sim)
@pack! sim = ϕi,μ

## Evolve in k space
@time sol = runsim(sim)

## ground state
ϕg = sol[end]
ψg = xspace(ϕg,sim)
showpsi(x,y,ψg)

## set simulation parameters
γ = 0.3
t = LinRange(ti,tf,Nt)
ϕi = kspace(ψg,sim)
# reltol = 1e-7
# alg = DP5()
@pack! sim = tf,t,γ,ϕi #,reltol,alg
@unpack_Sim sim

import FourierGPE: nlin!, Lgp!
sim

function Lz!(dϕ,ϕ,sim::Sim{2},t)
    #TODO
    @unpack X,K,T,params = sim; x,y = X; kx, ky = K
    χ = T.psi.x[1]
    Ω = params.κ

    # x*ky
    χ .= ϕ
    @. χ *= ky'
    xspace!(χ,sim)
    @. χ *= x
    kspace!(χ,sim)
    @. dϕ += Ω*χ

    # -y*kx
    χ .= ϕ
    @. χ *= kx
    xspace!(χ,sim)
    @. χ *= y'
    kspace!(χ,sim)
    @. dϕ -= Ω*χ
    return nothing
end

function Lz(ϕ,sim::Sim{2})
    @unpack X,K,params = sim; x,y = X; kx, ky = K
    Ω = params.κ
    # x*ky
    psi1 = ϕ.*ky'
    xspace!(psi1,sim)
    @. psi1 *= Ω*x
    kspace!(psi1,sim)

    # y*kx
    psi2 = ϕ.*kx
    xspace!(psi2,sim)
    @. psi2 *= Ω*y'
    kspace!(psi2,sim)
    return psi1 .- psi2
end

function nlin!(dϕ,ϕ,sim::Sim{2},t)
    @unpack g,X,V0 = sim; x,y = X
    y = y'
    dϕ .= ϕ
    xspace!(dϕ,sim)
    @. dϕ *= V0 + V(x,y,t) + g*abs2(dϕ)
    kspace!(dϕ,sim)
    return nothing
end

function Lgp!(dϕ,ϕ,sim,t)
    @unpack γ,μ,espec = sim
    nlin!(dϕ,ϕ,sim,t)
    dϕ .+= Lz(ϕ,sim)
    @. dϕ = -im*(1.0 - im*γ)*(dϕ + (espec - μ)*ϕ)
    return nothing
end

params = Params(κ = 0.9)
@pack! sim = params
sim
ti = 0.; tf = 5.
t = LinRange(ti,tf,Nt)
ϕi = ϕi + randn(N)+im*randn(N)
# ==== Evolve in k space
solv = runsim(sim)

# ===================================
ϕf = solv[end]
ψf = xspace(ϕf,sim)
showpsi(x,y,ψf)

anim = @animate for i in eachindex(t)
    ψ = xspace(solv[i],sim)
    showpsi(x,y,ψ)
end

gif(anim,"./examples/vortexlattice.gif",fps=25)


export randnCPR, randnP, randnC

function randnC(args...;kwargs...)
    return randn(args...;kwargs...) .+ im * randn(args...;kwargs...)
end

function randnCPR(args...;kwargs...)
    return randnP(args...;kwargs...) .+ im * randn(args...;kwargs...)
end

function randnP(args...;kwargs...)
    return abs.(randn(args...;kwargs...))
end


##########
# Addition
##########

"""
    Base.:+(a::AbstractUncertainValue, b::AbstractUncertainValue) -> UncertainValue

Addition operator for pairs of uncertain values.

Computes the element-wise sum between for a default of `n = 10000` realizations of `a` and
`b`, then returns an uncertain value based on a kernel density estimate to the distribution
of the element-wise sums.

Use the `+(a, b, n)` syntax to tune the number (`n`) of draws.
"""
function Base.:+(a::AbstractUncertainValue, b::AbstractUncertainValue; n::Int = 30000)
    UncertainValue(resample(a, n) .+ resample(b, n))
end

"""
    Base.:+(a::Real, b::AbstractUncertainValue) -> UncertainValue

Addition operator for between scalars and uncertain values.

Computes the element-wise sum between `a` and `b` for a default of `n = 10000` realizations
of `b`, then returns an uncertain value based on a kernel density estimate to the
distribution of the element-wise sums.

Use the `+(a, b, n)` syntax to tune the number (`n`) of draws.
"""
Base.:+(a::Real, b::AbstractUncertainValue; n::Int = 30000) =
    UncertainValue(a .+ resample(b, n))

"""
    Base.:+(a::AbstractUncertainValue, b::Real) -> UncertainValue

Addition operator for between uncertain values and scalars.

Computes the element-wise sum between `a` and `b` for a default of `n = 10000` realizations
of `a`, then returns an uncertain value based on a kernel density estimate to the
distribution of the element-wise sums.

Use the `+(a, b, n)` syntax to tune the number (`n`) of draws.
"""
Base.:+(a::AbstractUncertainValue, b::Real; n::Int = 30000) =
    UncertainValue(resample(a, n) .+ b)

"""
    Base.:+(a::AbstractUncertainValue, b::AbstractUncertainValue, n::Int) -> UncertainValue

Addition operator for pairs of uncertain values.

Computes the element-wise sum between `a` and `b` for `n` realizations
of `a` and `b`, then returns an uncertain value based on a kernel density estimate to the
distribution of the element-wise sums.

Call this function using the `+(a, b, n)` syntax.
"""
function Base.:+(a::AbstractUncertainValue, b::AbstractUncertainValue, n::Int)
    UncertainValue(resample(a, n) .+ resample(b, n))
end
"""
    Base.:+(a::Real, b::AbstractUncertainValue, n::Int) -> UncertainValue

Addition operator for scalar-uncertain value pairs.

Computes the element-wise sum between `a` and `b` for `n` realizations
of `b`, then returns an uncertain value based on a kernel density estimate to the
distribution of the element-wise sums.

Call this function using the `+(a, b, n)` syntax.
"""
Base.:+(a::Real, b::AbstractUncertainValue, n::Int) =
    UncertainValue(a .+ resample(b, n))

"""
    Base.:+(a::AbstractUncertainValue, b::Real, n::Int) -> UncertainValue

Addition operator for scalar-uncertain value pairs.

Computes the element-wise sum between `a` and `b` for `n` realizations
of `a`, then returns an uncertain value based on a kernel density estimate to the
distribution of the element-wise sums.

Call this function using the `+(a, b, n)` syntax.
"""
Base.:+(a::AbstractUncertainValue, b::Real, n::Int) =
    UncertainValue(resample(a, n) .+ b)

#######################################
# Adding vectors of uncertain values
#######################################
function Base.:+(a::AbstractVector{AbstractUncertainValue},
        b::AbstractVector{AbstractUncertainValue})
    [a[i] + b[i] for i = 1:length(a)]
end

function Base.:+(a::AbstractVector{AbstractUncertainValue}, b::AbstractUncertainValue)
    [a[i] + b for i = 1:length(a)]
end

function Base.:+(a::AbstractUncertainValue, b::AbstractVector{AbstractUncertainValue})
    [a + b[i] for i = 1:length(b)]
end

function Base.:+(a::AbstractVector{AbstractUncertainValue},
        b::AbstractVector{AbstractUncertainValue}, n::Int)

    [+(a[i], b[i], n) for i = 1:length(a)]
end

function Base.:+(a::AbstractVector{AbstractUncertainValue},
        b::AbstractUncertainValue, n::Int)

    [+(a[i], b, n) for i = 1:length(a)]
end

function Base.:+(a::AbstractUncertainValue,
        b::AbstractVector{AbstractUncertainValue}, n::Int)

    [+(a, b[i], n) for i = 1:length(b)]
end

#####################################################################################
# Special cases
#####################################################################################

import ..UncertainValues: CertainValue

##################
# `CertainValue`s
#################
"""
    Base.:+(a::Union{CertainValue, Real}, b::Union{CertainValue, Real})

Addition of certain values with themselves or scalars acts as regular addition, but
returns the result wrapped in a `CertainValue` instance.
"""
Base.:+(a::Union{CertainValue, Real}, b::Union{CertainValue, Real})

Base.:+(a::CertainValue, b::CertainValue) = CertainValue(a.value + b.value)
Base.:+(a::CertainValue, b::Real) = CertainValue(a.value + b)
Base.:+(a::Real, b::CertainValue) = CertainValue(a + b.value)


using Base.Test
using LibFoo

@test call_fooifier(2.2, 1.1) ≈ 2*2.2^2 - 1.1
@test call_libfoo(2.2, 1.1) ≈ 2*2.2^2 - 1.1


export Hypercube
export find_generators
export generate_coordinates
export volume
export isequiv

import LinearAlgebra
import LinearAlgebraX

"""
    Hypercube(shape)

Represent a hypercubic (Bravais) lattice.

# Fields
* `shape_matrix`: a matrix whose columns are the lattice vectors of the
* `inverse_shape_matrix`: `(shape_matrix)`
* `wrap`: periodic wrapping function which takes an integer array, and maps it onto a
  site in the Bravais lattice. Analogous to `fldmod`.
"""
struct Hypercube
    shape_matrix::Matrix{Int}
    inverse_shape_matrix::Matrix{Rational{Int}}
    wrap::Function

    function Hypercube(shape_matrix::AbstractMatrix{<:Integer})
        LinearAlgebra.checksquare(shape_matrix)
        det = LinearAlgebraX.detx(shape_matrix) # does not check whether shape_matrix is square
        iszero(det) && throw(ArgumentError("scale matrix null"))
        inverse_shape_matrix = LinearAlgebraX.invx(shape_matrix)
        function wrap(r::AbstractArray{<:Integer}, mode::RoundingMode=RoundDown)
            rnd = (x) -> round(Int, x, mode)
            R = rnd.(inverse_shape_matrix * r)
            r2 = r - shape_matrix * R
            return R, r2
        end
        return new(shape_matrix, inverse_shape_matrix, wrap)
    end
end

"""
    dimension(cube::Hypercube)

Return the spatial dimension of the hypercube.
"""
dimension(cube::Hypercube) = size(cube.shape_matrix, 1)

"""
    volume(cube::Hypercube)

Return the signed volume of the hypercube, defined by the determinant of the shape.
"""
volume(cube::Hypercube) = LinearAlgebraX.detx(cube.shape_matrix)

function Base.:(==)(lhs::Hypercube, rhs::Hypercube)
    return (lhs.shape_matrix == rhs.shape_matrix)
end

"""
    isequiv(lhs::Hypercube, rhs::Hypercube)

Check whether the two hypercubes are equivalent.
"""
function isequiv(lhs::Hypercube, rhs::Hypercube)
    R, r = lhs.wrap(rhs.shape_matrix)
    isone(abs(LinearAlgebraX.detx(R))) || return false
    iszero(r) || return false
    return true
end

"""
    find_generators(cube::Hypercube)

Find generators of an Hypercube. For one-dimensional lattice, which is isomorphic to Zₙ, the
generator is +1. For two-dimensional lattices, this function invokes
[`find_generators_2d`](@ref). Higher dimensions are currently not supported.
"""
function find_generators(cube::Hypercube)
    if dimension(cube) == 1
        return ones(Int, 1, 1)
    elseif dimension(cube) == 2
        return find_generators_2d(cube)
    else
        error("Currently, only 1D and 2D lattices are supported")
    end
end

"""
    find_generators_2d(cube::Hypercube)

Find translation generators of an Hypercube.
"""
function find_generators_2d(cube::Hypercube)
    dimension(cube) != 2 && throw(ArgumentError("shape matrix should be 2x2"))

    cubevolume = abs(volume(cube))
    allowed_pairs = [[1,0]]
    append!(
        allowed_pairs,
        [x, y] for y in 1:cubevolume, x in 0:cubevolume if gcd(x,y) == 1
    )

    function make_loop(t::Vector{Int})
        axis = Vector{Int}[zero(t)]
        r = cube.wrap(t)[2]
        while !iszero(r)
            push!(axis, r)
            r = cube.wrap(r .+ t)[2]
        end
        @assert allunique(axis)
        return axis
    end

    for r1p in allowed_pairs
        r1p[1] == 0 && continue
        r1 = [r1p[1], -r1p[2]]
        axis1 = make_loop(r1)
        iszero(mod(cubevolume, length(axis1))) || continue
        for r2 in allowed_pairs
            isone(LinearAlgebraX.detx(hcat(r1, r2))) || continue
            axis2 = make_loop(r2)
            cubevolume == length(axis1) * length(axis2) || continue
            coordinates = vec([cube.wrap(sum(x))[2] for x in Iterators.product(axis1, axis2)])
            allunique(coordinates) && return hcat(r1, r2)
        end
    end
    error("Failed to find decomposition") # COV_EXCL_LINE
end

"""
    generate_coordinates(cube, generators)

Generate a list of coordinates of the hypercube

# Arguments
* `cube::Hypercube`
* `generator_translations::AbstractMatrix{<:Integer}`
"""
function generate_coordinates(
    cube::Hypercube,
    generator_translations::AbstractMatrix{<:Integer},
)
    let dim = dimension(cube)
        if size(generator_translations) != (dim, dim)
            throw(DimensionMismatch("Hypercube and generator_translations have different dimensions"))
        elseif !isone(abs(LinearAlgebraX.detx(generator_translations)))
            throw(ArgumentError("generator_translations is not unimodular"))
        end
    end
    function make_loop(t::AbstractVector{<:Integer})
        axis = Vector{Int}[zero(t)]
        r = cube.wrap(t)[2]
        while !iszero(r)
            push!(axis, r)
            r = cube.wrap(r .+ t)[2]
        end
        return axis
    end
    axes = [make_loop(t) for t in eachcol(generator_translations)]
    coordinates = vec([cube.wrap(sum(x))[2] for x in Iterators.product(axes...)])

    if length(coordinates) != abs(volume(cube))
        msg = "translations $generator_translations generates $(length(coordinates)) " *
            "coordinates, while the volume is $(abs(volume(cube)))"
        throw(ArgumentError(msg))
    end
    @assert allunique(coordinates)
    return coordinates
end


#=
# 3. Applying pulse forcing to a flow
Here, we will show how to apply a transient force to a flow.
=#

using ViscousFlow
#-
using Plots

#=
For this case, we first seek to introduce a pulse to an otherwise quiescent fluid,
with no boundaries. The pulse shape will be a smooth Gaussian-distributed force, directed upward.

We will start with the usual steps: specify the problem parameters and the discretization.
=#
my_params = Dict()

my_params["Re"] = 200
xlim = (-2.0,2.0)
ylim = (-2.0,4.0)
my_params["grid Re"] = 4.0
g = setup_grid(xlim,ylim,my_params)

#=
### Construct the forcing
There are a variety of forcing types available to us. Here, we will
use *area-type forcing*, which is distributed over a region of space.
We will create a short-lived force applied in the vertical direction in this region. For the
spatial distribution of this forcing, we make use of a Gaussian field. We will center it at (0,0), and
give it a strength of 10, using the `SpatialGaussian` constructor. The x component
of the force we set to zero, using the `EmptySpatialField` constructor.
=#
σx = 0.5
σy = 0.1
x0 = y0 = 0.0
amp = 10
force_dist = [EmptySpatialField(),SpatialGaussian(σx,σy,x0,y0,amp)];

#=

When we introduce forcing in any problem, it is essential to
supply a *model function* that supplies the instantaneous strength of the
forcing. This gives us the opportunity to specify the transient
aspect of the forcing. In this example, we modulate the force in time with a Gaussian,
with a small half-width in time equal to $\sigma_t = 0.1$, effectively creating a pulse.
We center it at time $t_0 = 0.1$ (using the `>>` shift operator). This is carried out in the function below,
in which we create the modulating function `modfcn`.
Note that `fr.generated_field()` provides the spatial distribution that
we specified with our spatial field `force_dist`. This is multiplied
by `modfcn`, our Gaussian pulse, evaluated at the current time.
=#
function forcing_model!(σ,T,t,fr::AreaRegionCache,phys_params)
    σt = 0.1
    t0 = 0.1
    modfcn = Gaussian(σt,sqrt(π*σt^2)) >> t0
    σ .= modfcn(t)*fr.generated_field()
end

#=
We pack the model function and the spatial field together
into an `AreaForcingModel`, which we then put into a forcing dictionary.
It is important to use the key "forcing models" for this.
=#
afm = AreaForcingModel(forcing_model!,spatialfield=force_dist)
forcing_dict = Dict("forcing models" => afm);

#=
### Construct the system structure
We supply these forcing characteristics with the keyword argument `forcing` as we
set up the usual system structure:
=#
sys = viscousflow_system(g,phys_params=my_params,forcing=forcing_dict);

#=
The remaining steps go just as they did for the previous examples. We will simulate
the pulse for 4 time units:
=#
u0 = init_sol(sys)
tspan = (0.0,10.0) # longer than we need, but just being safe
integrator = init(u0,tspan,sys)
#-
step!(integrator,4.0)

#=
### Examine
Let's look at a sequence of snapshots of the forced flow. As we see, this
short-lived force creates a dipole-like pair of counter-rotating vortices that propagates upward under its
own influence.
=#
sol = integrator.sol
plt = plot(layout = (1,4), size = (800, 300), legend=:false)
tsnap = 0.1:1.0:3.1
for (i, t) in enumerate(tsnap)
    plot!(plt[i],vorticity(sol,sys,t),sys,title="t = $(round(t,digits=2))")
end
plt
#=
And here is another look a the flow, this time with streamlines:
=#
plot(streamfunction(integrator),sys,title="Streamfunction at t = $(round(integrator.t,digits=2))")

#=
We can also supply more than one pulse. If we don't change the spatial
distribution, then we just redefine the model function and run it again:
Let's give 3 pulses, each separated by 1 time unit.
=#
function forcing_model!(σ,T,t,fr::AreaRegionCache,phys_params)
    σt = 0.1
    t0 = [0.1, 1.1, 2.1]
    modfcn = (Gaussian(σt,sqrt(π*σt^2)) >> t0[1]) +
             (Gaussian(σt,sqrt(π*σt^2)) >> t0[2]) +
             (Gaussian(σt,sqrt(π*σt^2)) >> t0[3])
    σ .= modfcn(t)*fr.generated_field()
end
#-
u0 = init_sol(sys)
integrator = init(u0,tspan,sys)
#-
step!(integrator,4.0)
#=
### Examine
In this case, the pulses coalesce with one another
=#
sol = integrator.sol
plt = plot(layout = (1,4), size = (800, 300), legend=:false)
tsnap = 0.1:1.0:3.1
for (i, t) in enumerate(tsnap)
    plot!(plt[i],vorticity(sol,sys,t),sys,title="t = $(round(t,digits=2))")
end
plt


## Permutation Population Generators

#random
function Gen_perm(N::Int)
  function rperm(N::Int)
    return randperm(N)
  end
end

# Kmedian epsilon with K minpts
function Gen_Kmedian{T <: Real}(A::Matrix{T},k::Int)
  ϵ = eps_Kmedian(A,k)
  return Gen_clust(A,ϵ,k)
end

# Kmean epsilon with K minpts
function Gen_Kmean{T <: Real}(A::Matrix{T},k::Int)
  ϵ = eps_Kmean(A,k)
  return Gen_clust(A,ϵ,k)
end

# Grid estimate (uniform) epsilon with K minpts
function Gen_Kgrid{T <: Real}(A::Matrix{T},k::Int)
  ϵ = eps_grid(A)
  return Gen_clust(A,ϵ,k)
end

# Sample random from K [median,85%] epsilon with K minpts
function Gen_Km85{T <: Real}(A::Matrix{T},k::Int)
  eps = eps_randm85(A,k)
  return Gen_clust(A,eps,k)
end

# Generic cluster generator function
function Gen_clust{T<:Real}(D::DenseMatrix{T}, ϵ::Real, minpts::Int)
  rslt = dbscan(D,ϵ,minpts)
  fillZero!(rslt.assignments)
  cmap = clustmap(rslt.assignments)
  k = length(keys(cmap))
  function clustPop(N::Int)
    tgt = zeros(Int,N)
    p = 1
    for i in randperm(k)
      l = length(cmap[i])
      tgt[p:p+l-1] = cmap[i]
      p += l
    end
    return tgt
  end
  return clustPop
end

#nearest neighbor generator
function Gen_NN{T<:Real}(D::DenseMatrix{T})
  function nnPop(N::Int)
    nearest_neighbor(D,rand(1:N))
  end
  return nnPop
end


####### RAYLEIGH FLOW ########

HeatFlux(h01::StagnationEnthalpy,h02::StagnationEnthalpy) = HeatFlux(h02-h01)

function HeatFlux(T01::StagnationTemperature,T02::StagnationTemperature;gas::PerfectGas=DefaultPerfectGas)
  h01 = StagnationEnthalpy(T01;gas=gas)
  h02 = StagnationEnthalpy(T02;gas=gas)
  return HeatFlux(h01,h02)
end

function T0OverT0Star(M::MachNumber,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
  γ = SpecificHeatRatio(gas)
  return TemperatureRatio(((γ+1)*M^2*(2+(γ-1)*M^2))/(1+γ*M^2)^2)
end

function TOverTStar(M::MachNumber,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
  γ = SpecificHeatRatio(gas)
  return TemperatureRatio(((γ+1)^2*M^2)/(1+γ*M^2)^2)
end

function MachNumber(T0_over_T0star::TemperatureRatio,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
   value(T0_over_T0star) <= 1.0 || error("T0/T0* must be 1 or smaller")

    Msub = find_zero(x -> T0OverT0Star(MachNumber(x),RayleighFlow,gas=gas)-T0_over_T0star,(0.001,1),order=16)
    max_T0ratio = T0OverT0Star(MachNumber(1e10),RayleighFlow,gas=gas)

    if value(T0_over_T0star) < value(max_T0ratio)
        return MachNumber(Msub)
    else
        Msup = find_zero(x -> T0OverT0Star(MachNumber(x),RayleighFlow,gas=gas)-T0_over_T0star,(1,1e10),order=16)
        return MachNumber(Msub), MachNumber(Msup)
    end
end

function POverPStar(M::MachNumber,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
  γ = SpecificHeatRatio(gas)
  return PressureRatio((γ+1)/(1+γ*M^2))
end

function VOverVStar(M::MachNumber,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
  γ = SpecificHeatRatio(gas)
  return VelocityRatio(((γ+1)*M^2)/(1+γ*M^2))
end

function ρOverρStar(M::MachNumber,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
  return DensityRatio(1/VOverVStar(M,RayleighFlow,gas=gas))
end

function P0OverP0Star(M::MachNumber,::Type{RayleighFlow};gas::PerfectGas=DefaultPerfectGas)
  γ = SpecificHeatRatio(gas)
  return PressureRatio((γ+1)/(1+γ*M^2)*((2+(γ-1)*M^2)/(γ+1))^(γ/(γ-1)))
end


####################################################################################################
# Poincare shooting based on Sánchez, J., M. Net, B. Garcı́a-Archilla, and C. Simó. “Newton–Krylov Continuation of Periodic Orbits for Navier–Stokes Flows.” Journal of Computational Physics 201, no. 1 (November 20, 2004): 13–33. https://doi.org/10.1016/j.jcp.2004.04.018.

"""

`pb = PoincareShootingProblem(flow::Flow, M, sections; δ = 1e-8, interp_points = 50, parallel = false)`

This composite type implements the Poincaré Shooting method to locate periodic orbits by relying on Poincaré return maps. More details (maths, notations, linear systems) can be found [here](https://bifurcationkit.github.io/BifurcationKitDocs.jl/dev/periodicOrbitShooting/). The arguments are as follows
- `flow::Flow`: implements the flow of the Cauchy problem though the structure [`Flow`](@ref).
- `M`: the number of Poincaré sections. If `M==1`, then the simple shooting is implemented and the multiple one otherwise.
- `sections`: function or callable struct which implements a Poincaré section condition. The evaluation `sections(x)` must return a scalar number when `M==1`. Otherwise, one must implement a function `section(out, x)` which populates `out` with the `M` sections. See [`SectionPS`](@ref) for type of section defined as a hyperplane.
- `δ = 1e-8` used to compute the jacobian of the functional by finite differences. If set to `0`, an analytical expression of the jacobian is used instead.
- `interp_points = 50` number of interpolation point used to define the callback (to compute the hitting of the hyperplane section)
- `parallel = false` whether the shooting are computed in parallel (threading). Only available through the use of Flows defined by `EnsembleProblem`.

## Simplified constructors
- A simpler way is to create a functional is

`pb = PoincareShootingProblem(prob::ODEProblem, alg, section; kwargs...)`

for simple shooting or

`pb = PoincareShootingProblem(prob::Union{ODEProblem, EnsembleProblem}, alg, M::Int, section; kwargs...)`

for multiple shooting . Here `prob` is an `Union{ODEProblem, EnsembleProblem}` which is used to create a flow using the ODE solver `alg` (for example `Tsit5()`). Finally, the arguments `kwargs` are passed to the ODE solver defining the flow. We refer to `DifferentialEquations.jl` for more information.

- Another convenient call is

`pb = PoincareShootingProblem(prob::Union{ODEProblem, EnsembleProblem}, alg, normals::AbstractVector, centers::AbstractVector; δ = 1e-8, kwargs...)`

where `normals` (resp. `centers`) is a list of normals (resp. centers) which defines a list of hyperplanes ``\\Sigma_i``. These hyperplanes are used to define partial Poincaré return maps.

## Computing the functionals
A functional, hereby called `G` encodes this shooting problem. You can then call `pb(orbitguess, par)` to apply the functional to a guess. Note that `orbitguess::AbstractVector` must be of size M * N where N is the number of unknowns in the state space and `M` is the number of Poincaré maps. Another accepted `guess` is such that `guess[i]` is the state of the orbit on the `i`th section. This last form allows for non-vector state space which can be convenient for 2d problems for example.

Note that you can generate this guess from a function solution using `generateSolution`.

- `pb(orbitguess, par)` evaluates the functional G on `orbitguess`
- `pb(orbitguess, par, du)` evaluates the jacobian `dG(orbitguess).du` functional at `orbitguess` on `du`
- `pb`(Val(:JacobianMatrixInplace), J, x, par)` compute the jacobian of the functional analytically. This is based on ForwardDiff.jl. Useful mainly for ODEs.
- `pb(Val(:JacobianMatrix), x, par)` same as above but out-of-place.

!!! tip "Tip"
    You can use the function `getPeriod(pb, sol, par)` to get the period of the solution `sol` for the problem with parameters `par`.
"""
@with_kw_noshow struct PoincareShootingProblem{Tf, Tsection <: SectionPS} <: AbstractShootingProblem
	M::Int64 = 0						# number of Poincaré sections
	flow::Tf = Flow()					# should be a Flow
	section::Tsection = SectionPS(M)	# Poincaré sections
	δ::Float64 = 0e-8					# Numerical value used for the Matrix-Free Jacobian by finite differences. If set to 0, analytical jacobian is used
	parallel::Bool = false				# whether we use DE in Ensemble mode for multiple shooting
end

@inline isParallel(psh::PoincareShootingProblem) = psh.parallel
function Base.show(io::IO, pb::PoincareShootingProblem)
	println(io, "┌─ Poincaré shooting problem")
	println(io, "├─ time slices : ", getMeshSize(pb))
	println(io, "└─ parallel   : ", isParallel(pb))
end

R(pb::PoincareShootingProblem, x::AbstractVector, k::Int) = R(pb.section, x, k)
E(pb::PoincareShootingProblem, xbar::AbstractVector, k::Int) = E(pb.section, xbar, k)

"""
	update!(pb::PoincareShootingProblem, centers_bar; _norm = norm)

This function updates the normals and centers of the hyperplanes defining the Poincaré sections.
"""
@views function updateSection!(pb::PoincareShootingProblem, centers_bar, par; _norm = norm)
	M = getMeshSize(pb); Nm1 = div(length(centers_bar), M)
	centers_barc = reshape(centers_bar, Nm1, M)
	centers = [E(pb.section, centers_barc[:, ii], ii) for ii = 1:M]
	normals = [vf(pb.flow, c, par) for c in centers]
	for ii in eachindex(normals)
		normals[ii] ./= _norm(normals[ii])
	end
	update!(pb.section, normals, centers)
end

"""
$(SIGNATURES)

Compute the period of the periodic orbit associated to `x_bar`.
"""
function getPeriod(psh::PoincareShootingProblem, x_bar, par)
	M = getMeshSize(psh); Nm1 = div(length(x_bar), M)

	# reshape the period orbit guess
	x_barc = reshape(x_bar, Nm1, M)

	# variable to hold the computed result
	xc = similar(x_bar, Nm1 + 1, M)
	outc = similar(xc)

	Th = eltype(x_barc)
	period = Th(0)

	# we extend the state space to be able to call the flow, so we fill xc
	if ~isParallel(psh)
		for ii in 1:M
			@views E!(psh.section, xc[:, ii], x_barc[:, ii], ii)
			# We need the callback to be active here!!!
			period += @views evolve(psh.flow, xc[:, ii], par, Inf).t
		end
	else
		for ii in 1:M
			@views E!(psh.section, xc[:, ii], x_barc[:, ii], ii)
		end
		solOde =  evolve(psh.flow, xc, par, repeat([Inf], M))
		period = sum(x->x.t, solOde)
	end
	return period
end

"""
$(SIGNATURES)

Compute the full periodic orbit associated to `x`. Mainly for plotting purposes.
"""
function getPeriodicOrbit(prob::PoincareShootingProblem, x_bar::AbstractVector, p)
	# this function extracts the amplitude of the cycle
	M = getMeshSize(prob); Nm1 = length(x_bar) ÷ M

	# reshape the period orbit guess
	x_barc = reshape(x_bar, Nm1, M)
	xc = similar(x_bar, Nm1 + 1, M)

	T = getPeriod(prob, x_bar, p)

	# !!!! we could use @views but then Sundials will complain !!!
	if ~isParallel(prob)
		E!(prob.section, view(xc, :, 1), view(x_barc, :, 1), 1)
		# We need the callback to be active here!!!
		sol1 = @views evolve(prob.flow, Val(:Full), xc[:, 1], p, T; callback = nothing)
		return sol1
	else # threaded version
		E!(prob.section, view(xc, :, 1), view(x_barc, :, 1), 1)
		sol = @views evolve(prob.flow, Val(:Full), xc[:, 1:1], p, [T]; callback = nothing)
		return sol[1]
	end
end

function _getExtremum(psh::PoincareShootingProblem, x_bar::AbstractVector, par; ratio = 1, op = (max, maximum))
	# this function extracts the amplitude of the cycle
	M = getMeshSize(psh)
	Nm1 = div(length(x_bar), M)

	# reshape the period orbit guess
	x_barc = reshape(x_bar, Nm1, M)
	xc = similar(x_bar, Nm1 + 1, M)

	Th = eltype(x_bar)
	n = div(Nm1, ratio)

	if ~isParallel(psh)
		E!(psh.section, view(xc, :, 1), view(x_barc, :, 1), 1)
		# We need the callback to be active here!!!
		sol = @views evolve(psh.flow, Val(:Full), xc[:, 1], par, Inf)
		mx = op[2](sol[1:n, :])
		for ii in 2:M
			E!(psh.section, view(xc, :, ii), view(x_barc, :, ii), ii)
			# We need the callback to be active here!!!
			sol = @views evolve(psh.flow, Val(:Full), xc[:, ii], par, Inf)
			mx = op[1](mx, op[2](sol[1:n, :]))
		end
	else
		for ii in 1:M
			E!(psh.section, view(xc, :, ii), view(x_barc, :, ii), ii)
		end
		solOde =  evolve(psh.flow, Val(:Full), xc, par, repeat([Inf], M) )
		mx = op[2](solOde[1][1:n, :])
		for ii in 1:M
			mx = op[1](mx, op[2](solOde[ii][1:n, :]))
		end
	end
	return mx
end

"""
$(SIGNATURES)

Compute the projection of each vector (`x[i]` is a `Vector`) on the Poincaré section.
"""
function projection(psh::PoincareShootingProblem, x::AbstractVector)
	# create initial guess. We have to pass it through the projection R
	M = getMeshSize(psh)
	orbitguess_bar = Vector{eltype(x)}(undef, 0)
	@assert M == length(psh.section.normals)
	for ii=1:M
		push!(orbitguess_bar, R(psh, x[ii], ii))
	end
	return orbitguess_bar
end

"""
$(SIGNATURES)

Compute the projection of each vector (`x[i, :]` is a `Vector`) on the Poincaré section.
"""
function projection(psh::PoincareShootingProblem, x::AbstractMatrix)
	# create initial guess. We have to pass it through the projection R
	M = getMeshSize(psh)
	m, n = size(x)
	orbitguess_bar = Matrix{eltype(x)}(undef, m, n-1)
	@assert M == length(psh.section.normals)
	for ii=1:M
		orbitguess_bar[ii, :] .= @views R(psh, x[ii, :], ii)
	end
	return orbitguess_bar
end
####################################################################################################
# Poincaré (multiple) shooting with hyperplanes parametrization
function (psh::PoincareShootingProblem)(x_bar::AbstractVector, par; verbose = false)
	M = getMeshSize(psh)
	Nm1 = div(length(x_bar), M)

	# reshape the period orbit guess
	x_barc = reshape(x_bar, Nm1, M)

	# TODO the following declaration of xc allocates. It would be better to make it inplace
	xc = similar(x_bar, Nm1 + 1, M)

	# variable to hold the result of the computations
	outc = similar(xc)

	# we extend the state space to be able to call the flow, so we fill xc
	#TODO create the projections on the fly
	for ii in 1:M
		E!(psh.section, view(xc, :, ii), view(x_barc, :, ii), ii)
	end

	if ~isParallel(psh)
		for ii in 1:M
			im1 = ii == 1 ? M : ii - 1
			# We need the callback to be active here!!!
			outc[:, ii] .= xc[:, ii] .- evolve(psh.flow, xc[:, im1], par, Inf).u
		end
	else
		solOde = evolve(psh.flow, xc, par, repeat([Inf64], M))
		for ii in 1:M
			im1 = ii == 1 ? M : ii - 1
			# We need the callback to be active here!!!
			@views outc[:, ii] .= xc[:, ii] .- solOde[im1][2]
		end
	end

	# build the array to be returned
	out_bar = similar(x_bar)
	out_barc = reshape(out_bar, Nm1, M)
	for i in 1:M
		R!(psh.section, view(out_barc, :, i), view(outc, :, i), i)
	end
	return out_bar
end

"""
This function computes the derivative of the Poincare return map Π(x) = ϕ(t(x),x) where t(x) is the return time of x to the section.
"""
function diffPoincareMap(psh::PoincareShootingProblem, x, par, dx, ii::Int)
	normal = psh.section.normals[ii]
	abs(dot(normal, dx)) > 1e-12 && @warn "Vector does not belong to hyperplane!  dot(normal, dx) = $(abs(dot(normal, dx))) and $(dot(dx, dx))"
	# compute the Poincare map from x
	tΣ, solΣ = evolve(psh.flow, Val(:SerialTimeSol), x, par, Inf)
	z = vf(psh.flow, solΣ, par)
	# solution of the variational equation at time tΣ
	# We need the callback to be INACTIVE here!!!
	y = evolve(psh.flow, Val(:SerialdFlow), x, par, dx, tΣ; callback = nothing).du
	out = y .- (dot(normal, y) / dot(normal, z)) .* z
end

# jacobian of the shooting functional
function (psh::PoincareShootingProblem)(x_bar::AbstractVector, par, dx_bar::AbstractVector)
	δ = psh.δ
	if δ > 0
		# mostly for debugging purposes
		return (psh(x_bar .+  δ .* dx_bar, par) .- psh(x_bar, par)) ./ δ
	end

	# otherwise analytical Jacobian
	M = getMeshSize(psh)
	Nm1 = div(length(x_bar), M)

	# reshape the period orbit guess
	x_barc  = reshape( x_bar, Nm1, M)
	dx_barc = reshape(dx_bar, Nm1, M)

	# variable to hold the computed result
	xc  = similar( x_bar, Nm1 + 1, M)
	dxc = similar(dx_bar, Nm1 + 1, M)
	outc = similar(xc)

	# we extend the state space to be able to call the flow, so we fill xc
	for ii in 1:M
		 E!(psh.section,  view(xc, :, ii),  view(x_barc, :, ii), ii)
		dE!(psh.section, view(dxc, :, ii), view(dx_barc, :, ii), ii)
	end

	if ~isParallel(psh)
		for ii in 1:M
			im1 = (ii == 1 ? M : ii - 1)
			@views outc[:, ii] .= dxc[:, ii] .- diffPoincareMap(psh, xc[:, im1], par, dxc[:, im1], im1)
		end
	else
		@assert 1==0 "Analytical Jacobian for parallel Poincare Shooting not implemented yet. Please use the option δ > 0 to use Matrix-Free jacobian or chose `:FiniteDifferencesDense` to compute jacobian based on finite differences."
	end

	# build the array to be returned
	out_bar = similar(x_bar)
	out_barc = reshape(out_bar, Nm1, M)
	for ii in 1:M
		dR!(psh.section, view(out_barc, :, ii), view(outc, :, ii), ii)
	end
	return out_bar
end

# inplace computation of the matrix of the jacobian of the shooting problem, only serial for now
function (psh::PoincareShootingProblem)(::Val{:JacobianMatrixInplace}, J::AbstractMatrix, x_bar::AbstractVector, par)
	M = getMeshSize(psh)
	Nm1 = div(length(x_bar), M)
	N = Nm1 + 1

	x_barc = reshape(x_bar, Nm1, M)

	# TODO the following declaration of xc allocates. It would be better to make it inplace
	xc = similar(x_bar, N, M)

	# we extend the state space to be able to call the flow, so we fill xc
	#TODO create the projections on the fly
	for ii in 1:M
		E!(psh.section, view(xc, :, ii), view(x_barc, :, ii), ii)
	end

	# jacobian of the flow
	dflow = (_J, _x, _T) -> ForwardDiff.jacobian!(_J, z -> evolve(psh.flow, Val(:SerialTimeSol), z, par, _T; callback = nothing).u, _x)

	# initialize some temporaries
	Jtmp = zeros(N, N)
	normal = copy(psh.section.normals[1])
	F = zeros(N)
	Rm = zeros(Nm1, N)
	Em = zeros(N, Nm1)

	# put the matrices by blocks
	In = I(Nm1)
	for ii=1:M
		im1 = ii == 1 ? M : ii - 1
		# we find the point on the next section
		tΣ, solΣ = evolve(psh.flow, Val(:SerialTimeSol), view(xc,:,im1), par, Inf)
		# computation of the derivative of the return map
		F .= vf(psh.flow, solΣ, par) #vector field
		normal .= psh.section.normals[ii]
		@views dflow(Jtmp, xc[:, im1], tΣ)
		Jtmp .= Jtmp .- F * normal' * Jtmp ./ dot(F, normal)
		# projection with Rm, Em
		ForwardDiff.jacobian!(Rm, x->R(psh.section, x, ii), zeros(N))
		ForwardDiff.jacobian!(Em, x->E(psh.section, x, im1), zeros(Nm1))
		J[(ii-1)*Nm1+1:(ii-1)*Nm1+Nm1, (im1-1)*Nm1+1:(im1-1)*Nm1+Nm1] .= -Rm * Jtmp * Em
		if M == 1
			J[(ii-1)*Nm1+1:(ii-1)*Nm1+Nm1, (ii-1)*Nm1+1:(ii-1)*Nm1+Nm1] .+= In
		else
			J[(ii-1)*Nm1+1:(ii-1)*Nm1+Nm1, (ii-1)*Nm1+1:(ii-1)*Nm1+Nm1] .= In
		end
	end
	return J
end

# out of place version
(psh::PoincareShootingProblem)(::Val{:JacobianMatrix}, x::AbstractVector, par) = psh(Val(:JacobianMatrixInplace), zeros(eltype(x), length(x), length(x)), x, par)
####################################################################################################
# functions needed for Branch switching from Hopf bifurcation point
function reMake(prob::PoincareShootingProblem, F, dF, par, hopfpt, ζr, centers, period; k...)

	# create the section
	if isEmpty(prob.section)
	normals = [F(u, hopfpt.params) for u in centers]
	for n in normals; n ./= norm(n); end
	else
		normals = prob.section.normals
		centers = prob.section.centers
	end

	@assert isEmpty(prob.section) "Specifying its own section for aBS from a Hopf point is not allowed yet."

	@assert ~(prob.flow isa Flow) "Somehow, this method was not called as it should. `prob.flow` should be a Named Tuple, prob should be constructed with the simple constructor, not yielding a Flow for its flow field."

	# update the problem
	if length(prob.flow) == 4
		probPSh = PoincareShootingProblem(prob.flow.prob, prob.flow.alg, deepcopy(normals), deepcopy(centers); parallel = prob.parallel, prob.flow.kwargs...)
	else
		probPSh = PoincareShootingProblem(prob.flow.prob1, prob.flow.alg1, prob.flow.prob2, prob.flow.alg2, deepcopy(normals), deepcopy(centers); parallel = prob.parallel, prob.flow.kwargs...)
	end

	# create initial guess. We have to pass it through the projection R
	hyper = probPSh.section
	M = getMeshSize(probPSh)
	@assert length(normals) == M
	orbitguess_bar = zeros(length(centers[1])-1, M)
	for ii in 1:length(normals)
		orbitguess_bar[:, ii] .= R(hyper, centers[ii], ii)
	end

	return probPSh, vec(orbitguess_bar)
end

function predictor(pb::PoincareShootingProblem, bifpt, ampfactor, ζs, bptype::Symbol)
	@assert bptype in (:bp, :pd)
	if bptype == :pd
		@set! pb.section = _duplicate(pb.section)
		@set! pb.M = pb.section.M
		orbitguess = copy(bifpt.x) .+ ampfactor .* ζs
		orbitguess = vcat(orbitguess, orbitguess .- ampfactor .* ζs)
	else
		orbitguess = copy(bifpt.x)
		orbitguess .+= ampfactor .* ζs
	end
	return pb, orbitguess
end