# # Amplifier circuit
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/models/OpAmp.ipynb)
#
#md # !!! note "Overview"
#md #     System type: linear continuous system with piecewise-linear input\
#md #     State dimension: 2\
#md #     Application domain: Electrical Engineering
#
# ## Operational amplifiers
#
# Operational amplifiers ("op-amp") are electronic devices to amplify an input
# voltage signal. They are widely used to construct filters that remove a
# desired range of frequencies from the input signal.

# The schematic diagram of an op-amp is shown below. It has two terminals:
# - input side (left)
# - output terminal (right)

# ![](https://github.com/JuliaReach/ReachabilityAnalysis.jl/blob/master/examples/OpAmp/opamp.png?raw=true)

# The output voltage is ``e_o = K(e_B - e_A)``, where ``K`` is the voltage gain
# of the op-amp. ``K`` is usually vey large (the order of ``10^5 V / V``).

# In the *ideal op-amp* scenario, the following assumptions hold:
# - The input terminals of the op-amp draw negligible current.
# - The voltage difference at the input terminals ``e_B - e_A`` is zero.
# - The gain ``K`` is infinite.

# ## Inverting amplifier

# Consider the following circuit, which consists of an ideal op-amp in the so-called inverting
# configuration.

# ![](https://raw.githubusercontent.com/JuliaReach/ReachabilityAnalysis.jl/master/examples/OpAmp/opamp_c.png)

# It is not hard to show [^KLU15] that the output satisfies the following ODE:

# ```math
#    \dfrac{de_o(t)}{dt} = -\dfrac{1}{R_2 C}e_o(t) - \dfrac{1}{R_1C}e_{in}(t).
# ```
# The circuit exhibits a transient behavior because of the capacitor connected in parallel
# to the op-amp. In the stationary limit ``t \to \infty``, ``\dfrac{de_o(t)}{dt} = 0`` and
# ``e_o = - \frac{R_2}{R_1} e_{in}``. The gain is thus ``- \frac{R_2}{R_1}`` and the minus sign
# is the reason that this circuit is called inverting amplifier.

# The output voltage ``e_o(t)`` depends on the input voltage ``e_{in}(t)``.
# In order to highlight some typical assumptions on the inputs, in the rest
# of this notebook we model the input voltage in different ways:

#  1. **Non-deterministic input** with bounded range. This is the most general case,
#     in which ``e_{in}(t) \in E_{in}``, with ``E_{in} \subseteq \mathbb{R}`` is an interval
#     for the set of admissible inputs.
#     This approach guarantees that the flowpipe covers all possible trajectories for any
#     bounded, measurable, function ``e_{in}(t)`` satisfying the constraint ``e_{in}(t) \in E_{in}`` for all
#     ``t`` of the given time span.
#
#  2. **Constant input,** a special case such that
#     ``e_{in}(t) \in E_{in}`` and ``e_{in}(t)`` is *constant* along trajectories.
#
#  3. **Time-varying input** i.e. such that it is given by a (possibly time-varying)
#     predefined function ``e_{in}(t)`` that is given by the solution of some ODE.
#     In particular, we consider linear and exponential input functions.
#
# We will consider one by one the different approaches described above in the simple
# inverting amplifier circuit.

# ## Non-deterministic input

# It is convenient to define constants ``α`` and ``β`` such that
#
# ``\dfrac{de_o(t)}{dt} = α e_o(t) + β e_{in}(t)``.
#
# In this section we assume that ``e_{in}(t)`` can be any (measurable) function
# whose range is contained in the set ``E_{in}``.

using ReachabilityAnalysis, Plots, LaTeXStrings #!jl

