# # [Bezier curves and their acceleration](@id BezierCurvesTutorial)
#
# This tutorial illustrates how Bézier curves are generalized to manifolds and how to
# minimize their acceleration, i.e. how to get a curve that is as straight or as geodesic
# while fulfilling constraints
#
# This example also illustrates how to apply the minimization on the corresponding [`PowerManifold`](https://juliamanifolds.github.io/Manifolds.jl/latest/manifolds/power.html) manifold using a [`gradient_descent`](@ref) with [`ArmijoLinesearch`](@ref).
#
# ## Table of contents
# * [Setup](@ref SetupTB)
# * [de Casteljau algorithm on manifolds](@ref Casteljau)
# * [Composite Bézire curves](@ref CompositeBezier)
# * [Minimizing the acceleration of a Bézier curve](@ref MinAccBezier)
# * [Literature](@ref LiteratureBT)
#
# ## [Setup](@id SetupTB)
#
# We first initialize the necessary packages
export_folder = joinpath( #src
    @__DIR__, #src
    "..", #src
    "..", #src
    "docs", #src
    "src", #src
    "assets", #src
    "images", #src
    "tutorials", #src
) #src
using Manopt, Manifolds
# and we define some colors from [Paul Tol](https://personal.sron.nl/~pault/)
using Colors
black = RGBA{Float64}(colorant"#000000")
TolVibrantBlue = RGBA{Float64}(colorant"#0077BB")
TolVibrantOrange = RGBA{Float64}(colorant"#EE7733")
TolVibrantMagenta = RGBA{Float64}(colorant"#EE3377")
TolVibrantCyan = RGBA{Float64}(colorant"#33BBEE")
TolVibrantTeal = RGBA{Float64}(colorant"#009988")
geo_pts = collect(range(0.0, 1.0; length=101)) #hide
bezier_pts = collect(range(0.0, 3.0; length=201)) #hide
camera_position = (-1.0, -0.7, 0.3) #hide
nothing #hide
# Then we load our data, see [`artificial_S2_composite_bezier_curve`](@ref), a composite
# Bezier curve consisting of 3 segments on the Sphere
B = artificial_S2_composite_bezier_curve();
#
# ## [De Casteljau algorithm on manifolds](@id Casteljau)
# This curve can be evaluated using de [Casteljau's algorithm](https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm)[^Casteljau1959][^Casteljau1963] named after [Paul de Casteljau](https://en.wikipedia.org/wiki/Paul_de_Casteljau)(*1930).
# To simplify the idea and understand this algorithm, we will first only look at the points of the first segment
M = Sphere(2)
b = B[2].pts
# On Euclidean spaces Bézier curves of these $n=4$ so called control points like this segment yield polynomials of degree $3$.
# The resulting curve $\gamma: [0,1] \to \mathbb R^m$ is called [Bezier curve](https://en.wikipedia.org/wiki/Bézier_curve) or Bézier spline and is named after [Piérre Bezier](https://en.wikipedia.org/wiki/Pierre_Bézier) (1910–1999).
# They can be evaluated by the de Casteljau algorithm by evaluating line segments between points.
# While it is not easy to evaluate polynomials on a manifold, evaluating line segments generalizes to the evaluation of [`shortest_geodesic`](https://juliamanifolds.github.io/Manifolds.jl/latest/interface.html#ManifoldsBase.shortest_geodesic-Tuple{Manifold,Any,Any})s
#
# We will illustrate this using these points $b=(b_1,b_2,b_3,b_4)$ on the [`Sphere`](https://juliamanifolds.github.io/Manifolds.jl/latest/manifolds/sphere.html) $\mathbb S^2$.
# Let's evaliuate this at the point $t=\frac{1}{4}\in [0,1]$. We first compute
t = 0.66
pts1 = shortest_geodesic.(Ref(M), b[1:3], b[2:4], Ref(t))
# We obtain 3 points on the geodesics connecting the control points. Repeating this again twice
pts2 = shortest_geodesic.(Ref(M), pts1[1:2], pts1[2:3], Ref(t))
p = shortest_geodesic(M, pts2[1], pts2[2], t)
# we obtain the point on the Bézier curve $c(t)$.
# This procedure is illustrated in the following image:
#
curves1 = [shortest_geodesic(M, b[i], b[i + 1], geo_pts) for i in 1:3] #src
curves2 = [shortest_geodesic(M, pts1[i], pts1[i + 1], geo_pts) for i in 1:2] #src
curves3 = [shortest_geodesic(M, pts2[1], pts2[2], geo_pts)] #src
asymptote_export_S2_signals( #src
    export_folder * "/Casteljau-illustr.asy"; #src
    points=[b, pts1, pts2, [p]], #src
    curves=[curves1..., curves2..., curves3..., de_casteljau(M, B[2], geo_pts)], #src
    colors=Dict( #src
        :points => [TolVibrantBlue, TolVibrantCyan, TolVibrantTeal, TolVibrantOrange], #src
        :curves => [ #src
            TolVibrantBlue, #src
            TolVibrantBlue, #src
            TolVibrantBlue, #src
            TolVibrantCyan, #src
            TolVibrantCyan, #src
            TolVibrantTeal, #src
            black, #src
        ], #src
    ), #src
    dot_size=3.5, #src
    line_widths=[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.5], #src
    camera_position=camera_position, #src
) #src
render_asymptote(export_folder * "/Casteljau-illustr.asy"; render=2) #src
#md # ![Illustration of de Casteljau's algorithm on the Sphere.](../assets/images/tutorials/Casteljau-illustr.png)
#
# From the control points (blue) and their geodesics, ont evaluation per geodesic yields three interims points (cyan), their two successive geodeics another two points (teal) and at its geodesic at $t=0.66$ we obtain the point on the curve.
#
# In Manopt.jl, to evaluate a Bézier curve knowing its [`BezierSegment`](@ref), use [`de_casteljau`](@ref).
#
# There are a few nice observations to make, that hold also for these Bézier curves on manifolds:
# * The curve starts in the first controlpoint $b_0$ and ends in the last controlpoint $b_3$
# * The tangent vector to the curve at the start $\dot c(0)$ is equal to $\log_{b_0}b_1 = \dot\gamma_{b_0,b_0}(0)$, where $\gamma_{a,b}$ denotes the shortest geodesic.
# * The tangent vector to the curve at the end $\dot c(1)$ is equal to $-\log_{b_3}b_2 = -\dot\gamma_{b_3,b_2}(0) = \dot\gamma_{b_2,b_3}(1)$.
# * the curve is differentiable.
#
# For more details on these properties, see for example [^PopielNoakes2007].
# ## [Composite Bézier curves](@id CompositeBezier)
# With the properties of a single Bézier curve, also called Bézier segment, we can “stitch” curves together. Let $a_0,\ldots,a_n$ and $b_0,\ldots,b_m$ be two sets of controlpoints for the Bézier segments $c(t)$ and $d(t)$, respectively.
# We define the composite Bézier curve by $B(t) = \begin{cases} c(t) & \text{ if } 0\leq t < 1, \\ d(t-1) & \text{ if } 1\leq t \leq 2,\end{cases}$ where $t\in[0,2]$.
# This can of course be generalised straight forward to more than two cases.
# With the properties from the previous section we can now state that
#
# * the curve $B(t)$ is continuous if $c(1)=d(0)$ or in other words $a_n=b_0$
# * the curve $B(t)$ is differentiable if additionally $\dot c(1)=\dot d(0)$ or in other words $-\log_{a_n}a_{n-1} = \log_{b_0}b_1$. This is equivalent to $a_n=b_0 = \gamma_{a_{n-1}b_1}(\tfrac{1}{2})$.
#
# One nice interpretation of the last characterization is, that the tangents $\log_{a_n}a_{n-1}$ and $\log_{b_0}b_1$ point into opposite directions.
# For a continuous curve, the first point of every segment (except for the first segment) can be ommitted, for a differentiable curve the first two points (except for the first segment) can be ommitted.
# You can reduce storage by calling [`get_bezier_points`](@ref), though for econstruciton with [`get_bezier_segments`](@ref) you also need [`get_bezier_degrees`](@ref).
# The reduced storage is represented as an array of points, i.e. an element of the corresponding [`PowerManifold`](https://juliamanifolds.github.io/Manifolds.jl/latest/manifolds/power.html).
#
# For the three segment example from the beginning this looks as follows[^1]
#
asymptote_export_S2_signals( #src
    export_folder * "/Bezier-composite-curve.asy"; #src
    curves=[de_casteljau(M, B, bezier_pts)], #src
    points=[get_bezier_junctions(M, B), get_bezier_inner_points(M, B)], #src
    tangent_vectors=[[ #src
        Tuple(a) #src
        for #src
        a in #src
        zip(get_bezier_junctions(M, B, true), get_bezier_junction_tangent_vectors(M, B)) #src
    ]], #src
    colors=Dict( #src
        :curves => [black], #src
        :points => [TolVibrantBlue, TolVibrantTeal], #src
        :tvectors => [TolVibrantCyan], #src
    ), #src
    camera_position=camera_position, #src
    arrow_head_size=10.0, #src
    line_widths=[1.5, 1.5], #src
    dot_size=4.0, #src
) #src
render_asymptote(export_folder * "/Bezier-composite-curve.asy"; render=2) #src
#md # ![Illustration of a differentiable composite Bézier curve with 3 segments.](../assets/images/tutorials/Bezier-composite-curve.png)
#
# ## [Minimizing the acceleration of a composite Bézier curve](@id MinAccBezier)
# The motivation to minimize the acceleration of the composite Bézier curve is, that the curve should get “straighter” or more geodesic like.
# If we discretize the curve $B(t)$ with its control points denoted by $b_{i,j}$ for the $j$th note in the $i$th segment, the discretized model for equispaced $t_i$, $i=0,\ldots,N$ in the domain of $B$ reads[^BergmannGousenbourger2018]
#
# ````math
# A(b) \coloneqq\sum_{i=1}^{N-1}\frac{\mathrm{d}^2_2 \bigl[ B(t_{i-1}), B(t_{i}), B(t_{i+1}) \bigr]}{\Delta_t^3},
# ````
#
# where $\mathrm{d}_2$ denotes the second order finite difference using the mid point approach, see [`costTV2`](@ref)[^BacakBergmannSteidlWeinmann2016],
#
# ````math
# d_2(x,y,z) := \min_{c ∈ \mathcal C_{x,z}} d_{\mathcal M}(c,y),\qquad x,y,z∈\mathcal M.
# ````
#
# Another model is based on logarithmic maps, see [^BoumalAbsil2011], but that is not considered here.
# An advantage of the model considered here is, that it only consist of the evaluation of geodesics.
# This yields a gradient of $A(b)$ with respect to $b$ [`adjoint_Jacobi_field`](@ref)s. The following image shows the negative gradient (scaled)
#
gradFullB = Manopt._∇acceleration_bezier( #src
    M, #src
    get_bezier_points(M, B, :differentiable), #src
    [3, 3, 3], #src
    collect(range(0.0, 3.0; length=151)), #src
) #src
asymptote_export_S2_signals( #src
    export_folder * "/Bezier-composite-curve-gradient.asy"; #src
    curves=[de_casteljau(M, B, bezier_pts)], #src
    points=[get_bezier_junctions(M, B), get_bezier_inner_points(M, B)], #src
    tangent_vectors=[[ #src
        Tuple(a) #src
        for #src
        a in #src
        zip( #src
            get_bezier_points(M, B, :continuous), #src
            -0.05 .* get_bezier_points(M, gradFullB, :continuous), #src
        ) #src
    ]], #src
    colors=Dict( #src
        :curves => [black], #src
        :points => [TolVibrantBlue, TolVibrantTeal], #src
        :tvectors => [TolVibrantOrange], #src
    ), #src
    camera_position=camera_position, #src
    arrow_head_size=10.0, #src
    line_widths=[1.5, 1.5], #src
    dot_size=4.0, #src
) #src
render_asymptote(export_folder * "/Bezier-composite-curve-gradient.asy"; render=2) #src
#md # ![Illustration of the gradient of the acceleration with respect to the control points.](../assets/images/tutorials/Bezier-composite-curve-gradient.png)
#
# In the following we consider two cases: Interpolation, which fixes the junction and end points of $B(t)$
# and approximation, where a weight and a dataterm are additionally introduced.
#
# ### Interpolation
# For interpolation, the junction points are fixed and their gradient entries are hence set to zero.
# After transferring to the already mentioned [`PowerManifold`](https://juliamanifolds.github.io/Manifolds.jl/latest/manifolds/power.html), we can then perform a [`gradient_descent`](@ref) as follows
curve_samples = collect(range(0.0, 3.0; length=151)) #exactness of approximating d^2
pB = get_bezier_points(M, B, :differentiable)
N = PowerManifold(M, NestedPowerRepresentation(), length(pB))
F(pB) = cost_acceleration_bezier(M, pB, get_bezier_degrees(M, B), curve_samples)
∇F(pB) = ∇acceleration_bezier(M, pB, get_bezier_degrees(M, B), curve_samples)
x0 = pB
pB_opt_ip = gradient_descent(
    N,
    F,
    ∇F,
    x0;
    stepsize=ArmijoLinesearch(1.0, ExponentialRetraction(), 0.5, 0.0001),
    stopping_criterion=StopWhenChangeLess(5 * 10.0^(-7)),
    debug=[
        :Iteration,
        " | ",
        :Cost,
        " | ",
        DebugGradientNorm(),
        " | ",
        DebugStepsize(),
        " | ",
        :Change,
        "\n",
        :Stop,
        10,
    ],
)
#
# and the result looks like
#
B_opt_ip = get_bezier_segments(M, pB_opt_ip, get_bezier_degrees(M, B), :differentiable) #src
asymptote_export_S2_signals( #src
    export_folder * "/Bezier-IP-Min.asy"; #src
    curves=[de_casteljau(M, B_opt_ip, bezier_pts), de_casteljau(M, B, bezier_pts)], #src
    points=[get_bezier_junctions(M, B_opt_ip), get_bezier_inner_points(M, B_opt_ip)], #src
    tangent_vectors=[[ #src
        Tuple(a) #src
        for #src
        a in zip( #src
            get_bezier_junctions(M, B_opt_ip, true), #src
            get_bezier_junction_tangent_vectors(M, B_opt_ip), #src
        ) #src
    ]], #src
    colors=Dict( #src
        :curves => [TolVibrantBlue, black], #src
        :points => [TolVibrantBlue, TolVibrantTeal], #src
        :tvectors => [TolVibrantCyan], #src
    ), #src
    camera_position=camera_position, #src
    arrow_head_size=10.0, #src
    line_widths=[1.5, 0.75, 1.5], #src
    dot_size=4.0, #src
) #src
render_asymptote(export_folder * "/Bezier-IP-Min.asy"; render=2) #src
#md # ![Interpolation Min Acc](../assets/images/tutorials/Bezier-IP-Min.png)
#
# ### Approximation
# Similarly if we introduce the junction points as data fixed given $d_i$ and set (for simplicity) $p_i=b_{i,0}$ and $p_{n+1}=b_{n,4}$
# and set $λ=3$ in
#
# ````math
# \frac{\lambda}{2}\sum_{k=0}^3 d_{\mathcal M}(d_i,p_i)^2 + A(b),
# ````
#
# then $λ$ models how important closeness to the data $d_i$ is.
#
λ = 3.0
d = get_bezier_junctions(M, B)
F(pB) = cost_L2_acceleration_bezier(M, pB, get_bezier_degrees(M, B), curve_samples, λ, d)
∇F(pB) = ∇L2_acceleration_bezier(M, pB, get_bezier_degrees(M, B), curve_samples, λ, d)
x0 = pB
pB_opt_appr = gradient_descent(
    N,
    F,
    ∇F,
    x0;
    stepsize=ArmijoLinesearch(1.0, ExponentialRetraction(), 0.5, 0.001),
    stopping_criterion=StopWhenChangeLess(10.0^(-5)),
    debug=[
        :Iteration,
        " | ",
        :Cost,
        " | ",
        DebugGradientNorm(),
        " | ",
        DebugStepsize(),
        " | ",
        :Change,
        "\n",
        :Stop,
        50,
    ],
)
#
# and the result looks like
#
B_opt_appr = get_bezier_segments(M, pB_opt_appr, get_bezier_degrees(M, B), :differentiable) #src
asymptote_export_S2_signals( #src
    export_folder * "/Bezier-Appr-Min.asy"; #src
    curves=[de_casteljau(M, B_opt_appr, bezier_pts), de_casteljau(M, B, bezier_pts)], #src
    points=[ #src
        d, #src
        get_bezier_junctions(M, B_opt_appr), #src
        get_bezier_inner_points(M, B_opt_appr), #src
    ], #src
    tangent_vectors=[[ #src
        Tuple(a) #src
        for #src
        a in zip( #src
            get_bezier_junctions(M, B_opt_appr, true), #src
            get_bezier_junction_tangent_vectors(M, B_opt_appr), #src
        ) #src
    ]], #src
    colors=Dict( #src
        :curves => [TolVibrantBlue, black], #src
        :points => [TolVibrantOrange, TolVibrantBlue, TolVibrantTeal], #src
        :tvectors => [TolVibrantCyan], #src
    ), #src
    camera_position=camera_position, #src
    arrow_head_size=10.0, #src
    line_widths=[1.5, 0.75, 1.5], #src
    dot_size=4.0, #src
) #src
render_asymptote(export_folder * "/Bezier-Appr-Min.asy"; render=2) #src
#
#md # ![Approximation min Acc](../assets/images/tutorials/Bezier-Appr-Min.png)
#
# The role of $\lambda$ can be interpreted as follows: for large values of $\lambda$, the
# minimizer, i.e. the resulting curve, is closer to the original Bézier junction points.
# For small $\lambda$ the resting curve is closer to a geodesic and the control points are closer to the curve.
# For $\lambda=0$ _any_ (not necessarily shortest) geodesic is a solution and the problem is ill-posed.
# To illustrate the effect of $\lambda$, the following image contains 1000 runs for $\lambda=10$ in dark currant to $\lambda=0.01$ in bright yellow.
#
#md # ![Approximation min Acc](../assets/images/tutorials/Bezier_Approximation_video-Summary-result.png)
#
# The effect of the data term can also be seen in the following video, which starts a little slow and takes about 40 seconds.
#
#md # ![Video of the effect of lambda, the weight of the dataterm](../assets/videos/tutorials/Bezier_Approximation_video-movie.mp4)
#
#
# ## [Literature](@id LiteratureBT)
#
# [^BacakBergmannSteidlWeinmann2016]:
#     > Bačák, M., Bergmann, R., Steidl, G. and Weinmann, A.: _A second order nonsmooth
#     > variational model for restoring manifold-valued images_,
#     > SIAM Journal on Scientific Computations, Volume 38, Number 1, pp. A567–597,
#     > doi: [10.1137/15M101988X](https://doi.org/10.1137/15M101988X),
#     > arXiv: [1506.02409](https://arxiv.org/abs/1506.02409)
# [^BergmannGousenbourger2018]:
#     > Bergmann, R. and Gousenbourger, P.-Y.: _A variational model for data fitting on manifolds
#     > by minimizing the acceleration of a Bézier curve_.
#     > Frontiers in Applied Mathematics and Statistics, 2018.
#     > doi: [10.3389/fams.2018.00059](https://dx.doi.org/10.3389/fams.2018.00059),
#     > arXiv: [1807.10090](https://arxiv.org/abs/1807.10090)
# [^BoumalAbsil2011]:
#     > Boumal, N. and Absil, P.-A.: _A discrete regression method on manifolds and its application to data on SO(n)._
#     > In: IFAC Proceedings Volumes (IFAC-PapersOnline). Vol. 18. Milano (2011). p. 2284–89.
#     > doi: [10.3182/20110828-6-IT-1002.00542](https://dx.doi.org/10.3389/10.3182/20110828-6-IT-1002.00542),
#     > web: [www](https://web.math.princeton.edu/~nboumal/papers/Boumal_Absil_A_discrete_regression_method_on_manifolds_and_its_application_to_data_on_SOn.pdf)
# [^Casteljau1959]:
#     > de Casteljau, P.: _Outillage methodes calcul_, Enveloppe Soleau 40.040 (1959),
#     > Institute National de la Propriété Industrielle, Paris.
# [^Casteljau1963]:
#     > de Casteljau, P.: _Courbes et surfaces à pôles_, Microfiche P 4147-1,
#     > André Citroën Automobile SA, Paris, (1963).
# [^PopielNoakes2007]:
#     > Popiel, T. and Noakes, L.: _Bézier curves and $C^2$ interpolation in Riemannian
#     > manifolds_. Journal of Approximation Theory (2007), 148(2), pp. 111–127.-
#     > doi: [10.1016/j.jat.2007.03.002](https://doi.org/10.1016/j.jat.2007.03.002).
# [^1]:
#     > The images are rendered using [`asymptote_export_S2_signals`](@ref).
#     > For code examples, see [Get started: Optimize!](@ref Optimize).


