# Start with a copy of Petri.jl examples/lotka-volterra.jl.
#
using PNML
using Petri
using LabelledArrays
using Plots
using OrdinaryDiffEq

"""
PNML model for the original example below. Note that the type is "nonstandard"!
"""
 str = """<?xml version="1.0"?>
    <pnml xmlns="http://www.pnml.org/version-2009/grammar/pnml">
        <net id="net0" type="nonstandard">
        <page id="page0">
            <place id="rabbits"> <initialMarking> <text>100.0</text> </initialMarking> </place>
            <place id="wolves">  <initialMarking> <text>10.0</text> </initialMarking> </place>
            <transition id ="birth">     <rate> <text>0.3</text> </rate> </transition>
            <transition id ="death">     <rate> <text>0.7</text> </rate> </transition>
            <transition id ="predation"> <rate> <text>0.015</text> </rate> </transition>
            <arc id="a1" source="rabbits"   target="birth">     <inscription><text>1</text> </inscription> </arc>
            <arc id="a2" source="birth"     target="rabbits">   <inscription><text>2</text> </inscription> </arc>
            <arc id="a3" source="wolves"    target="predation"> <inscription><text>1</text> </inscription> </arc>
            <arc id="a4" source="rabbits"   target="predation"> <inscription><text>1</text> </inscription> </arc>
            <arc id="a5" source="predation" target="wolves">    <inscription><text>2</text> </inscription> </arc>
            <arc id="a6" source="wolves"    target="death">     <inscription><text>1</text> </inscription> </arc>
        </page>
        </net>
    </pnml>
"""

net = PNML.SimpleNet(str)

# **Step 1:** Define the states and transitions of the Petri Net
#
# Here we have 2 states, wolves and rabbits, and transitions to
# model predation between the two species in the system

S = PNML.place_ids(net) # [:rabbits, :wolves]
Δ = PNML.transition_function(net)
# keys are transition ids
# values are tuple of input, output vectors of "tuples" place id = inscription
#LVector(
#       birth=(LVector(rabbits=1), LVector(rabbits=2)),
#       predation=(LVector(wolves=1, rabbits=1), LVector(wolves=2)),
#       death=(LVector(wolves=1), LVector()),
#     )
lotka = Petri.Model(S, Δ)

display(Graph(lotka))

# **Step 2:** Define the parameters and transition rates
#
# Once a model is defined, we can define out initial parameters `u0`, a time
# span `tspan`, and the transition rates of the interactions `β`

u0 = PNML.initialMarking(net) #LVector(wolves=10.0, rabbits=100.0) # initialMarking
tspan = (0.0,100.0)
β = PNML.rates(net) #LVector(birth=.3, predation=.015, death=.7); # transition rate

# **Step 3:** Generate a solver and solve
#
# Finally we can generate a solver and solve the simulation

prob = ODEProblem(lotka, u0, tspan, β) # transform PetriNet problem using vectorfield(m)
sol = OrdinaryDiffEq.solve(prob,Tsit5(),reltol=1e-8,abstol=1e-8)

plot(sol)


# # Custom Cone Constraint
#
# This example demonstrate how the user can extend `COSMO` with his own custom convex cones and use them in constraints. To make things easy, we will implement the simple cone of **Nonpositives**, i.e. $\mathbb{R}_{-}^n = \{x \in   \mathbb{R}^n \mid x_i \leq 0\}$.
# ## Cone definition and projection function
# We start by creating a concrete subtype of our cone interface-type: `COSMO.AbstractConvexCone{T}`, where `T` is a parameter for the floating-point precision. The only field that is strictly required for our cone definition is `dim`, which is used by the solver to store information about the cone dimension. However, you can also use the object to store other information, or allocate the workspace necessary for the projection step.
using COSMO, LinearAlgebra, SparseArrays, Test

## define new cone type
struct Nonpositives{T} <: COSMO.AbstractConvexSet{T}
    dim::Int64
end
#-
# Next, we define a projection method for `Nonpositives{T}` that takes a vector `x` and projects it onto our custom cone. For the cone of nonpositive vectors the projection simply sets all positive elements of `x` to zero:

function COSMO.project!(x::AbstractVector{T}, C::Nonpositives{T}) where {T <: AbstractFloat}
    x .= min.(x, zero(T))
end

#-
# This is all that is required to get a basic constraint working. Let's test our new cone/constraint by solving the following LP:
# $$
# \begin{array}{ll} \text{maximize} & x_1 + x_2 + x_3\\
# \text{subject to} &  x_1 \leq 3 \\
#                   &  x_2 \leq 2 \\
#                   &  x_1 + x_3 =  5
# \end{array}
# $$
# We define our decision vector $x = (x_1, x_2, x_3)$ and setup the problem:
n = 3
P = spzeros(n, n)
q = -ones(n)

## A1 * x + b1 <= 0
A1 = diagm(0 => ones(2))
b1 = [-3.; -2.]
c1 = COSMO.Constraint(A1, b1, Nonpositives, n, 1:2); #here we use our new cone

## x_1 + x_3 == 5
A2 = [1. 0 1.]
b2 = [-5.]
c2 = COSMO.Constraint(A2, b2, COSMO.ZeroSet);

## assemple and solve
model = COSMO.Model();
assemble!(model, P, q, [c1; c2]);
res = COSMO.optimize!(model);

#-
res.x

#-
obj_val = -dot(q, res.x)
# The problem was solved using constraints involving our new cone and yields the expected result. Next, we want to add the ability to detect infeasible problems.
#
# ## Support infeasibility detection
# If no further information about the new cone is provided, the infeasibility detection is disabled. However, by defining the two additional methods `in_dual` and `in_pol_recc` we can support infeasibility detection. More information on how infeasible problems are detected in `COSMO` can be found here \[1\]. We will have to add the ability for the solver to check whether a vector is in the [dual cone](https://en.wikipedia.org/wiki/Dual_cone_and_polar_cone) $\mathcal{K}^*$ of our new cone $\mathcal{K}$ and whether a vector is in the recession cone of the [polar cone](https://en.wikipedia.org/wiki/Dual_cone_and_polar_cone) ${\mathcal{K}^\circ}^\infty$ of our cone. The first function is used to check for primal infeasibility, while the second function  is used to check for dual infeasibility. Luckily, for the Nonpositives-cone this is straightforward:
#
# $$
# {\mathbb{R}_{-}^n}^* = \mathbb{R}_{-}^n, \quad {{\mathbb{R}_{-}^n}^\circ}^\infty = \mathbb{R}_{+}^n.
# $$

function COSMO.in_dual(x::AbstractVector{T}, C::Nonpositives{T}, tol::T) where {T <: AbstractFloat}
	return !any( x-> (x > -tol), x)
end

function COSMO.in_pol_recc(x::AbstractVector{T}, C::Nonpositives{T}, tol::T) where {T <: AbstractFloat}
	return !any( x-> (x < tol), x)
end
# Notice that for numerical reasons, we give the check-functions a bit of slack using the tolerance parameter `tol`.
# To test the infeasibility detection, we will attempt to use our new cone to solve the clearly dual infeasible problem:
# $$
# \begin{array}{ll} \text{minimize} & x\\
# \text{subject to} &  x\leq 3
# \end{array}
# $$
n = 1
P = spzeros(n, n)
q = [1.]

##  x <= 3 <=> x - 3 in Nonpositives
Ai = [1.]
bi = [-3.]
ci = COSMO.Constraint(Ai, bi, Nonpositives);

model = COSMO.Model();
assemble!(model, P, q, [ci]);
res = COSMO.optimize!(model);
#-
@test res.status == :Dual_infeasible

# which is what we would expect.
# ## Using new cone with JuMP
# The nice composibility of Julia-code allows us to use our new cone definition even when the problem is modelled with `JuMP`. For this to work, we define a concrete subtype of `MOI.AbstractSet`, that JuMP can use to build a constraint.
using MathOptInterface, JuMP
import Base.copy
const MOI = MathOptInterface

struct NonPos <: MOI.AbstractVectorSet
	dimension::Int
end
Base.copy(set::NonPos) = set #this is needed for MOI
# Next, we tell `COSMO` that whenever `JuMP` wants to solve a problem with a `NonPos`-cone it should translate it into a `Nonpositives` constraint and we also tell `MOI` that we now support constraints of this new mysterious cone `NonPos`.
function COSMO.processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::NonPos) where {T <: AbstractFloat}
    push!(cs, Nonpositives{T}(length(rows)))
    nothing
end
#tell MOI, that COSMO supports constraints with this new cone
MOI.supports_constraint(optimizer::COSMO.Optimizer, ::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}}}, ::Type{NonPos}) = true

# We should now be able to model the LP from above in `JuMP` and solve it internally using our `Nonpositives` cone and projection function.
model = JuMP.Model(COSMO.Optimizer);
@variable(model, x[1:3]);
@objective(model, Max, x[1] + x[2] + x[3]);
@constraint(model, A1 * x[1:2] .+ b1 in NonPos(2));
@constraint(model, x[1] + x[3] == 5);
JuMP.optimize!(model)

#-
x_opt = JuMP.value.(x)
# You can see in the solver output that indeed a set `Nonpositives` of `dim: 2` was used.
#
# The discussed cone of Nonpositives is of course trivial, but the ability to define new cones and constraints for `COSMO` can be very powerful to model complex problems.

# ## References
# [1] Garstka et al. - COSMO: A conic operator splitting method for convex conic problems (2020)


#=
We compute the probability, $p_{\text{self}}$, of a match between a spacer of length $l_t = l_s + l_p$ and a host genome of length $L$. Both sequences are assumed to be random and uncorrelated, and have equal nucleotide usage.

* $l_s$ = spacer length
* $l_p$ = PAM length
* $L$ = host genome length

We consider the following model of cross-reactivity: within a length-$l_t$ alignment between spacer and host genome, up to $k_{\text{fix}}$ mismatches are tolerated at fixed positions, and up to $k_{\text{var}}$ mismatches are tolerated at any other position.

* $k_{\text{fix}}$ = no. of fixed-position mismatches
* $k_{\text{var}}$ = no. of variable-position mismatches

For a given alignment, the matching probability, $p_m$, is
$$\begin{aligned}
p_m(l_t,k_{\text{fix}},k_{\text{var}}) &= 4^{-l_t + k_{\text{fix}}} \sum_{i=0}^{k_{\text{var}}}\binom{l_t - k_{\text{fix}}}{i} 3^{i}\\
&\approx 4^{-l_t + k_{\text{fix}}} \binom{l_t - k_{\text{fix}}}{k_{\text{var}}} 3^{k_{\text{var}}},
\end{aligned}$$
since the largest term tends to dominate the sum.

Then, the probability that there exists an alignment somewhere in the host genome is
$$\begin{aligned}
p_{\text{self}}(L,l_t,k_{\text{fix}},k_{\text{var}}) &= 1 - [1 - p_m(l_t,k_{\text{fix}},k_{\text{var}})]^L\\
&\approx L p_m(l_t,k_{\text{fix}},k_{\text{var}}),
\end{aligned}$$
assuming $p_m \ll 1$.

Below, we define the following functions:

|                           | all terms of sum | largest term of sum |
|---------------------------|:----------------:|:-------------------:|
| not taking $p_m \ll 1$    | `F_self()`       | `p_self1()`         |
| taking $p_m \ll 1$        |                  | `p_self()`          |
=#

using SpecialFunctions

logbinom(l,k) = loggamma(l+1) - loggamma(l-k+1) - loggamma(k+1)

k_tot(k_fix,k_var,l_t) = k_fix + (logbinom(l_t-k_fix,k_var) + k_var*log(3))/log(4)

p_m(l_t,k_fix,k_var) = 4.0^(- l_t + k_tot(k_fix,k_var,l_t))

p_self(L,l_t,k_fix,k_var) = L * p_m(l_t,k_fix,k_var)

p_self1(L,l_t,k_fix,k_var) = 1 - exp(L * log(1 - p_m(l_t,k_fix,k_var)))

#=
$F_{\text{self}}(k)$ is also the CDF of the number of alignments with up to $k$ variable-position mismatches. Its PDF is
$$p_{nn}(k) = F_{\text{self}}(k) - F_{\text{self}}(k-1).$$

Note: `F_self()` and `p_nn()` require integer `k_var`, whereas `p_self()` and `p_self1()` allow non-integer `k_var` as input.
=#

F_self(L,l_t,k_fix,k_var::Int) = 1 - exp(L * log(1 - sum([p_m(l_t,k_fix,k) for k in 0:k_var])))

p_nn(L,l_t,k_fix,k_var::Int) = F_self(L,l_t,k_fix,k_var) - F_self(L,l_t,k_fix,k_var-1)


"""
Solves the equations of fluid flow using "Stable Fluids" by Jos Stam with the
FFT to obtain ultra-fast simulations.

Momentum:           ∂u/∂t + (u ⋅ ∇) u = − 1/ρ ∇p + ν ∇²u + f

Incompressibility:  ∇ ⋅ u = 0

u:  Velocity (2d vector)
p:  Pressure
f:  Forcing
ν:  Kinematic Viscosity
ρ:  Density
t:  Time
∇:  Nabla operator (defining nonlinear convection, gradient and divergence)
∇²: Laplace Operator

----

A unit square domain with periodic boundary conditions on each edge
(= simulation on a torus)

          1 +-------------------------------------------------+
            |                                                 |
            |             *                      *            |
            |          *           *    *    *                |
        0.8 |                                                 |
            |                                 *               |
            |     *       *                                   |
            |                      *     *                    |
        0.6 |                                            *    |
            |      *                         <- <- <-         |
            |                             *  <- <- <-         |
            |     -> -> ->               *   <- <- <-         |
            |     -> -> ->    *                *         *    |
        0.4 |     -> -> ->                                    |
            |                                                 |
            |      *            *             *               |
            |           *                             *       |
        0.2 |                       *           *             |
            |                               *                 |
            |  *          *      *                 *       *  |
            |                            *                    |
          0 +-------------------------------------------------+
            0        0.2       0.4       0.6       0.8        1

-> Centered in y but ofset in x there are two horizontal forcings in opposite
   directions

-> Two fluid streams will "collide" with each other

-----

Solution Strategy:

-> Start with zero velocity everywhere: u = [0, 0]

1. Add forces

    w₁ = u + Δt f

2. Convect by self-advection (set the value at the current
   location to be the value at the position backtraced
   on the streamline.) -> unconditionally stable

    w₂ = w₁(p(x, −Δt))

3. Diffuse and Project in Fourier Domain

    3.1 Forward Transformation into Fourier Domain

        w₂ → w₃

    3.2 Diffuse by "low-pass filtering" (convolution
        is multiplication in the Fourier Domain)

        w₄ = exp(− k² ν Δt) w₃

    3.3 Compute the (pseudo-) pressure in the Fourier Domain
        by evaluating the divergence in the Fourier Domain

        q = w₄ ⋅ k / ||k||₂

    3.4 Correct the velocities such that they are incompressible

        w₅ = w₄ − q k / ||k||₂

    3.5 Inverse Transformation back into spatial domain

        w₆ ← w₅

4. Repeat

k = [ kₓ, k_y ] are the spatial frequencies (= wavenumbers)

The Fourier Transformation implicitly prescribes the periodic
Boundary Conditions

------

Changes with respect to the original video (https://youtu.be/F7rWoxeGrko)

1. Use dark theme, set to equal aspect ratio and increase plot window size

2. Change to periodic clamping which respects the periodic boundary conditions,
   then also increase the number of time steps.

"""