function opamp_nondet(; X0 = Interval(0.0, 0.0),
                        R₁ = 2., R₂ = 6., C = 1.e-3,
                        Ein = Interval(1.9, 2.1))

    α = hcat(-1/(R₂ * C))
    β = hcat(-1/(R₁ * C))

    ## continuous evolution
    invariant = Universe(1) # no state constraint
    sys = @system(eout' = α*eout + β*ein, ein ∈ Ein, eout ∈ invariant)

    ## initial-value problem
    return @ivp(sys, x(0) ∈ X0)
end

#-
sol_nondet = solve(opamp_nondet(), T=0.1, alg=INT(δ=1e-4));

#md # !!! tip "Performance tip"
#md #     We have chosen the algorithm `INT` as it is the best choice in terms of efficiency for one-dimensional ODEs.

fig = plot()
plot!(fig, sol_nondet, vars=(0, 1), xlab=L"t", ylab=L"e_{out}", title="Solution for non-deterministic input", lw=0.2)

# The solution `sol_nondet` assumed that the initial state is at the origin.
# Now we solve for three different initial conditions of increasing width.

fig = plot(xlab=L"t", ylab=L"e_{out}(t)", title="Solution for non-deterministic input")

Δt = 0 .. 0.04

sol = solve(opamp_nondet(X0=Interval(-1.0, 1.0)), T=0.1, alg=INT(δ=1e-4));
plot!(fig, sol(Δt), vars=(0, 1), lab="X0 = -1 .. 1", lw=0.2)

sol = solve(opamp_nondet(X0=Interval(-0.5, 0.5)), T=0.1, alg=INT(δ=1e-4));
plot!(fig, sol(Δt), vars=(0, 1), lab="X0 = -0.5 .. 0.5", lw=0.2)

sol = solve(opamp_nondet(), T=0.1, alg=INT(δ=1e-4));
plot!(fig, sol(Δt), vars=(0, 1), lab="X0 = 0", lw=0.2)

fig

#md # !!! tip "Visualization tip"
#md #     The order in which we have defined the plots is such that the flowpipe
#md #     with largest width comes first, so it doesn't overlap with those
#md #     which are strictly included in it. This way, the final plot more clearly
#md #     reveals the different flowpipes by their color.

# ## Constant input

# We model the input voltage ``e_{in}(t)`` with a family of ODEs of the form
#
# ``\dfrac{d e_{in}(t)}{dt} = \gamma e_{in}(t) + \delta``.
#
# There are two distinct behaviors depending on the parameters ``\gamma`` and ``\delta``:

#  - ``\gamma = \delta = 0``: ``e_{in}(t) = e_{in, 0}`` i.e. the input is *constant* over the given domain.
#  - ``\gamma = 0``: linear with rate ``\delta``, it is given by ``e_{in}(t) = \delta t + e_{in, 0}``.
#  - ``\gamma \neq 0``: exponential with rate ``\gamma``, it is given by ``e_{in}(t) = (e_{in, 0} + \delta / \gamma)e^{\gamma t} - \delta / \gamma``.

# Moreover, it is natural to assume that the input saturates at a given threshold value.
# The following function, `opamp_with_saturation`, models the system as a
# hybrid automaton with two modes: in the first mode, the input signal ``e_{in}(t)``
# varies according to the specified dynamics, and in the second mode, the input
# signal saturates and ceases to increase (or decrease, depding on the signs chosen).

# In this section we consider the constant input scenario,
# - ``e_{in}(0) ∈ E_{in, 0}``, ``e_{in}(t)`` constant.
# - ``\delta = 0``
# - ``\gamma = 0``

# In the following section we consider the other cases.

using Symbolics

function opamp_with_saturation(; X0 = BallInf(zeros(2), 0.0),
                                 R₁ = 2., R₂ = 6., C = 1.e-3,
                                 γ = 100., δ = 100., Es = 2.)
    var = @variables eₒ ein
    α = hcat(-1/(R₂ * C))
    β = hcat(-1/(R₁ * C))

    ## transition graph
    automaton = LightAutomaton(2)
    add_transition!(automaton, 1, 2, 1)

    ## modes
    A = [α β; 0. γ]
    b = [0.; δ]
    invariant = HalfSpace(ein <= Es, var)
    mode1 = @system(x' = Ax + b, x ∈ invariant)
    A = [α β; 0. 0.];
    mode2 = @system(x' = Ax, x ∈ Universe(2))
    modes = [mode1, mode2]

    ## transition mode1 -> mode2 (saturation)
    guard = Hyperplane(ein == Es, var)
    t1 = @map(x -> x, dim:2, x ∈ guard)
    resetmaps = [t1]

    H = HybridSystem(automaton, modes, resetmaps, [AutonomousSwitching()])

    ## initial condition is at the origin in mode 1
    initial_condition = [(1, X0)]
    return IVP(H, initial_condition)
end

# Observe that in the model defined above, the first coordinate of the state-space
# corresponds to the output ``e_o(t)``, while the second coordinate, ``e_{in}(t)``,
# corresponds to the input signal ``e_{in}(t)``.

# We specify that the initial condition is such that ``e_o(0)`` is at the origin,
# and ``e_{in}(0) ∈ 1.4 .. 1.6``.

X0 = Hyperrectangle([0., 1.5], [0.0, 0.1]);

# The value of the saturation constant ``Es`` is chosen to be sufficiently high
# such that the system doesn't transition to mode 2.

prob_const = opamp_with_saturation(X0=X0, γ = 0., δ = 0., Es = 3.)
sol_const = solve(prob_const, T=0.1, alg=BOX(δ=1e-4));

#-

plot(sol_const, vars=(0, 2), xlab=L"t", ylab=L"e_{in}(t)", title="Constant input signal", lw=0.2)
plot!(x->x, x->1.5, 0., 0.1, line=2, color="red", linestyle=:dash, legend=nothing)

#-

plot(sol_const, vars=(0, 1), xlab=L"t", ylab=L"e_{out}(t)", title="Solution for constant input", lw=0.2)

# ## Time-varying input

# For the time-varying with a prescribed function case, we consider two scenarios:

# - Linearly increasing input voltage: ``\delta \neq 0``, ``\gamma = 0``.
# - Exponentially increasing input voltage (up to saturation at `Es = 1.0`): ``\delta \neq 0``, ``\gamma < 0``.

# For both cases we consider that set of initial states is the origin:

X0 = Singleton(zeros(2));

#-

# We solve both instances by choosing som values of ``δ`` and ``γ``:

## linearly increasing input signal
prob_lin = opamp_with_saturation(X0=X0, γ=0., δ=100., Es=1.);
sol_lin = solve(prob_lin, T=0.1, alg=BOX(δ=1e-4));

## exponentially increasing input signal
prob_exp = opamp_with_saturation(X0=X0, γ=-100., δ=100., Es=3.);
sol_exp = solve(prob_exp, T=0.1, alg=BOX(δ=1e-4));

#-
plot(sol_lin, vars=(0, 2), xlab=L"t", ylab=L"e_{in}(t)", title="Linear input w/saturation", lw=0.2)
#-
plot(sol_exp, vars=(0, 2), xlab=L"t", ylab=L"e_{in}(t)", title="Exponential input", lw=0.2)

#-
plot(sol_lin, vars=(0, 1), xlab=L"t", ylab=L"e_{out}(t)", title="Solution for linear input", lw=0.2)
#-
plot(sol_exp, vars=(0, 1), xlab=L"t", ylab=L"e_{out}(t)", title="Solution for exponential input", lw=0.2)

# ## References

# [^KLU15]: Kluever, C. A. (2015). Dynamic systems: modeling, simulation, and control. John Wiley & Sons.


function ρ(X, X_sim; metric = "absolute")
    """
    Calculate the sum of the absolute difference at each time point between observed data
    X and simulated data X_sim noting that each row of these corresponds to a realisation.
    that
    """

    ϵ = zeros(size(X, 2))

    if metric == "absolute"
        ϵ .= sum(abs.(X - X_sim), dims = 1)[:]
    elseif metric == "MSE"
        ϵ .= sum((X - X_sim) .^ 2, dims = 1)[:]
    elseif metric == "RMSE"
        N = size(X, 1)
        ϵ .= sqrt.(1 / N * sum((X - X_sim) .^ 2, dims = 1)[:])
    end

    return ϵ

end

function final_check(D_good, local_cases; p = 0.1, metric = "absolute")
    """
    Determines the top p×100% of simulations based on some metric ρ(X, X_sim) and returns
    those indices.
    """
    # get only the observed cases over the period we have cases for
    D_observed = D_good[begin:length(local_cases), 1, :] +
        D_good[begin:length(local_cases), 2, :]
    # tile local_cases to easily calculate the distance ρ(X, X_sim)
    local_cases_mat = repeat(local_cases, 1, size(D_observed, 2))

    # calculate distance between data and the simulations
    ϵ = ρ(local_cases_mat, D_observed, metric = metric)
    # determine the sim indices of the top p×100%
    good_inds = ϵ .< quantile(ϵ, p)

    return good_inds

end

function count_cases2!(
    case_counts,
    forecast::Forecast,
    sim,
)

    D = forecast.sim_realisations.D

    day = 1
    tmp = 0

    for i in 1:length(case_counts)
        case_counts[day] = D[i, 1, sim] + D[i, 2, sim]
        day += 1
    end

    return nothing

end

function calculate_bounds2(local_cases, τ, state)

    # tolerance for when we consider cases to be stable
    ϵ = 0.0

    # multipliers on the n-day average
    (ℓ, u) = (0.5, 2.5)

    # observation period
    T = length(local_cases)
    # the slope over the n-day period
    m = 0.0
    Mₜ = zeros(Float64, T)

    Lₜ = zeros(Float64, T)
    Uₜ = zeros(Float64, T)

    # consider τ = 3 and t = (0, 1, 2), clearly n = 2 - 0 = 2
    n = τ - 1

    n2 = 7

    for t in 1:T

        # if state == "NSW"
        #     if t < 30
        #         (ℓ, u) = (0, 10)
        #     elseif t < T - 7
        #         (ℓ, u) = (0.5, 2.0)
        #     else
        #         (ℓ, u) = (0.5, 2.0)
        #     end
        # elseif state == "NT"
        #     if t < 30
        #         (ℓ, u) = (0, 4.0)
        #     elseif t < T - 7
        #         (ℓ, u) = (0.25, 4.0)
        #     else
        #         (ℓ, u) = (0.5, 3.0)
        #     end
        # end

        if t < T - 7
            (ℓ, u) = (0.25, 2.0)
        else
            (ℓ, u) = (0.5, 2.0)
        end

        # approximate the slope naively
        if t < T - n
            m = 1 / n * (
                local_cases[t + n] -
                local_cases[t]
            )
        else
            m = 1 / n * (
                local_cases[t] -
                local_cases[t - n]
            )
        end

        Mₜ[t] = m

        # depending on the sign of the slope, take average of future or past cases
        if m < -ϵ
            Lₜ[t] = ℓ * mean(
                local_cases[t:min(T, t + n)]
            )
            Uₜ[t] = u * mean(
                local_cases[max(1, t - n):t]
            )
            # Lₜ[t] = mean(local_cases[t:min(T, t + n)])
            # Uₜ[t] = mean(local_cases[max(1, t - n):t])
        elseif m > ϵ
            Lₜ[t] = ℓ * mean(
                local_cases[max(1, t - n):t]
            )
            Uₜ[t] = u * mean(
                local_cases[t:min(T, t + n)]
            )
            # Lₜ[t] = mean(local_cases[max(1, t - 2):t])
            # Uₜ[t] = mean(local_cases[t:min(T, t + 2)])
        else
            n2 = n ÷ 2
            Lₜ[t] = ℓ * mean(
                local_cases[
                    max(1, t - n2):min(T, t + n2)
                ]
            )
            Uₜ[t] = u * mean(
                local_cases[
                    max(1, t - n2):min(T, t + n2)
                ]
            )
            # Lₜ[t] = mean(local_cases[max(1, t - n2):min(T, t + n2)])
            # Uₜ[t] = mean(local_cases[max(1, t - n2):min(T, t + n2)])
        end

        # adjust the bounds for periods with low cases
        if Lₜ[t] < 50
            Lₜ[t] = 0
        end

        if Uₜ[t] < 50
            Uₜ[t] = 50
        end

        if isnan(Lₜ[t])
            Lₜ[t] = Lₜ[t-1]
        end

        if isnan(Uₜ[t])
            Uₜ[t] = Uₜ[t-1]
        end
    end

    return (Lₜ, Uₜ)

end


using Poly

# multiply arrays and double
instructions = [Instruction(:mult, :(out[i, j] += A[i, k] * B[k, j]), Set()),
                Instruction(:double, :(out[i, j] *= 2), Set())]

domains = [Domain(:i, 1, :(size(out, 1)), :(i += 1), Set(), []),
           Domain(:j, 1, :(size(out, 2)), :(j += 1), Set(), []),
           Domain(:k, 1, :(size(A, 2)), :(k += 1), Set(), [])]

kern = compile(LoopKernel(instructions, domains, [:out, :A, :B], [Array{Float64, 2}, Array{Float64, 2}, Array{Float64, 2}], []))

A = rand(10, 10)
B = rand(10, 10)
out = zeros(10, 10)

kern(out=out, A=A, B=B)

@show isapprox(out, A*B*2)

# add arrays with global offset
instructions = [Instruction(:offset, :(offset = 4), Set()),
                Instruction(:add, :(out[i, j] = A[i, j] + B[i, j] + offset), Set())]

domains = [Domain(:i, 1, :(size(out, 1)), :(i += 1), Set(), []),
           Domain(:j, 1, :(size(out, 2)), :(j += 1), Set(), [])]

kern = compile(LoopKernel(instructions, domains, [:out, :A, :B], [Array{Float64, 2}, Array{Float64, 2}, Array{Float64, 2}], []))

A = rand(10, 10)
B = rand(10, 10)
out = zeros(10, 10)

kern(out=out, A=A, B=B)

@show isapprox(out, A+B.+4)


# ---
# title: 948. Bag of Tokens
# id: problem948
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Two Pointers, Greedy, Sort
# link: <https://leetcode.com/problems/bag-of-tokens/description/>
# hidden: true
# ---
#
# You have an initial **power** of `P`, an initial **score** of `0`, and a bag
# of `tokens` where `tokens[i]` is the value of the `ith` token (0-indexed).
#
# Your goal is to maximize your total **score** by potentially playing each
# token in one of two ways:
#
#   * If your current **power** is at least `tokens[i]`, you may play the `ith` token face up, losing `tokens[i]` **power** and gaining `1` **score**.
#   * If your current **score** is at least `1`, you may play the `ith` token face down, gaining `tokens[i]` **power** and losing `1` **score**.
#
# Each token may be played **at most** once and **in any order**. You do **not**
# have to play all the tokens.
#
# Return _the largest possible **score** you can achieve after playing any
# number of tokens_.
#
#
#
# **Example 1:**
#
#
#
#     Input: tokens = [100], P = 50
#     Output: 0
#     **Explanation** **:** Playing the only token in the bag is impossible because you either have too little power or too little score.
#
#
# **Example 2:**
#
#
#
#     Input: tokens = [100,200], P = 150
#     Output: 1
#     Explanation: Play the 0th token (100) face up, your power becomes 50 and score becomes 1.
#     There is no need to play the 1st token since you cannot play it face up to add to your score.
#
#
# **Example 3:**
#
#
#
#     Input: tokens = [100,200,300,400], P = 200
#     Output: 2
#     Explanation: Play the tokens in this order to get a score of 2:
#     1. Play the 0th token (100) face up, your power becomes 100 and score becomes 1.
#     2. Play the 3rd token (400) face down, your power becomes 500 and score becomes 0.
#     3. Play the 1st token (200) face up, your power becomes 300 and score becomes 1.
#     4. Play the 2nd token (300) face up, your power becomes 0 and score becomes 2.
#
#
#
#
# **Constraints:**
#
#   * `0 <= tokens.length <= 1000`
#   * `0 <= tokens[i], P < 104`
#
#
## @lc code=start
using LeetCode

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


abstract type MotionDef end

mutable struct KinemPar
    alpha :: Float64
    h :: Float64
    alphadot :: Float64
    hdot :: Float64
    u :: Float64
    udot :: Float64
end

mutable struct KinemDef
    alpha :: MotionDef
    h :: MotionDef
    u :: MotionDef
end

struct EldUpDef <: MotionDef
    amp :: Float64
    K :: Float64
    a :: Float64
end

function (eld::EldUpDef)(t)
    sm = pi*pi*eld.K/(2*(eld.amp)*(1 - eld.a))
    t1 = 1.
    t2 = t1 + ((eld.amp)/(2*eld.K))
    ((eld.K/sm)*log(cosh(sm*(t - t1))/cosh(sm*(t - t2))))+(eld.amp/2)
end

struct EldUptstartDef <: MotionDef
    amp :: Float64
    K :: Float64
    a :: Float64
    tstart :: Float64
end

function (eld::EldUptstartDef)(t)
    sm = pi*pi*eld.K/(2*(eld.amp)*(1 - eld.a))
    t1 = eld.tstart
    t2 = t1 + ((eld.amp)/(2*eld.K))
    ((eld.K/sm)*log(cosh(sm*(t - t1))/cosh(sm*(t - t2))))+(eld.amp/2)
end

struct EldRampReturnDef <: MotionDef
    amp :: Float64
    K :: Float64
    a :: Float64
end

function (eld::EldRampReturnDef)(tt)
    fr = eld.K/(pi*abs(eld.amp));
    t1 = 1.
    t2 = t1 + (1. /(2*pi*fr));
    t3 = t2 + ((1/(4*fr)) - (1/(2*pi*fr)));
    t4 = t3 + (1. /(2*pi*fr));
    t5 = t4+1.;

    nstep = round(Int,t5/0.015) + 1
    g = zeros(nstep)
    t = zeros(nstep)

    for i = 1:nstep
        t[i] = (i-1.)*0.015
        g[i] = log((cosh(eld.a*(t[i] - t1))*cosh(eld.a*(t[i] - t4)))/(cosh(eld.a*(t[i] - t2))*cosh(eld.a*(t[i] - t3))))
    end
    maxg = maximum(g);

    gg = log((cosh(eld.a*(tt - t1))*cosh(eld.a*(tt - t4)))/(cosh(eld.a*(tt - t2))*cosh(eld.a*(tt - t3))))

    return eld.amp*gg/(maxg);

end

struct ConstDef <: MotionDef
    amp :: Float64
end

function (cons::ConstDef)(t)
    cons.amp
end

struct LinearDef <: MotionDef
    tstart :: Float64
    vstart :: Float64
    vend :: Float64
    len :: Float64
end

function (lin::LinearDef)(t)
    if t < lin.tstart
        lin.vstart
    elseif t > lin.tstart + lin.len
        lin.vend
    else
        lin.vstart + (lin.vend - lin.vstart)/lin.len*(t - lin.tstart)
    end
end

struct BendingDef <: MotionDef
    spl :: Spline1D
    scale :: Float64
    k :: Float64
    phi :: Float64
end

struct SinDef <: MotionDef
  mean :: Float64
  amp :: Float64
  k :: Float64
  phi :: Float64
end

function (kin::SinDef)(t)
  (kin.mean) + (kin.amp)*sin(2*kin.k*t + kin.phi)
end

struct CosDef <: MotionDef
  mean :: Float64
  amp :: Float64
  k :: Float64
  phi :: Float64
end

function (kin::CosDef)(t)
  (kin.mean) + (kin.amp)*cos(2*kin.k*t + kin.phi)
end

struct StepGustDef <: MotionDef
    amp :: Float64
    tstart :: Float64
    tgust :: Float64
end
 function (sgust::StepGustDef)(t)
    if t >= sgust.tstart && t <= sgust.tstart+sgust.tgust
        amp = (sgust.amp)
    else
        amp = 0.
    end
    amp
end

struct EldUpIntDef <: MotionDef
    amp :: Float64
    K :: Float64
    a :: Float64
end

function (eld::EldUpIntDef)(t)

    dt = 0.015
    nsteps = t/dt + 1
    nsteps = round(Int,nsteps)
    dt = t/(nsteps-1)
    sm = pi*pi*eld.K/(2*eld.amp*(1 - eld.a))
    t1 = 1.
    t2 = t1 + ((eld.amp)/(2*eld.K))

    prev_h = 0
    amp = 0
    for i = 1:nsteps
      tmpt = (i-1)*dt
      if (eld.amp == 0.)
      	 hdot = 0.
      else
         hdot = ((eld.K/sm)*log(cosh(sm*(tmpt - t1))/cosh(sm*(tmpt - t2))))+(eld.amp/2.)
      end
      amp = prev_h + hdot*dt
      prev_h = amp
    end
    if (nsteps == 1)
      amp = 0.
    end
    amp
end

struct EldUpInttstartDef <: MotionDef
    amp :: Float64
    K :: Float64
    a :: Float64
    tstart :: Float64
end

function (eld::EldUpInttstartDef)(t)

    dt = 0.015
    nsteps = t/dt + 1
    nsteps = round(Int,nsteps)
    dt = t/(nsteps-1)
    sm = pi*pi*eld.K/(2*eld.amp*(1 - eld.a))
    t1 = eld.tstart
    t2 = t1 + ((eld.amp)/(2*eld.K))

    prev_h = 0
    amp = 0
    for i = 1:nsteps
      tmpt = (i-1)*dt
	  if (eld.amp == 0.)
      	 hdot = 0.
      else
         hdot = ((eld.K/sm)*log(cosh(sm*(tmpt - t1))/cosh(sm*(tmpt - t2))))+(eld.amp/2.)
      end
      amp = prev_h + hdot*dt
      prev_h = amp
    end
    if (nsteps == 1)
      amp = 0.
    end
    amp
end


# Implement MotifEnumeration

const NUCLEOTIDES = ('A', 'C', 'G', 'T')

convert_to_intlist(line) = map(x -> parse(Int, x), split(line))

calc_hamming_distance(s1, s2) = count(((c1, c2),) -> c1 != c2, zip(s1, s2))

function calc_immediate_neighbours(pattern)
    neighbours = Set{String}()
    for (ix, letter) in enumerate(pattern)
        for nucleotide in NUCLEOTIDES
            if nucleotide != letter
                pattern_array = collect(pattern)
                pattern_array[ix] = nucleotide
                push!(neighbours, join(pattern_array))
            end
        end
    end
    neighbours
end

function generate_neighbourhood(text, d)
    neighbourhood = Set{String}([text])
    for _ in 1:d
        neighbours_of_neighbours = Set{String}()
        for pattern in neighbourhood
            immediate_neighbours = calc_immediate_neighbours(pattern)
            union!(neighbours_of_neighbours, immediate_neighbours)
        end
        union!(neighbourhood, neighbours_of_neighbours)
    end
    neighbourhood
end

function does_k_mer_occurs_approximately_in_text(text, kmer, k, d)
    for ix in 1:(length(text)-k+1)
        substring = text[ix:ix+k-1]
        if calc_hamming_distance(substring, kmer) <= d
            return true
        end
    end
    false
end

function motif_enumeration(texts, k, d)
    all_texts_approximately_contain_pattern(kmer) =
        all(does_k_mer_occurs_approximately_in_text(text, kmer, k, d) for text in texts)

    motifs = Set{String}()
    for text in texts
        for ix in 1:(length(text)-k+1)
            pattern = text[ix:ix+k-1]
            neighbours = generate_neighbourhood(pattern, d)
            for neighbour in neighbours
                if all_texts_approximately_contain_pattern(neighbour)
                    push!(motifs, neighbour)
                end
            end
        end
    end
    motifs
end

function main()
    k, d = convert_to_intlist(readline())
    texts = readlines()
    result = motif_enumeration(texts, k, d)
    println(join(result, " "))
end

main()


# Testing:
#
# - consistency of first-passage time densities

using DiffModels
using Test

function testpdfequal(d::AbstractDrift, b::AbstractBounds, tmax,
                      g1ref::Vector{Float64}, g2ref::Vector{Float64}, tol)
    g1, g2 = pdf(d, b, tmax)
    @test length(g1) == length(g2)
    @test length(g1) == length(g1ref)
    @test length(g2) == length(g2ref)
    for n = 1:length(g1)
        @test g1[n] ≈ g1ref[n] atol=tol
        @test g2[n] ≈ g2ref[n] atol=tol
    end
end

# symmetric bounds

mu = 1.2
bound = 1.1
t = 0.5
dt = 0.005
tmax = 3.0
maxn = length(0:dt:tmax)

@test (pdful(ConstDrift(mu, dt), ConstSymBounds(bound, dt), t)[1] ≈
       pdfu(ConstDrift(mu, dt), ConstSymBounds(bound, dt), t))
@test (pdful(ConstDrift(mu, dt), ConstSymBounds(bound, dt), t)[2] ≈
       pdfl(ConstDrift(mu, dt), ConstSymBounds(bound, dt), t))
@test (pdful(ConstDrift(mu, dt), ConstAsymBounds(bound, -bound, dt), t)[1] ≈
       pdfu(ConstDrift(mu, dt), ConstAsymBounds(bound, -bound, dt), t))
@test (pdful(ConstDrift(mu, dt), ConstAsymBounds(bound, -bound, dt), t)[2] ≈
       pdfl(ConstDrift(mu, dt), ConstAsymBounds(bound, -bound, dt), t))

g1, g2 = pdf(VarDrift(fill(mu, maxn), dt),
             VarAsymBounds(VarBound(fill(bound, maxn), dt),
                           VarBound(fill(bound, maxn), dt)), tmax)
testpdfequal(ConstDrift(mu, dt), ConstSymBounds(bound, dt), tmax, g1, g2, 1e-4)
testpdfequal(ConstDrift(mu, dt), ConstAsymBounds(bound, -bound, dt), tmax, g1, g2, 1e-4)
testpdfequal(ConstDrift(mu, dt), LinearSymBounds(bound, 0.0, dt), tmax, g1, g2, 1e-4)
testpdfequal(ConstDrift(mu, dt),
             AsymBounds{LinearBound, LinearBound}(LinearBound(bound, 0.0, dt),
                                                  LinearBound(bound, 0.0, dt)),
             tmax, g1, g2, 1e-4)

# asymmetric bounds

mu = 1.2
bounds = [1.1, 0.5]
bound_slopes = [-0.02, -0.01]
t = 0.5
dt = 0.005
tmax = 3.0
maxn = length(0:dt:tmax)

@test (pdful(ConstDrift(mu, dt), ConstAsymBounds(bounds[1], -bounds[2], dt), t)[1] ≈
       pdfu(ConstDrift(mu, dt), ConstAsymBounds(bounds[1], -bounds[2], dt), t))
@test (pdful(ConstDrift(mu, dt), ConstAsymBounds(bounds[1], -bounds[2], dt), t)[2] ≈
       pdfl(ConstDrift(mu, dt), ConstAsymBounds(bounds[1], -bounds[2], dt), t))

g1, g2 = pdf(VarDrift(fill(mu, maxn), dt),
             VarAsymBounds(VarBound(fill(bounds[1], maxn), dt),
                           VarBound(fill(bounds[2], maxn), dt)), tmax)
testpdfequal(ConstDrift(mu, dt), ConstAsymBounds(bounds[1], -bounds[2], dt), tmax, g1, g2, 1e-4)
testpdfequal(ConstDrift(mu, dt),
             AsymBounds{LinearBound, LinearBound}(LinearBound(bounds[1], 0.0, dt),
                                                  LinearBound(bounds[2], 0.0, dt)),
             tmax, g1, g2, 1e-4)

g1, g2 = pdf(VarDrift(fill(mu, maxn), dt),
             VarAsymBounds(VarBound(bounds[1] .+ bound_slopes[1] * dt * (0:maxn-1), dt),
                           VarBound(bounds[2] .+ bound_slopes[2] * dt * (0:maxn-1), dt)),
             tmax)
testpdfequal(ConstDrift(mu, dt),
             AsymBounds{LinearBound, LinearBound}(LinearBound(bounds[1], bound_slopes[1], dt),
                                                  LinearBound(bounds[2], bound_slopes[2], dt)),
             tmax, g1, g2, 1e-4)


"""
    mean_square_disaplcement(df; method=[:ensemble_average, time_average], non_averaged::Bool) -> DataFrame, [DataFrame]

Compute mean_square_disaplcement over files.

**Example**
```julia
using SPT

```
"""
function _ensemble_time_average_msd(
    df::DataFrame,
    id::Symbol,
    x::Symbol,
    y::Symbol;
    return_tamsd::Bool = false,
)
    ta_msd = time_average_msd(df, id, x, y)
    return_tamsd ? (ensemble_tamsd(ta_msd), ta_msd) : ensemble_tamsd(ta_msd)
end

function _time_average_msd(
    df::DataFrame,
    id::Symbol = :TrackID,
    x::Symbol = :POSITION_X,
    y::Symbol = :POSITION_Y;
)
    tamsd = DataFrame(TrackID = Int64[], cell_name = String[], msd = Float64[], delta_t = Int64[], n = Int64[])
    @inbounds Threads.@threads for cell = sort(unique(df.cell_name))
        _data = df[df.cell_name .== cell, :]
        for n = sort(unique(_data[!, id]))
            data = extract(_data, Int(n), id, [x, y])
            T = size(data, 1)
            for δ = 1:T-1
                r² = 0.0
                @simd for t = 1:(T-δ)
                    r² += squared_displacement(data, t, δ)
                end
                r² /= (T - δ)
                push!(tamsd, [n, cell, r², δ, T - δ])
            end
        end
    end
    tamsd
end

function _ensemble_msd(df::DataFrame, id::Symbol, x::Symbol, y::Symbol)
    eamsd = DataFrame(TrackID = Int64[], cell_name = String[], msd = Float64[], n = Int[], delta_t = Int64[])
    @inbounds Threads.@threads for cell = sort(unique(df.cell_name))
        _data = df[df.cell_name .== cell, :]
        for n = sort(unique(_data[!, id]))
            data = extract(_data, Int(n), id, [x, y])
            T = size(data, 1)
            @simd for δ = 1:T-1
                push!(eamsd, [n, cell, squared_displacement(data, 1, δ), 1, δ])
            end
        end
    end
    eamsd
end

function _ensemble_tamsd(df::DataFrame)
    eatamsd = DataFrame(
        cell_name = String[],
        delta_t = Float64[],
        msd = Float64[],
        n = Int64[],
        std = Float64[],
        sem = Float64[],
        ci = Float64[],
    )
    @inbounds Threads.@threads for cell = sort(unique(df.cell_name))
        _data = df[df.cell_name .== cell, :]
        for i = 1:maximum(_data.delta_t)
            data = _data[_data.delta_t .== i, :]
            push!(
                eatamsd,
                [
                    cell,
                    i,
                    mean(data.msd),
                    sum(data.n),
                    std(data.msd),
                    sem(data.msd),
                    1.96 * sem(data.msd),
                ],
            )
        end
    end
    eatamsd
end


######################################################################
# fov.jl
#
# field of view sensor
######################################################################

struct FOV <: Sensor
    cone_width::Float64
    alpha::Float64          # mistake rate

    blind_distance::Float64     # distance in which any obs is equal likely
end
function FOV(cone_width::Real, alpha::Float64)
    return FOV(float(cone_width), alpha, 0.0)
end
FOV() = FOV(120.0, 0.1, 0.0)

# 1 means it is in the field of view of front antenna
# 0 means it is not
function observe(theta::TargetTuple, s::FOV, p::Pose)

    # ensure bearing is reflected across
    rel_bearing = fit_180(p[3] - true_bearing(p, theta))
    if rel_bearing < 0.0
        rel_bearing = -1.0 * rel_bearing
    end

    a1 = s.cone_width / 2.0
    a2 = 180.0 - a1

    prob_in_view = 0.0
    if rel_bearing < a1
        prob_in_view = 1.0 - s.alpha
    elseif rel_bearing < a2
        prob_in_view = 0.5
    else
        prob_in_view = s.alpha
    end

    # if we are too close, then prob_in_view = 0.5
    dx = p[1] - theta[1]
    dy = p[2] - theta[2]
    if (dx*dx + dy*dy) < s.blind_distance*s.blind_distance
        prob_in_view = 0.5
    end

    o = (rand() < prob_in_view) ? 1 : 0
    return o
end

# o technically has to be a float, but we only have two obs:
#  1 = in field of view
#  0 = not in field of view
function O(s::FOV, theta::LocTuple, p::Pose, o)

    # determine relative bearing and fix it in 0 to 180
    rel_bearing = fit_180(p[3] - true_bearing(p, theta))
    if rel_bearing < 0.0
        rel_bearing = -1.0 * rel_bearing
    end

    a1 = s.cone_width / 2.0
    a2 = 180.0 - a1

    prob_in_view = 0.0  # probability in view of front antenna

    if rel_bearing < a1
        prob_in_view = 1.0 - s.alpha
    elseif rel_bearing < a2
        prob_in_view = 0.5
    else
        prob_in_view = s.alpha
    end

    # if we are too close, then prob_in_view = 0.5
    dx = p[1] - theta[1]
    dy = p[2] - theta[2]
    if (dx*dx + dy*dy) < s.blind_distance*s.blind_distance
        prob_in_view = 0.5
    end

    # prob that it's one (in view of front antenna)
    ret_val = (o == 1) ? prob_in_view : (1.0 - prob_in_view)

    return ret_val
end

get_prob(s::FOV, theta::LocTuple, xp::Pose, o) = O(s, theta, xp, o)
export get_prob


using LinearAlgebra#dot to get the answer
#Julia core using BigFloat
# begin at 1

fname = "results.txt"
function printtofile(fout,result)#file output
    println(fout,result)
end

function commandMode()
    fout=open(fname,"w")
        println("您正在使用的是 命令行 模式，请输入您的向量的维度")
        dim = parse(Int,readline())
        vector = zeros(BigFloat,dim)
        println("请输入您的向量个数")
        n = parse(Int,readline())
        vectors = Vector{Any}(undef,n)
        println("请输入的时候以行为单位，每个数字请回车键入，不要用空格分割两个数字")
        for i in 1:n
            for j in 1:dim
                vector[j] = parse(BigFloat,readline())
            end
            vectors[i]=copy(vector)
        end
        k = Int(n/2)
        t1=time_ns()
        for i = 1:k
            result = dot(vectors[2*i],vectors[2*i-1])
            printtofile(fout,result)
        end
        t2=time_ns()
        println(string((t2-t1)/1e9,"seconds to finish this job"))
        close(fout)
end
function fileMode()
    fout=open(fname,"w")
    println("您正在使用的是 文件 模式，请输入您的向量的维度")
    dim = parse(Int,readline())
    vector = zeros(BigFloat,dim)
    println("请输入您的向量个数")
    n = parse(Int,readline())
    vectors = Vector{Any}(undef,n)
    println("请输入您想打开的文件路径，使用绝对路径")
    pathfile = readline()
    fin = open(pathfile,"r")
    println("正在读取文件请稍后")
    for i in 1:n
        for j in 1:dim
            vector[j] = BigFloat(readline(fin))
        end
        vectors[i]=copy(vector)
    end
    println("正在进行计算")
    k = Int(n/2)
    t1=time_ns()
    for i = 1:k
        result = dot(vectors[2*i],vectors[2*i-1])
        printtofile(fout,result)
    end
    t2=time_ns()
    println(string((t2-t1)/1e9-0.03*k,"seconds to finish this job"))
    close(fout)
end
println("Julia核心已经初始化完成，正在进入主程序")
println("欢迎继续使用本程序，输入-q退出程序，输入-f使用文件读取模式，输入-c进入命令行读取模式")
global cmd = readline()
while cmd!="-q"
    if cmd[1]!='-'
        print("wrong command, please try again")
    elseif cmd[2]=='f'
    fileMode()
    elseif cmd[2]=='c'
    commandMode()
    else
        print("wrong command, please try again")
    end
    println("请输入您的下一个指令，-q退出程序，输入-f使用文件读取模式，输入-c进入命令行读取模式")
    global cmd = readline()
end


# Tests automatic differentiation on sn.jl:
include("../src/sn_jacobian.jl")

@testset "sn_jacobian" begin

r0 = [0.01,100.0]
nb = 20
#l_max = 20
l_max = 10
n_max = l_max^2+2*l_max

# Now, carry out finite-difference derivative computation:
function sn_jac_num(l_max::Int64,r::T,b::T) where {T <: Real}
  dq = big(1e-24)
  n_max = l_max^2+2*l_max
# Allocate an array for s_n:
  sn_big = zeros(BigFloat,n_max+1)
# Make BigFloat versions of r & b:
  r_big = big(r); b_big = big(b)
# Compute s_n to BigFloat precision:
  s_n!(l_max,r_big,b_big,sn_big)
# Now, compute finite differences:
  sn_jac_big= zeros(BigFloat,n_max+1,2)
  sn_plus = zeros(BigFloat,n_max+1)
  s_n!(l_max,r_big+dq,b_big,sn_plus)
  sn_minus = zeros(BigFloat,n_max+1)
  s_n!(l_max,r_big-dq,b_big,sn_minus)
  sn_jac_big[:,1] = (sn_plus-sn_minus)*.5/dq
  s_n!(l_max,r_big,b_big+dq,sn_plus)
  s_n!(l_max,r_big,b_big-dq,sn_minus)
  sn_jac_big[:,2] = (sn_plus-sn_minus)*.5/dq
return convert(Array{Float64,2},sn_jac_big)
end

#sn_jacobian
#convert(Array{Float64,2},sn_jac_big)-sn_jacobian

using PyPlot
fig,axes = subplots(1,2)
get_cmap("plasma")
epsilon = 1e-12; delta = 1e-3
i=1
for i=1:2
  r=r0[i]
  if r < 1.0
    b = [linspace(1e-8,epsilon,nb); linspace(epsilon,delta,nb); linspace(delta,r-delta,nb);
#     linspace(r-delta,r-epsilon,nb); linspace(r-epsilon,r,nb); linspace(r,r+epsilon,nb); linspace(r+epsilon,r+delta,nb);
     linspace(r-delta,r-epsilon,nb); linspace(r-epsilon,r+epsilon,nb); linspace(r+epsilon,r+delta,nb);
     linspace(r+delta,1-r-delta,nb); linspace(1-r-delta,1-r-epsilon,nb); linspace(1-r-epsilon,1-r+epsilon,nb);
     linspace(1-r+epsilon,1-r+delta,nb); linspace(1-r+delta,1+r-delta,nb); linspace(1+r-delta,1+r-epsilon,nb);linspace(1+r-epsilon,1+r-1e-8,nb)]
  else
    b = [linspace(r-1+1e-8,r-1+epsilon,nb); linspace(r-1+epsilon,r-1+delta,nb); linspace(r-1+delta,r-delta,nb);
#     linspace(r-delta,r-epsilon,nb); linspace(r-epsilon,r,nb); linspace(r,r+epsilon,nb); linspace(r+epsilon,r+delta,nb);
     linspace(r-delta,r-epsilon,nb); linspace(r-epsilon,r+epsilon,nb); linspace(r+epsilon,r+delta,nb);
     linspace(r+delta,r+1-delta,nb); linspace(r+1-delta,r+1-epsilon,nb); linspace(r+1-epsilon,r+1-1e-8,nb)]
  end
  k2 = (1.-(r-b).^2)./(4.*b.*r)
  k2H = ((b+1).^2-r.^2)./(4b)
  igrid=linspace(1,length(b),length(b))-1
  sn_jac_grid = zeros(length(b),n_max+1,2)
  sn_grid = zeros(length(b),n_max+1)
  sn_jac_grid_num = zeros(length(b),n_max+1,2)
  for j=1:length(b)
    println("r: ",r," b: ",b[j])
    sn,sn_jacobian= sn_jac(l_max,r,b[j])
    sn_grid[j,:]=sn
    sn_jac_grid[j,:,:]=sn_jacobian
    sn_jac_grid_num[j,:,:]= sn_jac_num(l_max,r,b[j])
  end
# Now, make plots:
  ax = axes[i]
  m=0;l=0
  for n=0:n_max
    if m==0
#      ax[:plot](abs.(sn_jac_grid[:,n+1,1]-sn_jac_grid_num[:,n+1,1]),color=cmap(l/(l_max+2)),lw=1,label=string("l=",l))
#      ax[:semilogy](abs.(asinh.(sn_jac_grid[:,n+1,1])-sn_jac_grid_num[:,n+1,1]),lw=1,label=string("l=",l))
      ax[:semilogy](abs.(asinh.(sn_jac_grid[:,n+1,1])-asinh.(sn_jac_grid_num[:,n+1,1])),lw=1,label=string("l=",l))
    else
#      ax[:plot](abs.(sn_jac_grid[:,n+1,1]-sn_jac_grid_num[:,n+1,1]),color=cmap(l/(l_max+2)),lw=1)
#      ax[:semilogy](abs.(sn_jac_grid[:,n+1,1]-sn_jac_grid_num[:,n+1,1]),lw=1)
      ax[:semilogy](abs.(asinh.(sn_jac_grid[:,n+1,1])-asinh.(sn_jac_grid_num[:,n+1,1])),lw=1)
    end
#    ax[:plot](abs.(sn_jac_grid[:,n+1,2]-sn_jac_grid_num[:,n+1,2]),color=cmap(l/(l_max+2)),lw=1)
#    ax[:semilogy](abs.(sn_jac_grid[:,n+1,2]-sn_jac_grid_num[:,n+1,2]),lw=1)
    ax[:semilogy](abs.(asinh.(sn_jac_grid[:,n+1,2])-asinh.(sn_jac_grid_num[:,n+1,2])),lw=1)
    m +=1
    if m > l
      l += 1
      m = -l
    end
  end
  ax[:legend](loc="upper right",fontsize=6)
  ax[:set_xlabel]("b values")
  ax[:set_ylabel]("Derivative Error")
  ax[:axis]([0,length(b),1e-16,1])
end
#read(STDIN,Char)

# Loop over n and see where the differences between the finite-difference
# and AutoDiff are greater than the derivative value:
l=0; m=0
for n=0:n_max
  diff = sn_jac_grid[:,n+1,:]-sn_jac_grid_num[:,n+1,:]
  for ib=1:length(b)
    @test isapprox(sn_jac_grid[ib,n+1,1],sn_jac_grid_num[ib,n+1,1])
    @test isapprox(sn_jac_grid[ib,n+1,2],sn_jac_grid_num[ib,n+1,2])
  end
  end
  mask = (abs.(diff) .> 1e-3*abs.(sn_jac_grid[:,n+1,:])) .& (abs.(sn_jac_grid[:,n+1,:]) .> 1e-5)
  if sum(mask) > 0 || mod(l-m,4) == 0
    println("n: ",n," max dsn/dr: ",maximum(abs.(diff)))
    println("b: ",b[mask[:,1]],b[mask[:,2]]," k^2_H: ",k2H[mask[:,1]],k2H[mask[:,2]])
# end
#end
# Now, plot derivatives for each value of n:
#for n=0:n_max
    clf()
#    plot(sn_grid[:,n+1])
#    plot(sn_jac_grid[:,n+1,1])
#    plot(igrid[mask[:,1]],sn_jac_grid[mask[:,1],n+1,1],"o")
#    plot(sn_jac_grid[:,n+1,2])
#    plot(igrid[mask[:,2]],sn_jac_grid[mask[:,2],n+1,2],"o")
#    plot(igrid,sn_jac_grid_num[:,n+1,1],linestyle="--",linewidth=2)
#    plot(igrid,sn_jac_grid_num[:,n+1,2],linestyle="--",linewidth=2)
    plot(b,sn_grid[:,n+1])
    plot(b,sn_jac_grid[:,n+1,1])
    plot(b[mask[:,1]],sn_jac_grid[mask[:,1],n+1,1],"o")
    plot(b,sn_jac_grid[:,n+1,2])
    plot(b[mask[:,2]],sn_jac_grid[mask[:,2],n+1,2],"o")
    plot(b,sn_jac_grid_num[:,n+1,1],linestyle="--",linewidth=2)
    plot(b,sn_jac_grid_num[:,n+1,2],linestyle="--",linewidth=2)
    println("n: ",n," l: ",l," m: ",m," mu: ",l-m," nu: ",l+m)
#    read(STDIN,Char)
  end
  m +=1
  if m > l
    l += 1
    m = -l
  end
end

#end

#loglog(abs.(reshape(sn_jac_grid,length(b)*(n_max+1)*2)),abs.(reshape(sn_jac_grid-sn_jac_grid_num,length(b)*(n_max+1)*2)),".")
#loglog(abs.(reshape(sn_jac_grid,length(b)*(n_max+1)*2)),abs.(reshape(sn_jac_grid_num,length(b)*(n_max+1)*2)),".")
end


using Primes

println(factor(600851475143).pe[end][1])


using LinearAlgebra

struct ValueOne; end
ValueOne()

# Compute X <- a X + b I.
function matfun_axpby!(X,a,b,Y::UniformScaling)
    m,n=size(X)
    if ~(a isa ValueOne)
        rmul!(X,a)
    end
    @inbounds for i=1:n
        X[i,i]+=(b isa ValueOne) ? 1 : b
    end
end

@inline function exp_ps_m9(A)
    T=promote_type(eltype(A),Float64)
    A_copy=similar(A,T); A_copy .= A;
    return exp_ps_m9!(A_copy)
end

@inline function exp_ps_m9!(A)
    T=promote_type(eltype(A),Float64) # Make it work for many 'bigger' types (matrices and scalars)
    # max_memslots=8
    n=size(A,1)
    # The first slots are precomputed nodes [:A]
    memslots2 = similar(A,T)
    memslots3 = similar(A,T)
    memslots4 = similar(A,T)
    memslots5 = similar(A,T)
    memslots6 = similar(A,T)
    memslots7 = similar(A,T)
    memslots8 = similar(A,T)
    # Assign precomputed nodes memslots
    memslots1=A # overwrite A
    # Uniform scaling is exploited.
    # No matrix I explicitly allocated.
    value_one=ValueOne()
    # Computation order: A2 A3 A4 A5 A6 P4 C3 P3 C2 P2 C1 P1 C0 P0
    # Computing A2 with operation: mult
    mul!(memslots2,memslots1,memslots1)
    # Computing A3 with operation: mult
    mul!(memslots3,memslots1,memslots2)
    # Computing A4 with operation: mult
    mul!(memslots4,memslots1,memslots3)
    # Computing A5 with operation: mult
    mul!(memslots5,memslots1,memslots4)
    # Computing A6 with operation: mult
    mul!(memslots6,memslots1,memslots5)
    # Computing P4 = x*I+x*A+x*A2+x*A3+x*A4+x*A5+x*A6
    coeff1=1.6117375710961184e-24
    coeff2=6.446950284384474e-26
    coeff3=2.4795962632247976e-27
    coeff4=9.183689863795546e-29
    coeff5=3.279889237069838e-30
    coeff6=1.1309962886447716e-31
    coeff7=3.7699876288159054e-33
    memslots7 .= coeff2.*memslots1 .+ coeff3.*memslots2 .+ coeff4.*memslots3 .+ coeff5.*memslots4 .+ coeff6.*memslots5 .+ coeff7.*memslots6
    mul!(memslots7,true,I*coeff1,true,true)
    # Computing C3 with operation: mult
    mul!(memslots8,memslots7,memslots6)
    # Deallocating P4 in slot 7
    # Computing P3 = x*I+x*A+x*A2+x*A3+x*A4+x*A5+x*C3
    coeff1=1.5619206968586225e-16
    coeff2=8.22063524662433e-18
    coeff3=4.110317623312165e-19
    coeff4=1.9572941063391263e-20
    coeff5=8.896791392450574e-22
    coeff6=3.868170170630684e-23
    coeff7=1.0
    # Smart lincomb recycle C3
    memslots8 .= coeff2.*memslots1 .+ coeff3.*memslots2 .+ coeff4.*memslots3 .+ coeff5.*memslots4 .+ coeff6.*memslots5 .+ coeff7.*memslots8
    mul!(memslots8,true,I*coeff1,true,true)
    # Computing C2 with operation: mult
    mul!(memslots7,memslots8,memslots6)
    # Deallocating P3 in slot 8
    # Computing P2 = x*I+x*A+x*A2+x*A3+x*A4+x*A5+x*C2
    coeff1=2.08767569878681e-9
    coeff2=1.6059043836821613e-10
    coeff3=1.1470745597729725e-11
    coeff4=7.647163731819816e-13
    coeff5=4.779477332387385e-14
    coeff6=2.8114572543455206e-15
    coeff7=1.0
    # Smart lincomb recycle C2
    memslots7 .= coeff2.*memslots1 .+ coeff3.*memslots2 .+ coeff4.*memslots3 .+ coeff5.*memslots4 .+ coeff6.*memslots5 .+ coeff7.*memslots7
    mul!(memslots7,true,I*coeff1,true,true)
    # Computing C1 with operation: mult
    mul!(memslots8,memslots7,memslots6)
    # Deallocating P2 in slot 7
    # Computing P1 = x*I+x*A+x*A2+x*A3+x*A4+x*A5+x*C1
    coeff1=0.001388888888888889
    coeff2=0.0001984126984126984
    coeff3=2.48015873015873e-5
    coeff4=2.7557319223985893e-6
    coeff5=2.755731922398589e-7
    coeff6=2.505210838544172e-8
    coeff7=1.0
    # Smart lincomb recycle C1
    memslots8 .= coeff2.*memslots1 .+ coeff3.*memslots2 .+ coeff4.*memslots3 .+ coeff5.*memslots4 .+ coeff6.*memslots5 .+ coeff7.*memslots8
    mul!(memslots8,true,I*coeff1,true,true)
    # Computing C0 with operation: mult
    mul!(memslots7,memslots8,memslots6)
    # Deallocating P1 in slot 8
    # Deallocating A6 in slot 6
    # Computing P0 = x*I+x*A+x*A2+x*A3+x*A4+x*A5+x*C0
    coeff1=1.0
    coeff2=1.0
    coeff3=0.5
    coeff4=0.16666666666666666
    coeff5=0.041666666666666664
    coeff6=0.008333333333333333
    coeff7=1.0
    # Smart lincomb recycle A
    memslots1 .= coeff2.*memslots1 .+ coeff3.*memslots2 .+ coeff4.*memslots3 .+ coeff5.*memslots4 .+ coeff6.*memslots5 .+ coeff7.*memslots7
    mul!(memslots1,true,I*coeff1,true,true)
    # Deallocating A2 in slot 2
    # Deallocating A3 in slot 3
    # Deallocating A4 in slot 4
    # Deallocating A5 in slot 5
    # Deallocating C0 in slot 7
    return memslots1 # Returning P0
end


"""
An implementation of "Polychronization: computation with spikes"
Izhikevich EM. Neural Comput. 2006 Feb;18(2):245-82. doi: 10.1162/089976606775093882. PMID: 16378515.
https://pubmed.ncbi.nlm.nih.gov/16378515/
"""
using FastSpike
using LinearAlgebra: I
using Plots
using ProgressMeter

const time = 10_000
const Nₑ = 800
const Nᵢ = 200
const N = Nₑ + Nᵢ
const p = 0.1
#Izhikevich neurons parameters
a = [0.02 * ones(1, Nₑ) 0.1 * ones(1, Nᵢ)]
b = 0.2
c = -65
d = [8 * ones(1, Nₑ) 2 * ones(1, Nᵢ)]
# form adjacency matrix
adjacency = rand(N, N) .< p
adjacency -= adjacency .* (zeros(N, N) + I) # remove recurrent connections
# Inhibitory/Excitetory weights: excitatory: 0 → 10, inhibitory: -5 (rigid)
min_weight, max_weight = EI(
    Nₑ, Nᵢ;
    E_E=(0, 10), E_I=(0, 10), I_E=(-5, -5), I_I=(-5, -5),
    adjacency=adjacency
)
# Define Network
net = DelayNetwork(
    Izhikevich(1, a, b, c, d, 30.0),
    STDP(0.1, 0.12, 20, 20; min_weight=min_weight, max_weight=max_weight)
)
neurons = add_group!(net, N)
# connections in neural group
weights = [6 * ones(Nₑ, Nₑ + Nᵢ); -5 * ones(Nᵢ, Nₑ + Nᵢ)]
weights .*= adjacency
delay = rand(1:20, N, N)
delay .*= adjacency
connect!(net, neurons, neurons, weights, adjacency, delay)
# Define Monitor to record network activities
monitor = Monitor()
# Define training function
function train(time)
    spiked_neurons = []
    @showprogress 1 "training " for t = 1:time
        random_input = zeros(1, N)
        random_input[rand(1:N)] = 20
        run!(net; input_voltage=random_input)
        record!(monitor, net)
        append!(spiked_neurons, [(t, i[2]) for i in findall(net.spikes)])
        if t % 1000 == 0
            plot(
                scatter(Tuple.(spiked_neurons), markersize=1, c=:black, title="time: $(t÷1000)s",),
                histogram(net.weight[1:800, :][convert(Matrix{Bool}, net.adjacency)[1:800, :]], title="Excitatory weight histogram");
                layout=grid(4, 1, heights=[0.75, 0.25])
            )
            spiked_neurons = []
        end
    end
    return
end
# Run
train(time)


using PyPlot

using SubmodularMaximization

discretization = 1000

num_agents = 20
num_sensors = 6
nominal_area = 1.0

max_success_probability = 0.9
sensor_radius = sqrt(nominal_area / (num_agents * pi))
station_radius = 10 * sensor_radius

sensor = ProbabilisticSensor([0.5, 0.5], sensor_radius, max_success_probability)

density = [detection_probability(sensor, [x,y])
           for x in range(0, stop=1, length=discretization),
           y in range(0, stop=1, length=discretization)]

figure()
imshow(density', cmap="viridis", vmin=minimum(density), vmax=maximum(density[:]),
       extent=[0, 1, 0, 1], interpolation="nearest", origin="lower")
colorbar()

nothing


###############################################################
# Part of BDisposal.jl package
###############################################################

using LinearAlgebra

# Dispatching to either convexProblem or nonConvexProblem
function problem(gI₀,bI₀,gO₀,bO₀,gI,bI,gO,bO;
                 retToScale,prodStructure,convexAssumption=true,
                 directions=(),
                 startValues=(),crossTime=false,forceLinearModel=false)

     if convexAssumption
         return convexProblem(gI₀,bI₀,gO₀,bO₀,gI,bI,gO,bO;
                          retToScale=retToScale,prodStructure=prodStructure,
                          directions=directions,
                          startValues=startValues,crossTime=crossTime,forceLinearModel=forceLinearModel)
     else
         return nonConvexProblem(gI₀,bI₀,gO₀,bO₀,gI,bI,gO,bO;
                          retToScale=retToScale,prodStructure=prodStructure,
                          directions=directions)
     end

end

function convexProblem(inp₀,bInp₀,gO₀,bO₀,inp,bInp,gO,bO;
                       retToScale,prodStructure,
                       directions,
                       startValues,crossTime=false,forceLinearModel=false)

   nI, nGO, nBO, nDMUs, nbI = length(inp₀), length(gO₀), length(bO₀), size(inp,1), length(bInp₀)
   (dirGI,dirBI,dirGO,dirBO) = directions
   if !forceLinearModel
       (startθ,startμ,startλ)    = startValues
   end

   if prodStructure == "multiplicative"
       if !forceLinearModel
               # Redirecting the output to /deV/null as no way to suppress ipopt output
               oldstd = stdout
               redirect_stdout(open("nul", "w"))

               # Model declaration (efficiency model)
               effmodel = Model(() -> AmplNLWriter.Optimizer(Ipopt.amplexe))
               #effmodel = Model(optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0))
               MOI.set(effmodel, MOI.RawParameter("print_level"), 0)

               # Defining variables
               @variables effmodel begin
                   0 <= θ[z in 1:nDMUs] <= Inf, (start = startθ) # thetas (nDMUs)
                   0 <= μ[z in 1:nDMUs] <= Inf, (start = startμ) # mus (nDMUs)
               end
               if !crossTime
                  @variable(effmodel, 1 <= λ <= Inf, start = startλ)
               else
                  @variable(effmodel, 0 <= λ <= Inf, start = startλ)
               end

               # Defining constraints
               @NLconstraints effmodel begin
                   cgInp1[i in 1:nI],   # constraint on good inputs
                      λ^dirGI * inp₀[i] >= sum(θ[z]*inp[z,i] for z in 1:nDMUs)
                   cbInp1[i in 1:nbI], # constraint on bad inputs
                      λ^dirBI * bInp₀[i] <= sum(θ[z]*bInp[z,i] for z in 1:nDMUs)
                   cgInp2[i in 1:nI],   # second constraint on good inputs
                      λ^dirGI * inp₀[i] >= sum(μ[z]*inp[z,i] for z in 1:nDMUs)
                   cbInp2[i in 1:nbI], # second constraint on bad inputs
                      λ^dirBI * bInp₀[i] >= sum(μ[z]*bInp[z,i] for z in 1:nDMUs)
                   cgO1[j in 1:nGO],   # first constraint on good outputs
                      λ^dirGO * gO₀[j] <= sum(θ[z]*gO[z,j] for z in 1:nDMUs)
                   cbO1[j in 1:nBO],   # first constraint on bad outputs
                      λ^dirBO * bO₀[j] >= sum(θ[z]*bO[z,j] for z in 1:nDMUs)
                   cgO2[j in 1:nGO],   # second constraint on good outputs
                      λ^dirGO * gO₀[j] <= sum(μ[z]*gO[z,j] for z in 1:nDMUs)
                   cbO2[j in 1:nBO],   # second constraint on bad outputs
                      λ^dirBO * bO₀[j] <= sum(μ[z]*bO[z,j] for z in 1:nDMUs)
               end
               if retToScale == "variable"
                   @constraints effmodel begin
                       sumtheta,
                         sum(θ[z] for z in 1:1:nDMUs) == 1
                       summu,
                         sum(μ[z] for z in 1:1:nDMUs) == 1
                   end
               end
               # Defining objective
               @objective effmodel Max begin
                   λ
               end
               # Printing the model (for debugging)
               #print(effmodel) # The model in mathematical terms is printed

               # Solving the model
               JuMP.optimize!(effmodel)
               redirect_stdout(oldstd) # recover original stdout

               # Copy the results to the output matrix...
               status = termination_status(effmodel)

               #if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED || status == MOI.TIME_LIMIT) && has_values(effmodel)
               if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED) && has_values(effmodel)
                   return value(λ)
               else
                   return missing
               end
       else #forceLinearModel is then true

               # Model declaration (efficiency model)
               #effmodel = Model(() -> AmplNLWriter.Optimizer(path, ["print_level=0"]))
               #effmodel = Model(optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0))
               effmodel = Model(optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF)) # we choose GLPK with a verbose output
                #effmodel = Model(optimizer_with_attributes(GLPK.Optimizer))
               #effmodel = Model(optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF))

               # Defining variables
               @variables effmodel begin
                   0 <= θ[z in 1:nDMUs] <= Inf# thetas (nDMUs)
                   0 <= μ[z in 1:nDMUs] <= Inf # mus (nDMUs)
               end

               if dirGO == 1
                   if !crossTime # we  keep the original λ
                  @variable(effmodel, 1 <= λ <= Inf)
                   else
                      @variable(effmodel, 0 <= λ <= Inf)
                   end
                   # @variable(effmodel, λ)

               else
                   if !crossTime # we use as variable its reverse 1/λ
                      @variable(effmodel, 0 <= invλ <= 1)
                   else
                      @variable(effmodel, 0 <= invλ <= Inf)
                   end
                   # @variable(effmodel, invλ)
               end

               if directions == (-1,0,0,0)
                   # Defining constraints
                   @constraints effmodel begin
                       cgInp1[i in 1:nI],   # constraint on good inputs
                          invλ * inp₀[i] >= sum(θ[z]*inp[z,i] for z in 1:nDMUs)
                       cbInp1[i in 1:nbI], # constraint on bad inputs
                           bInp₀[i] <= sum(θ[z]*bInp[z,i] for z in 1:nDMUs)
                       cgInp2[i in 1:nI],   # second constraint on good inputs
                          invλ * inp₀[i] >= sum(μ[z]*inp[z,i] for z in 1:nDMUs)
                       cbInp2[i in 1:nbI], # second constraint on bad inputs
                           bInp₀[i] >= sum(μ[z]*bInp[z,i] for z in 1:nDMUs)
                       cgO1[j in 1:nGO],   # first constraint on good outputs
                           gO₀[j] <= sum(θ[z]*gO[z,j] for z in 1:nDMUs)
                       cbO1[j in 1:nBO],   # first constraint on bad outputs
                           bO₀[j] >= sum(θ[z]*bO[z,j] for z in 1:nDMUs)
                       cgO2[j in 1:nGO],   # second constraint on good outputs
                           gO₀[j] <= sum(μ[z]*gO[z,j] for z in 1:nDMUs)
                       cbO2[j in 1:nBO],   # second constraint on bad outputs
                           bO₀[j] <= sum(μ[z]*bO[z,j] for z in 1:nDMUs)
                   end
              elseif directions == (0,-1,0,0)
                  # Defining constraints
                  @constraints effmodel begin
                      cgInp1[i in 1:nI],   # constraint on good inputs
                          inp₀[i] >= sum(θ[z]*inp[z,i] for z in 1:nDMUs)
                      cbInp1[i in 1:nbI], # constraint on bad inputs
                         invλ * bInp₀[i] <= sum(θ[z]*bInp[z,i] for z in 1:nDMUs)
                      cgInp2[i in 1:nI],   # second constraint on good inputs
                          inp₀[i] >= sum(μ[z]*inp[z,i] for z in 1:nDMUs)
                      cbInp2[i in 1:nbI], # second constraint on bad inputs
                         invλ * bInp₀[i] >= sum(μ[z]*bInp[z,i] for z in 1:nDMUs)
                      cgO1[j in 1:nGO],   # first constraint on good outputs
                          gO₀[j] <= sum(θ[z]*gO[z,j] for z in 1:nDMUs)
                      cbO1[j in 1:nBO],   # first constraint on bad outputs
                          bO₀[j] >= sum(θ[z]*bO[z,j] for z in 1:nDMUs)
                      cgO2[j in 1:nGO],   # second constraint on good outputs
                          gO₀[j] <= sum(μ[z]*gO[z,j] for z in 1:nDMUs)
                      cbO2[j in 1:nBO],   # second constraint on bad outputs
                          bO₀[j] <= sum(μ[z]*bO[z,j] for z in 1:nDMUs)
                  end
              elseif directions == (0,0,1,0)
                  @constraints effmodel begin
                      cgInp1[i in 1:nI],   # constraint on good inputs
                          inp₀[i] >= sum(θ[z]*inp[z,i] for z in 1:nDMUs)
                      cbInp1[i in 1:nbI], # constraint on bad inputs
                          bInp₀[i] <= sum(θ[z]*bInp[z,i] for z in 1:nDMUs)
                      cgInp2[i in 1:nI],   # second constraint on good inputs
                          inp₀[i] >= sum(μ[z]*inp[z,i] for z in 1:nDMUs)
                      cbInp2[i in 1:nbI], # second constraint on bad inputs
                          bInp₀[i] >= sum(μ[z]*bInp[z,i] for z in 1:nDMUs)
                      cgO1[j in 1:nGO],   # first constraint on good outputs
                         λ * gO₀[j] <= sum(θ[z]*gO[z,j] for z in 1:nDMUs)
                      cbO1[j in 1:nBO],   # first constraint on bad outputs
                          bO₀[j] >= sum(θ[z]*bO[z,j] for z in 1:nDMUs)
                      cgO2[j in 1:nGO],   # second constraint on good outputs
                         λ * gO₀[j] <= sum(μ[z]*gO[z,j] for z in 1:nDMUs)
                      cbO2[j in 1:nBO],   # second constraint on bad outputs
                         bO₀[j] <= sum(μ[z]*bO[z,j] for z in 1:nDMUs)
                  end
              elseif directions == (0,0,0,-1)
                  # Defining constraints
                  @constraints effmodel begin
                      cgInp1[i in 1:nI],   # constraint on good inputs
                         inp₀[i] >= sum(θ[z]*inp[z,i] for z in 1:nDMUs)
                      cbInp1[i in 1:nbI], # constraint on bad inputs
                         bInp₀[i] <= sum(θ[z]*bInp[z,i] for z in 1:nDMUs)
                      cgInp2[i in 1:nI],   # second constraint on good inputs
                         inp₀[i] >= sum(μ[z]*inp[z,i] for z in 1:nDMUs)
                      cbInp2[i in 1:nbI], # second constraint on bad inputs
                          bInp₀[i] >= sum(μ[z]*bInp[z,i] for z in 1:nDMUs)
                      cgO1[j in 1:nGO],   # first constraint on good outputs
                          gO₀[j] <= sum(θ[z]*gO[z,j] for z in 1:nDMUs)
                      cbO1[j in 1:nBO],   # first constraint on bad outputs
                         invλ * bO₀[j] >= sum(θ[z]*bO[z,j] for z in 1:nDMUs)
                      cgO2[j in 1:nGO],   # second constraint on good outputs
                         gO₀[j] <= sum(μ[z]*gO[z,j] for z in 1:nDMUs)
                      cbO2[j in 1:nBO],   # second constraint on bad outputs
                         invλ * bO₀[j] <= sum(μ[z]*bO[z,j] for z in 1:nDMUs)
                  end

              else
                  @error "Directions not suppported when the option forceLinearModel is true"
              end
               if retToScale == "variable"
                   @constraints effmodel begin
                       sumtheta,
                         sum(θ[z] for z in 1:1:nDMUs) == 1
                       summu,
                         sum(μ[z] for z in 1:1:nDMUs) == 1
                   end
               end

               # Defining objective
               if dirGO == 1
                   @objective effmodel Max begin
                       λ
                   end
               else
                   @objective effmodel Min begin
                       invλ
                   end
               end
               # Printing the model (for debugging)
               #print(effmodel) # The model in mathematical terms is printed

               JuMP.optimize!(effmodel)

               # Copy the results to the output matrix...
               status = termination_status(effmodel)

               #if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED || status == MOI.TIME_LIMIT) && has_values(effmodel)
               if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED) && has_values(effmodel)
                   if dirGO == 1
                       return value(λ)
                   else
                       return 1/value(invλ)
                   end
               else
                   return missing
               end

        end   # end of forCeLinearModel == true
   else # prodStructure is then additive...
       # Model declaration (efficiency model)
       effmodel = Model(optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF)) # we choose GLPK with a verbose output
       # Defining variables
       @variables effmodel begin
           θ[z in 1:nDMUs] >= 0 # thetas (nDMUs)
           μ[z in 1:nDMUs] >= 0 # mus (nDMUs)
       end
       if !crossTime
          @variable(effmodel, 0 <= λ <= Inf)
       else
          @variable(effmodel, -Inf <= λ <= Inf)
       end
       # Defining constraints
       # additive structure
       @constraints effmodel begin
           cgInp1[i in 1:nI],   # constraint on good inputs
              inp₀[i]- λ*dirGI * inp₀[i] >= sum(θ[z]*inp[z,i] for z in 1:nDMUs)
           cbInp1[i in 1:nbI], # constraint on bad inputs
              bInp₀[i]- λ*dirBI * bInp₀[i] <= sum(θ[z]*bInp[z,i] for z in 1:nDMUs)
           cgInp2[i in 1:nI],   # second constraint on good inputs
               inp₀[i] - λ*dirGI * inp₀[i] >= sum(μ[z]*inp[z,i] for z in 1:nDMUs)
           cbInp2[i in 1:nbI], # second constraint on bad inputs
              bInp₀[i] - λ*dirBI * bInp₀[i] >= sum(μ[z]*bInp[z,i] for z in 1:nDMUs)
           cgO1[j in 1:nGO],   # first constraint on good outputs
              gO₀[j] + λ * dirGO * gO₀[j] <= sum(θ[z]*gO[z,j] for z in 1:nDMUs)
           cbO1[j in 1:nBO],   # first constraint on bad outputs
              bO₀[j] - λ * dirBO * bO₀[j] >= sum(θ[z]*bO[z,j] for z in 1:nDMUs)
           cgO2[j in 1:nGO],   # second constraint on good outputs
              gO₀[j] + λ*dirGO * gO₀[j] <= sum(μ[z]*gO[z,j] for z in 1:nDMUs)
           cbO2[j in 1:nBO],   # second constraint on bad outputs
              bO₀[j] - λ*dirBO * bO₀[j] <= sum(μ[z]*bO[z,j] for z in 1:nDMUs)
       end
       if retToScale == "variable"
           @constraints effmodel begin
               sumtheta,
                 sum(θ[z] for z in 1:1:nDMUs) == 1
               summu,
                 sum(μ[z] for z in 1:1:nDMUs) == 1
           end
       end
       # Defining objective
       @objective effmodel Max begin
           λ
       end
       # Printing the model (for debugging)
       # print(effmodel) # The model in mathematical terms is printed

       # Solving the model
       optimize!(effmodel)

       # Copy the results to the output matrix...
       status = termination_status(effmodel)

       #if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED || status == MOI.TIME_LIMIT) && has_values(effmodel)
       if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED) && has_values(effmodel)
           return value(λ)
       else
           return missing
       end
   end # ending addictive  prodStructure