using CompScienceMeshes, BEAST

Γ = readmesh(joinpath(dirname(@__FILE__),"sphere2.in"))
X, Y = raviartthomas(Γ), buffachristiansen(Γ)

ϵ, μ, ω = 1.0, 1.0, 1.0; κ = ω * √(ϵ*μ)
K, N = Maxwell3D.doublelayer(wavenumber=κ), NCross()
E = Maxwell3D.planewave(direction=ẑ, polarization=x̂, wavenumber=κ)
H = -1/(im*μ*ω)*curl(E)
h = (n × H) × n

@hilbertspace j
@hilbertspace m
mfie = @discretise (K+0.5N)[m,j] == h[m]  j∈X m∈Y
u = gmres(mfie)

include("utils/postproc.jl")
include("utils/plotresults.jl")


function hessian(fun, x, h)
    values = fun.(x)
    N = length(values)
    M = zeros(N, N)

    for i=1:length(values)
        M[i, i] = image2(fun(bicomplex(x[i]+im*h, h)))/h^2
    end
    return M
end


using LinearAlgebra
using GeometryTypes

struct ConstVelocity{F<:Number} <: Motion
    noise::F
    persistence_time::F
    persistence_length::F
    effective_diffusion::F
end

function ConstVelocity( noise::F, velocity::F ) where F<:Number
    persistence_time = velocity^2 / noise
    persistence_length = persistence_time * velocity
    effective_diffusion = persistence_length * velocity / 2
    ConstVelocity{F}( noise, persistence_time, persistence_length, effective_diffusion )
end # function

function step( motion::ConstVelocity, position::P, velocity::V, dt ) where {P,V}
    # Heun algorithm
    vabs = norm( velocity )
    ϕ = atan( velocity[2], velocity[1] )
    old_ϕ = ϕ

    ϕ += sqrt( 2motion.noise * dt ) * randn() / vabs
    new_velocity = vabs * V( cos(ϕ), sin(ϕ) )
    new_position = position + dt/2 * P(
        vabs * cos(old_ϕ) + new_velocity[1],
        vabs * sin(old_ϕ) + new_velocity[2],
        )
    return new_position, new_velocity
end # function


function Plots._apply_recipe(d::Dict, plm::PairwiseListMatrix; kw...)

    get!(d, :yflip, true)
    get!(d, :linetype, :heatmap)

    # args
    (plm, )
end

function arcdiagram(plm::PairwiseListMatrix)

  n = plm.nelements
  x = 1:n

  plt = scatter(x, zeros(n), m=(10,:orange), leg=false)

  zmin,zmax = extrema(plm)
  grad = ColorGradient(:bluesreds)
  curvecolor(v) = getColorZ(grad, (v-zmin)/(zmax-zmin))

  for i in 1:(n-1)
    for j in i:n
                                                       # i-j
      curve = BezierCurve(P2[(x[i],0), ((x[i]+x[j])/2, mat[i,j]), (x[j], 0)])
      plot!(curve_points(curve), line = (curvecolor(mat[i,j]), 0.5, 2))
    end
  end

  plt
end


struct PolarPoint{T} <: AbstractSet{T}
    a::Vector{T}
end
dimension(h::PolarPoint) = length(h.a)
space_variables(::PolarPoint) = nothing

function scaling_function(h::PolarPoint)
    @assert dimension(h) == 2
    return (x, y) -> begin
        return h.a[1] * x + h.a[2] * y
    end
end