using FFTW
using Plots   # Requires ColorSchemes.jl
using ProgressMeter
using Interpolations
using LinearAlgebra

N_POINTS = 250
KINEMATIC_VISCOSITY = 0.0001
TIME_STEP_LENGTH = 0.01
N_TIME_STEPS = 300

function backtrace!(
    backtraced_positions,
    original_positions,
    direction,
)
    # Euler Step backwards in time and periodically clamp into [0.0, 1.0]
    backtraced_positions[:] = mod1.(
        original_positions - TIME_STEP_LENGTH * direction,
        1.0,
    )
end

function interpolate_positions!(
    field_interpolated,
    field,
    interval_x,
    interval_y,
    query_points_x,
    query_points_y,
)
    interpolator = LinearInterpolation(
        (interval_x, interval_y),
        field,
    )
    field_interpolated[:] = interpolator.(query_points_x, query_points_y)
end

function main()
    element_length = 1.0 / (N_POINTS - 1)
    x_interval = 0.0:element_length:1.0
    y_interval = 0.0:element_length:1.0

    # Similar to meshgrid in NumPy
    coordinates_x = [x for x in x_interval, y in y_interval]
    coordinates_y = [y for x in x_interval, y in y_interval]

    wavenumbers_1d = fftfreq(N_POINTS) .* N_POINTS

    wavenumbers_x = [k_x for k_x in wavenumbers_1d, k_y in wavenumbers_1d]
    wavenumbers_y = [k_y for k_x in wavenumbers_1d, k_y in wavenumbers_1d]
    wavenumbers_norm = [norm([k_x, k_y]) for k_x in wavenumbers_1d, k_y in wavenumbers_1d]

    decay = exp.(- TIME_STEP_LENGTH .* KINEMATIC_VISCOSITY .* wavenumbers_norm.^2)

    wavenumbers_norm[iszero.(wavenumbers_norm)] .= 1.0
    normalized_wavenumbers_x = wavenumbers_x ./ wavenumbers_norm
    normalized_wavenumbers_y = wavenumbers_y ./ wavenumbers_norm

    # Define the forces
    force_x = 100.0 .* (
        exp.( - 1.0 / (2 * 0.005) * ((coordinates_x .- 0.2).^2 + (coordinates_y .- 0.45).^2))
        -
        exp.( - 1.0 / (2 * 0.005) * ((coordinates_x .- 0.8).^2 + (coordinates_y .- 0.55).^2))
    )

    # Preallocate all arrays
    backtraced_coordinates_x = zero(coordinates_x)
    backtraced_coordinates_y = zero(coordinates_y)

    velocity_x = zero(coordinates_x)
    velocity_y = zero(coordinates_y)

    velocity_x_prev = zero(velocity_x)
    velocity_y_prev = zero(velocity_y)

    velocity_x_fft = zero(velocity_x)
    velocity_y_fft = zero(velocity_y)
    pressure_fft = zero(coordinates_x)

    # Use a dark theme for plotting
    theme(:dark)

    @showprogress "Timestepping ..." for iter in 1:N_TIME_STEPS

        # (1) Apply the forces
        time_current = (iter - 1) * TIME_STEP_LENGTH
        pre_factor = max(1 - time_current, 0.0)
        velocity_x_prev += TIME_STEP_LENGTH * pre_factor * force_x

        # (2) Self-Advection by backtracing and interpolation
        backtrace!(backtraced_coordinates_x, coordinates_x, velocity_x_prev)
        backtrace!(backtraced_coordinates_y, coordinates_y, velocity_y_prev)
        interpolate_positions!(
            velocity_x,
            velocity_x_prev,
            x_interval,
            y_interval,
            backtraced_coordinates_x,
            backtraced_coordinates_y,
        )
        interpolate_positions!(
            velocity_y,
            velocity_y_prev,
            x_interval,
            y_interval,
            backtraced_coordinates_x,
            backtraced_coordinates_y,
        )

        # (3.1) Transform into Fourier Domain
        velocity_x_fft = fft(velocity_x)
        velocity_y_fft = fft(velocity_y)

        # (3.2) Diffuse by low-pass filtering
        velocity_x_fft .*= decay
        velocity_y_fft .*= decay

        # (3.3) Compute Pseudo-Pressure by Divergence in Fourier Domain
        pressure_fft = (
            velocity_x_fft .* normalized_wavenumbers_x
            +
            velocity_y_fft .* normalized_wavenumbers_y
        )

        # (3.4) Project the velocities to be incompressible
        velocity_x_fft -= pressure_fft .* normalized_wavenumbers_x
        velocity_y_fft -= pressure_fft .* normalized_wavenumbers_y

        # (3.5) Transform back into spatial domain
        velocity_x = real(ifft(velocity_x_fft))
        velocity_y = real(ifft(velocity_y_fft))

        # Advance in time
        velocity_x_prev = velocity_x
        velocity_y_prev = velocity_y

        # Visualize
        d_u__d_y = diff(velocity_x, dims=2)[2:end, :]
        d_v__d_x = diff(velocity_y, dims=1)[:, 2:end]
        curl = d_u__d_y - d_v__d_x
        display(heatmap(x_interval, y_interval, curl', c=:diverging_bkr_55_10_c35_n256, aspect_ratio=:equal, size=(680, 650)))
    end
end

main()


##############   EXAMPLE-0    ##############
# No errors in X nor in Y

using LinearFitXYerrors
using Plots; gr()

# Example-0a
# INPUT DATA:
# http://pba.ucdavis.edu/files/45007.pdf

X = [16.4, 17.2, 17.6, 18.0, 18.2, 18.5]
Y = [2.67, 2.75, 2.99, 3.14, 3.88, 4.23]

# COMPUTE and PLOT:
stxy = linearfitxy(X,Y, isplot=true);

# Example-0b
# INPUT DATA:
# Altman, D. and Gardner, M. [1988] Statistics in Medicine: Calculating confidence intervals for regression and correlation. British Medical Journal, 296(6631), pp.1238–1242.

# Table I
X = [91., 104, 107, 107, 106, 100, 92, 92, 105, 108]
Y = [9.8, 7.4, 7.9, 8.3, 8.3, 9.0, 9.7, 8.8, 7.6, 6.9]

# COMPUTE and PLOT:
stxy = linearfitxy(X,Y, isplot=true);    # σX =  σY = r = 0

savefig("Example0b_LinearFitXYerrors.png")
########################################


"""
---
Apply Chebyshev polynom of order L to input x
# --- Syntax
	y = cheb(L,x)
# --- Input parameters
- L	  : Chebyshev order [Int]
- x	  : Input [Any]
# --- Output parameters
- y	  : Output
# ---
# v 1.0 - Robin Gerzaguet.
"""
function cheb(L,x)
	## Apply Chebyshev polynom of order m to input x
	 x[abs.(x).<=1] = cos.(L .* acos.(x[abs.(x).<=1]));
	 x[abs.(x).>1]  = cosh.(L .* acosh.(abs.(x[abs.(x).>1])));
	 return x;
end

"""
---
Returns the Dolp Chebyshev filter of order L with desired attenation att

See Peter Lynch, "The Dolph-Chebyshev Window: A Simple Optimal Filter", Monthly Weather Review, Vol. 125, pp. 655-660, April 1997. (http://www.maths.tcd.ie/~plynch/Publications/Dolph.pdf)
Dolph, "A current distribution for broadside arrays which optimizes the relationship between beam width and side-lobe level", Proc. IEEE, 34, pp. 335-348.
		cheb(m-1, beta * cos(pi * k/m))
W(k) =	-------------------------------
			  cheb(m-1, beta)
# --- Syntax
 filterDC = dolphChebyshev(n,at)
# --- Input parameters
- n		  : Size of desired filter
- at	  : Attenation in dBB
# --- Output parameters
- filterDC : Filter impulse response [Array{Float64}]
# ---
# v 1.0 - Robin Gerzaguet.
"""
function dolphChebyshev(n,at);
	# --- Filter in frequency domain
	# --- DC constant
    gamma = 10^(-at/20);
    beta  = cosh(1/(n-1) * acosh(1/gamma));
	k     = collect(0:n-1);
    x     = beta*cos.(pi*k/n);
	p	  = cheb(n-1, x);
	if mod(n,2) == 1
		w = real(fft(p));
		M = Int((n+1)/2);
		w = w[1:M]/w[1];
		w = [w[M:-1:2];w];
	else
		off = exp.(1im*pi/n.*collect(0:n-1));
		# half-sample delay (even order)
		p .= p * transpose(off);
		w = real(fft(p));
		M = Int(n/2+1);
		w = w./w[2];
		w .= [w[M:-1:2];w[2:M]];
	end
	return w
end


using Statistics,QuadGK,FFTW

using Optim,Roots

mutable struct  EuropeanOption_crr
    St::Float64 #current assset price 当期价格
    K::Float64 #strike price 行权价
    T::Float64 #maturity time 到期时间
    t::Float64 #valuation date 有效日期
    r::Float64 #risk_less rate 无风险收益率
    σ::Float64 #volatility 波动率
    type::String #option type(call,put) 看涨、看跌
    # EuropeanOtion(St,K,T,t,r,σ,type)=new(St,K,T,t,r,σ,type)
end

function get_basic_parameters(o::EuropeanOption_crr,N::Int64)
    dt=o.T/N
    df=exp(-o.r*dt)
    u=exp(o.σ*√dt)
    d=1/u

    q=(exp(o.r*dt)-d)/(u-d)

    #初始化指数生成矩阵，即以u,d的概率生成二叉树网络，
    mu0=repeat(Vector(0:N),N+1)
    mu1=reshape(mu0,(N+1,N+1))
    md0=copy(mu1')
    mu2=mu1-md0
    mu=map(x->u^x,mu2)
    md=map(x->d^x,md0)
    S=o.St*(mu.*md)

    return dt,df,u,d,q,S

end

#CRR定价方法
function CRR_value(o::EuropeanOption_crr,N::Int64)

    dt,df,u,d,q,S=get_basic_parameters(o,N)

    if o.type=="call"
        V=map(x->max(x-o.K,0),S)
    else
        V=map(x->max(o.K-x,0),S)
    end

    VV=copy(V')
    z=0
    @simd for t= N:-1:1
        VV[1:N-z,t]=(q*VV[1:N-z,t+1]+(1-q)*VV[2:N-z+1,t+1])*df
        z+=1
    end
    return VV[1,1]
end

function CRR_call_value_FFT(o::EuropeanOption_crr,N::Int64)

    dt,df,u,d,q,S=get_basic_parameters(o,N)
    S0=copy(S')

    CT=map(x->max(x-o.K,0),S0[:,end])
    qv=zeros(N+1)
    qv[1]=q
    qv[2]=1-q

    C0_b=fft(exp(-o.r*o.T)*(ifft(CT).*fft(qv).^N))

    return real(C0_b[1])
end

#快速算法，如果不用struct,直接参数化输入，可大幅提升计算速度，6-7倍
function crr_value_fast(o::EuropeanOption_crr,N::Int64)
    Δt = o.T / N
    U = exp(o.σ * √Δt)
    D = 1 / U
    R = exp(o.r * Δt)
    p = (R - D) / (U - D)
    q = (U - R) / (U - D)

    if o.type == "put"
        Z = [max(0, o.K - o.St * exp((2 * i - N) *o.σ * √Δt)) for i = 0:N]
    else
        Z = [max(0, o.St * exp((2 * i - N) * σ * √Δt) - o.K) for i = 0:N]
    end

    @simd for n = N-1:-1:0
        for i = 0:n
            if o.type == "put"
                x = o.K - o.St * exp((2 * i - n) * o.σ * √Δt)
            else
                x = o.St * exp((2 * i - n) * o.σ * √Δt) - o.K
            end
            y = (q * Z[i+1] + p * Z[i+2]) / R
            Z[i+1] = max(x, y)
        end
    end

    return Z[1]
end

# @time t=crr(100,90,0.05,0.3,180/365,1000,"put")
o=EuropeanOption_crr(36.0,40.0,1.0,0,0.06,0.2,"put")
N=1000
#
# @time dt,df,u,d,q,S=get_basic_parameters(o,N)
# @time value=CRR_value(o,N)

# @time fft_value=CRR_value_FFT(o,N)

@time tg=crr_value_fast(o,N)


# ------------------------------------------------------------------------------------------
# # Quantifying Chaos for a Dynamical System
#
# Topics:
# * Introduction to Lyapunov exponents
# * Maximum Lyapunov Exponent
# * Lyapunov Spectrum
#
# **WARNING** - Compilation of functions in this tutorial takes *a lot of time*.
#
# ---
#
# In the previous tutorial we saw that for example the Shinriki oscillator showed something
# that "could be chaotic behavior" for a specific parameter value. How can we quantify that?
#
# # Lyapunov exponents
# * Lyapunov exponents measure the exponential separation rate of trajectories that are
# (initially) close.
#     * Consider the following picture, where two nearby trajectories are evolved in time:
#
#
#
# <img src="lyapunov.png" alt="Sketch of the Lyapunov exponent" style="width: 500px;"/>
#
#
# * $\lambda$ denotes the "maximum Lyapunov exponent".
# * A $D$-dimensional system has $D$ exponents.
# * In general, a trajectory is called "chaotic" if
#     1. it follows nonlinear dynamics
#     2. it is *bounded* (does not escape to infinity)
#     2. it has a positive Lyapunov exponent
#
# *(please be aware that the above is an over-simplification! See the textbooks cited in our
# documentation for more)*
#
# ---
# ------------------------------------------------------------------------------------------

using DynamicalSystems, BenchmarkTools, PyPlot

# ------------------------------------------------------------------------------------------
# Before computing Lyapunov exponents, we'll demonstrate the concept of exponential
# separation using a simple map: the *towel map*
#
# $$
# \begin{aligned}
# x_{n+1} &= a x_n (1-x_n) -0.05 (y_n +0.35) (1-2z_n) \\
# y_{n+1} &= 0.1 \left( \left( y_n +0.35 \right)\left( 1+2z_n\right) -1 \right)
# \left( 1 -1.9 x_n \right) \\
# z_{n+1} &= 3.78 z_n (1-z_n) + b y_n
# \end{aligned}
# $$
# ------------------------------------------------------------------------------------------

towel = Systems.towel()

# ------------------------------------------------------------------------------------------
# First we'll generate a trajectory for the towel map, `tr1`, from the default initial
# condition,
# ------------------------------------------------------------------------------------------

tr1 = trajectory(towel, 100)
summary(tr1)

# ------------------------------------------------------------------------------------------
# and then we will generate a second trajectory, `tr2`, with a starting point slightly
# shifted from the initial condition of `tr1`.
# ------------------------------------------------------------------------------------------

u2 = get_state(towel) + (1e-9 * rand(3))
tr2 = trajectory(towel, 100, u2)
summary(tr2)

figure(figsize=(8,5))

# Plot the x-coordinate of the two trajectories:
ax1 = subplot(2,1,1)
plot(tr1[:, 1], alpha = 0.5)
plot(tr2[:, 1], alpha = 0.5)
ylabel("x")

# Plot their distance in a semilog plot:
ax2 = subplot(2,1,2, sharex = ax1)
d = [norm(tr1[i] - tr2[i]) for i in 1:length(tr2)]
ylabel("d")
xlabel("n")
semilogy(d);

# ------------------------------------------------------------------------------------------
# # Maximum Lyapunov Exponent
# `lyapunov` is a function that calculates the maximum Lyapunov exponent for a
# `DynamicalSystem` (for a given starting point).
#
# Since `lyapunov` is not a trivial function, it is best to read the documentation string
# first:
# ------------------------------------------------------------------------------------------

?lyapunov

# ------------------------------------------------------------------------------------------
# ---
#
# Let's apply this to the example of the previous section, the Shinriki oscillator!
#
# *Reminder:* we found something that "could" be chaotic behavior for the parameter `R1 =
# 21.0`
#
# ------------------------------------------------------------------------------------------

shi = Systems.shinriki(;R1 = 21.0)

lyapunov(shi, 1000.0, Ttr = 10.0)

# ------------------------------------------------------------------------------------------
# Positive Lyapunov exponent!?!? That is definitely chaotic behavior, right?
#
# *Right?*
# ------------------------------------------------------------------------------------------

# ------------------------------------------------------------------------------------------
# Let's increase accuracy of computation, as well as the transient time, to see whether an
# orbit actually enters a limit cycle.
# ------------------------------------------------------------------------------------------

lyapunov(shi, 2000.0, Ttr = 1000.0)

# ------------------------------------------------------------------------------------------
# **surprise**
#
# Let's find out whats going on!
#
# To see why the lyapunov exponent was positive in one case and negative in another, we can
# produce a more detailed orbit diagram, around the "critical" value of `R = 21.0`.
# ------------------------------------------------------------------------------------------

pvalues = linspace(20.9,21.1,101)
i = 1
plane = (2, 0.0)
tf = 1000.0
p_index = 1

# use extremely long transient time:
output = produce_orbitdiagram(shi, plane, i, p_index, pvalues; tfinal = tf,
                              Ttr = 2000.0, direction = -1, printparams = false);
length(output)

using PyPlot
figure(figsize=(6,4))
for (j, p) in enumerate(pvalues)
    plot(p .* ones(output[j]), output[j], lw = 0,
    marker = "o", ms = 0.2, color = "black")
end
plot([21, 21], [-2.1, 0.1], color = "red", alpha = 0.55)
xlabel("\$R_1\$"); ylabel("\$V_1\$");

# result of orbit diagram at R1 = 21.0
values = output[51]

# Amount of unique points
un = unique(round.(output[51], 8))
println("Total: $(length(output[51])), unique: $(length(un))")

# ------------------------------------------------------------------------------------------
# ## `lyapunov` for discrete system
#
# * All functions that accept a `DynamicalSystem` work with *any* instance of
# `DynamicalSystem`, regardless of whether it is continuous, discrete, in-place, out-of-
# place or whatever.
#
# ------------------------------------------------------------------------------------------

# Get the Henon map from the library of pre-defined systems:
hen = Systems.henon()
λ = lyapunov(hen, 10000)

# ------------------------------------------------------------------------------------------
# ---
#
# # Lyapunov Spectrum
#
# Besides the maximum Laypunov exponent, the function `lyapunovs` (with `s` at the end)
# returns the entire Lyapunov spectrum (or as many exponents the user wants).
#
#
#
# Once again, because the function `lyapunovs` is not trivial, we will view the
# documentation string first:
# ------------------------------------------------------------------------------------------

?lyapunovs

# ------------------------------------------------------------------------------------------
# ### Lyapunovs for discrete systems
#
# In our first example of calling `lyapunovs`, let's pass a discrete system.
# ------------------------------------------------------------------------------------------

towel = Systems.towel()
lyapunovs(towel, 2000; Ttr = 200)

# ------------------------------------------------------------------------------------------
# Here we're choosing to compute only the first two exponents.
# ------------------------------------------------------------------------------------------

lyapunovs(towel, 2000, 2; Ttr = 200)

# ------------------------------------------------------------------------------------------
# If you only want the first exponent (maximum), use the `lyapunov` function instead
# ------------------------------------------------------------------------------------------

lyapunov(towel, 2000; Ttr = 200)

# ------------------------------------------------------------------------------------------
# How much time does this take?
# ------------------------------------------------------------------------------------------

using BenchmarkTools
@btime lyapunovs($towel, 2000; Ttr = 200);

@btime lyapunov($towel, 2000; Ttr = 200);

# ------------------------------------------------------------------------------------------
# ### Lyapunov exponents for continuous systems
#
# Next, let's initialize the Lorenz system with random initial condition
# ------------------------------------------------------------------------------------------

lor = Systems.lorenz()

# ------------------------------------------------------------------------------------------
# We'll compute the Lyapunov spectrum with specified initial parallepiped matrix `Q0`:
# ------------------------------------------------------------------------------------------

Q0 = eye(3)
lyapunovs(lor, 2000, Q0; Ttr = 10.0)

# ------------------------------------------------------------------------------------------
# And we find that results "converge" already with 2000 iterations:
# ------------------------------------------------------------------------------------------

lyapunovs(lor, 3000, Q0; Ttr = 10.0)

# ------------------------------------------------------------------------------------------
# * Even the continuous systems are quite performant (note that compilation takes a **lot**
# of time):
# ------------------------------------------------------------------------------------------

@btime lyapunovs($lor, 2000, $Q0; Ttr = 10.0);

# ------------------------------------------------------------------------------------------
# * The above integration is done with a 9th order solver and tolerances of `1e-9`. But you
# can get away with lower tolerances.
#
# Let's load in `OrdinaryDiffEq` and specify keyword arguments for the integrators of
# DifferentialEquations.jl.
# ------------------------------------------------------------------------------------------

using OrdinaryDiffEq

dek = Dict(:solver => Tsit5(), :abstol => 1e-6, :reltol => 1e-6)

# ------------------------------------------------------------------------------------------
# And now we can call `lyapunovs` with the keyword `diff_eq_kwargs`
# ------------------------------------------------------------------------------------------

lyapunovs(lor, 2000, Q0; Ttr = 10.0, diff_eq_kwargs = dek)

@btime lyapunovs($lor, 2000.0, $Q0; Ttr = 10.0, diff_eq_kwargs = $dek);

.nbexports/introductory-tutorials/broader-topics-and-ecosystem/introduction-to-dynamicalsystems.jl/3. Quantifying Chaos (Lyapunov Exponents).jl


# This file is part of the IntervalArithmetic.jl package; MIT licensed

"""
    in(x, a)
    ∈(x, a)

Checks if the number `x` is a member of the interval `a`, treated as a set.
Corresponds to `isMember` in the ITF-1788 Standard.
"""
function in(x::T, a::Interval) where T<:Real
    isinf(x) && return false
    a.lo <= x <= a.hi
end

"""
    issubset(a,b)
    ⊆(a,b)

Checks if all the points of the interval `a` are within the interval `b`.
"""
function ⊆(a::Interval, b::Interval)
    isempty(a) && return true
    b.lo ≤ a.lo && a.hi ≤ b.hi
end

function ⊂(a::Interval, b::Interval)
    a == b && return false
    a ⊆ b
end

function ⊇(a::Interval, b::Interval)
    b ⊆ a
end

function ⊃(a::Interval, b::Interval)
    b ⊂ a
end

# isinterior
function isinterior(a::Interval, b::Interval)
    isempty(a) && return true
    islessprime(b.lo, a.lo) && islessprime(a.hi, b.hi)
end
const ⪽ = isinterior  # \subsetdot

# Disjoint:
function isdisjoint(a::Interval, b::Interval)
    (isempty(a) || isempty(b)) && return true
    islessprime(b.hi, a.lo) || islessprime(a.hi, b.lo)
end

# Intersection
"""
    intersect(a, b)
    ∩(a,b)

Returns the intersection of the intervals `a` and `b`, considered as
(extended) sets of real numbers. That is, the set that contains
the points common in `a` and `b`.
"""
function intersect(a::Interval{T}, b::Interval{T}) where T
    isdisjoint(a,b) && return emptyinterval(T)

    Interval(max(a.lo, b.lo), min(a.hi, b.hi))
end
# Specific promotion rule for intersect:
intersect(a::Interval{T}, b::Interval{S}) where {T,S} =
    intersect(promote(a, b)...)

## Hull
"""
    hull(a, b)

Returns the "interval hull" of the intervals `a` and `b`, considered as
(extended) sets of real numbers, i.e. the smallest interval that contains
all of `a` and `b`.
"""
hull(a::Interval, b::Interval) = Interval(min(a.lo, b.lo), max(a.hi, b.hi))
#
# hull{T,S}(a::Interval{T}, b::Interval{S}) = hull(promote(a, b)...)

"""
    union(a, b)
    ∪(a,b)

Returns the union (convex hull) of the intervals `a` and `b`; it is equivalent
to `hull(a,b)`.
"""
union(a::Interval, b::Interval) = hull(a, b)
#
# union(a::Interval, b::Interval) = union(promote(a, b)...)

"""
    setdiff(x::Interval, y::Interval)

Calculate the set difference `x ∖ y`, i.e. the set of values
that are inside the interval `x` but not inside `y`.

Returns an array of intervals.
The array may:

- be empty if `x ⊆ y`;
- contain a single interval, if `y` overlaps `x`
- contain two intervals, if `y` is strictly contained within `x`.
"""
function setdiff(x::Interval, y::Interval)
    intersection = x ∩ y

    isempty(intersection) && return [x]
    intersection == x && return typeof(x)[]  # x is subset of y; setdiff is empty

    x.lo == intersection.lo && return [Interval(intersection.hi, x.hi)]
    x.hi == intersection.hi && return [Interval(x.lo, intersection.lo)]

    return [Interval(x.lo, y.lo),
            Interval(y.hi, x.hi)]

end


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

function mphase(jd::AbstractFloat)
    ram, decm, dism = moonpos(jd, radians=true)
    ras, decs = sunpos(jd, radians=true)
    # phi: geocentric elongation of the Moon from the Sun
    # inc: selenocentric (Moon centered) elongation of the Earth from the Sun
    sin_decs, cos_decs = sincos(decs)
    sin_decm, cos_decm = sincos(decm)
    phi = acos(sin_decs * sin_decm + cos_decs * cos_decm * cos(ras - ram))
    # "dism" is in kilometers, AU in meters
    sin_phi, cos_phi = sincos(phi)
    inc = atan(AU * sin_phi, dism * 1000 - AU * cos_phi)
    return (1 + cos(inc))/2
end

"""
    mphase(jd) -> k

### Purpose ###

Return the illuminated fraction of the Moon at given Julian date(s).

### Arguments ###

* `jd`: the Julian ephemeris date.

### Output ###

The illuminated fraction \$k\$ of Moon's disk, with \$0 \\leq k \\leq 1\$. \$k
= 0\$ indicates a new moon, while \$k = 1\$ stands for a full moon.

### Method ###

Algorithm from Chapter 46 of "Astronomical Algorithms" by Jean Meeus
(Willmann-Bell, Richmond) 1991.  `sunpos` and `moonpos` are used to get
positions of the Sun and the Moon, and the Moon distance.  The selenocentric
elongation of the Earth from the Sun (phase angle) is then computed, and used to
determine the illuminated fraction.

### Example ###

Plot the illuminated fraction of the Moon for every day in January 2018 with a
hourly sampling.  Use [PyPlot.jl](https://github.com/JuliaPlots/Plots.jl/) for
plotting

```julia
using PyPlot
points = DateTime(2018,01,01):Dates.Hour(1):DateTime(2018,01,31,23,59,59);
plot(points, mphase.(jdcnv.(points)))
```

Note that in this calendar month there are two full moons, this event is called
[blue moon](https://en.wikipedia.org/wiki/Blue_moon).

### Notes ###

Code of this function is based on IDL Astronomy User's Library.
"""
mphase(jd::Real) = mphase(float(jd))


## coordinate.jl
##     Functions for coordinate transformation

"""
    eq2ec(α, δ) -> λ, β

Coordinate transform from the equatorial to ecliptic coordinate systems

# Arguments
- `α`: right ascension (RA)
- `δ`: declination (Dec)
# Returns
- `λ`: ecliptic longitude
- `β`: ecliptic latitude
"""
function eq2ec(α, δ)

    ε = deg2rad(23.439279444444445)  # obliquity of the Earth's axis

    sin_ε = sin(ε)
    cos_ε = cos(ε)

    sin_α = sin(α)
    cos_α = cos(α)

    sin_δ = sin(δ)
    cos_δ = cos(δ)

    λ = atan(cos_ε*cos_δ*sin_α + sin_ε*sin_δ, cos_δ*cos_α)
    λ < 0 && (λ += 2π)

    β = asin(-sin_ε*cos_δ*sin_α + cos_ε*sin_δ)

    return λ, β
end

function ec2eq(λ, β)

    ε = deg2rad(23.439279444444445)  # obliquity of the Earth's axis

    sin_ε = sin(ε)
    cos_ε = cos(ε)

    sin_λ = sin(λ)
    cos_λ = cos(λ)

    sin_β = sin(β)
    cos_β = cos(β)

    α = atan(cos_ε*cos_β*sin_λ - sin_ε*sin_β, cos_β*cos_λ)
    α < 0 && (α += 2π)

    δ = asin(sin_ε*cos_β*sin_λ + cos_ε*sin_β)

    return α, δ
end

# cpdef inline tuple eq2hz(double RA, double Dec, double obslon, double obslat):
#     """
#     Coordinate transform from equatorial to horizontal coordinate system
#         for an observer in (obslon, obslat)
#     """
#     cdef:
#         double hour_angle = obslon - RA  # hour angle = GST + longitude - RA
#         double azimuth, elevation

#     azimuth = atan2(-cos(Dec)*sin(hour_angle), sin(Dec)*cos(obslat) - cos(Dec)*sin(obslat)*cos(hour_angle))

#     if azimuth < 0:
#         azimuth += 2*pi

#     elevation = asin(sin(Dec)*sin(obslat) + cos(Dec)*cos(obslat)*cos(hour_angle))

#     return azimuth, elevation

################################################################
#                 Rotation transformation (inpace)
################################################################

"""
Rotation transformation around X-axis
Parameters:
    v : vector to be transformed
    θ : rotation angle
"""
function rotateX!(v, θ)
    sinθ = sin(θ)
    cosθ = cos(θ)

    v[1], v[2], v[3] = v[1], v[2]*cosθ + v[3]*sinθ, - v[2]*sinθ + v[3]*cosθ
end

"""
Rotation transformation around Y-axis
Parameters:
    v : vector to be transformed
    θ : rotation angleon
"""
function rotateY!(v, θ)
    sinθ = sin(θ)
    cosθ = cos(θ)

    v[1], v[2], v[3] = v[1]*cosθ - v[3]*sinθ, v[2], v[1]*sinθ + v[3]*cosθ
end

"""
Rotation transformation around Z-axis
Parameters:
    v : vector to be transformed
    θ : rotation angleon
"""
function rotateZ!(v, θ)
    sinθ = sin(θ)
    cosθ = cos(θ)

    v[1], v[2], v[3] = v[1]*cosθ + v[2]*sinθ, - v[1]*sinθ + v[2]*cosθ, v[3]
end

################################################################
#                 Rotation transformation (non-inpalce)
################################################################

rotateX(v, θ) = rotateX!(copy(v), θ)
rotateY(v, θ) = rotateY!(copy(v), θ)
rotateZ(v, θ) = rotateZ!(copy(v), θ)

function rotateX(v::SVector, θ)
    sinθ = sin(θ)
    cosθ = cos(θ)

    x =   v[1]
    y =   v[2]*cosθ + v[3]*sinθ
    z = - v[2]*sinθ + v[3]*cosθ

    SA_F64[x, y, z]
end

function rotateY(v::SVector, θ)
    sinθ = sin(θ)
    cosθ = cos(θ)

    x = v[1]*cosθ - v[3]*sinθ
    y = v[2]
    z = v[1]*sinθ + v[3]*cosθ

    SA_F64[x, y, z]
end

function rotateZ(v::SVector, θ)
    sinθ = sin(θ)
    cosθ = cos(θ)

    x =   v[1]*cosθ + v[2]*sinθ
    y = - v[1]*sinθ + v[2]*cosθ
    z =   v[3]

    SA_F64[x, y, z]
end

################################################################
#                   Coordinate transformation
################################################################

"""
Transform the inertial coordinate system to the orbital plane system

# Parameters
- v : vector to be transformed
- ω : argument of periapsis
- I : inclination
- Ω : longitude of the ascneding node
"""
function inertia_to_orbit!(v, ω, I, Ω)
    rotateZ!(v, Ω)
    rotateX!(v, I)
    rotateZ!(v, ω)
end

function inertia_to_orbit(v, ω, I, Ω)
    v = rotateZ(v, Ω)
    v = rotateX(v, I)
    v = rotateZ(v, ω)
end

inertia_to_orbit!(v, orbit) = inertia_to_orbit!(v, orbit.ω, orbit.I, orbit.Ω)
inertia_to_orbit(v, orbit) = inertia_to_orbit(v, orbit.ω, orbit.I, orbit.Ω)

"""
Transform the orbital plane system to the inertial coordinate system

# Parameters
- v : vector to be transformed
- ω : argument of periapsis
- I : inclination
- Ω : longitude of the ascneding node
"""
function orbit_to_inertia!(v, ω, I, Ω)
    rotateZ!(v, -ω)
    rotateX!(v, -I)
    rotateZ!(v, -Ω)
end

function orbit_to_inertia(v, ω, I, Ω)
    v = rotateZ(v, -ω)
    v = rotateX(v, -I)
    v = rotateZ(v, -Ω)
end

orbit_to_inertia!(v, orbit) = orbit_to_inertia!(v, orbit.ω, orbit.I, orbit.Ω)
orbit_to_inertia(v, orbit) = orbit_to_inertia(v, orbit.ω, orbit.I, orbit.Ω)

"""
    orbit_to_body(v::SArray{Tuple{3},Float64,1,3}, γ, ε, ϕ) -> v

# Parameters
- `v` : vector in the orbital plane frame
- `γ` : longitude of vernal equinox direction of the body
- `ε` : obliquity of the spin pole
- `ϕ` : spin phase of the body

# Return
- `v` : vector in the body-fixed frame
"""
function orbit_to_body(v, γ, ε, ϕ)
    v = rotateZ(v, γ)  # body's ecliptic coordinate
    v = rotateX(v, ε)  # body's equatorial coordinate
    v = rotateZ(v, ϕ)  # body-fixed frame
end

function orbit_to_body!(v, γ, ε, ϕ)
    rotateZ!(v, γ)  # body's ecliptic coordinate
    rotateX!(v, ε)  # body's equatorial coordinate
    rotateZ!(v, ϕ)  # body-fixed frame
end

"""
    body_to_orbit(v::SArray{Tuple{3},Float64,1,3}, γ, ε, ϕ) -> v

# Parameters
- `v` : vector in the body-fixed frame
- `γ` : longitude of vernal equinox direction of the body
- `ε` : obliquity of the spin pole
- `ϕ` : spin phase of the body

# Return
- `v` : vector in the orbital plane frame
"""
function body_to_orbit(v, γ, ε, ϕ)
    v = rotateZ(v, -ϕ)  # body's equatorial coordinate
    v = rotateX(v, -ε)  # body's ecliptic coordinate
    v = rotateZ(v, -γ)  # orbital plane frame
end

function body_to_orbit!(v, γ, ε, ϕ)
    rotateZ!(v, -ϕ)  # body's equatorial coordinate
    rotateX!(v, -ε)  # body's ecliptic coordinate
    rotateZ!(v, -γ)  # orbital plane frame
end


function linearTrussStiffnessMatrix( E, A, nodalCoords  )

  diff = nodalCoords[ 2, : ] - nodalCoords[ 1, : ] ;

  length   = sqrt( diff' * diff ) ;
  c        = diff[1] / length ;
  s        = diff[3] / length ;

  Qloc2glo = [  c -s 0  0 ;
                s  c 0  0 ;
                0 0  c -s ;
                0 0  s  c ] ;

  Kloc     = E*A/length * [ 1  0 -1 0 ;
                            0  0  0 0 ;
                            -1 0  1 0 ;
                            0  0  0 0 ] ;
  Kglo     = Qloc2glo * Kloc * transpose(Qloc2glo) ;

  return Kglo
end
#


#Import Turing, Distributions and DataFrames
using Turing, Distributions, DataFrames, Distributed

# Import MCMCChain, Plots, and StatsPlots for visualizations and diagnostics.
using MCMCChains, Plots, StatsPlots

# Set a seed for reproducibility.
using Random
Random.seed!(12);

# Turn off progress monitor.
Turing.setprogress!(false)

theta_noalcohol_meds = 1    # no alcohol, took medicine
theta_alcohol_meds = 3      # alcohol, took medicine
theta_noalcohol_nomeds = 6  # no alcohol, no medicine
theta_alcohol_nomeds = 36   # alcohol, no medicine

# no of samples for each of the above cases
q = 100

#Generate data from different Poisson distributions
noalcohol_meds = Poisson(theta_noalcohol_meds)
alcohol_meds = Poisson(theta_alcohol_meds)
noalcohol_nomeds = Poisson(theta_noalcohol_nomeds)
alcohol_nomeds = Poisson(theta_alcohol_nomeds)

nsneeze_data = vcat(
    rand(noalcohol_meds, q),
    rand(alcohol_meds, q),
    rand(noalcohol_nomeds, q),
    rand(alcohol_nomeds, q),
)
alcohol_data = vcat(zeros(q), ones(q), zeros(q), ones(q))
meds_data = vcat(zeros(q), zeros(q), ones(q), ones(q))

df = DataFrame(;
    nsneeze=nsneeze_data,
    alcohol_taken=alcohol_data,
    nomeds_taken=meds_data,
    product_alcohol_meds=meds_data .* alcohol_data,
)
df[sample(1:nrow(df), 5; replace=false), :]

#Data Plotting

p1 = Plots.histogram(
    df[(df[:, :alcohol_taken] .== 0) .& (df[:, :nomeds_taken] .== 0), 1];
    title="no_alcohol+meds",
)
p2 = Plots.histogram(
    (df[(df[:, :alcohol_taken] .== 1) .& (df[:, :nomeds_taken] .== 0), 1]);
    title="alcohol+meds",
)
p3 = Plots.histogram(
    (df[(df[:, :alcohol_taken] .== 0) .& (df[:, :nomeds_taken] .== 1), 1]);
    title="no_alcohol+no_meds",
)
p4 = Plots.histogram(
    (df[(df[:, :alcohol_taken] .== 1) .& (df[:, :nomeds_taken] .== 1), 1]);
    title="alcohol+no_meds",
)
plot(p1, p2, p3, p4; layout=(2, 2), legend=false)

# Convert the DataFrame object to matrices.
data = Matrix(df[:, [:alcohol_taken, :nomeds_taken, :product_alcohol_meds]])
data_labels = df[:, :nsneeze]
data

# # Rescale our matrices.
data = (data .- mean(data; dims=1)) ./ std(data; dims=1)

# Bayesian poisson regression (LR)
@model function poisson_regression(x, y, n, σ²)
    b0 ~ Normal(0, σ²)
    b1 ~ Normal(0, σ²)
    b2 ~ Normal(0, σ²)
    b3 ~ Normal(0, σ²)
    for i in 1:n
        theta = b0 + b1 * x[i, 1] + b2 * x[i, 2] + b3 * x[i, 3]
        y[i] ~ Poisson(exp(theta))
    end
end;

# Retrieve the number of observations.
n, _ = size(data)

# Sample using NUTS.

num_chains = 4
m = poisson_regression(data, data_labels, n, 10)
chain = sample(m, NUTS(200, 0.65), MCMCThreads(), 2_500, num_chains; discard_adapt=false)

gelmandiag(chain)

# Taking the first chain
c1 = chain[:, :, 1]

# Calculating the exponentiated means
b0_exp = exp(mean(c1[:b0]))
b1_exp = exp(mean(c1[:b1]))
b2_exp = exp(mean(c1[:b2]))
b3_exp = exp(mean(c1[:b3]))

print("The exponent of the meaned values of the weights (or coefficients are): \n")
println("b0: ", b0_exp)
println("b1: ", b1_exp)
println("b2: ", b2_exp)
println("b3: ", b3_exp)
print("The posterior distributions obtained after sampling can be visualised as :\n")

plot(chain)

# Note the standard deviation before removing the warmup samples
describe(chain)

# Removing the first 200 values of the chains.
chains_new = chain[201:2500, :, :]
describe(chains_new)

plot(chains_new)

if isdefined(Main, :TuringTutorials)
    Main.TuringTutorials.tutorial_footer(WEAVE_ARGS[:folder], WEAVE_ARGS[:file])
end


"""

License: MIT
Julia 1.02
Dynamic programming formulation to count number of matchings of maximum cardinality
"""

using Printf
# INPUT
# G - graph generated using Graph_generation
# Recursive_table - dictionary computed in CountingMatchings_rosso.jl with recursive approach
# maxCard - size of the maximum cardinality matching
using Distributions
function UniformMaxMatching_julia(G,E,Recursive_table,maxCard)
    set_V = sort(collect(keys(G)))
    s_aux = [1 for v in set_V]
    r = length(E)
    sol_M = [] # it will contain the edges in the generated matching
    for e in reverse(E)
        # e is in set_E with prob:
        aux = rand()
        if r-1== 0 # if r-1 = 0 then x_r must be added, otherwise sol_M alread had maxCard nodes and the algorithm had stopped
            prob_no_e = 0
        else
            # probability of x_e = 0
            prob_no_e = Recursive_table[r-1,maxCard,s_aux]/(Recursive_table[r,maxCard,s_aux])
        end
        if aux > prob_no_e
            # then x_e = 1
            append!(sol_M,[Set(e)])
            #s_aux = [1 if v not in e else 0 for v in set_V]
            s_aux = [if !(v in e) s_aux[j] else s_aux[j]-1 end for (j,v) in enumerate(set_V)]
            maxCard = maxCard - 1
        end
        r = r-1
        if maxCard == 0
            return sol_M
        end
    end
    return sol_M
end

# ask the number of matching to generate
function Uniform_Number(G,E,gen_num,info)
    # maximum cardinality
    maxCard = OptimalMatching(G,E)
    ## save maxCard
    if info==1
        maxCardinality = @sprintf "%d" maxCard
        fres = open("results.txt","a")
        write(fres,"maxCard= ",maxCardinality, " \n")
        close(fres)
    end
    ## end save
    r = length(E)
    s = Dict(v=>1 for v in keys(G))
    F_table,Numb_matchings= Count_Mat_Paper(G, maxCard,E,r,s,1)
    ## save number of maximum matchings
    Numb_OPT_matchings = @sprintf "%d" Numb_matchings
    if info==1
        fres = open("results.txt","a")
        write(fres,"Numb_matchings= ",Numb_OPT_matchings, " \n")
        close(fres)
    end
    ## end save
    fgen = open("tmp.txt","w")
    for _ in 1:gen_num
        gen_M =UniformMaxMatching_julia(G,E,F_table,maxCard)
        aux = 0
        for e in gen_M
            e = [v for v in e]
            if aux ==0
                write(fgen,"[{")
            else
                write(fgen,",{")
            end
            write(fgen,string(e[1])*","*string(e[2])*"}")
            aux = aux+1
        end
        if length(gen_M)==0
            write(fgen,"[{}]\n")
        else
            write(fgen,"] \n")
        end
    end
    close(fgen)
end


"""
    transformLog(data::T, base::Real = ℯ) where T <: AbstractArray{<: Number}

Log transform `data` using log-base, `base`.

# Examples:

```julia-repl
julia> data = [0:5;];

julia> transformLog(data)
6-element Array{Float64,1}:
 -Inf
   0.0
   0.6931471805599453
   1.0986122886681098
   1.3862943611198906
   1.6094379124341003

julia> transformLog(data,2)
6-element Array{Float64,1}:
 -Inf
   0.0
   1.0
   1.5849625007211563
   2.0
   2.321928094887362

```

See also: [`transformRoot`](@ref), [`transformBoxCox`](@ref)
"""
function transformLog(data::T, base::Real = ℯ) where T <: AbstractArray{<: Number}
    log.(base,data)
end

"""
    transformRoot(data::T, index::Real = 10) where T <: AbstractArray{<: Number}

Root transform `data` using root index, `index`.

# Examples:

```julia-repl
julia> data = [0:5;];

julia> transformRoot(data)
6-element Array{Float64,1}:
 0.0
 1.0
 1.0717734625362931
 1.1161231740339044
 1.148698354997035
 1.174618943088019

julia> transformRoot(data,2)
6-element Array{Float64,1}:
 0.0
 1.0
 1.4142135623730951
 1.7320508075688772
 2.0
 2.23606797749979
```

See also: [`transformLog`](@ref), [`transformBoxCox`](@ref)
"""
function transformRoot(data::T, index::Real = 10) where T <: AbstractArray{<: Number}
    data .^ (1/index)
end

"""
    transformBoxCox(data::T, λ::Real = 0.0) where T <: AbstractArray{<: Number}

[Box-Cox power transformation](https://en.wikipedia.org/wiki/Power_transform#Box%E2%80%93Cox_transformation)
following the following function:

```math
y_i^{(\\lambda)} = \\left\\{\\begin{matrix}
\\frac{y_i^\\lambda - 1}{\\lambda} & \\mathrm{if} \\lambda \\neq  0, \\\\
\\mathrm{ln} y_i & \\mathrm{if} \\lambda = 0,
\\end{matrix}\\right.
```

# Examples:

```julia-repl
julia> data = [0:5;];

julia> transformBoxCox(data)
6-element Array{Float64,1}:
 -Inf
   0.0
   0.6931471805599453
   1.0986122886681098
   1.3862943611198906
   1.6094379124341003

julia> transformBoxCox(data,.1)
6-element Array{Float64,1}:
 -10.0
   0.0
   0.7177346253629313
   1.1612317403390437
   1.486983549970351
   1.7461894308801895

julia> transformBoxCox(data,1)
6-element Array{Float64,1}:
 -1.0
  0.0
  1.0
  2.0
  3.0
  4.0
```

See also: [`transformLog`](@ref), [`transformRoot`](@ref)
"""
function transformBoxCox(data::T, λ::Real = 0.0) where T <: AbstractArray{<: Number}
    λ == 0 ? log.(data) : (data .^ λ .- 1) ./ λ
end


module SimpleQuaternions

using LinearAlgebra

export SimpleQuaternion, jm, km, get_parts

import Base: (+), (-), (*), (/), (//), (==), inv, string, show, adjoint, conj
import Base: real, isreal, zero, one
import LinearAlgebra: norm
export norm

struct SimpleQuaternion{T} <: Number
    a::T
    b::T
    c::T
    d::T
    function SimpleQuaternion(x::Real=0, y::Real=0, z::Real=0, w::Real=0)
        (aa,bb,cc,dd) = promote(x,y,z,w)
        T = typeof(aa)
        new{T}(aa,bb,cc,dd)
    end
end

"""
`get_parts(x::SimpleQuaternion)` returns the tuple `(a,b,c,d)` that are
real and various imaginary get_parts of `x`. Specifically,
`x == a + b*im + c*jm + d*km`.
"""
get_parts(x::SimpleQuaternion) = (x.a,x.b,x.c,x.d)

"""
`SimpleQuaternion` creates a quaternion number. The basic constructor
is `SimpleQuaternion(a,b,c,d)` which is equivalent to
`a + b*im + c*jm + d*km`.

`SimpleQuaternion(x::Real)` yields `x + 0*im + 0*jm + 0*km`.

If `z` is the `Complex` number `a+b*im` then `SimpleQuaternion(z)`
yields `a + b*im + 0*jm + 0*km`.
"""
SimpleQuaternion(z::Complex) = SimpleQuaternion(real(z),imag(z),0,0)

function SimpleQuaternion{T}(a::Real=0,b::Real=0,c::Real=0,d::Real=0) where T<:Real
    (x,aa,bb,cc,dd) = promote(one(T),a,b,c,d)
    SimpleQuaternion(aa,bb,cc,dd)
end

"""
`jm` is the quaternion unit *j*.
"""
jm = SimpleQuaternion(false,false,true,false)

"""
`km` is the quaternion unit *k*.
"""
km = SimpleQuaternion(false,false,false,true)

function (==)(x::SimpleQuaternion,y::SimpleQuaternion)
    get_parts(x) == get_parts(y)
end

"""
`real(x::SimpleQuaternion)` returns the real part of `x`.
"""
real(x::SimpleQuaternion) = x.a

"""
`isreal(x::SimpleQuaternion)` returns `true` exactly when `x` is a real
number (all complex get_parts are zero).
"""
isreal(x::SimpleQuaternion) = x == x.a

(==)(x::SimpleQuaternion, y::Number) = x == SimpleQuaternion(y)
(==)(x::Number, y::SimpleQuaternion) = SimpleQuaternion(x) == y

one(a::SimpleQuaternion{T}) where T = SimpleQuaternion(one(T))
zero(a::SimpleQuaternion{T}) where T = SimpleQuaternion(zero(T))

one(::Type{SimpleQuaternion{T}}) where T = SimpleQuaternion(one(T))
zero(::Type{SimpleQuaternion{T}}) where T = SimpleQuaternion(zero(T))

include("arithmetic.jl")
include("show.jl")
include("matrices.jl")
include("random.jl")

end # module


#solve eq x=cos(x)
#Reference: http://nbviewer.jupyter.org/gist/genkuroki/ce7f62d10365b9a25c82aad499c886c2

function bisection(f, x₀::T, x₁::T; ε=eps(T), maxiter=10^8) where T<:AbstractFloat
    y₀, y₁ = f(x₀), f(x₁)
    iter = 0
    y₀ == 0 && return x₀, iter
    y₁ == 0 && return x₁, iter
    y₀ * y₁ > 0 && return T(NaN), -1
    for iter in 1:maxiter
        x₂ = (x₀ + x₁)/2
        y₂ = f(x₂)
        y₂ == 0 && return x₂, iter
        if y₀ * y₂ > 0
            x₀, y₀ = x₂, y₂
        else
            x₁, y₁ = x₂, y₂
        end
        abs(x₁ - x₀) < ε && return x₂, iter
    end
    return T(NaN), iter
end

function main()
    f(x)=x-cos(x)
    x₀, x₁ = 0.0, 3.14
    @show α, iter = bisection(f, x₀, x₁)
    @show f(α)
end

main()


function energy(planets)
    eng = 0.0
    # kinetic energy
    for p in planets
        eng += 1/2*p.m*norm2(p.v)
    end
    # potential energy
    for j in 1:nplanets
        pj = planets[j]
        for k in j+1:nplanets
            pk = planets[k]
            eng -= G*pj.m*pk.m/sqdist(pj.r, pk.r)
        end
    end
    eng
end

function barycenter(m,mTot,x,y,z) # Find Barycenter
    #    m : mass of planet
    # mTot : total mass of system
    # x,y,z: position coordinates of planet
    #
    baryX = (m*x)/mTot
    baryY = (m*y)/mTot
    baryZ = (m*z)/mTot
    return baryX,baryY,baryZ;
end

function momentum(m,x,y,z,vx,vy,vz)
    r = [x,y,z];
    vel = [vx,vy,vz];
    H = m*cross(r,vel);
    return H;
end


using Distributions
using Gadfly
using PMTK4Stats

"""
Plot CI and HPD for beta posterior
"""
function drawBetaHPD()
    a = 3
    b = 9
    α = 0.05
    β = Beta(a, b)

    CI = quantile(β, [α/2, 1-α/2])

    xs = linspace(0.001, 0.999, 40)
    ps = pdf(β, xs)

    icdf(p::Real) = quantile(β, p)
    HPD = hdiFromIcdf(icdf)

    ints = hcat(CI, HPD)
    names = ["CI"; "HPD"]

    function plot_ci(int, name)
        l, u = int
        pl = pdf(β, l)
        pu = pdf(β, u)
        vert_up_l = layer(x=[l, l], y=[0, pl], Geom.line, Theme(default_color=colorant"blue", line_width=3px))
        hor_l     = layer(x=[l, u], y=[pl, pu], Geom.line, Theme(default_color=colorant"blue", line_width=3px))
        vert_dn_l = layer(x=[u, u], y=[pu, 0], Geom.line, Theme(default_color=colorant"blue", line_width=3px))
        pdf_l     = layer(x=xs, y=ps, Geom.line, Theme(default_color=colorant"black", line_width=3px))

        pp = plot(vert_up_l, hor_l, vert_dn_l, pdf_l)
    end

    [plot_ci(slicedim(ints, 2, i), names[i]) for i=1:length(names)]
end


function lucrout(A)
    A = convert(Array{Float64,2},A);
    numRow, numCol = size(A);
    if numRow != numCol
        return println("Non-square matrix")
    end

    L, U = zeros(numRow,numCol), zeros(numRow,numCol); # initialize

    # We first put the first column of L and the ones in U
    for j in 1:numRow
        L[j,1] = A[j,1];
        U[j,j] = 1.0;
    end

    # We then obtain the fist row of U by dividing
    for j = 2:numRow
        U[1,j] = A[1,j]/L[1,1];
    end

    # Now we try to calculate the rest

    for i = 2:numCol
        # We calculate all the i-th column of L
        for j in i:numRow
            S = 0; #initialize
            for k in 1:i-1
                S += L[j,k]*U[k,i];
            end
            L[j,i] = A[j,i] - S;
        end

        #We know calculate the U-th row of U
        for j = (i+1):numCol
            S = 0; # Initialize
            for k= 1:(i-1)
                S += L[i,k]*U[k,j];
            end
            U[i,j] = 1/L[i,i]*(A[i,j]-S);
        end

    end
        return L,U
end


"""
    determinant(mat)

Given a non singluar matrix, calculate its determinant using LU decomposition.

L and U are lower triangular and upper triangular matrices respectively such that

A = L*U

If we want to find the determinant, then

det(A) = det(LU) = det(L)*det(U)

Determinant of triangualar matrices is the product of their diagonal entries. Hence, makes finding the determinant easy.
"""
function determinant(mat)
    n, m = size(mat)
    if n != m
        DomainError(mat, "The matrix should be a square matrix.")
    end
    L, U = lu_decompose(mat)
    l_prod = prod([L[i, i] for i in 1:n])
    u_prod = prod([U[i, i] for i in 1:n])

    return l_prod * u_prod
end


"""

```
sphericalshellvolume(i,step)
```

Computes the volume of the spherical shell defined within [(i-1)*step,i*step].

"""
function sphericalshellvolume(i,step)
  rmin = (i-1)*step
  return (4*pi/3)*( (rmin+step)^3 - rmin^3 )
end


using Plots

function rosenbrock(x; a=1, b=100)
    (a-x[1])^2 + b * (x[2] - x[1]^2)^2;
end

# plot

# contour(-2:0.1:2, -1:0.1:3, (x, y)->rosenbrock([x, y]))
heatmap(-2:0.01:2, -1:0.01:3, (x, y)->rosenbrock([x, y]))


using LinearAlgebra

"""Optimize some function f
# Args:
    ∇f: Gradient of f
    x_0: Initial guess
    η: learning rate
    ε: cancellation tolerance
    max_iters: maximum number of iterations
"""
function gradient_descent(∇f::Function,
  x_0,
  η::Number,
  ε::Number,
  max_iters::Integer)
    x_k = x_0
    count = 0
    for _ = 1:max_iters
        if any(isnan.(x_k))
            error("Encoutered NaN")
        end
        if any(isinf.(x_k))
            error("Encoutered Inf")
        end
        count += 1
        p_k = -∇f(x_k)
        if norm(∇f(p_k)) < ε
            break
        end
        x_k += η * p_k
    end
    x_k
end

#numeric_differentiation(f, h) = x -> (f(x + h) - f(x)) / h
numeric_differentiation(f, h) = x -> (f(x + h) - f(x - h)) / (2 * h)

"""BFGS Optimization Algorithm
Broyden–Fletcher–Goldfarb–Shanno algorithm
for details see:
    https://en.wikipedia.org/wiki/Broyden%E2%80%93Fletcher%E2%80%93Goldfarb%E2%80%93Shanno_algorithm

# Args:
    ∇f: Gradient of function to optimize
    x_0: Initial guess for optimal value
    iters: Maximum number of iterations before cancellation
    line_search: Line search function that's used
    ε: Optimization stops once the norm of the gradient is below this value
    ls_x_0: Initial value for the line search
    ls_η: Learning rate for the line search
    ε_ls: same as ε but for the line search
"""
function BFGS(f::Function,
    ∇f::Function,
    x_0::Vector{<:Number},
    iters::Integer,
    line_search::Function,
    ε = 10e-12::Real,
    )
    n = size(x_0)[1]
    x_k = x_0
    B_k = Matrix{typeof(x_0[1])}(I, n, n)
    did_iters = 0
    for i = 1:iters
        did_iters += 1
        if norm(∇f(x_k)) < ε
            break
        end
        # Step 1: obtain direction p_k by solving B_k ∙ p_k = - (gradient of f at x_k)
        p_k = B_k \ -∇f(x_k)

        # Step 2.: Find stepsize α_k such that α_k = arg min ∂f(x_k + α_k * p_k)/∂α
        α_k = line_search(numeric_differentiation(α->(f(x_k + α * p_k)), 10e-10))
        if isnan(α_k)
            break
        end

        # Step 3.
        s_k = α_k * p_k
        x_k_prime = x_k + s_k

        # Step 4.
        y_k = ∇f(x_k_prime) - ∇f(x_k)

        # Step 5.
        B_k += (y_k * y_k') / (y_k' * s_k) - (B_k * s_k * s_k' * B_k) / (s_k' * B_k * s_k)
        x_k = x_k_prime
    end
    println(did_iters)
    x_k
end

# f(x) = -5 * x[1] + 10 * x[1]^2
# ∇f(x) = [-5 + 20 * x[1]]
f(x) = x[1]^5 / 5000 + 21 * x[1]^4 / 4000 + 17 * x[1]^3 / 375 + 293 * x[1]^2 / 1000 + 521 * x[1] / 1000
∇f(x) = [x[1]^4 / 1000 + 21 * x[1]^3 / 1000 + 17 * x[1]^2 / 125 + 293 * x[1] / 500 + 521 / 1000]
a = 1
b = 100
r(X) = begin
    x = X[1]
    y = X[2]
    (a - x)^2 + b*(y - x^2)^2
end
∇r(X) = begin
    x = X[1]
    y = X[2]
    [-2*a+4*b*x^3-4*b*x*y+2*x, 2*b*(y-x^2)]
end
#f(X) = begin
#    x = X[1]
#    y = X[2]
#    x^2 + y^2
#end
#∇f(X) = begin
#    x = X[1]
#    y = X[2]
#    [2*x, 2*y]
#end
println("optimal x = ", BFGS(
    f,
    ∇f,
    [0],
    500,
    ∇f -> gradient_descent(∇f, 10e-10, 1, 1e-10, 1000),
    10e-5))

# f(w, x) = w[1] * x[1] + w[2] * x[1]^2

# ∇f(w, x) = [w[1] + 2 * x[1] * w[2]] # [x; x^2]

# # initialize parameters
# w_0 = [-5, 10]
# x_0 = [-5]
# B_0 = Matrix{Float64}(I, 1, 1) # initialize approximate hessian with identity matrix
# ε = 1e-10 # cancellation parameter

# w_k = w_0
# x_k = x_0
# B_k = B_0

# while begin
#     x = norm(∇f(w_k, x_k))
#     println("∇f = ", x)
#     x > ε
# end
#     global B_k
#     global x_k
#     # Step 1.: obtain direction p_k by solving B_k*p_k = - (gradient of f at x_k)

#     p_k = B_k \ -∇f(w_k, x_k)

#     # Step 2.: Find stepsize α_k such that α_k minimizes f(x_k + α_k * p_k)

#     g_k(α) = w_k[1] + 2 * (x_k[1] + α * p_k[1]) * w_k[2]
#     α_k = line_search_gradient_descent(g_k, 0, 0.000001, 1e-10)
#     println("α_k = ", α_k)

#     # Step 3.
#     s_k = α_k * p_k
#     x_k_prime = x_k + s_k

#     # Step 4.
#     y_k = ∇f(w_k, x_k_prime) - ∇f(w_k, x_k)

#     # Step 5.
#     B_k += (y_k * y_k') / (y_k' * s_k) - (B_k * s_k * s_k' * B_k) / (s_k' * B_k * s_k)
#     x_k = x_k_prime
# end

# println(x_k)

"""
begin
    inv_B = inv(B_k)
    (s_k' * y_k + y_k' * inv_B * y_k) * (s_k * s_k') / (s_k' * y_k)^2 - (inv_B * y_k * s_k' + s_k * y_k' * inv_B) / (s_k' * y_k)
end
"""


using Printf;
using Plots;

function init()
	## 横の長さ
	global lx = 1.0;
	## 縦の長さ
	global ly = 1.0;
	## 横の分割数
	global nx = 11;
	## 縦の分割数
	global ny = 11;
	global dx = lx / (nx - 1);
	global dy = ly / (ny - 1);
	## 係数
	beta_x = 1.0 / dx^2;
	beta_y = 1.0 / dy^2;
	global x = range(0.0, stop=lx, length=nx);
	global y = range(0.0, stop=ly, length=ny);
	global u = zeros(Float64, (ny, nx));
	## 境界条件の適用
	u[:, begin] .= 0.0;
	u[:, end] = 100y;
	u[begin, :] .= 0.0;
	u[end, :] = 100x;
	global u_t = zeros(Float64, (ny, nx));
	for j = 1:ny
		for i = 1:nx
			u_t[j, i] = 100*x[i]*y[j];
		end
	end
	global pre_u = copy(u);
	## SOR法のωの値を決定
	mu = cos(pi/nx) + cos(pi/ny);
	global omega = 2.0/(1.0 + sqrt(1-(mu/2)^2));
	global epsilon = 2e-4;
	global a_ir = beta_x;
	global a_il = beta_x;
	global a_ia = beta_y;
	global a_ib = beta_y;
	global a_0 = -2.0(beta_x + beta_y);
	global rhs = 0.0;
	global iter_max = 100;
	global epsilon = 2e-4;
end

function main()
	init();
	for iter=1:iter_max
		err = 0.0;
		for j = 1:ny-2
			for i = 1:nx-2
				tmp = (rhs - a_ir*u[j+1, i+2] - a_il*u[j+1, i] - a_ia*u[j+2, i+1] - a_ib*u[j, i+1]) / a_0;
				u[j+1, i+1] = u[j+1, i+1] + (tmp - u[j+1, i+1])*omega;;
				err += sqrt((u_t[j+1, i+1] - u[j+1, i+1])^2)/(nx*ny);
			end
		end
		err = sqrt(sum((u - u_t).^2)/(nx*ny))
		#err = sqrt(sum((u - pre_u).^2)/(nx*ny)) global pre_u = copy(u);
		@printf("iter: %d, err: %5e\n", iter, err);
		if err < epsilon
			break;
		end
	end
	contour(u);
	savefig("Laplace.png");
end

main()


"""
    unitvector(v)

Normalize input vector `v`.
"""
function unitvector(v)
    n = norm(v)
    if n > 0
        return v / n
    else
        # zero vector
        return v
    end
end

"""
    random_in_unit_disk()

Generate a uniformly random point within the unit disk.
"""
function random_in_unit_disk()
    while true
        p = 2*rand(2) .- 1
        if dot(p, p) < 1
            return p
        end
    end
end

"""
    random_in_unit_sphere()

Generate a uniformly random point within the unit sphere.
"""
function random_in_unit_sphere()
    while true
        p = 2*rand(3) .- 1
        if dot(p, p) < 1
            return p
        end
    end
end


#=
Takes one step in the Markov chain according to the given state's transition
vector.
Inputs:
- transition_vector: An array of length p where the ith element denotes the
                     probability of transitioning into state i from the current
                     state (which is implicitly given).
Outputs:
- An integer in {1,2,..., p} denoting the index of the next state which occurs
with the probability specified in transition_vector.
=#
function one_step(transition_vector)
    cumulative_vector = cumsum(transition_vector)
    state_index = 1
    U = rand()
    while(U > cumulative_vector[state_index])
        state_index += 1
    end

    return state_index
end

#=
Runs a Markov chain a fixed number of times and returns the final state.
Inputs:
- states: An array of length p where each element represents a state of the
          chain. Assumes the ordering is such that the probability of
          transitioning from state i to state j is given by P[i,j].
          Examples:
          [0,1,2,3,4,5]
          ["red", "green", "blue"]
          [0,"blue",3,2]
- P: A (p x p) matrix denoting the transition matrix of the Markov chain.
     Assumes the rows of P sum to 1 and P[i,j] >= 0 for all i,j.
- S0: The initial state of the Markov chain. Assumed to be an element of the
      state variable.
- steps: A nonnegative integer specifying the number of steps for the Markov
         chain to go through.
Outputs:
- An element of the states variable denoting the state the chain is in after
  the specified number of steps.
=#
function run_fixed_simulation(states, P, S0, steps)

    # Define a dictionary mapping state to their index in P
    state_dict = Dict(states[i] => i for i = 1:length(states))

    # Define the current state of the chain
    Sn = S0

    # Run through the specified number of steps
    for i = 1:steps
        state_index = get(state_dict, Sn, -1)
        next_state_index = one_step(P[state_index,:])
        Sn = states[next_state_index]
    end

    return Sn
end

#=
Runs a Markov chain until a state is reached.
Inputs:
- states: Same as defined in ``run_fixed_simulation`` function
- P: Same as defined in ``run_fixed_simulation`` function
- S0: Same as defined in ``run_fixed_simulation`` function
- Sf: An array of states defining a possible final state. Assumed to be a
      subset of the states variable.
Outputs:
- An integer representing the number of steps it took to get to get to state
  contained in the Sf array.
=#
function run_simulation(states, P, S0, Sf)

    # Define a dictionary mapping state to their index in P
    state_dict = Dict(states[i] => i for i = 1:length(states))

    # Define the current state of the chain
    Sn = S0

    # Keep tracks of number of steps taken
    steps = 0

    # Run through the Markov changing
    while !(Sn in Sf) && steps < 1000
        state_index = get(state_dict, Sn, -1)
        next_state_index = one_step(P[state_index,:])
        Sn = states[next_state_index]
        steps += 1
    end

    return steps
end

# Define gambler's ruin Markov chain with N = 5 and p = .3

# gambler's ruin state space (χ)
states = [0,1,2,3,4,5]

# Transition matrix for gambler's ruin
P = [1 0 0 0 0 0;
    .7 0 .3 0 0 0;
    0 .7 0 .3 0 0;
    0 0 .7 0 .3 0;
    0 0 0 .7 0 .3;
    0 0 0 0 0 1]

# Estimate probability of having $5 after 6 turns when starting with $2
num_simulations = 100000
mc_est = mean([run_fixed_simulation(states, P, 2, 6) == 5
 for i = 1:num_simulations])

# Define masters program Markov chain
states = [1,2,"G","D"]

# Transition matrix for masters Markov chain
P = [.4 .5 0 .1;
     0 .3 .6 .1;
     0 0 1 0;
     0 0 0 1]

mc_est = mean([run_simulation(states, P, 1, ["G", "D"]) for i = 1:10000])


################################################################################
#    loggingOptimisation Problem
#
# You are the decision-maker of a production unit (here a logging company) and have a set of possible activities to undergo.
# How do you choose the activities that lead to a maximisation of the unit's profit?
# The objective of this problem is hence to find the optimal level of activities to maximise the company profit, given (a) the profitability (gross margin) of each activity, (b) the resources available to the company and (c) the matrix of technical coefficients that link each activity to the resources required (positive coefficient) or provided (negative coefficient) by that specific activity.

# The problem is the same as those in the SpreadSheet file "Optimal production mix.ods" (available in the `data` folder) and can be solved in LibreOffice by using the "Solver tool" as indicated in that file. You can use it to check your results.
#
#

# ### Environment set-up and data loading

# 1) Start by setting the working directory to the directory of this file and activate it. If you have the `Manifest.toml` file in the directory, just run `Pkg.instantiate()`, otherwise manually add the packages JuMP, GLPK, DataFrames, CSV, Pipe, HTTP.

# 2) Load the packages DelimitedFiles, JuMP, GLPK, DataFrames, CSV, Pipe, HTTP

# 3) Load from internet or from local files the following data:

urlActivities   = "https://raw.githubusercontent.com/sylvaticus/IntroSPMLJuliaCourse/main/lessonsMaterial/02_JULIA2/loggingOptimisation/data/activities.csv"
urlResources    = "https://raw.githubusercontent.com/sylvaticus/IntroSPMLJuliaCourse/main/lessonsMaterial/02_JULIA2/loggingOptimisation/data/resources.csv"
urlCoefficients = "https://raw.githubusercontent.com/sylvaticus/IntroSPMLJuliaCourse/main/lessonsMaterial/02_JULIA2/loggingOptimisation/data/coefficients.csv"

# The result must be:
# - `activities`, a dataframe with the columns `label`, `gm` and `integer`
# - `resources`, a dataframe with the columns `label`, `initial` and `initial2`
# - `coeff`, a 10x12 Matrix of Float64

# For example to download from internet and import the matrix you can use something like:
coef  = @pipe HTTP.get(urlCoefficients).body |> readdlm(_,';')

# 4) Determine `nA` and `nR` as the number of activities and the number of resources (use the `size` function)

# ### Optimisation model definition

# 5) Define `profitModel` as a model to be optimised using the `GLPK.Optimizer`
# 6) [OPTIONAL] set `GLPK.GLP_MSG_ALL` as the `msg_lev` of GLPK

# ### Model's endogenous variables definition

# 7) Define the non-negative model `x` variable, indexed by the positions between 1 and `nA` (i.e. `x[1:nA] >= 0`) (you could use the @variables macro). Don't set the `x` variable to be integer at this step, as some variables are continuous, just set them to be non-negative.

# 8) Set the variables for which the corresponding `integer` column in the `activity` dataframe is equal to 1 as a integer variable.
# To set the specific vaciable `x[i]` as integer use  `set_integer(x[i])`

# ### Model's constraint definition

# 9) Define the `resLimit[r in 1:nR]` family of contraints, such that when you sum `coef[r,a]*x[a]` for all the `1:nA` activities you must have a value not greater than `resources.initial[r]`

# ### Objective definition

# 10) Define the objective as the maximisation of the profit given by summing for each of the `1:nA` activities `activities.gm[a] * x[a]`

# ### Model resolution
# 11) [OPTIONAL] Print the model to check it
# 12) Optimize the model
# 13) Check with the function `status = termination_status(profitModel)` that the status is `OPTIMAL` (it should be!)

# ### Print optimal level of activities

# 14) Run the following code to print the results (optimal level of activities)

if (status == MOI.OPTIMAL || status == MOI.LOCALLY_SOLVED || status == MOI.TIME_LIMIT) && has_values(profitModel)
    println("#################################################################")
    if (status == MOI.OPTIMAL)
        println("** Problem solved correctly **")
    else
        println("** Problem returned a (possibly suboptimal) solution **")
    end
    println("- Objective value (total costs): ", objective_value(profitModel))
    println("- Optimal Activities:\n")
    optValues = value.(x)
    for a in 1:nA
      println("* $(activities.label[a]):\t $(optValues[a])")
    end
    if JuMP.has_duals(profitModel)
        println("\n\n- Shadow prices of the resources:\n")
        for r in 1:nR
            println("* $(resources.label[r]):\t $(dual(resLimit[r]))")
        end
    end
else
    println("The model was not solved correctly.")
    println(status)
end

# 15) [OPTIONAL] Observe the emergence of scale effects
# Optionally re-run the model with `initial2` initial resources (instead of `initial`) and notice how this larger company can afford to perform different types of activities (logging high forest instead of coppices in this example) and obtain a better profitability per unit of resource employed.
# Can you guess which are the aspects of the optimisation model that allow for the emergence of these scale effects?


#
#Simple example of a Poisson problem with a variable coefficient:
#
#           y
#             |
#             |           u_y = 0
#          Ly ----------------------------------
#             |                                |
#             |                                |
#             |                                |
#       u = 0 |   -(au_x)_x - (au_y)_y = f     | u = 0
#             |                                |
#             |                                |
#             |                                |
#           -----------------------------------|-----  x
#                         u_y = 0               Lx
#
#Here,
#
#      u = sin(ωx x) * cos(ωy y)
#      a = exp(x-y)
#      f = ω exp(x-y) ( 2ω sin(ω x) cos(ω y) - cos ω(x-y) )
#      ωx = mπ / Lx
#      ωy = nπ / Ly
#

using FinElt
using FinElt.PlanarPoisson

include("params.jl")
const ωx = pi / Lx
const ωy = pi / Ly

function exact_u(x)
    return sin(ωx*x[1]) * cos(ωy*x[2])
end

function a(x)
    return exp(x[1]-x[2])
end

function f(x)
    return exp(x[1]-x[2]) * (
             ωx * ( ωx * sin(ωx*x[1]) - cos(ωx*x[1]) ) * cos(ωy*x[2])
           + ωy * ( ωy * cos(ωy*x[2]) - sin(ωy*x[2]) ) * sin(ωx*x[1])
           )
end

ell_f(v, z) = P1SourceTimesFunc!(v, z, f)
lin_functionals = [ ("Omega", ell_f) ]

maxerr = zeros(refinements+1)
@printf("%10s  %12s  %8s  %8s\n\n",
        "N", "max error", "rate", "seconds")
for k = 0:refinements
    start = time()
    mesh = read_msh_file("rect$k.msh")
    vp = VariationalProblem(mesh, ["Left", "Right"])
    add_bilin_form!(vp, "Omega", grad_dot_grad!, a)
    add_lin_functnl!(vp, "Omega", source_times_func!, f)
    # Implicit zero boundary conditions.
    A, b = assembled_linear_system(vp)
    ufree = A \ b
    uh = complete_soln(ufree, vp)
    u = get_nodal_vals(exact_u, mesh)
    finish = time()
    maxerr[k+1] = maximum(abs.(uh-u))
    N = length(ufree)
    if k == 0
        @printf("%10d  %12.4e\n", N, maxerr[k+1])
    else
        rate = log2(maxerr[k]/maxerr[k+1])
        elapsed = finish - start
        @printf("%10d  %12.4e  %8.4f  %8.4f\n",
                N, maxerr[k+1], rate, elapsed)
    end
end


using Random
using LinearAlgebra

"""
Generate an N×N matrix with uniformly random entries in [0,1].
"""
function uniform_matrix(N)
	return rand(N, N)
end

"""
Generate a random N×N matrix with entries satisfying the triangle inequality.
"""
function metric_matrix(N; dim=5, p=2)
	points = rand(N, dim)
	M = [norm(points[i,:] - points[j,:], p) for i in 1:N, j in 1:N]
	return M
end

"""
Generate a random 0-1 N×N matrix with edge probability p.
"""
function zeroone_matrix(N; p=0.25, selfedges=true, symmetric=false)
	M = rand(N, N) .≤ p
	if symmetric
		M = triu(M) .| triu(M)'
	end
	if !selfedges
		for i in 1:N
			M[i,i] = 0
		end
	end
	return M
end

"""
Generate a pair of N×N matricies with known optimal permutation using
Algorithm 4 from "Generating Quadratic Assignment Test Problems with
Known Optimal Permutations" by Li and Pardalos.
"""
function generate_qap(N)
	D = Matrix(I, N, N)
	O = .~D

	ΔA = rand(1:100)
	ΔB = rand(1:100)

	A = fill(ΔA, N, N)
	A[D] .= 0

	B = Array{Int,2}(undef, N, N)
	for i in 1:N, j in 1:N
		if i == j
			B[i,j] = 0
		else
			B[i,j] = rand(0:ΔB)
		end
	end

	R = findall(O)
	sort!(R, by=(ij -> B[ij]))

	X = rand(1:ΔA, N*(N-1))
	sort!(X, rev=true)

	for (k, ij) in enumerate(R)
		A[ij] = A[ij] - X[k]
	end

	σ = randperm(N)
	σinv = invperm(σ)
	B = B[σinv,σinv]

	return A, B, σ
end


using PyPlot

pospart(x) = ( x > 0 ? x : zero(x) )

function K2(x, y, a, b)
    pt = [a, (a+b)/2, b]
    ℓ2 = (x-pt[1])*(x-pt[3])/((pt[2]-pt[1])*(pt[2]-pt[3]))
    ℓ3 = (x-pt[1])*(x-pt[2])/((pt[3]-pt[1])*(pt[3]-pt[2]))
    Qπ = pospart(pt[2]-y)^2 * ℓ2 / 2 + (b-y)^2 * ℓ3 / 2
    return Qπ - pospart(x-y)^2/2
end

a = -1.0
b = 1.0
N = 200
x = LinRange(a, b, N+1)
y = LinRange(a, b, N+1)
z = Float64[ K2(x[k], y[j], a, b) for j=1:N+1, k=1:N+1 ]
fig = figure(1)
surf(x, y, z, cstride=4, rstride=4, cmap="jet", linewidth=0.25)
xlabel(L"x")
ylabel(L"y")
ax = gca()
ax[:view_init](elev=40,azim=-100)
savefig("Quadratic_PeanoK_3d.pdf")

figure(2)
contour(x, y, z, 12)
colorbar()
xlabel(L"x")
ylabel(L"y")
grid(true)
savefig("Quadratic_PeanoK_contour.pdf")


## sigmoidal functions

export logistic, logistic_prime, weibull, weibull_prime

logistic(x) = 1/(1 + exp(-x))
logistic_prime(x) = exp(-x)/(1 + exp(-x))^2
weibull(x, coef, scale, shape) = (1 - exp(-((x/scale)^shape)))*coef
weibull_prime(x, coef, scale, shape) = coef*(shape/scale)*((x/scale)^(shape-1))*exp(-((x/scale)^shape))


# ---
# title: 633. Sum of Square Numbers
# id: problem633
# author: Indigo
# date: 2021-06-27
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/sum-of-square-numbers/description/>
# hidden: true
# ---
#
# Given a non-negative integer `c`, decide whether there're two integers `a` and
# `b` such that `a2 + b2 = c`.
#
#
#
# **Example 1:**
#
#
#
#     Input: c = 5
#     Output: true
#     Explanation: 1 * 1 + 2 * 2 = 5
#
#
# **Example 2:**
#
#
#
#     Input: c = 3
#     Output: false
#
#
# **Example 3:**
#
#
#
#     Input: c = 4
#     Output: true
#
#
# **Example 4:**
#
#
#
#     Input: c = 2
#     Output: true
#
#
# **Example 5:**
#
#
#
#     Input: c = 1
#     Output: true
#
#
#
#
# **Constraints:**
#
#   * `0 <= c <= 231 - 1`
#
#
## @lc code=start
using LeetCode

function judge_square_sum(n::Int)
    upper = isqrt(n)
    i = 0
    while i <= upper
        ss = i ^ 2 + upper ^ 2
        ss == n && return true
        ss > n ? (upper -= 1) : (i += 1)
    end
    return false
end
## @lc code=end


"""
    lis(arr::Array{Int}, ::Val{:bs})

# Arguments:
- `arr`: sequence of integers

# Examples/Tests
```julia
julia> lis([3, 10, 2, 1, 20], Val(:bs))
[3, 10, 20]

julia> lis([2, 2, 3], Val(:bs))
[2, 3]
```

# Brief:
Given a sequence of integers, the function finds the longest, strictly increasing, subsequence in that sequence.

# For more information:
https://cp-algorithms.com/sequences/longest_increasing_subsequence.html

# Contributed by:
- [Igor Malheiros](https://github.com/igormalheiros)
"""

function lis(arr::Array{Int}, ::Val{:bs})
    len = length(arr)
    memo = ones(Int, len)
    p = ones(Int, len)

    lis_arr = Int[]

    len == 0 && return lis_arr # if `arr` is empty

    lis_len = 1
    memo[1] = arr[1]

    for i = 2:len
        # binary search in current `memo`, it uses @view and slicing for in-place verification
        p[i] = searchsortedfirst((@view memo[1:lis_len]), arr[i])
        # if arr[i] is greater than the greatest element in `memo`, it increases `lis_len`
        lis_len = max(lis_len, p[i])
        # updates `memo`
        memo[p[i]] = arr[i]
    end

    # Restoring
    last_pos = lis_len
    for i = len:-1:1
        if p[i] == last_pos
            push!(lis_arr, arr[i])
            last_pos -= 1
        end
    end

    reverse!(lis_arr)

    return lis_arr
end


module JacobiAlgorithm

    struct EquilibriumProblem{T<:Real}
        nbz::Int64
        e_z::Function
        pmin::T
        pmax::T
        q_z::Vector{T}
        p0_z::Vector{T}
        function EquilibriumProblem(nbz::Int64,e_z::Function,pmin::T,pmax::T,q_z::Vector{T},p0_z::Vector{T})  where {T<:Real}
            this = new{T}(nbz,e_z,pmin,pmax,q_z,p0_z) #,NaN,[],[],[],NaN)
        end
    end

    function bisection(f::Function, a::T, b::T;
        tol::T=1e-5, maxiter::Int64=100) where T<:Real
        fa = f(a)
        fa*f(b) <= 0 || error("No real root in [a,b]")
        i = 0
        local c
        while b-a > tol
                i += 1
                i != maxiter || error("Max iteration exceeded")
                c = (a+b)/2
                fc = f(c)
            if fc == 0
                break
            elseif fa*fc > 0
                a = c  # Root is in the right half of [a,b].
                fa = fc
            else
                b = c  # Root is in the left half of [a,b].
            end
        end
        return c
    end

    function coordinate_update(problem::EquilibriumProblem{T}, z::Int64, price_z::Vector{T}) where T<:Real
        old_price = price_z[z]
        f(p) = problem.e_z(setindex!(price_z,p,z))[z] - problem.q_z[z]
        new_price = bisection(f, problem.pmin, problem.pmax,tol=1e-5,maxiter=1000)
        price_z[z] = old_price
        return new_price, new_price - old_price
    end

    function fJ_z(problem::EquilibriumProblem{T}, p_z::Vector{T}) where T<:Real
        nbz = size(p_z,1)
        pp_z = zeros(T, nbz)
        diffp_z = zeros(T, nbz)
        for z = 1:nbz
            pp_z[z], diffp_z[z] = coordinate_update(problem,z,p_z)
        end
        return pp_z, diffp_z
    end

    function fJ_threaded_z(problem::EquilibriumProblem{T}, p_z::Vector{T}) where T<:Real
        nbz = size(p_z,1)
        pp_z = zeros(T, nbz)
        diffp_z = zeros(T, nbz)
        Threads.@threads for z = 1:nbz
            p_z_safe = deepcopy(p_z)
            pp_z[z], diffp_z[z] = coordinate_update(problem,z,p_z_safe)
        end
        return pp_z, diffp_z
    end

    function solve(problem::EquilibriumProblem{T};
        maxit::Int64 = 10_000,
        method::Symbol = :jacobian,
        valtol::T = 1e-5,
        steptol::T = 1e-9,
        output::Int64 = 0) where T<:Real

        code = 0
        start_time = time()
        delta_z = zeros(T,problem.nbz)
        diffp_z = zeros(T,problem.nbz)
        iterations = 0
        p_z = deepcopy(problem.p0_z)
        if method != :jacobian
            error("Method not implemented")
        end
        for i = 1:maxit
            p_z, diffp_z = fJ_z(problem, p_z)
            delta_z = problem.e_z(p_z) .- problem.q_z'
            if output>1
                println("p = $p_z")
            end
            if maximum(abs.(delta_z)) < valtol
                code = 0
                iterations = i
                break
            end
            if maximum(abs.(diffp_z)) < steptol
                code = 1
                iterations = i
                break
            end
            code = 2
        end
        comp_time = time() - start_time
        if output > 0
            println("$(String(method)) method converged in $iterations iterations.")
            println("Value of p = $(p_z)")
            println("Value of p' - p = $(diffp_z)")
            println("Value of e(p)-q = $(delta_z)")
            println("Time elapsed: $comp_time")
            println("Code: $code")
        end
        return code
    end

    function solve_threaded(problem::EquilibriumProblem{T};
        maxit::Int64 = 10_000,
        method::Symbol = :jacobian,
        valtol::T = 1e-5,
        steptol::T = 1e-9,
        output::Int64 = 0) where T<:Real

        code = 0
        start_time = time()
        delta_z = zeros(T,problem.nbz)
        diffp_z = zeros(T,problem.nbz)
        iterations = 0
        p_z = deepcopy(problem.p0_z)
        if method != :jacobian
            error("Method not implemented")
        end
        for i = 1:maxit
            p_z, diffp_z = fJ_threaded_z(problem, p_z)
            delta_z = problem.e_z(p_z) .- problem.q_z'
            if output>1
                println("p = $p_z")
            end
            if maximum(abs.(delta_z)) < valtol
                code = 0
                iterations = i
                break
            end
            if maximum(abs.(diffp_z)) < steptol
                code = 1
                iterations = i
                break
            end
            code = 2
        end
        comp_time = time() - start_time
        if output > 0
            println("$(String(method)) method converged in $iterations iterations.")
            println("Value of p = $(p_z)")
            println("Value of p' - p = $(diffp_z)")
            println("Value of e(p)-q = $(delta_z)")
            println("Time elapsed: $comp_time")
            println("Code: $code")
        end
        return code
    end
end


# this is a little Monte Carlo exercise that illustrates that
# the OLS estimator is biased and inconsistent when errors are
# correlated with regressors, but that the IV estimator is consistent
using Econometrics, Plots, LinearAlgebra
function main()
reps = 1000 # number of Monte Carlo reps.
betaols = zeros(reps,2)
betaiv = zeros(reps,2)
n = 1000 # sample size

# covariance of X, W, e
cor_X_W = 0.2  # experiment with lowering or raising this: quality of instrument
cor_X_e = 0.5  # try setting this to zero to observe inefficiency of IV
sig = [
      1.0       cor_X_W     cor_X_e;
      cor_X_W   1.0         0.0;
     cor_X_e    0.0         1.0]
truebeta = [1, 2] # true beta
p = cholesky(sig).U
for i = 1:reps
	XWE = randn(n,3)*p
	e = XWE[:,3:3]
	w = [ones(n) XWE[:,2:2]]
	x = [ones(n) XWE[:,1:1]]
	y = x*truebeta + e
	# OLS
	betaols[i,:] = (x\y)'
	# IV
	betaiv[i,:] = (inv(w'*x)*w'*y)'
end

p1 = npdensity(betaols[:,2])
plot!(p1,title="OLS")
p2 = npdensity(betaiv[:,2])
plot!(p2,title="IV")
p = plot(p1,p2,layout=(2,1))
#savefig("olsiv.png")
gui()
println("true betas are ", truebeta)
println("OLS results")
dstats(betaols, short=true)
println("IV results")
dstats(betaiv, short=true)
return p
end
main()


using OrdinaryDiffEq
using DiffEqCallbacks
using GlobalSensitivity
using Distributions
using Plots

function sir_ode!(du,u,p,t)
    (S,I,R) = u
    (β,c,γ) = p
    N = S+I+R
    @inbounds begin
        du[1] = -β*c*I/N*S
        du[2] = β*c*I/N*S - γ*I
        du[3] = γ*I
    end
    nothing
end;

tmax = 10000.0
tspan = (0.0,tmax);

cb_ss = TerminateSteadyState();

N = 1000.0;
u0 = [990.0,10.0,0.0];
p = [0.05,10.0,0.25]; # β,c,γ

n_samples = 1000 # Number of samples
# Parameters are β, c, γ, I₀
lb = [0.01, 5.0, 0.1, 1.0]
ub = [0.1, 20.0, 1.0, 50.0]
n_params = 4;

prob_ode = ODEProblem(sir_ode!,u0,tspan,p);

f1 = function(pu0)
  p = pu0[1:3]
  I0 = pu0[4]
  u0 = [N-I0,I0,0.0]
  prob = remake(prob_ode;p=p,u=u0)
  sol = solve(prob, ROS34PW3(),callback=cb_ss)
  [maximum(sol[2,:]), sol.t[argmax(sol[2,:])], sol[end][3]]
end;

m_morris = gsa(f1, Morris(num_trajectory=n_samples), [[lb[i],ub[i]] for i in 1:n_params]);

m_morris.means

m_morris.variances

m_sobol = gsa(f1, Sobol(), [[lb[i],ub[i]] for i in 1:n_params],N=n_samples);

m_sobol.ST

m_sobol.S1

m_regression = gsa(f1, RegressionGSA(rank=true), [[lb[i],ub[i]] for i in 1:n_params]; samples = n_samples);

m_regression.partial_correlation

m_regression.partial_rank_correlation

m_efast = gsa(f1, eFAST(), [[lb[i],ub[i]] for i in 1:n_params]; n = n_samples);

m_efast.ST

m_efast.S1

pf1 = function (pu0)
  p = pu0[1:3,:]
  I0 = pu0[4,:]
  prob_func(prob,i,repeat) = remake(prob;p=p[:,i],u=[N-I0[i],I0[i],0.0])
  ensemble_prob = EnsembleProblem(prob_ode,prob_func=prob_func)
  sol = solve(ensemble_prob,ROS34PW3(),EnsembleThreads();trajectories=size(p,2))
  out = zeros(3,size(p,2))
  for i in 1:size(p,2)
    out[1,i] = maximum(sol[i][2,:])
    out[2,i] = sol[i].t[argmax(sol[i][2,:])]
    out[3,i] = sol[i][end][3]
  end
  out
end;

m_efast_parallel = gsa(pf1, eFAST(), [[lb[i],ub[i]] for i in 1:n_params]; n = n_samples, batch = true);


# # [Time integration](@id Time-integration)

# Time integration methods for the numerical solution of Ordinary Differential
# Equations (ODEs), also called timesteppers, can be of different nature and
# flavor (e.g., explicit, semi-implicit, single-stage, multi-stage, single-step,
# multi-step, single-rate, multi-rate, etc). ClimateMachine supports several
# of them. Before showing the different nature of some of these methods, let us
# introduce some common notation.

# A commonly used notation for Initial Value Problems (IVPs) is:

# ```math
# \begin{align}
#     \frac{\mathrm{d} \boldsymbol{q}}{ \mathrm{d} t} &= \mathcal{T}(t, \boldsymbol{q}),\\
#     \boldsymbol{q}(t_0) &= \boldsymbol{q_0},
# \end{align}
# ```

# where ``\boldsymbol{q}`` is an unknown function (vector in most of our cases)
# of time ``t``, which we would like to approximate, and at the initial time ``t_0``
# the corresponding initial value ``\boldsymbol{q}_0`` is given.

# The given general formulation, is suitable for single-step explicit schemes.
# Generally, the equation can be represented in the following canonical form:

# ```math
# \begin{align}
#      \dot {\boldsymbol{q}} + \mathcal{F}(t, \boldsymbol{q}) &= \mathcal{G}(t, \boldsymbol{q}),
# \end{align}
# ```

# where we have used ``\dot {\boldsymbol{q}} = d \boldsymbol{q} / dt``.
# We refer to the term ``\mathcal{G}``
# as the right-hand-side (RHS) or explicit term, and to the spatial terms of
# ``\mathcal{F}`` as the left-hand-side (LHS) or implicit term.


"""
    logistic2_bidir(u₀, c_xy, c_yx, r₁, r₂, σ_xy, σ_yx)

Equations of motion for a bidirectional logistic model for the chaotic
population dynamics of two interacting species. This system is from [1],
and is given by

```math
\\begin{align}
x(t+1) &= r_1 f_{yx}^{t}(1 - f_{yx}^{t}) \\
y(t+1) &= r_2 f_{xy}^{t}(1 - f_{xy}^{t}) \\
f_{xy}^t &= \\dfrac{y(t) + c_{xy}(x(t) + \\sigma_{xy} \\xi_{xy}^t )}{1 + c_{xy} (1 + \\sigma_{xy} )} \\
f_{yx}^t &= \\dfrac{x(t) + c_{yx}(y(t) + \\sigma_{yx} \\xi_{yx}^t )}{1 + c_{yx} (1 + \\sigma_{yx} )},
\\end{align}
```

where the coupling strength ``c_{xy}`` controls how strongly species ``x`` influences species
``y``, and vice versa for ``c_{yx}``. To simulate time-varying influence of unobserved
processes, we use the dynamical noise terms ``\\xi_{xy}^t`` and ``\\xi_{yx}^t``, drawn from a
uniform distribution with support on ``[0, 1]``. If ``\\sigma_{xy} > 0``, then the influence
of ``x`` on ``y`` is masked by dynamical noise equivalent to ``\\sigma_{xy} \\xi_{xy}^{t}`` at
the ``t``-th iteration of the map, and vice versa for ``\\sigma_{yx}``.

## References

1. Diego, David, Kristian Agasøster Haaga, and Bjarte Hannisdal. "Transfer entropy computation
    using the Perron-Frobenius operator." Physical Review E 99.4 (2019): 042212.
"""
function eom_logistic2_bidir(dx, x, p, n)

    # c_xy is the coupling from x to y
    # c_yx is the coupling from y to x
    # σ_yx is the dynamical noise from y to x
    # σ_xy is the dynamical noise from y to x
    c_xy, c_yx, r₁, r₂, σ_xy, σ_yx = (p...,)

    ξ₁ = rand() # random number from flat distribution on [0, 1]
    ξ₂ = rand() # random number from flat distribution on [0, 1]
    x, y = x[1], x[2]

    f_xy = (y +  c_xy*(x + σ_xy*ξ₁) ) / (1 + c_xy*(1+σ_xy))
    f_yx = (x +  c_yx*(y + σ_yx*ξ₂) ) / (1 + c_yx*(1+σ_yx))

    dx[1] = r₁ * (f_yx) * (1 - f_yx)
    dx[2] = r₂ * (f_xy) * (1 - f_xy)
    return
end

function logistic2_bidir(u₀, c_xy, c_yx, r₁, r₂, σ_xy, σ_yx)
    p = [c_xy, c_yx, r₁, r₂, σ_xy, σ_yx]
    DiscreteDynamicalSystem(eom_logistic2_bidir, u₀, p)
end

"""
    logistic2_bidir(;u₀ = rand(2), c_xy = 0.1, c_yx = 0.1,
        r₁ = 3.78, r₂ = 3.66, σ_xy = 0.05, σ_yx = 0.05)

A bidirectional logistic model for the chaotic population dynamics of two interacting
species [1].

## Equations of motion

The equations of motion are

```math
\\begin{align}
x(t+1) &= r_1 f_{yx}^{t}(1 - f_{yx}^{t}) \\\\
y(t+1) &= r_2 f_{xy}^{t}(1 - f_{xy}^{t}) \\\\
f_{xy}^t &= \\dfrac{y(t) + c_{xy}(x(t) + \\sigma_{xy} \\xi_{xy}^t )}{1 + c_{xy} (1 + \\sigma_{xy} )} \\\\
f_{yx}^t &= \\dfrac{x(t) + c_{yx}(y(t) + \\sigma_{yx} \\xi_{yx}^t )}{1 + c_{yx} (1 + \\sigma_{yx} )},
\\end{align}
```

where the coupling strength ``c_{xy}`` controls how strongly species ``x`` influences species
``y``, and vice versa for ``c_{yx}``. To simulate time-varying influence of unobserved
processes, we use the dynamical noise terms ``\\xi_{xy}^t`` and ``\\xi_{yx}^t``, drawn from a
uniform distribution with support on ``[0, 1]``. If ``\\sigma_{xy} > 0``, then the influence
of ``x`` on ``y`` is masked by dynamical noise equivalent to ``\\sigma_{xy} \\xi_{xy}^{t}`` at
the ``t``-th iteration of the map, and vice versa for ``\\sigma_{yx}``.

## References

1. Diego, David, Kristian Agasøster Haaga, and Bjarte Hannisdal. "Transfer entropy computation
    using the Perron-Frobenius operator." Physical Review E 99.4 (2019): 042212.
"""
logistic2_bidir(;u₀ = rand(2), c_xy = 0.1, c_yx = 0.1,
    r₁ = 3.78, r₂ = 3.66, σ_xy = 0.05, σ_yx = 0.05) =
    logistic2_bidir(u₀, c_xy, c_yx, r₁, r₂, σ_xy, σ_yx)


# coding: utf-8

# In[1]:

#coin change is a straight forward dynamic programming problem
#you are given one note and a set of coins of different values
#assuming you have infinite supply of coins of different values
#we need to compute different ways of making change
#the order of the coins doesnt matter in this case
#more details can be found in the following link
# https://www.geeksforgeeks.org/coin-change-dp-7/
#the script can also be done in recursion
# https://github.com/je-suis-tm/recursion-and-dynamic-programming/blob/master/coin%20change%20recursion.jl

# In[2]:

#to solve this problem via tabulation
#we divide one big problem into two sub problems
#the case where coin of η value is excluded in the solutions
#and the case where at least one coin of η value is included
function coin_change(num,choice)

    #create matrix (num+1)*length(choice)
    #the raison d'être is the computation starts from 0 to num
    #0 is when num is perfectly substituted by coins
    tabulation=[[0 for _ in 1:length(choice)] for _ in 1:num+1]

    #initialize
    #when the remain value happens to be η
    #one solution is found
    for i in 1:length(choice)

        tabulation[1][i]=1

    end

    #since we initialize the null case
    #the outerloop starts from 2
    #i actually refers to i-1
    for i in 2:num+1

        for j in 1:length(choice)

            #annoying part of julia
            #if index starts at zero
            #will be a lot easier
            if i-choice[j]>=1

                #the case where at least one coin of η value is included
                #we just need the computation where η is deducted
                include=tabulation[i-choice[j]][j]

            else

                include=0

            end

            #the case where coin of η value is excluded in the solutions
            if j>=2

                exclude=tabulation[i][j-1]

            else

                exclude=0

            end

            #two sub problems merge into a big one
            tabulation[i][j]=exclude+include

        end

    end

    #get the final answer
    return tabulation[num+1][length(choice)]

end

# In[3]:

coin_change(10,[1,2,5])


#=
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
=#

function calc()
  filter(x -> x % 3 == 0 || x % 5 == 0, 1:999) |> sum |> println
end
@time calc()