end # ending convexProblem

function nonConvexProblem(gI₀,bI₀,gO₀,bO₀,gI,bI,gO,bO;
                       retToScale,prodStructure,
                       directions)

    if retToScale == "constant" && directions != (0,0,1,-1) @error "Not supported" end
    nGI, nGO, nBO, nDMUs, nBI = length(gI₀), length(gO₀), length(bO₀), size(gI,1), length(bI₀)
    (dirGI,dirBI,dirGO,dirBO) = directions

    if directions == (-1,0,0,0) && prodStructure == "multiplicative"
        gI_ratio  =  gI ./ gI₀' # nDMU x ngI
        # Normal dist function
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .<=   bO, dims=2)
        globalContraint = dropdims(all(hcat(bIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : minimum(maximum(gI_ratio[globalContraint,:],dims=2))
        # Bdisposal Distance function
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .>=   bO, dims=2)
        globalContraint = dropdims(all(hcat(bIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : minimum(maximum(gI_ratio[globalContraint,:],dims=2))
        effscore = max(effScore_normal,effScore_bfrontier)
        return effscore
    elseif  directions == (1,0,0,0) && prodStructure == "additive"
        gI_ratio  =  1 .- (gI ./ gI₀') # nDMU x ngI
        # Normal dist function
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .<=   bO, dims=2)
        globalContraint = dropdims(all(hcat(bIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : maximum(minimum(gI_ratio[globalContraint,:],dims=2))
        # Bdisposal Distance function
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .>=   bO, dims=2)
        globalContraint = dropdims(all(hcat(bIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : maximum(minimum(gI_ratio[globalContraint,:],dims=2))
        effscore = min(effScore_normal,effScore_bfrontier)
        return effscore

    elseif  directions == (0,-1,0,0) && prodStructure == "multiplicative"
        bI_ratio  =  bI ./ bI₀' # nDMU x ngI
        # Normal dist function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .<=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : minimum(maximum(bI_ratio[globalContraint,:],dims=2))
        # Bdisposal Distance function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .>=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : minimum(maximum(bI_ratio[globalContraint,:],dims=2))
        effscore = max(effScore_normal,effScore_bfrontier)
        return effscore
    elseif  directions == (0,1,0,0) && prodStructure == "additive"
        bI_ratio  =  1 .- (bI ./ bI₀') # nDMU x ngI
        # Normal dist function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .<=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : maximum(minimum(bI_ratio[globalContraint,:],dims=2))
        # Bdisposal Distance function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        bOConstraint = all(bO₀'  .>=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,gOConstraint,bOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : maximum(minimum(bI_ratio[globalContraint,:],dims=2))
        effscore = min(effScore_normal,effScore_bfrontier)
        return effscore
    elseif  directions == (0,0,1,0) && prodStructure == "multiplicative"
        gO_ratio  =  gO₀' ./ gO  # nDMU x ngI
        # Normal dist function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        bOConstraint = all(bO₀'  .<=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,bOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : minimum(maximum(gO_ratio[globalContraint,:],dims=2))
        # Bdisposal Distance function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .<=   bI, dims=2)
        bOConstraint = all(bO₀'  .>=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,bOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : minimum(maximum(gO_ratio[globalContraint,:],dims=2))
        effscore = max(effScore_normal,effScore_bfrontier)
        return 1/effscore
    elseif  directions == (0,0,1,0) && prodStructure == "additive"
        gO_ratio  =  (gO₀' ./ gO ) .- 1 # nDMU x ngI
        # Normal dist function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        bOConstraint = all(bO₀'  .<=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,bOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : maximum(minimum(gO_ratio[globalContraint,:],dims=2)) #todo check this
        # Bdisposal Distance function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .<=   bI, dims=2)
        bOConstraint = all(bO₀'  .>=   bO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,bOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : maximum(minimum(gO_ratio[globalContraint,:],dims=2)) # todo check this
        effscore = min(effScore_normal,effScore_bfrontier)
        return -effscore
    elseif  directions == (0,0,0,-1) && prodStructure == "multiplicative"
        bO_ratio  =  bO ./ bO₀' # nDMU x ngI
        # Normal dist function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,gOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : minimum(minimum(bO_ratio[globalContraint,:],dims=2))
        # Bdisposal Distance function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .<=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,gOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : minimum(maximum(bO_ratio[globalContraint,:],dims=2))
        effscore = max(effScore_normal,effScore_bfrontier)
        return 1/effscore
    elseif  directions == (0,0,0,1) && prodStructure == "additive"
        bO_ratio  =  1 .- (bO ./ bO₀') # nDMU x ngI
        # Normal dist function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .>=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,gOConstraint), dims=2), dims=2)
        effScore_normal = ! any(globalContraint) ? missing : maximum(minimum(bO_ratio[globalContraint,:],dims=2)) #todo check this
        # Bdisposal Distance function
        gIConstraint = all(gI₀'  .>=   gI, dims=2)
        bIConstraint = all(bI₀'  .<=   bI, dims=2)
        gOConstraint = all(gO₀'  .<=   gO, dims=2)
        globalContraint = dropdims(all(hcat(gIConstraint,bIConstraint,gOConstraint), dims=2), dims=2)
        effScore_bfrontier = ! any(globalContraint) ? missing : minimum(minimum(bO_ratio[globalContraint,:];dims=2)) # todo check this
        effscore = min(effScore_normal,effScore_bfrontier)
        return effscore
    elseif  directions == (0,0,1,-1) && prodStructure == "multiplicative"
        gI_ratio  =  gI₀' ./ gI
        bI_ratio  =  bI₀' ./ bI
        gO_ratio  =  gO ./ gO₀'
        bO_ratio  =  bO ./ bO₀'

        if retToScale == "constant"
            normalFrontierDistances   = [minimum(gI_ratio[z,:]) * minimum(hcat(gO_ratio,bO_ratio)[z,:]) for z in 1:nDMUs]
            bFrontierDistances        = [minimum(gI_ratio[z,:]) * minimum(gO_ratio[z,:]) * (minimum(gO_ratio[z,:]) >= minimum(bO_ratio[z,:])) for z in 1:nDMUs]
        else
            normalFrontierDistances   = [minimum(gI_ratio[z,:]) * minimum(hcat(gO_ratio,bO_ratio)[z,:] * ( minimum(gI_ratio[z,:]) >= (1.0 - eps()) ) ) for z in 1:nDMUs]
            bFrontierDistances        = [minimum(gI_ratio[z,:]) * minimum(gO_ratio[z,:]) * (minimum(gO_ratio[z,:]) >= minimum(bO_ratio[z,:]) &&  minimum(gI_ratio[z,:]) >= (1.0 - eps())    ) for z in 1:nDMUs]
        end
        return min(maximum(normalFrontierDistances),maximum(bFrontierDistances))

    else
        @error "Directions not supported"
        return missing
    end

end # end function

"""
     dmuEfficiency(I₀,O₀,I,O)

Compute the efficiency score for a DMU using vanilla Data Envelope Analysis.

## Parameters:
  *  `I₀`: This DMU inputs (nI)
  *  `O₀`: This DMU outputs (n0)
  *  `I`: All DMUs inputs (nDMU x nI)
  *  `O`: All DMUs outputs (nDMU x n0)

## Returns:
- A named tuple with:
  - `computed`: a boolean to indicase wheter the underlying optimisation succeeded
  - `eff`: a boolean to indicate if the DMU is efficient or not
  - `obj`: a scalar representing the efficiency score of the DMU
  - `wI`: a vector of the optimal input weights
  - `wO`: a vector of the optimal output weights
  - `refSet`: a dictionary having as key the numeral of the reference DMUs and
  values the relative constraint duals
"""
function dmuEfficiency(I₀,O₀,I,O) # # Cooper & oth., p.23-24 and 43
    (nDMU,nI,nO) = size(I,1), size(I,2), size(O,2)

    effmodel = Model(optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF)) # we choose GLPK with a null output
    # Defining variables
    @variables effmodel begin
        wI[i in 1:nI] >= 0  # inputs weigths
        wO[o in 1:nO] >= 0  # outputs weigths
    end

    # Defining constraints
    @constraints effmodel begin
        othDMUsEffConstr[d in 1:nDMU],   #
           dot(O[d,:], wO) - dot(I[d,:], wI) <=  0.0
        iRegularisationConstr, # regularisation of inputs
            dot(I₀,wI) == 1.0

    end

    # Defining objective
    @objective effmodel Max begin
         dot(O₀, wO)
    end

    # Printing the model (for debugging)
    #print(effmodel) # The model in mathematical terms is printed

    # Solving the model
    optimize!(effmodel)

    status = termination_status(effmodel)

    if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED) && has_values(effmodel)
        wI = value.(wI)
        wO = value.(wO)
        othDMUsEffConstrDuals = -dual.(othDMUsEffConstr)
        iRegConstrDual = -dual(iRegularisationConstr)
        obj = dot(O₀,wO) / dot(I₀,wI)
        refSet = Dict{Int64,Float64}()
        [refSet[i] = f for (i,f) in enumerate(othDMUsEffConstrDuals) if f != 0.0]
        eff  = (isapprox.(obj,1.0,atol=1e-15) && !any(isapprox.(wI,0.0,atol=1e-15)) && !any(isapprox.(wI,0.0,atol=1e-15))) # efficient if the objective is 1 AND no weigths are zero (otherwise it is on one periphal side of the frontier and shadowed by other points)
        return (computed=true,eff=eff, obj=obj, wI=wI, wO = wO, refSet=refSet, othDMUsEffConstrDuals=othDMUsEffConstrDuals,iRegConstrDual=iRegConstrDual)
    else
        return (computed=false,eff=missing,obj=missing,wI=missing,wO = missing,refSet=missing,othDMUsEffConstrDuals=missing,iRegConstrDual=missing)
    end
end

# Cooper & oth., p.43
"""
     dmuEfficiencyDual(I₀,O₀,I,O)

Compute the efficiency score for a DMU using vanilla Data Envelope Analysis in the dual representation using the two pass method.

## Parameters:
  *  `I₀`: This DMU inputs (nI)
  *  `O₀`: This DMU outputs (n0)
  *  `I`: All DMUs inputs (nDMU x nI)
  *  `O`: All DMUs outputs (nDMU x n0)

## Returns:
- A named tuple with:
  - `computed`: a boolean to indicase wheter the underlying optimisation succeeded
  - `eff`: a boolean to indicate if the DMU is efficient or not
  - `obj`: a scalar representing the efficiency score of the DMU
  - `λ`
  - `θ`
  - `dualsI`
  - `dualsO`
  - `s⁻`
  - `s⁺`
"""
function dmuEfficiencyDual(I₀,O₀,I,O)
    (nDMU,nI,nO) = size(I,1), size(I,2), size(O,2)

    effmodeldual = Model(optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF)) # we choose GLPK with a verbose output
    # Defining variables
    @variables effmodeldual begin
        λ[i in 1:nDMU] >= 0  # the constrain dual in the primal
        θ                    # the costs per output ???
    end

    # Defining constraints
    @constraints effmodeldual begin
        inConstr[i in 1:nI],   #
           θ*I₀[i] - dot(I[:,i], λ) >=  0.0
        oRegularisationConstr[j in 1:nO], # regularisation of outputs ??
            dot(O[:,j],λ) >= O₀[j]
    end

    # Defining objective
    @objective effmodeldual Min begin
         θ
    end

    # Printing the model (for debugging)
    #print(effmodel) # The model in mathematical terms is printed

    # Solving the model
    optimize!(effmodeldual)

    status = termination_status(effmodeldual)

    if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED) && has_values(effmodeldual)
        λv = value.(λ)
        θv = value(θ)
        dualsI = dual.(inConstr)
        dualsO = dual.(oRegularisationConstr)
        obj = objective_value(effmodeldual)
        outPass2 = dmuPass2(θv,I₀,O₀,I,O)
        s⁻ = outPass2.s⁻ # input excesses
        s⁺ = outPass2.s⁺ # output shortfalls
        eff  = (isapprox.(obj,1.0,atol=1e-15) && all(isapprox.(s⁻,0.0,atol=1e-15)) && all(isapprox.(s⁺,0.0,atol=1e-15))) # efficient if the objective is 1 AND no weigths are zero (otherwise it is on one periphal side of the frontier and shadowed by other points)
        #refSet = Dict{Int64,Float64}()
        #[refSet[i] = -f for (i,f) in enumerate(duals) if f != 0.0]
        #eff  = (isapprox.(obj,1.0,atol=1e-15) && !any(isapprox.(wI,0.0,atol=1e-15)) && !any(isapprox.(wI,0.0,atol=1e-15))) # efficient if the objective is 1 AND no weigths are zero (otherwise it is on one periphal side of the frontier and shadowed by other points)
        #return (eff=eff, obj=obj, wI=wI, wO = wO, refSet=refSet)
        return (computed=true, eff=eff, obj=obj,  λ=λv, θ=θv, dualsI=dualsI, dualsO=dualsO, s⁻=s⁻, s⁺=s⁺)
    else
        #return (eff=missing,obj=missing,wI=missing,wO = missing,refSet=missing)
        return (computed=false, eff=eff, obj=missing, λ=missing, θ=missing, dualsI=missing , dualsO=missing , s⁻=missing, s⁺=missing)
    end
end

# Cooper & oth., p.44
function dmuPass2(θ,I₀,O₀,I,O)
    (nDMU,nI,nO) = size(I,1), size(I,2), size(O,2)

    effmodeldual = Model(optimizer_with_attributes(GLPK.Optimizer, "msg_lev" => GLPK.GLP_MSG_OFF)) # we choose GLPK with a verbose output
    # Defining variables
    @variables effmodeldual begin
        λ[i in 1:nDMU] >= 0  # the constrain dual in the primal
        s⁻[i in 1:nI]  >= 0  # input excesses
        s⁺[i in 1:nO]  >= 0  # output shortfalls
    end

    # Defining constraints
    @constraints effmodeldual begin
        inConstr[i in 1:nI],   #
           θ*I₀[i] - dot(I[:,i], λ) ==  s⁻[i]
        oRegularisationConstr[j in 1:nO], # regularisation of outputs ??
            dot(O[:,j],λ) - O₀[j] == s⁺[j]
    end

    # Defining objective
    @objective effmodeldual Max begin
         sum(s⁻)+sum(s⁺)
    end

    # Printing the model (for debugging)
    #print(effmodel) # The model in mathematical terms is printed

    # Solving the model
    optimize!(effmodeldual)

    status = termination_status(effmodeldual)

    if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED) && has_values(effmodeldual)
        λv = value.(λ)
        s⁻v = value.(s⁻)
        s⁺v = value.(s⁺)
        obj = objective_value(effmodeldual)
        return (computed=true, obj=obj, λ=λv, s⁻=s⁻v, s⁺=s⁺v )
    else
        #return (eff=missing,obj=missing,wI=missing,wO = missing,refSet=missing)
        return (computed=false, obj=missing, λ=missing,s⁻=missing, s⁺=missing )
    end
end


using JuMP
using CPLEX
using Combinatorics

include("LSP_PLNE.jl")
include("InstanceLoader.jl")
include("ResolvePlne.jl")
include("Helper.jl")
include("VRP_PLNE.jl")

function createPDI_Bard_Nananukul_compacte(params, nodes, demands, costs)
    model = Model(CPLEX.Optimizer)
    n = params["n"] #nombre de revendeur (exclu le fournisseur donc)
    m = params["k"] #nb de véhicules
    l = params["l"] #nombre de pas de temps
    Q = params["Q"] #capacité d'un vahicule
    C = params["C"] #production max du fournisseur à un certain pas de temps
    u = params["u"] #coût de production d'une unité
    f = params["f"] #coût de setup

    M=Dict(t=>min(C,sum(sum( demands[i,j] for i in 1:n) for j in t:l)) for t in 1:l )
    M_tilde=Dict((i,t)=> min( nodes[i]["L"], Q, sum(demands[i,j] for j in t:l) ) for i in 1:n,t in 1:l)

    @variable(model, p[t=1:l]>=0) #(13)

    @variable(model, I[0:n, 0:l] >= 0) #(13)
    for i in 0:params["n"]
        @constraint(model, I[i, 0] == nodes[i]["L0"])
    end

    @variable(model, q[1:n, 1:l] >= 0) #(13)
    @variable(model, y[1:l], Bin) #(14)

    @variable(model, x[i=0:n, j=0:n,t=1:l], Bin) #(14)
    for i in 0:n, t in 1:l
        delete(model, x[i, i, t]) #on enlève les variables qui correspondent aux arêtes en trop (les (i, i))
    end

    @variable(model, z[i=1:n, t=1:l], Bin) #(15)
    @variable(model, 0 <= z0[ 0 , t=1:l ] <= m, Int) #(16) et (10) #ATTENTION IL Y A DEUX z À GERER: z0=z QUAND i=0 CAR JuMP NE NOUS LAISSE PAS INSTANCIER EN DEUX FOIS UNE VARIABLE, OR C'EST NECESSAIRE CAR LES z0 SONT DES ENTIERS POSITIFS ET LES z SONT DES NOMBRE BINAIRES
    # @variable(model, 0<=w[i=1:n,t=1:l]) #(12 partie 1)

    for t in 1:l

        @constraint(model, I[0, t-1] + p[t] == I[0, t] + sum(q[i, t] for i in 1:n)) #(2)
        @constraint(model, p[t] <= M[t] * y[t]) #(4)
        @constraint(model, I[0, t] <= nodes[0]["L"]) #(5)

        @constraint(model, sum( x[j,0,t] + x[0,j,t] for j in 1:n ) == 2*z0[0,t]) #(9 que pour i=0)

        for i in 1:n
			@constraint(model, I[i, t-1] + q[i, t] == demands[i, t] + I[i, t]) #(3)
			@constraint(model, I[i, t-1] + q[i, t] <= nodes[i]["L"]) #(6)
            @constraint(model,q[i,t]<=M_tilde[i,t]*z[i,t]) #(7) la qté à livrer ne peut être positive que si on visite le noeud à ce pas de temps et ne doit pas dépasser la capacité d'un seul véhicule (un noeud n'est servi que par un seul véhicule)

            nodesIndexWithoutI = filter(e -> e != i, 0:n)
            @constraint(model, sum( x[i,j,t] for j in nodesIndexWithoutI ) == z[i,t]) #(8)
            @constraint(model, sum( x[j,i,t] + x[i,j,t] for j in nodesIndexWithoutI ) == 2*z[i,t]) #(9 avec i in 1:n)

            # @constraint(model,w[i , t] <= Q*z[i,t]) # (12 partie 2)
		end

        # for ((i,j),edgeCost) in costs
        #     if(i!=0 && j!=0)
        #         @constraint(model,w[i,t]-w[j,t]>=q[i,t]-M_tilde[i,t]*(1-x[i,j,t])) #(11)
        #     end
        # end
    end

    @objective(model,Min,sum(u*p[t]+f*y[t]+sum(nodes[i]["h"]*I[i,t] for i in 0:n)+sum(edgeCost*x[i,j,t] for ((i,j),edgeCost) in costs) for t in 1:l)) #(1)

    return model

end

#=
Formulation non compacte, la contrainte (29) contient 2^n inégalités
=#
function createPDI_Boudia(params, nodes, demands, costs)
    model = Model(CPLEX.Optimizer)
    n = params["n"] #nombre de revendeur (exclu le fournisseur donc)
    m = params["k"] #nb de véhicules
    l=params["l"] #nombre de pas de temps
    Q = params["Q"] #capacité d'un vahicule
    C=params["C"] #production max du fournisseur à un certain pas de temps
    u=params["u"] #coût de production d'une unité
    f=params["f"] #coût de setup
    a=1

    M=Dict(t=>min(C,sum(sum( demands[i,j] for i in 1:n) for j in t:l)) for t in 1:l )
    M_tilde=Dict((i,t)=> min( nodes[i]["L"], Q, sum(demands[i,j] for j in t:l) ) for i in 1:n,t in 1:l)

    @variable(model,p[t=1:l]>=0) #(31)

    @variable(model, I[0:n, 0:l] >= 0) #(31)
    for i in 0:params["n"]
        @constraint(model, I[i, 0] == nodes[i]["L0"])
    end

    @variable(model, q[1:n, k=1:m, 1:l] >= 0) #(31)
    @variable(model, y[1:l], Bin) #(32)

    @variable(model, x[i=0:n, j=0:n, k=1:m, t=1:l], Bin) #(32)
    for i in 0:n, t in 1:l, k=1:m
        delete(model, x[i, i, k, t]) #on enlève les variables qui correspondent aux arêtes en trop (les (i, i))
    end

    @variable(model, z[i=0:n, k=1:m, t=1:l], Bin) #(32)

    for t in 1:l
        @constraint(model, I[0,t-1]+p[t] == sum(sum(q[i,k,t]+I[0,t] for k in 1:m) for i in 1:n)) #(21)
        @constraint(model,p[t]<=M[t]*y[t]) #(23)
        @constraint(model, I[0,t]<=nodes[0]["L"]) #(24)
        for i in 1:n
            @constraint(model,I[i,t-1]+sum(q[i,k,t] for k in 1:m)==demands[i,t]+I[i,t]) #(22)
            #@constraint(model, I[i,t-1]+sum(q[k,i,t] for k in 1:m)<=nodes[i]["L"]) #(25) #erreur sur le PDF ?
            @constraint(model, I[i,t-1]+sum(q[i,k,t] for k in 1:m)<=nodes[i]["L"]) #(25)
            @constraint(model,sum(z[i,k,t] for k in 1:m)<=1) #(27)
            for k in 1:m
                @constraint(model,q[i,k,t]<=M_tilde[i,t]*z[i,k,t]) #(26)
                nodesIndexWithoutI = filter(e -> e != i, 0:n)
                @constraint(model,sum(x[j,i,k,t] for j in nodesIndexWithoutI)+sum(x[i,j,k,t] for j in nodesIndexWithoutI)==2*z[i,k,t]) #(28 pour i>=1)
            end
        end

        for k in 1:m
            @constraint(model,sum(x[j,0,k,t] for j in 0:n)+sum(x[0,j,k,t] for j in 1:n)==2*z[0,k,t]) #(28 pour i=0)
            @constraint(model,sum(q[i,k,t] for i in 1:n)<=Q*z[0,k,t]) #(30)

            for S in powerset(1:n, 2, n)
                @constraint(model,sum(sum(x[i,j,k,t] for j in S) for i in S)<=length(S)-1) #(29)
            end
        end

    end
    @objective(model,Min,sum(u*p[t]+f*y[t]+sum(nodes[i]["h"]*I[i,t] for i in 0:n)+sum(edgeCost*sum(x[i,j,k,t] for k in 1:m) for ((i,j),edgeCost) in costs) for t in 1:l)) #(20)

    return model

end


"""
  derivative_series_from_initial!(; initial_series::ComplexOffsetVector,
    derivatives_series ::ComplexOffsetMatrix)

Transform initial series into array of derivatives' and integrals' series

Given long Taylor series
``f(z) = \\sum_{n=0}^N a_n z^n``
generates array of series
``\\frac{1}{k!}f^{(k)}(z) = \\sum_{n=0}^M b_{k,n} z^n, 0 < z ≤ K_2``,
``f^{(k)}(z) = \\sum_{n=0}^M b_{k,n} z^n, K_1 ≤ z < 0``,
where K1 and K2 are first and last indices if *derivative_series*.

Assuming  N ≥ M + (K_2 - K_1 + 1).
"""
function derivative_series_from_initial!(;
        initial_series     ::ComplexOffsetVector,
        derivatives_series ::ComplexOffsetMatrix
        )
    #####
    upper_term       = lastindex(derivatives_series, 1)
    upper_derivative = lastindex(derivatives_series, 2)
    lower_derivative = firstindex(derivatives_series, 2)

    #derivatives_series[0 : upper_term, 0] .= initial_series[0 : upper_term]
    for n in 0 : upper_term
        derivatives_series[n, 0] = initial_series[n]
    end

    for k in 1 : upper_derivative
        for n in 1 : (upper_term + upper_derivative - k + 1)
            initial_series[n-1] = initial_series[n] * n / (k+1)
        end
        initial_series[upper_term + upper_derivative - k + 1] = .0im

        #@views derivatives_series[:, k] .= initial_series[0 : upper_term]
        for n in 0 : upper_term
            derivatives_series[n, k] = initial_series[n]
        end
    end

    for k in -1 : -1 : lower_derivative
        derivatives_series[0, k] = initial_series[k]
        for n in 1 : upper_term
            derivatives_series[n, k] = derivatives_series[n-1, k+1] / n
        end
    end
end

function construct_dict_of_wei_series(;
    delta_to_nterms ::RComplex2IntDict,
    wei             ::Weierstrass
    )
#####
    max_n_terms = 0
    for v in values(delta_to_nterms)
        if v[1] > max_n_terms
            max_n_terms = v[1]
        end
    end

    wei_sequence = OffsetArray{ComplexF64}(undef, -2 : 2*max_n_terms)

    wei_series_dict = Dict{RationalComplex, ComplexOffsetMatrix}()

    for delta in keys(delta_to_nterms)
        upper_term = delta_to_nterms[delta]

        # Для вычисления разложений в ряд производных нужно разложить исходную
        # функцию с запасом членов.
        # Всего нужно n производных по n членов в каждой.
        diff_upper_term = 2 * upper_term

        if delta != 0
            z = real(delta)*wei.ω1 + imag(delta)*wei.ω3
            norm_factor = abs(z)

            weierstrass_normalized!(z=z,
                                norm_factor=norm_factor,
                                w=wei,
                                upper_term=diff_upper_term,
                                output=wei_sequence)
        else
            expand_at_pole!(output=wei_sequence,
                            weierstrass_data=wei,
                            upper_term=diff_upper_term)
        end

        series_array = OffsetArray{ComplexF64}(undef, 0:upper_term, -1:upper_term-2)

        derivative_series_from_initial!(initial_series=wei_sequence,
                                        derivatives_series=series_array)

        wei_series_dict[delta] = series_array
    end

    return wei_series_dict
end


using Base.Test
using ThreePhotons

function test_random_rotations()
  p1 = [0,1,0]
  p2 = rand(3)#[0,0,1]#/sqrt(2)
  s1 = 0
  s2 = 0
  for i = 1:10000
      rot = random_rotation(3)
      s1 += rot*p1
      s2 += rot*p2
  end
  println(s1/10000)
  println(s2/10000)
end

function test_random_rotations2()
  val = sum([det(random_rotation(3)) for i = 1:10000])
  println(val)
end

#Tests if random_rotation for D=3 samples all rotations uniformly
function test_complettness_of_rotations(random_rotation_generator=random_rotation)

  angles = [get_euler_angles(random_rotation_generator(3)) for l = 1:100000]
  a = Base.hist(convert(Array{Float64},map((x)->x[1], angles)), 100)
  b = Base.hist(convert(Array{Float64},map((x)->x[2], angles)), 100)
  c = Base.hist(convert(Array{Float64},map((x)->x[3], angles)), 100)

  x1 = collect(a[1])
  x2 = collect(b[1])
  x3 = collect(c[1])
  plot(x1[1:length(x1)-1], a[2]/sum(abs, a[2]), label="phi")
  plot(x2[1:length(x2)-1], b[2]/sum(abs, b[2]), label="theta")
  plot(x3[1:length(x3)-1], c[2]/sum(abs, c[2]), label="gamma")
  xlabel("Angle [rad]")
  ylabel("Distribution of Angles")
  legend()
  # [a[1];]

end

function test_random_rotations3(func=random_rotation)
    global veclist = Float64[];
    init = [1,0,0]
    append!(veclist, init)
    num = int(1e3)
    for i = 1:num
        append!(veclist, func(3)*init)
    end
    # global z = zeros(3,num)
    global v = reshape(veclist, 3, num+1)
    scatter3D( v[1,:], v[2,:], v[3,:])
    xlim(-1, 1)
    ylim(-1,1)
    zlim(-1,1)
end

function test_random_rotations4(func=random_rotation)
    global veclist = Float64[];
    init = [1,0]
    append!(veclist, init)
    num = int(1e3)
    for i = 1:num
        append!(veclist, func(2)*init)
    end
    # global z = zeros(3,num)
    global v = reshape(veclist, 2, num+1)
    scatter( v[1,:], v[2,:])
    xlim(-1, 1)
    ylim(-1,1)
    # zlim(-1,1)
end

function test_random_roation_stepper()
    global veclist = Float64[];
    init = [1,0,0]
    num = int(1e4)
    for i = 1:num
        init = random_rotation_step(3, pi/8)*init
        append!(veclist, init)
    end
    # global z = zeros(3,num)
    global v = reshape(veclist, 3, num)
    scatter3D( v[1,:], v[2,:], v[3,:])
    xlim(-1, 1)
    ylim(-1,1)
    zlim(-1,1)
end

"""Tests SON_parametrized by throwing random angles and plotting the point cloud in 3D. This should give a seamlessly but ununiform sampling of the 3D sphere."""
function test_SON_paramtrized()
  r = 0:0.1:pi
  list = []
  for i = 1:1000
    push!(list, [0 0 1] * SON_parametrized(3, [2*pi*rand(), 2*pi*rand(), 2*pi*rand()]))
  end
  x = [list[i][1] for i=1:length(list)]
  y = [list[i][2] for i=1:length(list)]
  z = [list[i][3] for i=1:length(list)]
  scatter3D(x,y,z)
end


@testset "Generic" begin

    let P = 4, Q = 3, rng = MersenneTwister(123456), N = 100

        # Utility for generating square matrices, vectors, non-square matrices and scalars.
        mPP, mQQ = ()->randn(rng, P, P), ()->randn(rng, Q, Q)
        mPQ, mQP = ()->randn(rng, P, Q), ()->randn(rng, Q, P)
        mPQQP = ()->randn(rng, P * Q, P * Q)
        v, sc = ()->randn(rng, P), ()->randn(rng)
        psd = ()->(A = randn(rng, P, P); transpose(A) * A + 1e-3I)

        # Test all of the unary sensitivities.
        @test check_errs(N, unary_ȲD(-)..., mPQ, mPQ, mPQ)
        @test check_errs(N, unary_ȲD(trace)..., sc, mPP, mPP)
        @test check_errs(N, unary_ȲD(inv)..., mPP, mPP, mPP)
        @test check_errs(N, unary_ȲD(det)..., sc, mPP, mPP)
        @test check_errs(N, unary_ȲD(logdet)..., sc, psd, psd)
        @test check_errs(N, unary_ȲD(transpose)..., mQP, mPQ, mPQ)
        @test check_errs(N, unary_ȲD(ctranspose)..., mQP, mPQ, mPQ)
        @test check_errs(N, unary_ȲD(vecnorm)..., sc, mPQ, mPQ)
        @test check_errs(N, unary_ȲD(vecnorm)..., sc, sc, sc)

        # Test all of the binary sensitivities.
        @test check_errs(N, binary_ȲD(*, 1, mQP)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(*, 2, mPQ)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(At_mul_B, 1, mQP)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(At_mul_B, 2, mQP)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(A_mul_Bt, 1, mPQ)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(A_mul_Bt, 2, mPQ)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(At_mul_Bt, 1, mPQ)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(At_mul_Bt, 2, mQP)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(Ac_mul_B, 1, mQP)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(Ac_mul_B, 2, mQP)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(A_mul_Bc, 1, mPQ)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(A_mul_Bc, 2, mPQ)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(Ac_mul_Bc, 1, mPQ)..., mPP, mQP, mQP)
        @test check_errs(N, binary_ȲD(Ac_mul_Bc, 2, mQP)..., mPP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(/, 1, mQQ)..., mPQ, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(/, 2, mPQ)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(At_rdiv_B, 1, mQQ)..., mPQ, mQP, mQP)
        @test check_errs(N, binary_ȲD(At_rdiv_B, 2, mQP)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(A_rdiv_Bt, 1, mQQ)..., mPQ, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(A_rdiv_Bt, 2, mPQ)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(At_rdiv_Bt, 1, mQQ)..., mPQ, mQP, mQP)
        @test check_errs(N, binary_ȲD(At_rdiv_Bt, 2, mQP)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(Ac_rdiv_B, 1, mQQ)..., mPQ, mQP, mQP)
        @test check_errs(N, binary_ȲD(Ac_rdiv_B, 2, mQP)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(A_rdiv_Bc, 1, mQQ)..., mPQ, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(A_rdiv_Bc, 2, mPQ)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(Ac_rdiv_Bc, 1, mQQ)..., mPQ, mQP, mQP)
        @test check_errs(N, binary_ȲD(Ac_rdiv_Bc, 2, mQP)..., mPQ, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(\, 1, mQP)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(\, 2, mQQ)..., mQP, mQP, mQP)
        @test check_errs(N, binary_ȲD(At_ldiv_B, 1, mQP)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(At_ldiv_B, 2, mQQ)..., mQP, mQP, mQP)
        @test check_errs(N, binary_ȲD(A_ldiv_Bt, 1, mPQ)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(A_ldiv_Bt, 2, mQQ)..., mQP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(At_ldiv_Bt, 1, mPQ)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(At_ldiv_Bt, 2, mQQ)..., mQP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(Ac_ldiv_B, 1, mQP)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(Ac_ldiv_B, 2, mQQ)..., mQP, mQP, mQP)
        @test check_errs(N, binary_ȲD(A_ldiv_Bc, 1, mPQ)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(A_ldiv_Bc, 2, mQQ)..., mQP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(Ac_ldiv_Bc, 1, mPQ)..., mQP, mQQ, mQQ)
        @test check_errs(N, binary_ȲD(Ac_ldiv_Bc, 2, mQQ)..., mQP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(vecnorm, 1, sc)..., sc, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(vecnorm, 2, mPQ)..., sc, sc, sc)
        @test check_errs(N, binary_ȲD(vecnorm, 1, sc)..., sc, sc, sc)
        @test check_errs(N, binary_ȲD(vecnorm, 2, sc)..., sc, sc, sc)
        @test check_errs(N, binary_ȲD(kron, 1, mQP)..., mPQQP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD(kron, 2, mPQ)..., mPQQP, mQP, mQP)
        @test check_errs(N, binary_ȲD_inplace(kron, 1, mQP, mPQ())..., mPQQP, mPQ, mPQ)
        @test check_errs(N, binary_ȲD_inplace(kron, 2, mPQ, mQP())..., mPQQP, mQP, mQP)
    end
end


using PWS
using Plots

system = PWS.chemotaxis_system(
    mean_L = 20,
    num_receptors = 10_000,
    Y_tot = 5_000,
    LR_timescale = 1e-2,
    Y_timescale = 1e-1,
    Y_ratio = 1/8,
    LR_ratio = 0.5,
    q=0,
    dtimes = 0:0.04:2
)

@time sol = PWS._solve(system)

p = plot(sol)

@time conf = PWS.generate_configuration(system)

cond_ens = PWS.ConditionalEnsemble(system)

alg = PWS.SMCEstimate(256)

@profview result = PWS.simulate(alg, conf, cond_ens).samples

result

using Distributions

for x in eachrow(result)
    @show mean(x) / var(x)
end

θ = 11.5
p = plot(xlabel="Path Action", ylabel="Probability")
fitted_dists = []
for (c, i) in enumerate(11:10:51)
    x = result[i,:]
    t = dtimes[i]
    x = (mean(x) + var(x) / θ) .- x
    plot!(p, x, seriestype=:stephist, xlim=(-10,400), fill=true, normalize=true, fillalpha=0.1, label="T=$t", color=c)

    fitted_gamma = @show fit(Gamma{Float64}, x)
    push!(fitted_dists, fitted_gamma)
    plot!(p, 0:1:400, y->pdf(fitted_gamma, y), color=c, label="Gamma distribution fit")
end
p
savefig(plot(p, dpi=300), "~/Downloads/histograms.png")

p = plot(result[end,:], seriestype=:stephist, fill=true, legend=false, yscale=:log10, ylim=(1e-6,1e-2), normalize=true)
savefig(p, "~/Downloads/histogram.png")

using Statistics

function bootstrap(f, data::AbstractVector; bsamples=100)
    N = length(data)
    map(1:bsamples) do i
        sample_indices = rand(1:N, N)
        f(@view data[sample_indices])
    end
end

PWS.logmeanexp(result[end, :])

bdata = bootstrap(PWS.logmeanexp, result[end,:], bsamples=10^4)
h = histogram(bdata, title="Bootstrapping Histogram", xlabel="Path Action", ylabel="Count")
savefig(h, "~/Downloads/bootstrap.png")

using Distributions

p=plot()
for (i, shape_param) in enumerate([2, 5, 10, 20])
    dist = Gamma(shape_param, 1.0)
    plot!(p, 0.0:0.01:30, x->pdf(dist, x), color=i, linestyle=:solid, label="Gamma α=$shape_param")
    plot!(p, 0.0:0.01:30, x->pdf(dist, x)*exp(-x)*(scale(dist) + 1)^shape(dist), color=i, linestyle=:dot, fill=true, fillalpha=0.1, label="exp(-x)*Gamma(x)")
end
p
savefig(plot(p, dpi=300), "~/Downloads/gamma.png")

p=plot()
for (i, dist) in enumerate(fitted_dists)
    plot!(p, 0.0:1:400, x->pdf(dist, x), color=i, linestyle=:solid)
    plot!(p, 0.0:1:400, x->pdf(dist, x)*exp(-x)*(scale(dist) + 1)^shape(dist), color=i, linestyle=:dot, fill=true, fillalpha=0.1)
end
p