function _print_gauge_function(h::PolarPoint; digits=6)
    DynamicPolynomials.@polyvar x[1:2]
    print(" ")
    a = h.a
    if digits !== nothing
        a = round.(a, digits=6)
    end
    println(x' * a)
end


module Day2
export solvePart1, solvePart2

import DelimitedFiles
import Combinatorics

function getInput()
    open("day2input.txt") do f
        return DelimitedFiles.readdlm(f, String)
    end
end

function getLetterCounts(s)
    counts = Dict()
    for c in s
        counts[c] = get(counts, c, 0) + 1
    end
    return counts
end

function numCodesWithNRepeats(n, inputs)
    letters = Char[]
    num = 0
    for code in inputs
        counts = getLetterCounts(code)
        if n in values(counts)
            num +=1
        end
    end
    return num
end

function solvePart1()
    inputs = getInput()
    repeat2 = numCodesWithNRepeats(2, inputs)
    repeat3 = numCodesWithNRepeats(3, inputs)
    return repeat2 * repeat3
end

function numChanges(a, b)
    n = 0
    for (c1, c2) in zip(a, b)
        if c1 != c2
            n +=1
        end
    end
    return n
end

function pairsWithOneChange(inputs)
    pairs = []
    for (s1, s2) in Combinatorics.combinations(inputs, 2)
        if numChanges(s1, s2) == 1
            push!(pairs, (s1, s2))
        end
    end
    return pairs
end

function commonLetters(s1, s2)
    output = []
    for (c1, c2) in zip(s1, s2)
        if c1 == c2
            push!(output, c1)
        end
    end
    return join(output)
end

function solvePart2()
    inputs = getInput()
    oneChange = pairsWithOneChange(inputs)
    commonLetters(oneChange[1]...)
end
end


lines = readlines("input.txt")
x,y,aim = 0,0,0

for line = lines
    cmd, arg_s = split(line)
    arg_i = parse(Int, arg_s)
    if cmd == "forward"
        global x += arg_i
        global y += aim * arg_i
    elseif cmd == "down"
        global aim += arg_i
    elseif cmd == "up"
        global aim -= arg_i
    end
end

println(x*y)


#######################################################################
function test_run_ppca(σ, seed)
#######################################################################

    x, = simulate3Ddata(σ, seed)

    W, μ, σ, proj, fwd = ppca(x')

    figure(1)

    cla()

    plot3D(x[:,1], x[:,2], x[:,3], "bo", alpha=0.3)

    z = sort([proj(x[i,:]) for i=1:size(x,1)])

    zgrid = collect(LinRange(minimum(z), maximum(z), 100))

    X = reduce(hcat, fwd.(zgrid))'

    plot3D(X[:,1], X[:,2], X[:,3], "-")

    return W, μ

end


using EnergyExpressions
using AtomicLevels
using Test

include("conjugate_orbitals.jl")
include("bit_configurations.jl")
include("minors.jl")


# Abstract Rational-Quadratic Kernel =======================================================
abstract type AbstractRationalQuadraticKernel{T<:AbstractFloat} <: MercerKernel{T} end

@inline basefunction(::AbstractRationalQuadraticKernel) = SquaredEuclidean()

# Rational-Quadratic Kernel ================================================================
@doc raw"""
    RationalQuadraticKernel([α [,β]])

The rational quadratic kernel is an isotropic Mercer kernel given by the formula:

```
    κ(x,y) = (1 + α‖x-y‖²)⁻ᵝ   α > 0, β > 0
```
where `α` is a scaling parameter and `β` is a shape parameter. The rational quadratic
kernel is a special of the more general gamma-rational-quadratic kernel (see
[`GammaRationalQuadraticKernel`](@ref)) with `γ = 1`.

# Examples

```jldoctest; setup = :(using MLKernels)
julia> RationalQuadraticKernel()
RationalQuadraticKernel{Float64}(1.0,1.0)

julia> RationalQuadraticKernel(2.0f0)
RationalQuadraticKernel{Float32}(2.0,1.0)

julia> RationalQuadraticKernel(2.0f0, 2.0)
RationalQuadraticKernel{Float64}(2.0,2.0)
```
"""
struct RationalQuadraticKernel{T<:AbstractFloat} <: AbstractRationalQuadraticKernel{T}
    α::T
    β::T
    function RationalQuadraticKernel{T}(
            α::Real=T(1),
            β::Real=T(1)
        ) where {T<:AbstractFloat}
        @check_args(RationalQuadraticKernel, α, α > zero(T), "α > 0")
        @check_args(RationalQuadraticKernel, β, β > zero(T), "β > 0")
        return new{T}(α, β)
    end
end
function RationalQuadraticKernel(
        α::T₁=1.0,
        β::T₂=T₁(1)
    ) where {T₁<:Real, T₂<:Real}
    RationalQuadraticKernel{promote_float(T₁, T₂)}(α, β)
end

@inline function kappa(κ::RationalQuadraticKernel{T}, d²::T) where {T}
    return (one(T) + κ.α*d²)^(-κ.β)
end

function convert(
        ::Type{K},
        κ::RationalQuadraticKernel
    ) where {K>:RationalQuadraticKernel{T}} where T
    return RationalQuadraticKernel{T}(κ.α, κ.β)
end

# Gamma Rational-Quadratic Kernel ==========================================================
@doc raw"""
    GammaRationalKernel([α [,β [,γ]]])

The gamma-rational-quadratic kernel is a generalization of the rational-quadratic kernel
with an additional shape parameter `γ`:

```
    κ(x,y) = (1 + α‖x-y‖²ᵞ)⁻ᵝ   α > 0, β > 0, γ ∈ (0,1]
```
where ``\alpha`` is a scaling parameter, ``\beta`` is a shape parameter and ``\gamma`` is a
shape parameter of the Euclidean distance.

# Examples

```jldoctest; setup = :(using MLKernels)
julia> GammaRationalQuadraticKernel()
GammaRationalQuadraticKernel{Float64}(1.0,1.0,1.0)

julia> GammaRationalQuadraticKernel(2.0f0)
GammaRationalQuadraticKernel{Float32}(2.0,1.0,1.0)

julia> GammaRationalQuadraticKernel(2.0f0, 2.0f0)
GammaRationalQuadraticKernel{Float32}(2.0,2.0,1.0)

julia> GammaRationalQuadraticKernel(2.0f0, 2.0f0, 0.5f0)
GammaRationalQuadraticKernel{Float32}(2.0,2.0,0.5)
```
"""
struct GammaRationalQuadraticKernel{T<:AbstractFloat} <: AbstractRationalQuadraticKernel{T}
    α::T
    β::T
    γ::T
    function GammaRationalQuadraticKernel{T}(
            α::Real=T(1),
            β::Real=T(1),
            γ::Real=T(1)
        ) where {T<:AbstractFloat}
        @check_args(GammaRationalQuadraticKernel, α, α > zero(T), "α > 0")
        @check_args(GammaRationalQuadraticKernel, β, β > zero(T), "β > 0")
        @check_args(GammaRationalQuadraticKernel, γ, one(T) >= γ > zero(T), "γ ∈ (0,1]")
        return new{T}(α, β, γ)
    end
end
function GammaRationalQuadraticKernel(
        α::T₁ = 1.0,
        β::T₂ = T₁(1),
        γ::T₃ = one(promote_float(T₁, T₂))
    ) where {T₁<:Real, T₂<:Real, T₃<:Real}
    GammaRationalQuadraticKernel{promote_float(T₁,T₂,T₃)}(α, β, γ)
end

@inline function kappa(κ::GammaRationalQuadraticKernel{T}, d²::T) where {T}
    return (one(T) + κ.α*(d²^κ.γ))^(-κ.β)
end

function convert(
        ::Type{K},
        κ::GammaRationalQuadraticKernel
    ) where {K>:GammaRationalQuadraticKernel{T}} where T
    return GammaRationalQuadraticKernel{T}(κ.α, κ.β, κ.γ)
end


#######################################
#### Canopy-Atmosphere Decoupling  ####
#######################################

#' Canopy-Atmosphere Decoupling Coefficient
#'
#' The canopy-atmosphere decoupling coefficient 'Omega'.
#'
#' - data        DataFrame or matrix containing all required input variables
#' - Tair        Air temperature (deg C)
#' - pressure    Atmospheric pressure (kPa)
#' - Ga          Aerodynamic conductance to heat/water vapor (m s-1)
#' - Gs          Surface conductance (m s-1)
#' - approach    Approach used to calculate omega. Either `"Jarvis&McNaughton_1986"` (default)
#'                    or `"Martin_1989"`.
#' - LAI         Leaf area index (m2 m-2), only used if `approach = "Martin_1989"`.
#' - Esat_formula  Optional: Esat_formula to be used for the calculation of esat and the slope of esat.
#'                      One of `"Sonntag_1990"` (Default), `"Alduchov_1996"`, or `"Allen_1998"`.
#'                      See [`Esat_slope`](@ref).
#' - constants   Kelvin - conversion degree Celsius to Kelvin
#'                    cp - specific heat of air for constant pressure (J K-1 kg-1)
#'                    eps - ratio of the molecular weight of water vapor to dry air (-)
#'                    sigma - Stefan-Boltzmann constant (W m-2 K-4)
#'                    Pa2kPa - conversion pascal (Pa) to kilopascal (kPa)
#'
#' # Details
 The decoupling coefficient Omega ranges from 0 to 1 and quantifies the
#'          linkage of the conditions (foremost humidity and temperature) at the canopy surface
#'          to the ambient air. Values close to 0 indicate well coupled conditions
#'          characterized by high physiological (i.e. stomatal) control on transpiration
#'          and similar conditions at the canopy surface compared to the atmosphere above
#'          the canopy. Values close to 1 indicate the opposite, i.e. decoupled conditions and
#'          a low stomatal control on transpiration (Jarvis & McNaughton 1986).
#'          The `"Jarvis&McNaughton_1986"` approach (default option) is the original
#'          formulation for the decoupling coefficient, given by (for an amphistomatous
#'          canopy):
#'
#'          ``\\Omega = \frac{\epsilon + 1``{\epsilon + 1 + \frac{Ga}{Gc}}}{%
#'          \\Omega = (\epsilon + 1) / ( \epsilon + 1 + Ga/Gc)}
#'
#'          where ``\epsilon = \frac{s``{\\gamma}}{\epsilon = s/\\gamma} is a dimensionless coefficient
#'          with s being the slope of the saturation vapor pressure curve (Pa K-1), and ``\\gamma`` the
#'          psychrometric constant (Pa K-1).
#'
#'          The approach `"Martin_1989"` by Martin 1989 additionally takes radiative coupling
#'          into account:
#'
#'          ``\\Omega = \frac{\epsilon + 1 + \frac{Gr``{Ga}}{\epsilon + (1 + \frac{Ga}{Gs}) (1 + \frac{Gr}{Ga})}}{%
#'          \\Omega = (\epsilon + 1 + Gr/Ga) / (\epsilon + (1 + Ga/Gs) (1 + Gr/Ga))}
#'
#' # Value
 - ``\\Omega`` -: the decoupling coefficient Omega (-)
#'
#' #References
#' Jarvis P_G., McNaughton K_G., 1986: Stomatal control of transpiration:
#'             scaling up from leaf to region. Advances in Ecological Research 15, 1-49.
#'
#'             Martin P., 1989: The significance of radiative coupling between
#'             vegetation and the atmosphere. Agricultural and Forest Meteorology 49, 45-53.
#'
#' #See also
#' [`aerodynamic_conductance!`](@ref), [`surface_conductance`](@ref),
#'          [`equilibrium_imposed_ET`](@ref)
#'
#' ```@example; output = false
#' ```
#' # Omega calculated following Jarvis & McNaughton 1986
#' set_seed(3)
#' df = DataFrame(Tair=rnorm(20,25,1),pressure=100,Ga_h=rnorm(20,0.06,0.01),
#'                  Gs_ms=rnorm(20,0.005,0.001))
#' decoupling(df,approach="Jarvis&McNaughton_1986")
#'
#' # Omega calculated following Martin 1989 (requires LAI)
#' decoupling(df,approach="Martin_1989",LAI=4)
#'
"""
"""
function decoupling(data,Tair="Tair",pressure="pressure",Ga="Ga_h",Gs="Gs_ms",
                       approach=c("Jarvis&McNaughton_1986","Martin_1989"),
                       LAI,Esat_formula=c("Sonntag_1990","Alduchov_1996","Allen_1998"),
                       constants=BigleafConstants())

  approach    = match_arg(approach)

  check_input(data,list(Tair,pressure,Ga,Gs))

  Delta   = Esat_slope(Tair,Esat_formula,constants)[,"Delta"]
  gamma   = psychrometric_constant(Tair,pressure,constants)
  epsilon = Delta/gamma

  if (approach == "Jarvis&McNaughton_1986")

    Omega = (epsilon + 1) / (epsilon + 1 + Ga/Gs)

elseif (approach == "Martin_1989")

    if (isnothing(LAI))

      stop("LAI is not provided!")

else

      Gr    = longwave_conductance(Tair,LAI,constants)
      Omega = (epsilon + 1 + Gr/Ga) / (epsilon + 1 + Ga/Gs + Gr/Gs + Gr/Ga)

end
end

  return(Omega)

end

#' Longwave Radiative Transfer Conductance of the Canopy
#'
#' - Tair      Air temperature (deg C)
#' - LAI       Leaf area index (m2 m-2)
#' - constants Kelvin - conversion degree Celsius to Kelvin
#'                  sigma - Stefan-Boltzmann constant (W m-2 K-4)
#'                  cp - specific heat of air for constant pressure (J K-1 kg-1)
#'
#' # Details
 the following Esat_formula is used (Martin, 1989):
#'
#'          ``Gr = 4 \sigma Tair^3 LAI / cp``
#'
#' # Value
 - Gr -: longwave radiative transfer conductance of the canopy (m s-1)
#'
#' #References
#' Martin P., 1989: The significance of radiative coupling between
#'             vegetation and the atmosphere. Agricultural and Forest Meteorology 49, 45-53.
#'
#' ```@example; output = false
#' ```
#' longwave_conductance(25,seq(1,8,1))
#'
#' @export
function longwave_conductance(Tair,LAI,constants=BigleafConstants())

  Tair = Tair + constants.Kelvin

  Gr = 4 * constants.sigma] * Tair^3 * LAI / constants[:cp

  return(Gr)
end


using BenchmarkTools
const BT=BenchmarkTools.DEFAULT_PARAMETERS;
BT.seconds = 10; BT.samples = 10_000; BT.time_tolerance = 1.0e-10;

using LinearAlgebra, Statistics
using Quadmath, RandomNumbers
using PairArithmetic


##################################################
# Hamiltonians
##################################################

"""
    overlap([T::Type], f, args...; kwargs...)

Construct the matrix corresponding to the overlap operator of an atom,
bond or molecule.  `f` is a function that accepts the atom types and
distances between the atoms to compute the overlap matrix element.

"""
overlap(f, args...; kwargs...) = overlap(Complex{Float64}, f, args...; kwargs...)

function overlap(T::Type, f, atom::Atom; kwargs...)
    N = countorbitals(atom)
    H = fill(zero(T), N, N)
    U = localbasis(atom)
    for (i, s1) in enumerate(atom.orbitals)
        H[i,i] += f(chemical_symbol(atom) => shortform(s1))
    end
    return U'H*U
end

function overlap(T::Type, f, atom1::Atom, atom2::Atom; fixed_distance=nothing, kwargs...)
    N1 = countorbitals(atom1)
    N2 = countorbitals(atom2)
    U1 = localbasis(atom1)
    U2 = localbasis(atom2)
    H = fill(zero(T), N1, N2)

    if fixed_distance === nothing
        r = atom1.position - atom2.position
    else
        r = fixed_distance .* normalize(atom1.position - atom2.position)
    end

    for (j, s2) in enumerate(atom2.orbitals), (i, s1) in enumerate(atom1.orbitals)
        H[i,j] += f(r, chemical_symbol(atom1) => shortform(s1), chemical_symbol(atom2) => shortform(s2))
    end
    return U1'H*U2
end

function overlap(T::Type, f, mol::Molecule; kwargs...)
    N = countorbitals(mol)
    H = fill(zero(T), N, N)

    i = 1
    for atom1 in mol.atoms
        j = 1
        Ni = countorbitals(atom1)
        for atom2 in mol.atoms
            Nj = countorbitals(atom2)
            if atom1 === atom2
                H[i:i+Ni-1, j:j+Nj-1] += overlap(T, f, atom1; kwargs...)
            elseif Bond(atom1, atom2) in mol.bonds
                H[i:i+Ni-1, j:j+Nj-1] += overlap(T, f, atom1, atom2; kwargs...)
            end
            j += Nj
        end
        i += Ni
    end
    return H
end

function overlap(T::Type, f, mol1::Molecule, mol2::Molecule, bonds::Set{<:Bond}; kwargs...)
    N1 = countorbitals(mol1)
    N2 = countorbitals(mol2)
    H = fill(zero(T), N1, N2)

    i = 1
    for atom1 in mol1.atoms
        j = 1
        Ni = countorbitals(atom1)
        for atom2 in mol2.atoms
            Nj = countorbitals(atom2)
            if Bond(atom1, atom2) in bonds
                H[i:i+Ni-1, j:j+Nj-1] += overlap(T, f, atom1, atom2; kwargs...)
            end
            j += Nj
        end
        i += Ni
    end
    return H
end


"""
三角形有关的功能
"""

using Printf

struct AbstractTriangle{T} <: AbstractPolygon{T}
    vertex :: Array{Point2D, 1}
    edges :: Array{Segment, 1}
    center :: Point2D
    area :: Float64
end

"""
构造一个普通的三角形
"""
function Triangle(pt1, pt2, pt3)
    verts = [pt1, pt2, pt3]
    edges = Array{Segment, 1}(undef, 3)
    edges[1] = Segment(pt1, pt2)
    edges[2] = Segment(pt2, pt3)
    edges[3] = Segment(pt3, pt1)
    center = Point2D(
        sum([pt1.x, pt2.x, pt3.x]) / 3.0,
        sum([ver.y for ver in verts]) / 3.0
    )
    vec1 = pt2 - pt1
    vec2 = pt3 - pt1
    tarea =  0.5abs(vec1.x * vec2.y - vec1.y * vec2.x)
    return AbstractTriangle{:COM}(verts, edges, center, tarea)
end

"""
构造一个普通的三角形
"""
function Triangle(verts::Array{Point2D, 1})
    @assert length(verts) == 3
    return Triangle(verts[1], verts[2], verts[3])
end

"""
构造一个直角三角形，第一个角必须是直角
"""
function RtTriangle(pt1, pt2, pt3)
    verts = [pt1, pt2, pt3]
    edges = Array{Segment, 1}(undef, 3)
    edges[1] = Segment(pt1, pt2)
    edges[2] = Segment(pt2, pt3)
    edges[3] = Segment(pt3, pt1)
    center = middle_point(
        pt1,
        middle_point(pt2, pt3),
        sc1=1., sc2=1.414
    )
    vec1 = pt2 - pt1
    vec2 = pt3 - pt1
    tarea =  0.5abs(vec1.x * vec2.y - vec1.y * vec2.x)
    return AbstractTriangle{:RT}(verts, edges, center, tarea)
end

"""
构造一个直角三角形，会检查第一个角是否是直角
"""
function RtTriangle(verts::Array{Point2D, 1})
    @assert length(verts) == 3
    vec1 = verts[2] - verts[1]
    vec2 = verts[3] - verts[1]
    @assert abs(vec1.x * vec2.x + vec1.y * vec2.y) < 1e-6
    return RtTriangle(verts[1], verts[2], verts[3])
end

"""
构造一个等边三角形
"""
function EqTriangle(pt1, pt2, pt3)
    verts = [pt1, pt2, pt3]
    edges = Array{Segment, 1}(undef, 3)
    edges[1] = Segment(pt1, pt2)
    edges[2] = Segment(pt2, pt3)
    edges[3] = Segment(pt3, pt1)
    center = Point2D(
        sum([pt1.x, pt2.x, pt3.x]) / 3.0,
        sum([ver.y for ver in verts]) / 3.0
    )
    vec1 = pt2 - pt1
    vec2 = pt3 - pt1
    tarea =  0.5abs(vec1.x * vec2.y - vec1.y * vec2.x)
    return AbstractTriangle{:EQ}(verts, edges, center, tarea)
end

"""
构造一个等边三角形，会检查边长
"""
function EqTriangle(verts::Array{Point2D, 1})
    @assert length(verts) == 3
    ret = EqTriangle(verts[1], verts[2], verts[3])
    @assert abs(ret.edges[1].length - ret.edges[2].length) < 1e-6
    @assert abs(ret.edges[3].length - ret.edges[2].length) < 1e-6
    return ret
end

"""
返回一个三角形的面积
"""
function area(tri::AbstractTriangle{N}) where N
    vec1 = tri.vertex[2] - tri.vertex[1]
    vec2 = tri.vertex[3] - tri.vertex[1]
    return 0.5abs(vec1.x * vec2.y - vec1.y * vec2.x)
end

#display函数在中间加::MIME"text/plain"
function Base.show(io::IO, tri::AbstractTriangle{N}) where N
    name = Dict(
        :COM => "Triangle",
        :RT => "RTTriangle",
        :EQ => "EqTriangle"
    )
    @printf(io, "%s\n center: %s\n vertex: %s",
    name[N], tri.center, tri.vertex)
end


using Plots
using Random
Random.seed!(1)

# Differential Dynamic Programming
include_ddp()
include(joinpath(pwd(), "src/differential_dynamic_programming/multiple_models.jl"))

# Model
include_model("double_integrator")

function f(model::DoubleIntegratorContinuous, x, u, w)
    [x[2] + w[1];
	 (1.0 + w[3]) * u[1] + w[2]]
end

model = DoubleIntegratorContinuous{Midpoint, FixedTime}(2, 1, 3)
n = model.n
m = model.m

# Models
N = 1000#2 * n + 1

# Time
T = 101
h = 0.01
tf = h * (T - 1)
t = range(0, stop = tf, length = T)

# Reference position trajectory
# z = range(0.0, stop = 3.0 * 2.0 * π, length = T)
# p_ref = 1.0 * cos.(1.0 * z)
# plot(z, p_ref)
p_ref = [0.0 for t = 1:T]

# Initial conditions, controls, disturbances
# x1 = [p_ref[1]; 0.0]
x1 = [1.0; 0.0]
_ū = [0.1 * rand(model.m) for t = 1:T-1]
ū = [_ū for i = 1:N]

W = Distributions.MvNormal(zeros(model.d), Diagonal([0.0, 0.0, 0.1]))
wi = [vec(rand(W, 1)) for i = 1:N]
w = [[zeros(model.d) for t = 1:T-1], [[wi[i] for t = 1:T-1] for i = 1:N-1]...]
# w = [[vec(rand(W, 1)) for t = 1:T-1] for i = 1:N]
# w = [[zeros(model.d) for t = 1:T-1] for i = 1:N]

# Rollout
x̄ = [rollout(model, x1, ū[i], w[1], h, T) for i = 1:N]

# x_ref = [sum([x̄i[t] for x̄i in x̄]) ./ N for t = 1:T]
# u_ref = [sum([ūi[t] for ūi in ū]) ./ N for t = 1:T-1]

p_idx = (1:1)
plt = plot()
for i = 1:N
	plt = plot!(t, hcat(x̄[i]...)[p_idx, :]',
		label = "")
end
display(plt)

# Objective
Q = [t < T ? h * Diagonal([1.0e-1; 1.0e-1]) : Diagonal([1000.0; 1000.0]) for t = 1:T]
R = [h * Diagonal(1.0e-2 * ones(model.m)) for t = 1:T-1]

obj = StageCosts([QuadraticCost(Q[t], nothing,
	t < T ? R[t] : nothing, nothing) for t = 1:T], T)

function g(obj::StageCosts, x, u, t)
	T = obj.T
    if t < T
		Q = obj.cost[t].Q
	    R = obj.cost[t].R
        return (x - [p_ref[t]; 0.0])' * Q * (x - [p_ref[t]; 0.0]) + u' * R * u
    elseif t == T
		Q = obj.cost[T].Q
        return (x - [p_ref[T]; 0.0])' * Q * (x - [p_ref[T]; 0.0])
    else
        return 0.0
    end
end

function ddp_solve!(prob::ProblemData;
    max_iter = 10,
    grad_tol = 1.0e-5,
    verbose = true)

	println()
    verbose && println("Differential Dynamic Programming")

	# data
	p_data = prob.p_data
	m_data = prob.m_data
	s_data = prob.s_data

    # compute objective
    s_data.obj = objective(m_data, mode = :nominal)

	N = length(prob.m_data)

    for i = 1:max_iter
        # derivatives
        derivatives!(m_data)

        # backward pass
        backward_pass!(p_data, m_data)

		# # set nominal trajectories
		# x_ref = [sum([m.x̄[t] for m in prob.m_data]) ./ N for t = 1:T]
		# u_ref = [sum([m.ū[t] for m in prob.m_data]) ./ N for t = 1:T-1]
		#
		# # disturbances
		# w = [[rand(W, 1) for t = 1:T-1] for i = 1:N]
		#
		# for i = 1:N
		# 	# m_data[i].x̄ .= deepcopy(x_ref)
		# 	# m_data[i].ū .= deepcopy(u_ref)
		# 	m_data[i].w .= deepcopy(w[i])
		# end

        # forward pass
        forward_pass!(p_data, m_data, s_data)

		if i > 1
			for i = 2:N
				m_data[i].x̄ .= deepcopy(m_data[1].x̄)
				m_data[i].ū .= deepcopy(m_data[1].ū)
				# m_data[i].w .= deepcopy(w[i])
			end
		end
		# # set nominal trajectories
		# x_ref = [sum([m.x̄[t] for m in prob.m_data]) ./ N for t = 1:T]
		# u_ref = [sum([m.ū[t] for m in prob.m_data]) ./ N for t = 1:T-1]

        # check convergence
        grad_norm = norm(s_data.gradient, Inf)
        verbose && println("     iter: $i
             cost: $(s_data.obj)
			 grad_norm: $(grad_norm)")
		grad_norm < grad_tol && break
        !s_data.status && break
    end
end

models_data = models(model, obj, deepcopy(x̄), deepcopy(ū), w, h, T)

prob = problem_data(models_data)

# Solve
# for j = 1:10
# 	wi = [vec(rand(W, 1)) for i = 1:N]
# 	w = [[zeros(model.d) for t = 1:T-1], [[wi[i] for t = 1:T-1] for i = 1:N-1]...]
# 	# w = [[vec(rand(W, 1)) for t = 1:T-1] for i = 1:N]
# 	# w = [[zeros(model.d) for t = 1:T-1] for i = 1:N]
# 	for i = 1:N
# 		prob.m_data[i].w .= w[i]
# 		prob.m_data[i].x̄ .= rollout(model,
# 			prob.m_data[i].x̄[1], prob.m_data[1].ū,
# 			prob.m_data[1].w, h, T)
# 	end
# 	# Rollout
#
# 	@time ddp_solve!(prob,
# 	    max_iter = 1000, verbose = true,
# 		grad_tol = 1.0e-8)
# end
@time ddp_solve!(prob,
	max_iter = 1000, verbose = true,
	grad_tol = 1.0e-8)

x = [m.x for m in prob.m_data]
u = [m.u for m in prob.m_data]

x̄ = [m.x̄ for m in prob.m_data]
ū = [m.ū for m in prob.m_data]

x_ref = deepcopy(prob.m_data[1].x̄)#[sum([m.x̄[t] for m in prob.m_data]) ./ N for t = 1:T]
u_ref = deepcopy(prob.m_data[1].ū)#[sum([m.ū[t] for m in prob.m_data]) ./ N for t = 1:T-1]

# Visualize
idx = (1:2)
_plt = plot(hcat([[p_ref[t]; 0.0][idx] for t = 1:T]...)',
    width = 2.0, color = :black, label = "")
for i = 1:N
	_plt = plot!(hcat(x[i]...)[idx, :]', color = :magenta, label = "")
end
_plt = plot!(hcat(x_ref...)[idx, :]', color = :orange, width = 5.0, label = "")
display(_plt)
# plot(hcat(u..., u[end])', linetype = :steppost)

# Simulate policy
include_dpo()
include(joinpath(pwd(), "examples/direct_policy_optimization/simulate.jl"))

# Model
model_sim = model
x1_sim = copy(x1)
T_sim = 10 * (T-1) + 1

# Disturbance distributions

# Time
t_sim = range(0, stop = tf, length = T_sim)
dt_sim = tf / (T_sim - 1)

# Policy
policy = linear_feedback(model.n, model.m)
# K, P = tvlqr(model, x̄, ū, h, [Q for t = 1:T], [R for t = 1:T-1])
K = [-K for K in prob.p_data.K]

# Simulate
N_sim = 100
x_sim = []
u_sim = []
J_sim = []
Random.seed!(1)
W_sim = W#Distributions.MvNormal(zeros(model.d), Diagonal([0.0, 0.0, 1.0e-1]))

for i = 1:N_sim
	println("sim: $i")
	wi_sim = vec(rand(W_sim, 1))
	w_sim = hcat([wi_sim for t = 1:T_sim]...)

	x_ddp, u_ddp, J_ddp, Jx_ddp, Ju_ddp = simulate(
		model_sim,
		linear_feedback(model.n, model.m), K,
	    x_ref, u_ref,
		Q, R,
		T_sim, h,
		x1_sim,
		w_sim)

	push!(x_sim, x_ddp)
	push!(u_sim, u_ddp)
	push!(J_sim, J_ddp)
end

# Visualize
idx = (1:2)
_plt = plot(t, hcat(x_ref...)[idx, :]',
    width = 2.0, color = :black, label = "")

for (i, xi) in enumerate(x_sim)
	_plt = plot!(t_sim, hcat(xi...)[idx, :]',
    	width = 1.0, color = :magenta, label = i == 1 ? "sim" : "")
end
_plt = plot!(t, hcat(x_ref...)[idx, :]',
    width = 2.0, color = :black, label = "ref")
_plt = plot!(; xlabel = "time (s)",
	ylabel = "position",
	legend = :topleft,
	title = "N_mc = $N, N_sim = $(N_sim), J_avg = $(round(mean(J_sim), digits = 3))")
display(_plt)

_plt = plot(t, hcat(u_ref..., u_ref[end])[1:1, :]',
    width = 2.0, color = :black, label = "")
for (i, ui) in enumerate(u_sim)
	_plt = plot!(t_sim, hcat(ui..., ui[end])[1:1, :]',
    	width = 1.0, color = :magenta, label = i == 1 ? "sim" : "")
end
_plt = plot!(t, hcat(u_ref..., u_ref[end])[1:1, :]',
    width = 2.0, color = :black, label = "ref", linetype = :steppost)
_plt = plot!(; xlabel = "time (s)",
	ylabel = "control",
	title = "N_mc = $N, N_sim = $(N_sim), J_avg = $(round(mean(J_sim), digits = 3))",
	linetype = :steppost)
display(_plt)
@show mean(J_sim)
round(mean(J_sim), digits = 3)


module lmp_data

using Statistics
using Printf

include("Constants.jl")
#=
export Const_k_b, Const_n_a, Const_density_wat,
    Const_kcal2j, Const_kcalm2j, Const_kcalm2t,
    Const_g2kg, Const_kg2g, Const_gm2g, Const_gm2kg,
    Const_an2m, Const_an2nm, Const_nm2m, Const_cm2an, Const_dm2m, Const_m2dm,
    Const_fs2s, Const_ps2s, Const_ns2s
=#

include("useful_funcs.jl")
export dist, str2float, diag, copy_array, leastsq

include("read_dump.jl")
export read_dump, read_dump_prim, split_info!, split_atom, momentum!, mass!, time_step!, coord_scl!

include("box.jl")
export genr_box_diag, genr_box_inv, PBC!

include("diffusion.jl")
export MSD!, diffusion

include("RDF.jl")
export RDF, RDF_double, num_coordination

end # module


prettify_expr(expr) = expr
prettify_expr(f::Function) = nameof(f)
prettify_expr(expr::Expr) = Expr(expr.head, prettify_expr.(expr.args)...)

function cleanup_exprs(ex)
    return postwalk(x -> x isa Expr && length(x.args) == 1 ? x.args[1] : x, ex)
end

function latexify_derivatives(ex)
    return postwalk(ex) do x
        if x isa Expr && x.args[1] == :_derivative
            if x.args[2] isa Expr && length(x.args[2].args) == 2
                return :($(Symbol(:d, x.args[2]))/$(Symbol(:d, x.args[2].args[2])))
            else
                return Expr(:call, Expr(:call, :/, :d, Expr(:call, :*, :d, x.args[3])), x.args[2])
            end
        else
            return x
        end
    end
end

@latexrecipe function f(n::Num)
    env --> :equation
    cdot --> false

    return latexify_derivatives(cleanup_exprs(_toexpr(n)))
end

@latexrecipe function f(eqs::Vector{Equation})
    env --> :align

    return Num.(getfield.(eqs, :lhs)), Num.(getfield.(eqs, :rhs))
end

@latexrecipe function f(eq::Equation)
    env --> :equation

    return Expr(:(=), Num(eq.lhs), Num(eq.rhs))
end

Base.show(io::IO, ::MIME"text/latex", x::Num) = print(io, latexify(x))
Base.show(io::IO, ::MIME"text/latex", x::Symbolic) = print(io, latexify(x))
Base.show(io::IO, ::MIME"text/latex", x::Vector{Equation}) = print(io, latexify(x))
Base.show(io::IO, ::MIME"text/latex", x::AbstractArray{Num}) = print(io, latexify(x))

# `_toexpr` is only used for latexify
function _toexpr(O)
    !istree(O) && return O

    op = operation(O)
    args = arguments(O)

    if (op===(*)) && (args[1] === -1)
        arg_mul = Expr(:call, :(*), _toexpr(args[2:end])...)
        return Expr(:call, :(-), arg_mul)
    end

    if op isa Differential
        ex = _toexpr(args[1])
        wrt = _toexpr(op.x)
        return :(_derivative($ex, $wrt))
    elseif op isa Sym
        isempty(args) && return nameof(op)
        return Expr(:call, _toexpr(op), _toexpr(args)...)
    end
    return Expr(:call, Symbol(op), _toexpr(args)...)
end
function _toexpr(m::Mul{<:Number})
    numer = Any[]
    denom = Any[]

    # We need to iterate over each term in m, ignoring the numeric coefficient.
    # This iteration needs to be stable, so we can't iterate over m.dict.
    for term in Iterators.drop(arguments(m), isone(m.coeff) ? 0 : 1)
        if !(term isa Pow)
            push!(numer, _toexpr(term))
            continue
        end

        base = term.base
        pow  = term.exp
        if pow > 0
            if isone(pow)
                pushfirst!(numer, _toexpr(base))
            else
                pushfirst!(numer, Expr(:call, :^, _toexpr(base), pow))
            end
        else
            if isone(-1*pow)
                pushfirst!(denom, _toexpr(base))
            else
                pushfirst!(denom, Expr(:call, :^, _toexpr(base), -1*pow))
            end
        end
    end

    if isempty(numer) || !isone(abs(m.coeff))
        numer_expr = Expr(:call, :*, abs(m.coeff), numer...)
    else
        numer_expr = Expr(:call, :*, numer...)
    end

    if isempty(denom)
        frac_expr = numer_expr
    else
        denom_expr = Expr(:call, :*, denom...)
        frac_expr = Expr(:call, :/, numer_expr, denom_expr)
    end

    if m.coeff < 0
        return Expr(:call, :-, frac_expr)
    else
        return frac_expr
    end
end
_toexpr(s::Sym) = nameof(s)
_toexpr(x::Integer) = x
_toexpr(x::AbstractFloat) = x

function _toexpr(eq::Equation)
    Expr(:(=), _toexpr(eq.lhs), _toexpr(eq.rhs))
end

_toexpr(eqs::AbstractArray) = map(eq->_toexpr(eq), eqs)
_toexpr(x::Num) = _toexpr(value(x))


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    BlockDiscretization(dims)

Discretize spatial region into `dims` blocks.
"""
struct BlockDiscretization{N} <: DiscretizationMethod
  dims::Dims{N}
end

discretize(r::Rectangle, method::BlockDiscretization) =
  RegularGrid(extrema(r)..., dims=method.dims)


using StochasticDiffEq, Test, LinearAlgebra, Random

a = ones(3)
t = 0.4
a .- t

const mm_A = [-2.0 1 4
            4 -2 1
            2 1 3]
const mm_b = mm_A*ones(3)
function mm_f(du,u,p,t)
  mul!(du,mm_A,u)
  tmp = t*mm_b
  @. du += tmp
end
function mm_analytic(u0,p,t,W)
  2.0.*ones(3).*exp.(t) .- t .- 1
end
function mm_g(du,u,p,t)
  @. du = u + t
end
function g!(du,u,p,t)
  @. du = 0.0
end

prob2 = SDEProblem(SDEFunction(mm_g,g!;analytic=mm_analytic),g!,ones(3),(0.0,1.0))
prob = SDEProblem(SDEFunction(mm_f,g!;analytic=mm_analytic,mass_matrix=mm_A),g!,
                  ones(3),(0.0,1.0))

sol = solve(prob, ImplicitRKMil(theta=1), dt = 0.01, adaptive = false)
sol2 = solve(prob2, ImplicitRKMil(theta=1), dt = 0.01, adaptive = false)

@test norm(sol .- sol2) ≈ 0 atol=1e-11

sol = solve(prob, ImplicitEM(theta=1), dt = 0.01, adaptive = false)
sol2 = solve(prob2, ImplicitEM(theta=1), dt = 0.01, adaptive = false)

@test norm(sol .- sol2) ≈ 0 atol=1e-11

sol = solve(prob, ImplicitRKMil(symplectic=true), dt = 0.01, adaptive = false)
sol2 = solve(prob2, ImplicitRKMil(symplectic=true), dt = 0.01, adaptive = false)

@test norm(sol .- sol2) ≈ 0 atol=1e-11

sol = solve(prob, ImplicitEM(symplectic=true), dt = 0.01, adaptive = false)
sol2 = solve(prob2, ImplicitEM(symplectic=true), dt = 0.01, adaptive = false)

@test norm(sol .- sol2) ≈ 0 atol=1e-11

function mm_f2(du,u,p,t)
      mul!(du,mm_A,u)
end
function no_mm_f2(du,u,p,t)
      du .= u
end
function no_mm_g2(du,u,p,t)
      du .= u
end
function mm_g2(du,u,p,t)
    mul!(du,mm_A,u)
end
prob2 = SDEProblem(no_mm_f2,no_mm_g2,ones(3),(0.0,1.0))
prob = SDEProblem(SDEFunction(mm_f2,no_mm_g2;mass_matrix=mm_A),no_mm_g2,ones(3),(0.0,1.0))

Random.seed!(1)
sol = solve(prob, ImplicitEM(theta=1), dt = 0.01, adaptive = false)
Random.seed!(1)
sol2 = solve(prob2, ImplicitEM(theta=1), dt = 0.01, adaptive = false)

@test norm(sol .- sol2) ≈ 0 atol=1e-11


using Oceananigans.Grids: Center, Face

linear(x) = 0.5x - 3
quadratic(x) = 0.5x^2 -3x + 5

@testset "Coarse Graining" begin
    @testset "Center" begin
        for func in [linear, quadratic]
            @testset "$func" begin
                x = range(0, 100, length=100)
                y = func.(x)

                N = 20
                y_coarse = coarse_grain(y, N, Center())

                @test length(y_coarse) == N
                @test mean(y_coarse) ≈ mean(y_coarse)

                if func == linear
                    @test all(diff(y_coarse) .≈ diff(y_coarse)[1])
                end
            end
        end
    end

    @testset "Face" begin
        for func in [linear, quadratic]
            @testset "$func" begin
                # 102 -> 22 tests the case where the interior can be easily coarse grained like `Center`.
                # 129 -> 33 tests the case where we need to interpolate between grid cells.
                lengths = [(102, 22), (129, 33)]

                for (n, N) in lengths
                    x = range(0, 100, length=n)
                    y = func.(x)

                    y_coarse = coarse_grain(y, N, Face())

                    y_interior = y[2:end-1]
                    y_coarse_interior = y_coarse[2:end-1]

                    @test length(y_coarse) == N
                    @test mean(y_interior) ≈ mean(y_coarse_interior)
                end
            end
        end
    end
end


#  Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at https://mozilla.org/MPL/2.0/.
#############################################################################
# JuMP
# An algebraic modelling language for Julia
# See https://github.com/jump-dev/JuMP.jl
#############################################################################
# Hock-Schittkowski Nonlinear Test Suite
# These tests include JuMP implementations of the models described in
#  W. Hock, K. Schittkowski, Test Examples for Nonlinear Programming
#  Codes, Lecture Notes in Economics and Mathematical Systems,
#  Springer, No, 187, 1981
# More information, including original model descriptions, is available at
# http://www.klaus-schittkowski.de/downloads.htm
#############################################################################

# These tests are not executed as part of runtests because they depend on an
# NLP solver.
# TODO: Move these tests out of JuMP and into MOI so that JuMP can be tested
# separately from the solver, as it is for everything except NLP.

using Ipopt, JuMP
using Test
using MathOptInterface
const MOI = MathOptInterface

@testset "NLP solver tests" begin
    @testset "HS071" begin
        # hs071
        # Polynomial objective and constraints
        # min x1 * x4 * (x1 + x2 + x3) + x3
        # st  x1 * x2 * x3 * x4 >= 25
        #     x1^2 + x2^2 + x3^2 + x4^2 = 40
        #     1 <= x1, x2, x3, x4 <= 5
        # Start at (1,5,5,1)
        # End at (1.000..., 4.743..., 3.821..., 1.379...)
        m = Model(Ipopt.Optimizer)
        initval = [1, 5, 5, 1]
        @variable(m, 1 <= x[i = 1:4] <= 5, start = initval[i])
        @NLobjective(m, Min, x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3])
        @NLconstraint(m, x[1] * x[2] * x[3] * x[4] >= 25)
        @NLconstraint(m, sum(x[i]^2 for i in 1:4) == 40)

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.value.(x) ≈ [1.000000, 4.742999, 3.821150, 1.379408] atol =
            1e-3
    end

    @testset "HS071 (no macros)" begin
        # hs071
        # Polynomial objective and constraints
        # min x1 * x4 * (x1 + x2 + x3) + x3
        # st  x1 * x2 * x3 * x4 >= 25
        #     x1^2 + x2^2 + x3^2 + x4^2 = 40
        #     1 <= x1, x2, x3, x4 <= 5
        # Start at (1,5,5,1)
        # End at (1.000..., 4.743..., 3.821..., 1.379...)
        m = Model(Ipopt.Optimizer)
        initval = [1, 5, 5, 1]
        @variable(m, 1 <= x[i = 1:4] <= 5, start = initval[i])
        JuMP.set_NL_objective(
            m,
            MOI.MIN_SENSE,
            :($(x[1]) * $(x[4]) * ($(x[1]) + $(x[2]) + $(x[3])) + $(x[3])),
        )
        JuMP.add_NL_constraint(
            m,
            :($(x[1]) * $(x[2]) * $(x[3]) * $(x[4]) >= 25),
        )
        JuMP.add_NL_constraint(
            m,
            :($(x[1])^2 + $(x[2])^2 + $(x[3])^2 + $(x[4])^2 == 40),
        )
        bad_expr = :(x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2 == 40)
        @test_throws ErrorException JuMP.add_NL_constraint(m, bad_expr)

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.value.(x) ≈ [1.000000, 4.742999, 3.821150, 1.379408] atol =
            1e-3
    end

    @testset "HS071 (epigraph)" begin
        # hs071, with epigraph formulation
        # Linear objective, nonlinear constraints
        # min t
        # st  t >= x1 * x4 * (x1 + x2 + x3) + x3
        #     ...
        model = Model(Ipopt.Optimizer)
        start = [1.0, 5.0, 5.0, 1.0]
        @variable(model, 1 <= x[i = 1:4] <= 5, start = start[i])
        @variable(model, t, start = 100)
        @objective(model, Min, t)
        @NLconstraint(model, t >= x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3])
        @NLconstraint(model, x[1] * x[2] * x[3] * x[4] >= 25)
        @NLconstraint(model, sum(x[i]^2 for i in 1:4) == 40)

        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value.(x) ≈ [1.000000, 4.742999, 3.821150, 1.379408] atol =
            1e-3
    end

    @testset "HS109" begin
        a = 50.176
        b1 = 0.25
        b = sin(b1)
        c = cos(b1)

        L = [0.0, 0.0, -0.55, -0.55, 196, 196, 196, -400, -400]
        U = [Inf, Inf, 0.55, 0.55, 252, 252, 252, 800, 800]

        m = Model(Ipopt.Optimizer)
        @variable(m, L[i] <= x[i = 1:9] <= U[i], start = 0.0)

        @NLobjective(
            m,
            Min,
            3 * x[1] + 1e-6 * x[1]^3 + 2 * x[2] + 0.522074e-6 * x[2]^3
        )

        @NLconstraint(m, x[4] - x[3] + 0.55 >= 0)
        @NLconstraint(m, x[3] - x[4] + 0.55 >= 0)
        @NLconstraint(m, 2250000 - x[1]^2 - x[8]^2 >= 0)
        @NLconstraint(m, 2250000 - x[2]^2 - x[9]^2 >= 0)
        @NLconstraint(
            m,
            x[5] * x[6] * sin(-x[3] - 0.25) +
            x[5] * x[7] * sin(-x[4] - 0.25) +
            2 * b * x[5]^2 - a * x[1] + 400 * a == 0
        )
        @NLconstraint(
            m,
            x[5] * x[6] * sin(x[3] - 0.25) +
            x[6] * x[7] * sin(x[3] - x[4] - 0.25) +
            2 * b * x[6]^2 - a * x[2] + 400 * a == 0
        )
        @NLconstraint(
            m,
            x[5] * x[7] * sin(x[4] - 0.25) +
            x[6] * x[7] * sin(x[4] - x[3] - 0.25) +
            2 * b * x[7]^2 +
            881.779 * a == 0
        )
        @NLconstraint(
            m,
            a * x[8] +
            x[5] * x[6] * cos(-x[3] - 0.25) +
            x[5] * x[7] * cos(-x[4] - 0.25) - 200 * a - 2 * c * x[5]^2 +
            0.7533e-3 * a * x[5]^2 == 0
        )
        @NLconstraint(
            m,
            a * x[9] +
            x[5] * x[6] * cos(x[3] - 0.25) +
            x[6] * x[7] * cos(x[3] - x[4] - 0.25) - 2 * c * x[6]^2 +
            0.7533e-3 * a * x[6]^2 - 200 * a == 0
        )
        @NLconstraint(
            m,
            x[5] * x[7] * cos(x[4] - 0.25) +
            x[6] * x[7] * cos(x[4] - x[3] - 0.25) - 2 * c * x[7]^2 +
            22.938 * a +
            0.7533e-3 * a * x[7]^2 == 0
        )

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.objective_value(m) ≈ 5326.851310161077 atol = 1e-5
    end

    @testset "HS110" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, -2.001 <= x[1:10] <= 9.999, start = 9)

        @NLobjective(
            m,
            Min,
            sum(log(x[j] - 2)^2 + log(10 - x[j])^2 for j in 1:10) -
            prod(x[i] for i in 1:10)^0.2
        )

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        # Ipopt returns AlmostLOCALLY_SOLVED and NearlyFEASIBLE_POINT on this instance.
        # @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        # @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.objective_value(m) ≈ -45.77846971 atol = 1e-5
    end

    @testset "HS111" begin
        c = [
            -6.089,
            -17.164,
            -34.054,
            -5.914,
            -24.721,
            -14.986,
            -24.100,
            -10.708,
            -26.662,
            -22.179,
        ]

        m = Model(Ipopt.Optimizer)
        @variable(m, -100 <= x[1:10] <= 100, start = -2.3)

        @NLobjective(
            m,
            Min,
            sum(
                exp(x[j]) * (c[j] + x[j] - log(sum(exp(x[k]) for k in 1:10)))
                for j in 1:10
            )
        )

        @NLconstraint(
            m,
            exp(x[1]) +
            2 * exp(x[2]) +
            2 * exp(x[3]) +
            exp(x[6]) +
            exp(x[10]) == 2
        )
        @NLconstraint(m, exp(x[4]) + 2 * exp(x[5]) + exp(x[6]) + exp(x[7]) == 1)
        @NLconstraint(
            m,
            exp(x[3]) + exp(x[7]) + exp(x[8]) + 2 * exp(x[9]) + exp(x[10]) == 1
        )

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.objective_value(m) ≈ -47.76109026 atol = 1e-5
    end

    @testset "HS112" begin
        c = [
            -6.089,
            -17.164,
            -34.054,
            -5.914,
            -24.721,
            -14.986,
            -24.100,
            -10.708,
            -26.662,
            -22.179,
        ]

        m = Model(Ipopt.Optimizer)
        @variable(m, x[1:10] >= 1e-6, start = 0.1)

        @NLobjective(
            m,
            Min,
            sum(
                x[j] * (c[j] + log(x[j] / sum(x[k] for k in 1:10))) for
                j in 1:10
            )
        )

        @NLconstraint(m, x[1] + 2 * x[2] + 2 * x[3] + x[6] + x[10] == 2)
        @NLconstraint(m, x[4] + 2 * x[5] + x[6] + x[7] == 1)
        @NLconstraint(m, x[3] + x[7] + x[8] + 2 * x[9] + x[10] == 1)

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.objective_value(m) ≈ -47.76109026 atol = 1e-5
    end

    @testset "HS114" begin
        n = 10
        a = 0.99
        b = 0.9

        lower = [0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 85, 90, 3, 1.2, 145]
        upper = [2000, 16000, 120, 5000, 2000, 93, 95, 12, 4, 162]
        start = [1745, 12000, 110, 3048, 1974, 89.2, 92.8, 8, 3.6, 145]

        m = Model(Ipopt.Optimizer)
        @variable(m, lower[i] <= x[i = 1:n] <= upper[i], start = start[i])

        @NLobjective(
            m,
            Min,
            5.04 * x[1] + 0.035 * x[2] + 10 * x[3] + 3.36 * x[5] -
            0.063 * x[4] * x[7]
        )

        @NLconstraint(m, 35.82 - 0.222 * x[10] - b * x[9] >= 0)
        @NLconstraint(m, -133 + 3 * x[7] - a * x[10] >= 0)
        @NLconstraint(
            m,
            -(35.82 - 0.222 * x[10] - b * x[9]) + x[9] * (1 / b - b) >= 0
        )
        @NLconstraint(
            m,
            -(-133 + 3 * x[7] - a * x[10]) + (1 / a - a) * x[10] >= 0
        )
        @NLconstraint(
            m,
            1.12 * x[1] + 0.13167 * x[1] * x[8] - 0.00667 * x[1] * x[8]^2 -
            a * x[4] >= 0
        )
        @NLconstraint(
            m,
            57.425 + 1.098 * x[8] - 0.038 * x[8]^2 + 0.325 * x[6] - a * x[7] >=
            0
        )
        @NLconstraint(
            m,
            -(
                1.12 * x[1] + 0.13167 * x[1] * x[8] - 0.00667 * x[1] * x[8]^2 -
                a * x[4]
            ) + (1 / a - a) * x[4] >= 0
        )
        @NLconstraint(
            m,
            -(
                57.425 + 1.098 * x[8] - 0.038 * x[8]^2 + 0.325 * x[6] -
                a * x[7]
            ) + (1 / a - a) * x[7] >= 0
        )
        @NLconstraint(m, 1.22 * x[4] - x[1] - x[5] == 0)
        @NLconstraint(m, 98000 * x[3] / (x[4] * x[9] + 1000 * x[3]) - x[6] == 0)
        @NLconstraint(m, (x[2] + x[5]) / x[1] - x[8] == 0)

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.objective_value(m) ≈ -1768.80696 atol = 1e-3
    end

    @testset "HS116" begin
        N = 13
        a = 0.002
        b = 1.262626
        c = 1.231059
        d = 0.03475
        e = 0.975
        f = 0.00975

        lower = [
            0.1,
            0.1,
            0.1,
            0.0001,
            0.1,
            0.1,
            0.1,
            0.1,
            500,
            0.1,
            1.0,
            0.0001,
            0.0001,
            0.0,
            0.0,
            0.0,
        ]
        upper = [
            1.0,
            1.0,
            1.0,
            0.1,
            0.9,
            0.9,
            1000,
            1000,
            1000,
            500,
            150,
            150,
            150,
            Inf,
            Inf,
            Inf,
        ]
        start =
            [0.5 2 0.8 3 0.9 4 0.1 5 0.14 6 0.5 7 489 8 80 9 650 0.5 2 0.8 3 0.9 4 0.1 5 0.14 6 0.5 7 489 8 80 9 650]

        m = Model(Ipopt.Optimizer)
        @variable(m, lower[i] <= x[i = 1:N] <= upper[i], start = start[i])
        @NLobjective(m, Min, x[11] + x[12] + x[13])

        @NLconstraints m begin
            x[3] - x[2] >= 0
            x[2] - x[1] >= 0
            1 - a * x[7] + a * x[8] >= 0
            x[11] + x[12] + x[13] >= 50
            x[13] - b * x[10] + c * x[3] * x[10] >= 0
            x[5] - d * x[2] - e * x[2] * x[5] + f * x[2]^2 >= 0
            x[6] - d * x[3] - e * x[3] * x[6] + f * x[3]^2 >= 0
            x[4] - d * x[1] - e * x[1] * x[4] + f * x[1]^2 >= 0
            x[12] - b * x[9] + c * x[2] * x[9] >= 0
            x[11] - b * x[8] + c * x[1] * x[8] >= 0
            x[5] * x[7] - x[1] * x[8] - x[4] * x[7] + x[4] * x[8] >= 0
            1 - a * (x[2] * x[9] + x[5] * x[8] - x[1] * x[8] - x[6] * x[9]) -
            x[5] - x[6] >= 0
            x[2] * x[9] - x[3] * x[10] - x[6] * x[9] - 500 * x[2] +
            500 * x[6] +
            x[2] * x[10] >= 0
            x[2] - 0.9 - a * (x[2] * x[10] - x[3] * x[10]) >= 0
            x[11] + x[12] + x[13] <= 250
        end

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        # This test occasionally fails, for unknown reasons.
        @test JuMP.objective_value(m) ≈ 97.588409 atol = 1e-3
    end

    @testset "HS118" begin
        m = Model(Ipopt.Optimizer)

        L = zeros(15)
        L[1] = 8.0
        L[2] = 43.0
        L[3] = 3.0

        U = zeros(15)
        U[1] = 21.0
        U[2] = 57.0
        U[3] = 16.0
        for k in 1:4
            U[3*k+1] = 90.0
            U[3*k+2] = 120.0
            U[3*k+3] = 60.0
        end

        start = [
            20.0,
            55.0,
            15.0,
            20.0,
            60.0,
            20.0,
            20.0,
            60.0,
            20.0,
            20.0,
            60.0,
            20.0,
            20.0,
            60.0,
            20.0,
        ]

        @variable(m, L[i] <= x[i = 1:15] <= U[i])

        # Initial solution (could also use 'start' keyword in @variable)
        JuMP.set_start_value.(x, start)

        @NLobjective(
            m,
            Min,
            sum(
                2.3 * x[3*k+1] +
                0.0001 * x[3*k+1]^2 +
                1.7 * x[3*k+2] +
                0.0001 * x[3*k+2]^2 +
                2.2 * x[3*k+3] +
                0.00015 * x[3*k+3]^2 for k in 0:4
            )
        )

        # constr1
        for j in 1:4
            @constraint(m, x[3*j+1] - x[3*j-2] + 7 <= 13)
            @constraint(m, x[3*j+1] - x[3*j-2] + 7 >= 0)
        end

        # constr2
        for j in 1:4
            @constraint(m, x[3*j+2] - x[3*j-1] + 7 <= 14)
            @constraint(m, x[3*j+2] - x[3*j-1] + 7 >= 0)
        end

        # constr3
        for j in 1:4
            @constraint(m, x[3*j+3] - x[3*j] + 7 <= 13)
            @constraint(m, x[3*j+3] - x[3*j] + 7 >= 0)
        end

        @constraint(m, x[1] + x[2] + x[3] >= 60)
        @constraint(m, x[4] + x[5] + x[6] >= 50)
        @constraint(m, x[7] + x[8] + x[9] >= 70)
        @constraint(m, x[10] + x[11] + x[12] >= 85)
        @constraint(m, x[13] + x[14] + x[15] >= 100)

        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

        @test JuMP.value.(x[1:4]) ≈ [8.0, 49.0, 3.0, 1.0] atol = 1e-4
        @test JuMP.objective_value(m) ≈ 664.82045 atol = 1e-5
    end

    @testset "Two-sided constraints" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, x)
        @NLobjective(m, Max, x)
        l = -1
        u = 1
        @NLconstraint(m, l <= x <= u)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ u atol = 1e-6

        @NLobjective(m, Min, x)
        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ l atol = 1e-6
    end

    @testset "Two-sided constraints (no macros)" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, x)
        JuMP.set_NL_objective(m, MOI.MAX_SENSE, x)
        l = -1
        u = 1
        JuMP.add_NL_constraint(m, :($l <= $x <= $u))
        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ u atol = 1e-6

        JuMP.set_NL_objective(m, MOI.MIN_SENSE, x)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ l atol = 1e-6
    end

    @testset "Duals" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, x >= 0)
        @variable(m, y <= 5)
        @variable(m, 2 <= z <= 4)
        @variable(m, 0 <= r[i = 3:6] <= i)
        @NLobjective(m, Min, -((x + y) / 2.0 + 3.0) / 3.0 - z - r[3])
        @constraint(m, cons1, x + y >= 2)
        @constraint(m, cons2, sum(r[i] for i in 3:5) <= (2 - x) / 2.0)
        @NLconstraint(m, cons3, 7.0 * y <= z + r[6] / 1.9)

        function test_result()
            @test JuMP.has_values(m)
            @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
            @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

            @test JuMP.value(x) ≈ 0.9774436 atol = 1e-6
            @test JuMP.value(y) ≈ 1.0225563 atol = 1e-6
            @test JuMP.value(z) ≈ 4.0 atol = 1e-6
            @test JuMP.value(r[3]) ≈ 0.5112781 atol = 1e-6
            @test JuMP.value(r[4]) ≈ 0.0 atol = 1e-6
            @test JuMP.value(r[5]) ≈ 0.0 atol = 1e-6
            @test JuMP.value(r[6]) ≈ 6.0 atol = 1e-6
            @test JuMP.dual_status(m) == MOI.FEASIBLE_POINT
            # Reduced costs
            @test JuMP.dual(JuMP.LowerBoundRef(x)) ≈ 0.0 atol = 1e-6
            @test JuMP.dual(JuMP.UpperBoundRef(y)) ≈ 0.0 atol = 1e-6
            @test JuMP.dual(JuMP.UpperBoundRef(z)) ≈ -1.0714286 atol = 1e-6
            @test JuMP.dual(JuMP.LowerBoundRef(r[3])) ≈ 0.0 atol = 1e-6
            @test JuMP.dual(JuMP.UpperBoundRef(r[3])) ≈ 0.0 atol = 1e-6
            @test JuMP.dual(JuMP.LowerBoundRef(r[4])) ≈ 1.0 atol = 1e-6
            @test JuMP.dual(JuMP.UpperBoundRef(r[4])) ≈ 0.0 atol = 1e-6
            @test JuMP.dual(JuMP.LowerBoundRef(r[5])) ≈ 1.0 atol = 1e-6
            @test JuMP.dual(JuMP.UpperBoundRef(r[5])) ≈ 0.0 atol = 1e-6
            @test JuMP.dual(JuMP.UpperBoundRef(r[6])) ≈ -0.03759398 atol = 1e-6
            @test JuMP.dual(JuMP.LowerBoundRef(r[6])) ≈ 0.0 atol = 1e-6

            # Constraint duals
            @test JuMP.dual(cons1) ≈ 0.333333 atol = 1e-6
            @test JuMP.dual(cons2) ≈ -1.0 atol = 1e-6
            @test JuMP.dual(cons3) ≈ -0.0714286 atol = 1e-6
        end

        set_silent(m)
        JuMP.optimize!(m)
        test_result()
        @test JuMP.objective_value(m) ≈ -5.8446115 atol = 1e-6

        # Same objective with sense/sign flipped.
        @NLobjective(m, Max, ((x + y) / 2.0 + 3.0) / 3.0 + z + r[3])

        JuMP.optimize!(m)
        test_result()
        @test JuMP.objective_value(m) ≈ 5.8446115 atol = 1e-6
    end

    @testset "Quadratic inequality constraints, linear objective" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, -2 <= x <= 2)
        @variable(m, -2 <= y <= 2)
        @objective(m, Min, x - y)
        @constraint(m, x + x^2 + x * y + y^2 <= 1)
        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ -1 - 4 / sqrt(3) atol = 1e-6
        @test JuMP.value(x) + JuMP.value(y) ≈ -1 / 3 atol = 1e-3
    end

    @testset "Quadratic inequality constraints, NL objective" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, -2 <= x <= 2)
        @variable(m, -2 <= y <= 2)
        @NLobjective(m, Min, x - y)
        @constraint(m, x + x^2 + x * y + y^2 <= 1)
        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ -1 - 4 / sqrt(3) atol = 1e-6
        @test JuMP.value(x) + JuMP.value(y) ≈ -1 / 3 atol = 1e-3
    end

    @testset "Quadratic equality constraints" begin
        m = Model(Ipopt.Optimizer)
        @variable(m, 0 <= x[1:2] <= 1)
        @constraint(m, x[1]^2 + x[2]^2 == 1 / 2)
        @NLobjective(m, Max, x[1] - x[2])
        set_silent(m)
        JuMP.optimize!(m)

        @test JuMP.has_values(m)
        @test JuMP.termination_status(m) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
        @test JuMP.objective_value(m) ≈ sqrt(1 / 2) atol = 1e-6
        @test JuMP.value.(x) ≈ [sqrt(1 / 2), 0] atol = 1e-6
    end

    @testset "Fixed variables" begin
        m = Model(Ipopt.Optimizer)
        set_silent(m)
        @variable(m, x == 0)
        @variable(m, y ≥ 0)
        @objective(m, Min, y)
        @NLconstraint(m, y ≥ x^2)
        for α in 1:4
            JuMP.fix(x, α)
            JuMP.optimize!(m)
            @test JuMP.value(y) ≈ α^2 atol = 1e-6
        end
    end

    @testset "ifelse" begin
        model = Model(Ipopt.Optimizer)
        @variable(model, x, start = 2)
        # The minimizer is at smooth point, so solvers should be okay.
        @NLobjective(model, Min, ifelse(x <= 1, x^2, x))
        set_silent(model)
        JuMP.optimize!(model)

        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value(x) ≈ 0.0 atol = 1e-5
    end

    @testset "infeasible problem" begin
        # (Attempt to) solve an infeasible problem
        model = Model(Ipopt.Optimizer)
        n = 10
        @variable(model, 0 <= x[i = 1:n] <= 1)
        @NLobjective(model, Max, x[n])
        for i in 1:n-1
            @NLconstraint(model, x[i+1] - x[i] == 0.15)
        end
        set_silent(model)
        JuMP.optimize!(model)

        @test JuMP.termination_status(model) == MOI.LOCALLY_INFEASIBLE
    end

    @testset "unbounded problem" begin
        model = Model(Ipopt.Optimizer)
        @variable(model, x >= 0)
        @NLobjective(model, Max, x)
        @NLconstraint(model, x >= 5)
        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.termination_status(model) == MOI.NORM_LIMIT
    end

    @testset "Derivatives of x^4, x < 0" begin
        model = Model(Ipopt.Optimizer)
        @variable(model, x >= -1, start = -0.5)
        @NLobjective(model, Min, x^4)
        set_silent(model)
        JuMP.optimize!(model)

        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value(x) ≈ 0.0 atol = 1e-2
    end

    # This test seems to be checking for a bug in expression handling.
    @testset "Entropy maximization" begin
        model = Model(Ipopt.Optimizer)
        @variable(model, x[1:4] >= 0, start = 1)
        @variable(model, z[1:4], start = 0)
        @NLexpression(model, entropy[i = 1:4], -x[i] * log(x[i]))
        @NLobjective(
            model,
            Max,
            sum(z[i] for i in 1:2) + sum(z[i] / 2 for i in 3:4)
        )
        @NLconstraint(model, z_constr1[i = 1], z[i] <= entropy[i])
        @NLconstraint(model, z_constr1_dup[i = 2], z[i] <= entropy[i]) # duplicate expressions
        @NLconstraint(model, z_constr2[i = 3:4], z[i] <= 2 * entropy[i])
        @constraint(model, sum(x) == 1)
        set_silent(model)
        JuMP.optimize!(model)

        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value.(x) ≈ [1 / 4, 1 / 4, 1 / 4, 1 / 4] atol = 1e-3
        @test JuMP.value(entropy[1]) ≈ -(1 / 4) * log(1 / 4) atol = 1e-4
    end

    @testset "Changing objectives" begin
        model = Model(Ipopt.Optimizer)
        @variable(model, x >= 0)
        @variable(model, y >= 0)
        @objective(model, Max, x + y)
        @NLconstraint(model, x + 2y <= 1)
        JuMP.optimize!(model)
        @test JuMP.value(x) ≈ 1.0 atol = 1e-4
        @test JuMP.value(y) ≈ 0.0 atol = 1e-4
        @test JuMP.objective_value(model) ≈ 1.0 atol = 1e-4

        @objective(model, Max, 2x + y)
        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.value(x) ≈ 1.0 atol = 1e-4
        @test JuMP.value(y) ≈ 0.0 atol = 1e-4
        @test JuMP.objective_value(model) ≈ 2.0 atol = 1e-4
    end

    @testset "Setting NLobjective then objective" begin
        model = Model(Ipopt.Optimizer)
        @variable(model, x >= 0)
        @variable(model, y >= 0)
        @NLobjective(model, Max, x + y)
        @NLconstraint(model, x + 2y <= 1)
        JuMP.optimize!(model)
        @test JuMP.value(x) ≈ 1.0 atol = 1e-4
        @test JuMP.value(y) ≈ 0.0 atol = 1e-4
        @test JuMP.objective_value(model) ≈ 1.0 atol = 1e-4

        @objective(model, Max, 2x + y)
        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.value(x) ≈ 1.0 atol = 1e-4
        @test JuMP.value(y) ≈ 0.0 atol = 1e-4
        @test JuMP.objective_value(model) ≈ 2.0 atol = 1e-4
    end

    my_square(x) = x^2
    function my_f(x, y)
        return (x - 1)^2 + (y - 2)^2
    end

    @testset "User-defined functions" begin
        model = Model(Ipopt.Optimizer)
        JuMP.register(model, :my_f, 2, my_f, autodiff = true)
        JuMP.register(model, :my_square, 1, my_square, autodiff = true)

        @variable(model, x[1:2] >= 0.5)
        @NLobjective(model, Min, my_f(x[1], my_square(x[2])))

        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value.(x) ≈ [1, sqrt(2.0)]
    end

    @testset "Univariate user-defined functions" begin
        model = Model(Ipopt.Optimizer)
        JuMP.register(model, :my_square, 1, my_square, autodiff = true)

        # Test just univariate functions because this is a path where hessians
        # are enabled.
        @variable(model, x[1:2] >= 0.5)
        @NLobjective(model, Min, my_square(x[1] - 0.4) + my_square(x[2] - 2.0))
        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value.(x) ≈ [0.5, 2.0] atol = 1e-4
    end

    @testset "Issue #927" begin
        model = Model(Ipopt.Optimizer)
        JuMP.register(model, :my_f, 2, my_f, autodiff = true)
        @variable(model, x)
        @NLobjective(model, Min, my_f(x, x))
        set_silent(model)
        JuMP.optimize!(model)
        @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED
        @test JuMP.primal_status(model) == MOI.FEASIBLE_POINT
        @test JuMP.value.(x) ≈ 1.5 atol = 1e-4
    end
end


@testset "Regions" begin
  @testset "Rectangle" begin
    r = RectangleRegion((1.,1.), (2.,3.))
    @test GeoStatsBase.center(r) == [3/2, 4/2]
    @test lowerleft(r) == [1., 1.]
    @test upperright(r) == [2., 3.]
    @test sides(r) == [1., 2.]
    @test GeoStatsBase.diagonal(r) == sqrt(1^2 + 2^2)
    @test volume(r) == 1*2
  end
end


# The jackknife is a resampling technique especially useful for variance and bias estimation.
"""
    jackknife_avg(`scores`)

Apply jackknife on the input list of `scores`
"""
function jackknife_avg(scores)
    if length(collect(Set(scores))) == 1
        #= In case the elements of the array are all equal=#
        return scores[1]
    else
        #=store the maximum scores
        from the m different sets of m-1 scores.
        such that m is the len(score_list)=#
        average = []
        for i in scores
            # dummy : list a particular combo of m-1 scores
            dummy = [j for j in scores if i != j]
            append!(average, max(dummy...))
            end

        return sum(average)/length(average)
    end
end

"""
    weighted_lcs(X, Y, weight_score::Bool, returns_string::Bool, weigthing_function::Function)

Compute the Weighted Longest Common Subsequence of X and Y.
"""
function weighted_lcs(X, Y, weighted = true, returns_string = false, f = sqrt)
    m, n = length(X), length(Y)
    c_table = [zeros(n+1) for i in 1:m+1]
    w_table = [zeros(n+1) for i in 1:m+1]

    for i in 2:(m+1)
        for j in 2:(n+1)
            if X[i-1] == Y[j-1]
                k = w_table[i-1][j-1]
                if weighted == true
                    increment = (f(k+1)) - (f(k))
                else
                    increment = 1
                end
                c_table[i][j] = c_table[i-1][j-1] + increment
                w_table[i][j] = k + 1
            else
                if c_table[i-1][j] > c_table[i][j-1]
                    c_table[i][j] = c_table[i-1][j]
                    w_table[i][j] = 0  # no match at i,j
                else
                    c_table[i][j] = c_table[i][j-1]
                    w_table[i][j] = 0  # no match at i,j
                end
            end
        end
    end

    lcs_length = (c_table[m+1][n+1])
    if returns_string == false
        return lcs_length
    end

    if weighted == true
        lcs_length = c_table[m][n]^(2)
    end

    lcs_length = round(Int64, lcs_length)
    lcs_length = convert(Int64, lcs_length)
    lcs = ["" for i in  1:(lcs_length+1)]
    lcs[lcs_length+1] = ""
    i = m + 1
    j = n + 1

    while i>1 && j>1
        if X[i-1] == Y[j-1]
            lcs[lcs_length+1] = X[i-1]
            i -= 1
            j -= 1
            lcs_length -= 1
        elseif c_table[i-1][j] > c_table[i][j-1]
            i -= 1
        else
            j -= 1
        end
    end

    return (join(lcs, " "))  # the lcs string
end

"""
    fmeasure_lcs(RLCS, PLCS, β)

Compute the F-measure based on WLCS.

# Arguments

- `RLCS` - Recall Factor
- `PLCS` - Precision Factor
- `β` - Parameter
"""
function fmeasure_lcs(RLCS, PLCS, β=1)
    try
        return ((1 + β ^ 2) * RLCS * PLCS) / (RLCS + (β ^ 2) * PLCS)
    catch ex
        if ex isa DivideError
            return 0
        else
            rethrow(ex)
        end
    end
end


"""
Compute the upper bounds on `A_i ⋅ x under common linear constraints`
"""
function compute_bounds(bp::BilevelProblem; optimizer, silent=true, iterated=true)
    m = Model(optimizer)
    silent && MOI.set(m, MOI.Silent(), true)
    (_, x, v, λ, σ, s, _) = high_point_relaxation(m, bp)
    optimize!(m)
    st = termination_status(m)
    @assert st == MOI.OPTIMAL
    function bound_vector()
        bounds_val = Float64[]
        for i in 1:bp.ml
            @objective(m, Max, bp.A[i,:] ⋅ x)
            optimize!(m)
            st = termination_status(m)
            if st == MOI.INFEASIBLE || st == MOI.INFEASIBLE_OR_UNBOUNDED
                @warn "Status $st"
                return (st, [NaN])
            elseif st == MOI.DUAL_INFEASIBLE
                push!(bounds_val, Inf)
            else
                st == MOI.OPTIMAL || error("Status $st")
                push!(bounds_val, objective_value(m))
            end
        end
        return (MOI.OPTIMAL, bounds_val)
    end
    (st, Abounds) = bound_vector()
    if st != MOI.OPTIMAL
        return (st, Abounds)
    end
    niter = 1
    while iterated && all(isfinite, Abounds)
        @info niter
        Aprev = copy(Abounds)
        add_bound_inequalities(m, bp, x, v, λ, Aprev)
        (st, Abounds) = bound_vector()
        if st != MOI.OPTIMAL
            return (st, Abounds)
        end
        if all(tup -> tup[1] ≈ tup[2], zip(Abounds, Aprev))
            break
        end
        niter += 1
    end
    return (MOI.OPTIMAL, Abounds)
end

function add_bound_inequalities(m::JuMP.Model, bp::BilevelProblem, x, v, λ, bounds::AbstractVector{<:Real})
    @constraint(m, λ ⋅ bp.b + v ⋅ bp.d ≤ bounds ⋅ λ)
    return nothing
end

function add_bound_inequalities(m::JuMP.Model, bp::BilevelProblem, x, v, λ; optimizer, silent=true, verbose=false)
    (st, Aplus) = compute_bounds(bp; optimizer=optimizer, silent=silent)
    if st != MOI.OPTIMAL
        return false
    end
    if all(isfinite, Aplus)
        add_bound_inequalities(m, bp, x, v, λ, Aplus)
        return true
    end
    verbose && @warn("Not all bounds are finite: # inf: $(count(isfinite, Aplus))")
    return false
end


@testset "CoDaArray" begin
  table = (a=[1,2,3], b=[4,5,6], c=[7,8,9])
  array = CoDaArray(table)
  rows  = array
  @test length(array) == 3
  @test array[1] == Composition(a=1, b=4, c=7)
  @test array[2] == Composition(a=2, b=5, c=8)
  @test array[3] == Composition(a=3, b=6, c=9)
  @test Tables.istable(array)
  @test Tables.rowaccess(array) == true
  @test Tables.rows(array) == rows
  @test Tables.getcolumn(rows[1], 1) == 1.0
  @test Tables.getcolumn(rows[1], :a) == 1.0
  @test Tables.columnnames(rows[1]) == (:a, :b, :c)

  array = compose(jura, (:Cd, :Cu, :Pb, :Co, :Cr, :Ni, :Zn), keepcols=false)
  @test parts(array) == (:Cd, :Cu, :Pb, :Co, :Cr, :Ni, :Zn)
  @test length(array) == 359
  @test array[1] == Composition(Cd=1.74, Cu=25.72, Pb=77.36, Co=9.32, Cr=38.32, Ni=21.32, Zn=92.56)
  @test length(array.Cd) == 359
  @test length(array.Cu) == 359
  @test array.Cd[1] == 1.74
  @test array.Cu[1] == 25.72
  @test_throws KeyError array.INVALID

  table = compose(jura, (:Cd, :Cu, :Pb, :Co, :Cr, :Ni, :Zn))
  @test Tables.columnnames(table) == [:X,:Y,:Rock,:Land,:coda]
  @test Tables.getcolumn(table, :coda) == array

  table = compose(jura, (:Cd, :Cu, :Pb, :Co, :Cr, :Ni, :Zn), as=:comps)
  @test Tables.columnnames(table) == [:X,:Y,:Rock,:Land,:comps]
  @test Tables.getcolumn(table, :comps) == array
end


using Printf, IterTools

import Base.IteratorSize, Base.iterate, Base.length

iscontseq(n::Integer) = count_zeros(n) == leading_zeros(n) + trailing_zeros(n)
iscontseq(n::BigInt)  = !ismatch(r"0", rstrip(bin(n), '0'))

function makeint2seq(n::Integer)
    idex = collect(1:n)
    function int2seq(m::Integer)
        d = digits(m, base=2, pad=n)
        idex[d .== 1]
    end
    return int2seq
end

struct NCSubSeq{T<:Integer}
    n::T
end

mutable struct NCSubState{T<:Integer}
    m::T
    m2s::Function
end

Base.IteratorSize(::NCSubSeq) = Base.HasLength()
Base.length(a::NCSubSeq) = 2 ^ a.n - a.n * (a.n + 1) ÷ 2 - 1
function Base.iterate(a::NCSubSeq, as::NCSubState=NCSubState(5, makeint2seq(a.n)))
    if 2 ^ a.n - 3 < as.m
        return nothing
    end
    s = as.m2s(as.m)
    as.m += 1
    while iscontseq(as.m)
        as.m += 1
    end
    return (s, as)
end

n = 4
println("Testing NCSubSeq for ", n, " items:\n ", join(NCSubSeq(n), " "))

s = "Rosetta"
cs = split(s, "")
m = 10
n = length(NCSubSeq(length(cs))) - m
println("\nThe first and last ", m, " NC sub-sequences of \"", s, "\":")
for (i, a) in enumerate(NCSubSeq(length(s)))
    i <= m || n < i || continue
    println(@sprintf "%6d %s" i join(cs[a], ""))
    i == m || continue
    println("    .. ......")
end

println("\nThe first and last ", m, " NC sub-sequences of \"", s, "\"")
for x in IterTools.Iterators.flatten([1:10; 20:10:40; big.(50:50:200)])
    @printf "%7d → %d\n" x length(NCSubSeq(x))
end


### Kernel Density Estimation
export
    kde,
    KDEMethod,
    FixedBandwidth,
    SilvermansRule,
    bandwidth
include("kde_methods.jl")

export
    gmm_matrix,
    gmm_matrix!
include("gmm_matrix.jl")

### Information Measure calculation
export
    information,
    InformationMeasure,
    NoInformation
export
    ParticleInformationMeasure,
    DiscreteEntropy,
    discrete_entropy
export
    KDEInformationMeasure,
    KDEEntropy,
    GMMEntropy

abstract type InformationMeasure end
"""
    Measures how much information is contained in the given belief.
    Measuring is done with the given InformationMeasure.
"""
information(m::InformationMeasure, args...; kwargs...)::Float64 = (error("information not implemented for $m"); return 0.0)

struct NoInformation <: InformationMeasure end
information(::NoInformation; kwargs...)::Float64 = 0.0

abstract type ParticleInformationMeasure <: InformationMeasure end

struct DiscreteEntropy <: ParticleInformationMeasure end
information(::DiscreteEntropy; bp::AbstractParticleBelief, kwargs...)::Float64 = -discrete_entropy(bp)
# Works only if enough particles in every discrete state, by summing up the weights
# for continuous distributions where all particles are in different states this yields -log(1/n_particles(b))
discrete_entropy(b::AbstractParticleBelief)::Float64 = -sum(p * log(p) for p in values(ParticleFilters.probdict(b)) if p > 0.0)

abstract type KDEInformationMeasure <: InformationMeasure end

struct GMMEntropy <: KDEInformationMeasure
    kde_method::KDEMethod
end
function information(m::GMMEntropy; bp::AbstractParticleBelief, kwargs...)::Float64
    ws = weight_sum(bp)
    dot(weights(bp)/ws, log.(gmm_matrix(bp, bandwidth(m.kde_method, bp)) * weights(bp)/ws))
end


"""
    AbstractGradient{T}

Magnetic field gradient ``\\vec{g}(t) \\in \\mathbb{R}^3``.
"""
abstract type AbstractGradient{T} end

"""
    GeneralGradient(g⃗)

General gradient sequence ``\\vec{g}(t) \\in \\mathbb{R}^3``. The direction and amplitude may
vary in time.
"""
Base.@kwdef struct GeneralGradient{T,F}
    g⃗::F
    TE::T
end

"""
    ScalarGradient(dir, profile, amplitude)

Gradient sequence with amplitude `amplitude`, normal direction `dir` and scalar time profile
`profile`.

The direction is constant, while the amplitude is controlled by the time profile.

The direction is normalized upon construction.
"""
struct ScalarGradient{T}
    dir::Vector{T}
    profile::TimeProfile{T}
    amplitude::T
    ScalarGradient(dir, profile, amplitude) =
        new{typeof(amplitude)}(dir / norm(dir), profile, amplitude)
end

(g::GeneralGradient)(t) = g.g⃗(t)
(g::ScalarGradient)(t) = g.amplitude * g.profile(t) * g.dir


using Bokeh
using Compat
if VERSION < v"0.4-"
    using Dates
else
    using Base.Dates
end

start = now()
x = map(d -> start + Dates.Hour(d), 1:(30 * 24))
println(typeof(x))
# x: Array{DateTime,1}
day = 1000 * 3600 * 24
@compat y = 0.5sin(map(Int, x) * pi / day) + cos(map(Int, x) * pi / (day * 7))
# note x needs to be an array of type DateTime or Date
plot(x, y)
showplot("dates_xy.html")

start = Date(1980, 1, 1)
x = map(d -> start + Dates.Day(d), 1:(365 * 30))
println(typeof(x))
# x: Array{Date,1}
@compat y = 0.5sin(map(Int, x) * pi / 365) + cos(map(Int, x) * pi / (365 * 7))

plot(y, x)
showplot("dates_yx.html")


function ufpair(rt, data :: Data)

	@unpack sig6, eps4, eps24 = data

	r6 = (rt[3])^3
	r4 = (rt[3])^2

	u = eps4*(sig6/r6 - 1.0)/r6

	f = eps24 * (r6 - 2*sig6) / (r6*r4*r4)

	fx = f * rt[1]
	fy = f * rt[2]

	return u, (-fx, -fy)
end


include(joinpath(@__DIR__, "types.jl"));

abstract type DipoleResponse end

(dr::DipoleResponse)(E0::Real) = begin; error("Not yet implemented"); E0; end

function μ_dielectric(E0::Real, K1::Real, K2::Real, cϕ::Real, sϕ::Real)
  n̂i = n̂(cϕ, sϕ);
  return ((K1 - K2) * E0 * cϕ * n̂i) + (K2 * [E0; 0.0]);
end

struct DielectricResponse <: DipoleResponse
  K1::Real;
  K2::Real;
end

function (dr::DielectricResponse)(E0::Real, cϕ::Real, sϕ::Real)
  return μ_dielectric(E0, dr.K1, dr.K2, cϕ, sϕ);
end

struct PolarResponse <: DipoleResponse
  M::FdMatrix;
end

function (pr::PolarResponse)(::Real, cϕ::Real, sϕ::Real)
  return pr.M * n̂(cϕ, sϕ);
end


using DiffEqFinancial, StochasticDiffEq
using Test

# write your own tests here
u0 = [1.0; 0.5]
σ = 0.25
prob = HestonProblem(1.0, 1.0, σ, 1.0, 1.0, u0, (0.0, 1.0))
sol = solve(prob, SRIW1(), adaptive = false, dt = 1 / 10)

prob = BlackScholesProblem((t) -> t^2, (t, u) -> 1.0, σ, 0.5, (0.0, 1.0))
sol = solve(prob, SRIW1())