using Random # Random stdlib
import ForwardDiff: derivative
import StatsBase: sample, weights # To include the basic sample from array function
# export Line, Envelop, RejectionSampler # Structures/classes
# export run_sampler!, sample_envelop, eval_envelop, add_segment! # Methods

"""
    Line(slope::Float64, intercept::Float64)
Basic tangent ensamble-unit for an envelop.
"""
struct Line
    slope::Float64
    intercept::Float64
end
eval_line(l::Line, x::Float64) = l.slope * x + l.intercept

"""
    intersection(l1::Line, l2::Line)
Finds the horizontal coordinate of the intersection between lines
"""
function intersection(l1::Line, l2::Line)
    @assert l1.slope ≥ l2.slope "slopes should be weakly ordered (l1: $(l1.slope), l2: $(l2.slope))"
    @assert l1.slope != l2.slope
    - (l2.intercept - l1.intercept) / (l2.slope - l1.slope)
end

"""
    exp_integral(l::Line, x1::Float64, x2::Float64)
Computes the integral
    ``LaTeX \\int_{x_1} ^ {x_2} \\exp\\{ax + b\\} dx. ``
The resulting value is the weight assigned to the segment [x1, x2] in the envelop
"""
function exp_integral(l::Line, x1::Float64, x2::Float64)::Tuple{Float64, Float64}
    (x1 == x2) && (return (0.0, 0.0))
    @assert x1 ≤ x2 "invalid x1=$x1, x2=$x2, $l"
    a, b = l.slope, l.intercept
    # for numerical stability: multiply by 0.5 * a(x1 - x2)
    if a != 0.0
        z1, z2 = eval_line(l, x1), eval_line(l, x2)
        scale = max(z1, z2)
        out = (exp(z2 - scale) - exp(z1 - scale)) / a
    else
        scale = 0.0
        out = (x2 - x1) * exp(b)
    end
    @assert out ≥ 0 "negative weight (out: $out, x1: $x1, x2: $x2, l: $l, scale: $scale"
    # debug info
    #  println("  @exp_integral:")
    #  println("  |-- line: $l")
    #  println("  |-- x1: $x1")
    #  println("  |-- x2: $x2")
    #  println("  |-- exp(b + a * x2): $(exp(b + a * x2))")
    #  println("  |-- exp(b + a * x1): $(exp(b + a * x1))")
    #  println("  |-- exp(a * x2): $(exp(a * x2))")
    #  println("  |-- exp(a * x1): $(exp(a * x1))")
    #  println("  |-- out: $out")
    return out, scale
end

"""
    Envelop(lines::Vector{Line}, support::Tuple{Float64, Float64})
A piecewise linear function with k segments defined by the lines `L_1, ..., L_k` and cutpoints
`c_1, ..., c_k+1` with `c1 = support[1]` and `c2 = support[2]`. A line L_k is active in the segment
[c_k, c_k+1], and it's assigned a weight w_k based on [exp_integral](@exp_integral). The weighted integral
over c_1 to c_k+1 is one, so that the envelop is interpreted as a density.
"""
struct Envelop
    lines::Vector{Line}
    cutpoints::Vector{Float64}
    weights::Vector{Tuple{Float64, Float64}}
    secant_knots::Vector{Float64}
    secant_values::Vector{Float64}

    Envelop(
        lines::Vector{Line},
        support::Tuple{Float64, Float64},
        secant_knots::Vector{Float64} = Float64[],
        secant_values::Vector{Float64} = Float64[]
    ) = begin
        @assert length(secant_knots) == length(secant_values)
        #  println("lines: $lines")
        @assert issorted([l.slope for l in lines], rev = true) "line slopes must be decreasing"
        intersections = [intersection(lines[i], lines[i + 1]) for i in 1:(length(lines) - 1)]
        #  println("intersections: $intersections")
        cutpoints = [support[1]; intersections; support[2]]
        #  println("cutpoints: $cutpoints")
        weights = [exp_integral(l, cutpoints[i], cutpoints[i + 1]) for (i, l) in enumerate(lines)]
        # println("weights: $weights")
        new(lines, cutpoints, weights, secant_knots, secant_values)
    end
end
Base.length(e::Envelop) = length(e.lines)

function eval_secants(e::Envelop, x::Float64; log::Bool=false)
    pos = searchsortedfirst(e.secant_knots, x)
    if pos == 1 || pos == length(e) + 1
        out =- Inf
    else
        x1 = e.secant_knots[pos - 1]
        y1 = e.secant_values[pos - 1]
        x2 = e.secant_knots[pos]
        y2 = e.secant_values[pos]
        out = (y2 - y1) / (x2 - x1) * (x - x1) + y1
    end
    !log && (out = exp(out))
    return out
end

# struct SecantBuffer
#     secants::Vector{Buffer}
# end
# len(s::SecantBuffer) = length(s.secants)

# function eval_secant_buffer(s::SecatBuffer, x::Float64)
#     for sec in s.secants
#         (sec.x1 ≥ x) && (return eval_secant(sec, x))
#     end
#     error("point $x outside secant range $(s.secants[1].x1, s.secants[end].x2)")
# end

"""
    add_segment!(e::Envelop, l::Line)
Adds a new line segment to an envelop based on the value of its slope (slopes must be decreasing
always in the envelop). The cutpoints are automatically determined by intersecting the line with
the adjacent lines.
"""
function add_segment!(e::Envelop, l::Line)
    #  println("@add_segment")
    #  println("|-- adding segment $l")
    # printstyled("|-- envelop: $e\n", color=:blue)
    # Find the position in sorted array with binary search
    pos = searchsortedfirst([-line.slope for line in e.lines], -l.slope)
    # printstyled("|-- adding $l to position $pos\n", color=:green)
    # remember cutpoints has the support at the beginning and end!
    # Find the new cutpoints
    if pos == 1
        new_cut = intersection(l, e.lines[pos])  # smaller than all values
        # printstyled("|-- adding cut $(new_cut)\n", color=:yellow)
        # Insert in second position, first one is the support bound
        # 1. measure weight from support to new first cut
        # 2. measure weight from new cut to previous first cut after support
        ws1 = exp_integral(l, e.cutpoints[1], new_cut)  # support to new
        ws2 = exp_integral(e.lines[1], new_cut, e.cutpoints[2])  # new to next
        insert!(e.cutpoints, 2, new_cut)
        splice!(e.weights, 1, [ws1, ws2])
        # printstyled("|-- new weights: ($ws1, $ws2)\n", color=:green)
        # printstyled("|-- new envelop: $e\n", color=:blue)
        # error("early exit")
        # must update weights for first two segments only
    elseif pos == length(e) + 1  # greater than all values
        new_cut = intersection(e.lines[end], l)
        # printstyled("|-- adding cut $(new_cut)\n", color=:yellow)
        # 1. measure weight from new last cut to support
        # 2. measure weight from new cut to previous first cut after support
        ws1 = exp_integral(e.lines[end], e.cutpoints[end - 1], new_cut)
        ws2 = exp_integral(l, new_cut, e.cutpoints[end])
        insert!(e.cutpoints, length(e) + 1, new_cut)  # just before support end
        splice!(e.weights, length(e), [ws1, ws2])
        # printstyled("|-- new weights: ($ws1, $ws2)\n", color=:green)
        # printstyled("|-- new envelop: $e\n", color=:blue)
        # error("early exit")
    else
        (l.slope == e.lines[pos].slope) && return -1 # can't add segment
        new_cut1 = intersection(e.lines[pos - 1], l)
        new_cut2 = intersection(l, e.lines[pos])
        supp = e.cutpoints[1], e.cutpoints[end]
        @assert supp[1] ≤ new_cut1 ≤ new_cut2 "new_cut1 ≤ supp[2]: $new_cut1, new_cut2: $new_cut2, supp: $supp"
        # println("Current cuts: $(e.cutpoints)")
        # printstyled("|-- adding cuts $(new_cut1) and $(new_cut2) from tangent $(l.tangent_point)\n", color=:yellow)
        # must update three weights!
        ws1 = exp_integral(e.lines[pos - 1], e.cutpoints[pos - 1], new_cut1)
        ws2 = exp_integral(l, new_cut1, new_cut2)
        ws3 = exp_integral(e.lines[pos], new_cut2, e.cutpoints[pos + 1])
        # printstyled("|-- new weights: ($ws1, $ws2, $ws3)\n", color=:green)
        splice!(e.cutpoints, pos, [new_cut1, new_cut2])
        splice!(e.weights, (pos - 1):pos, [ws1, ws2, ws3])
        # printstyled("|-- new envelop: $e\n", color=:blue)
        # error("early exit")
        # @assert issorted(e.cutpoints)  "incompatible line: resulting intersection points aren't sorted"
    end
    # Insert the new line
    insert!(e.lines, pos, l)
    return pos
    # e.weights = [exp_integral(line, e.cutpoints[i], e.cutpoints[i + 1]) for (i, line) in enumerate(e.lines)]
end

"""
    sample_envelop(rng::AbstractRNG, e::Envelop)
    sample_envelop(e::Envelop)
Samples an element from the density defined by the envelop `e` with it's exponential weights.
See [`Envelop`](@Envelop) for details.
"""
function sample_envelop(rng::AbstractRNG, e::Envelop)
    # Randomly select a segment from the weights
    # scale each weight accordingly for numerical stability
    max_scale = maximum(s for (_, s) in e.weights)
    rel_wts = weights([w * exp(s - max_scale) for (w, s) in e.weights])
    i = sample(rng, 1:length(e), rel_wts)

    # use the inverse CDF method to sample within segment
    #  println("Sampling $i from current weights $(e.weights)")
    # println("Sampling with weights $rel_wts, max_scale: $max_scale, x1: $x1")
    l = e.lines[i]
    denom, scale = e.weights[i]
    a, b = e.lines[i].slope, e.lines[i].intercept
    x1, x2 = e.cutpoints[i:(i + 1)]
    u = rand(rng)
    if a == 0.0
        out = x1 + u * (x2 - x1)
    else
        # out = (log(a * U * denom + exp(eval_line(e.lines[i], x1) - scale)) - b + scale) / a
        # out = (
        #     log(exp(-b + scale) * u * denom * a + exp(eval_line(l, x1) - scale))
        #     - b + scale
        # ) / a
        out = (
            log(u * e.weights[i][1] * a + exp(a * x1 + b - scale))
            -b + scale
        ) / a

    end

    #  println("Sampled $out from segment [$(e.cutpoints[i]), $(e.cutpoints[i+1])]")
    return out
end

function sample_envelop(e::Envelop)
    sample_envelop(Random.GLOBAL_RNG, e)
end

"""
    eval_envelop(e::Envelop, x::Float64)
Eval point a point `x` in the piecewise linear function defined by `e`. Necessary for evaluating
the density assigned to the point `x`.
"""
function eval_envelop(e::Envelop, x::Float64; log::Bool = false)
    # searchsortedfirst is the proper method for and ordered list
    pos = searchsortedfirst(e.cutpoints, x)
    if pos == 1 || pos == length(e.cutpoints) + 1
        return log ? - Inf : 0.0
    else
        pos = searchsortedfirst(e.cutpoints, x)
        logv = eval_line(e.lines[pos - 1], x)
        return log ? logv : exp(logv)
    end
end

# --------------------------------

"""
    RejectionSampler(f::Function, support::Tuple{Float64, Float64}[ ,δ::Float64])
    RejectionSampler(f::Function, support::Tuple{Float64, Float64}, init::Tuple{Float64, Float64})
An adaptive rejection sampler to obtain iid samples from a logconcave function `f`, supported in the
domain `support` = (support[1], support[2]). To create the object, two initial points `init = init[1], init[2]`
such that `logf'(init[1]) > 0` and `logf'(init[2]) < 0` are necessary. If they are not provided, the constructor
will perform a greedy search based on `δ`.

The argument `support` must be of the form `(-Inf, Inf), (-Inf, a), (b, Inf), (a,b)`, and it represent the
interval in which f has positive value, and zero elsewhere.

## Keyword arguments
- `max_segments::Int = 10` : max size of envelop, the rejection-rate is usually slow with a small number of segments
- `max_failed_factor::Float64 = 0.001`: level at which throw an error if one single sample has a rejection rate
    exceeding this value
"""
struct RejectionSampler
    objective::Function
    envelop::Envelop
    max_segments::Int
    max_failed_rate::Float64
    support::Tuple{Float64, Float64}
    use_secants::Bool
    # Constructor when initial points are provided
    RejectionSampler(
        f::Function,
        support::Tuple{Float64, Float64},
        init::Tuple{Union{Float64, Nothing}, Union{Float64, Nothing}};
        apply_log::Bool = true,
        max_segments::Int = 25,
        max_failed_rate::Float64 = 0.001,
        autograd::Bool = true,
        use_secants::Bool = false
    ) = begin
        # check support
        logf = apply_log ? (x -> log(f(x))) : f
        objective = autograd ? (x -> (logf(x), derivative(logf, x))) : logf
        @assert support[1] < support[2] "invalid support, not an interval"

        # check initial cutpoints
        x1, x2 = init
        isnothing(x1) && (x1 = support[1])
        isnothing(x2) && (x2 = support[2])

        @assert !isinf(x1) "initial left point can't be infinite if support unbounded below"
        @assert !isinf(x2) "initial right point can't be infinite if support unbounded above"
        @assert x1 < x2 "Initial points ($x1, $x2) must be ordered"

        # validate slopes
        lines = Line[]
        secant_knots = Float64[]
        secant_values = Float64[]

        if x1 != support[1]
            # println(objective(x1))
            y1, a1 = objective(x1)
            @assert !isinf(support[1]) || a1 > 0.0 "slope must be positive in left init when unbounded below"
            b1 = y1 - a1 * x1
            line1 = Line(a1, b1)
            push!(lines, line1)
            if use_secants
                push!(secant_knots, x1)
                push!(secant_values, y1)
            end
        end
        if x2 != support[2]
            y2, a2 = objective(x2)
            @assert !isinf(support[2]) || a2 < 0.0 "slope must be positive in right init when unbounded above"
            b2 = y2 - a2 * x2
            line2 = Line(a2, b2)
            push!(lines, line2)
            if use_secants
                push!(secant_knots, x2)
                push!(secant_values, y2)
            end
        end

        if isempty(lines)
             # special case when starting with support only bounds
            @assert !isinf(support[1]) && !isinf(support[2]) "when not providing initial points, support must be bounded"
            xmid = 0.5(support[1] + support[2])
            ymid, amid = objective(xmid)
            bmid = ymid - amid * xmid
            line12 = Line(amid, bmid)
            push!(lines, line12)
            if use_secants
                push!(secant_knots, xmid)
                push!(secant_values, ymid)
            end
        end
        envelop = Envelop(lines, support, secant_knots, secant_values)
        # println("initial envelop: $envelop")
        new(
            objective,
            envelop,
            max_segments,
            max_failed_rate,
            support,
            use_secants
        )
    end

    # Constructor for greedy search of starting points
    RejectionSampler(
            f::Function,
            support::Tuple{Float64, Float64},
            δ::Float64;
            search_range::Tuple{Float64, Float64} = (-10.0,10.0),
            apply_log::Bool = true,
            grad::Union{Function, Nothing} = nothing,
            autograd::Bool = true,
            kwargs...
    ) = begin
        logf = apply_log ? (x -> log(f(x))) : f
        objective = autograd ? (x -> (logf(x), derivative(logf, x))) : logf
        grid_lims = max(search_range[1], support[1]), min(search_range[2], support[2])
        grid = grid_lims[1]:δ:grid_lims[2]
        i1, i2 = findfirst(x -> objective(x)[2] > 0.0, grid), findfirst(objective -> f(x)[2] < 0., grid)
        @assert !isnothing(i1) &&  !isnothing(i2) "couldn't find initial points, please provide them or change `search_range`"
        x1, x2 = grid[i1], grid[i2]
        # pass autograd false since we already checked it
        RejectionSampler(objective, support, (x1, x2); apply_log=false, autograd=false, kwargs...)
    end
end

"""
    run_sampler!(rng::AbstractRNG, sampler::RejectionSampler, n::Int)
    run_sampler!(sampler::RejectionSampler, n::Int)
It draws `n` iid samples of the objective function of `sampler`, and at each iteration it adapts the envelop
of `sampler` by adding new segments to its envelop.
"""
function run_sampler!(rng::AbstractRNG, s::RejectionSampler, n::Int)
    i = 0
    failed, max_failed = 0, trunc(Int, n / s.max_failed_rate)
    out = zeros(n)
    env = s.envelop
    while i < n
        c = sample_envelop(rng, env)
        u = rand(rng)
        majorant = eval_envelop(env, c, log=true)
        if s.use_secants # squeeze test
            if u < exp(eval_secants(env, c, log=true) - majorant)
                i += 1
                out[i] = c
                continue
            end
        end
        failed += 1
        @assert failed < max_failed "max_failed_factor reached"
        y, a = s.objective(c)
        if u < exp(y - majorant) # evalf
            i += 1
            out[i] = c
            (i == n) && break
            # println(c)
            # error("early stop")
        end
        # add to segments
        if length(env) <= s.max_segments
            b = y - a * c
            pos = add_segment!(env, Line(a, b))
            if pos > 0 && s.use_secants
                insert!(env.secant_knots, pos, c)
                insert!(env.secant_values, pos, y)
            end
        end
    end
    out
end

function run_sampler!(s::RejectionSampler, n::Int)
    run_sampler!(Random.GLOBAL_RNG, s, n)
end

function eval_envelop(s::RejectionSampler, x::Float64, kwargs...)
    eval_envelop(s.envelop, x, kwargs...)
end
function eval_envelop(s::RejectionSampler, x::AbstractVector{Float64}, kwargs...)
    [eval_envelop(s.envelop, xᵢ, kwargs...) for xᵢ in x]
end
function eval_secants(s::RejectionSampler, x::Float64, kwargs...)
    eval_secants(s.envelop, x, kwargs...)
end
function eval_secants(s::RejectionSampler, x::AbstractVector{Float64}, kwargs...)
    [eval_secants(s.envelop, xᵢ, kwargs...) for xᵢ in x]
end


# ---
# title: 480. Sliding Window Median
# id: problem480
# author: Indigo
# date: 2021-02-03
# difficulty: Hard
# categories: Sliding Window
# link: <https://leetcode.com/problems/sliding-window-median/description/>
# hidden: true
# ---
#
# Median is the middle value in an ordered integer list. If the size of the list
# is even, there is no middle value. So the median is the mean of the two middle
# value.
#
# Examples:
#
# `[2,3,4]` , the median is `3`
#
# `[2,3]`, the median is `(2 + 3) / 2 = 2.5`
#
# Given an array _nums_ , there is a sliding window of size _k_ which is moving
# from the very left of the array to the very right. You can only see the _k_
# numbers in the window. Each time the sliding window moves right by one
# position. Your job is to output the median array for each window in the
# original array.
#
# For example,
# Given _nums_ = `[1,3,-1,-3,5,3,6,7]`, and _k_ = 3.
#
#
#
#     Window position                Median
#     ---------------               -----
#     [1  3  -1] -3  5  3  6  7       1
#      1 [3  -1  -3] 5  3  6  7       -1
#      1  3 [-1  -3  5] 3  6  7       -1
#      1  3  -1 [-3  5  3] 6  7       3
#      1  3  -1  -3 [5  3  6] 7       5
#      1  3  -1  -3  5 [3  6  7]      6
#
#
# Therefore, return the median sliding window as `[1,-1,-1,3,5,6]`.
#
# **Note:**
# You may assume `k` is always valid, ie: `k` is always smaller than input
# array's size for non-empty array.
# Answers within `10^-5` of the actual value will be accepted as correct.
#
#
## @lc code=start
using LeetCode

function median_sliding_window(nums::Vector{Int}, k::Int)
    tree = AVLTree{Tuple{Int, Int}}()
    res = Float64[]
    for i in 1:length(nums)
        push!(tree, (nums[i], i))
        if i ≥ k
            push!(res, (tree[k ÷ 2 + 1][1] + tree[(k - 1) ÷ 2 + 1][1]) / 2)
            delete!(tree, (nums[i - k + 1], i - k + 1))
        end
    end
    res
end
## @lc code=end


include("activate.jl")

import LinearAlgebra

n = Int(1e5)
k = 10000

xpi = rand(n);
total = sum(xpi);
const xp = xpi ./ total;

f(x) = LinearAlgebra.norm(x - xp)^2

function grad!(storage, x)
    storage .= 2 * (x - xp)
    return nothing
end

# better for memory consumption as we do coordinate-wise ops

function cf(x, xp)
    return @. LinearAlgebra.norm(x - xp)^2
end

# lmo = FrankWolfe.KSparseLMO(100, 1.0)
lmo = FrankWolfe.LpNormLMO{Float64,1}(1.0)
# lmo = FrankWolfe.ProbabilitySimplexOracle(1.0);
# lmo = FrankWolfe.UnitSimplexOracle(1.0);
x00 = FrankWolfe.compute_extreme_point(lmo, zeros(n))
# print(x0)

gradient = similar(x00)

FrankWolfe.benchmark_oracles(
    f,
    grad!,
    () -> randn(n),
    lmo;
    k=100,
)

# 1/t *can be* better than short step

println("\n==> Short Step rule - if you know L.\n")

x0 = copy(x00)
@time x, v, primal, dual_gap, trajectory_shortstep = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Shortstep(),
    L=2,
    print_iter=k / 10,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
);

println("\n==> Short Step rule with momentum - if you know L.\n")

x0 = copy(x00)

@time x, v, primal, dual_gap, trajectoryM = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Shortstep(),
    L=2,
    print_iter=k / 10,
    emphasis=FrankWolfe.blas,
    verbose=true,
    trajectory=true,
    momentum=0.9,
);

println("\n==> Adaptive if you do not know L.\n")

x0 = copy(x00)

@time x, v, primal, dual_gap, trajectory_adaptive = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Adaptive(),
    L=100,
    print_iter=k / 10,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
);

println("\n==> Agnostic if function is too expensive for adaptive.\n")

x0 = copy(x00)

@time x, v, primal, dual_gap, trajectory_agnostic = FrankWolfe.frank_wolfe(
    f,
    grad!,
    lmo,
    x0,
    max_iteration=k,
    line_search=FrankWolfe.Agnostic(),
    print_iter=k / 10,
    emphasis=FrankWolfe.memory,
    verbose=true,
    trajectory=true,
);

data = [trajectory_shortstep, trajectory_adaptive, trajectory_agnostic, trajectoryM]
label = ["short step" "adaptive" "agnostic" "momentum"]

FrankWolfe.plot_trajectories(data, label)


include("getdigit_n_xc.jl")
include("simpson_2.jl")
include("simpson_noparallel.jl")
include("myprintf.jl")
using Printf
using .GetDigit_n_xc
using .Simpson_2
using .Simpson_noparallel
using .MyPrintf

const DIGIT = 17
const MAXDH = BigFloat(1) / BigFloat(500)
const MAXN = 1000

function gauss(xc::BigFloat, dh::BigFloat)
    res = Simpson_2.simpson(x::BigFloat -> exp(- x * x), BigFloat(0), xc, dh)

    return BigFloat(4) * res * res
end

function gauss(xc::BigFloat, n::Int64)
    res = Simpson_noparallel.simpson(x::BigFloat -> exp(- x * x), BigFloat(0), xc, n)
    return BigFloat(4) * res * res
end

function main()
    xc = GetDigit_n_xc.getxc(gauss, MAXDH, DIGIT, true)
    n = GetDigit_n_xc.getn_even_linear(gauss, xc, MAXN, DIGIT, true)
    precision = GetDigit_n_xc.getdigit(gauss, xc, n, DIGIT, true)
    precision = GetDigit_n_xc.getdigit_2(gauss, xc, n, DIGIT, precision)

    setprecision(precision)
    MyPrintf.myprintf("π（計算値） = ", DIGIT, gauss(xc, n))
    MyPrintf.myprintf("π（厳密値） = ", DIGIT, BigFloat(π))
    @printf("xc = %.14f, n = %d, 計算%d桁で、%d桁求まりました\n", xc, n, precision, DIGIT)
end

main()


module FamaMacBeth

import StatsBase, StatsBase.coef, StatsBase.stderror
using Statistics: mean, std, cov

export fmb, coef, stderror, tstat, nobs, predict, residuals, r2, adjr2, group

# response object
struct FMBResp
    group
    coef::Array
    nobs::Vector{Int}
end

"""
    FMBModel(X,y,g,resp::FMBResp) <: StatsBase.RegressionModel
"""
mutable struct FMBModel <: StatsBase.RegressionModel
    X
    y
    g
    resp::Union{FMBResp, Nothing}

    function FMBModel(X,y,g,resp)
        # checks on input
        @assert size(X,1)==length(y) "y and X must have the same number of observations"
        @assert length(y)==length(g) "g and y must have the same number of observations"
        new(X,y,g,resp)
    end
end

"""
    FMBModel(X,y,g) -> FMBModel()
"""
FMBModel(X,y,g) = FMBModel(X,y,g,nothing)

"""
    group(m::FMBResp)
    group(m::FMBModel)

Returns the group labels used.
"""
group(m::FMBResp) = m.group
group(m::FMBModel) = group(m.resp)

"""
    coef(m::FMBResp) -> Vector
    coef(m::FMBModel) -> Vector

The estimated coefficients.
"""
coef(m::FMBResp) = mean(m.coef, dims=2)
coef(m::FMBModel) = coef(m.resp)

"""
    stderror(m::FMBResp) -> Vector
    stderror(m::FMBModel) -> Vector

The standard error around the estimated coefficients.
"""
stderror(m::FMBResp) = std(m.coef, dims=2)./sqrt(size(m.coef,2))
stderror(m::FMBModel) = stderror(m.resp)

"""
    vcov(m::FMBResp) -> Matrix
    vcov(m::FMBModel) -> Matrix

Variance-covariance matrix of the estimated coefficients.
"""
vcov(m::FMBResp) = cov(m.coef, dims=2)./size(m.coef,2)
vcov(m::FMBModel) = vcov(m.resp)

"""
    tstat(m::FMBModel) -> Vector
    tstat(m::FMBModel) -> Vector

The t-statistic per coefficient whether the coefficient equals zero or not.
"""
tstat(m::FMBResp) = coef(m)./stderror(m)
tstat(m::FMBModel) = tstat(m.resp)

"""
    nobs(m::FMBModel) -> Vector{Int}

Returns the number of observations, per group as identified by `g`. See also `group(m::FMBResp)`.
"""
nobs(m::FMBModel) = m.resp.nobs

"""
    predict(m::FMBModel) -> Vector
    predict(m::FMBModel, X) -> Vector

Predicts the outcome using the estimated coefficients and provided independent variables.
If independent variables are not provided, uses those from the model.
"""
predict(m::FMBModel, X) = X * coef(m)
predict(m::FMBModel) = predict(m, m.X)

"""
    residuals(m::FMBModel) -> Vector
"""
residuals(m::FMBModel) = m.y .- predict(m)

"""
    r2(m::FMBModel) -> Float64

The un-adjusted R2.
"""
function r2(m::FMBModel)
    SSE = sum(residuals(m).^2)
    SST = sum((m.y .- mean(m.y)).^2)
    1-SSE/SST
end

"""
    adjr2(m::FMBModel) -> Float64

The adjusted R2.
"""
function adjr2(m::FMBModel)
    n = length(m.y)
    p = size(m.X,2)
    1.0-(1.0-r2(m))*(n-1)/(n-p)
end

"""
    fmb(X, y, g) -> FMBModel()

Short-hand for fit(FMBModel(X,y,g))
"""
fmb(X::Array, y, g) = fit(FMBModel(X,y,g))

"""
    fit(m::FMBModel) -> FMBModel()

Runs a Fama-MacBeth (1973) panel regression by first conducting OLS of `y` on `X`
per group indicated by `g`, and subsequently computes the mean and standard error
using the resulting coefficients. An intercept is not added to `X`.
"""
function fit(m::FMBModel)
    # pre-allocate output
    ug = unique(m.g)
    coef = zeros(eltype(m.X), size(m.X,2), length(ug))
    nobs = zeros(Int64, length(ug))

    # per group: OLS
    for (i, gs) in enumerate(ug)
        select = m.g.==gs
        coef[:,i] = m.X[select,:]\m.y[select]
        nobs[i] = sum(select)
    end

    m.resp = FMBResp(ug, coef, nobs)
    return m
end

end


#=
Reconstruct "brain" from Fourier measurements.
The measurements are generated with the Matlab files from
http://bigwww.epfl.ch/algorithms/mriphantom
=#

# ------------------------------------------------------------
# Load data

using JLD
data_file = joinpath(dirname(@__FILE__), "brain512.jld")
F = load( data_file )
f = F["freqs"]

# Sampling locations
M = size( f )
J = Int( log2(M[1]) ) - 1

# ------------------------------------------------------------
# Compute reconstruction

using GeneralizedSampling

xi = GeneralizedSampling.grid(M)
T = Freq2Wave( xi, "db4", J )
w = T \ f

# ------------------------------------------------------------
# Plot reconstruction

using IntervalWavelets, Winston

y = weval( abs(w), "db4", 10 )

miny = minimum(y)
maxy = maximum(y)
imagesc( y, (miny,maxy) )


# !((A & B) | (!A & !B))
#
# A B | A & B
# T T | T
# T F | F
# F T | F
# F F | F
#
# A B | !A & !B
# T T | F
# T F | F
# F T | F
# F F | T
#
# neural net layer 2[1] is going to be (A & B)
# neural net layer 2[2] is going to be (!A & !B)
#
# 2[1]: -7.5*bias + 5.0*A + 5.0*B
# 2[2]: 7.5*bias - 10.0*A - 10.0*B
#
# neural net layer 3[1] is going to be !(3[1] | 3[2])
# 3[1]: 5.0*bias + -10.0*A + -10.0*B
#
# A B | !(A | B)
# T T | F
# T F | F
# F T | F
# F F | T

#generate a multilayer perceptron
xornet = GenML.MLP.MultilayerPerceptron{Float64, (2,2,1)}()

#hand-written transition matrices.
xornet.layers[1].bias[:] = [-7.5, 7.5]
xornet.layers[1].transition[:] = [5.0 5.0; -10.0 -10.0]
xornet.layers[2].bias[:] = [5.0]
xornet.layers[2].transition[:] = [-10.0 -10.0]

@test xornet([true, true])[1] < 0.5
@test xornet([true, false])[1] > 0.5
@test xornet([false, true])[1] > 0.5
@test xornet([false, false])[1] < 0.5

#testing the allocations for evaluation.
dataset = rand(Bool, 2, 500)
results = Array{Float64}(1, 500)

GenML.evaluate!(results, xornet, dataset)
@time GenML.evaluate!(results, xornet, dataset)


module Day12

using Advent: save_input

# - Originally used tuples as coordinates but complex numbers are faster.
# - Better performance with ComplexF64 than Complex{Int64}.
# - Slight speedup but higher memory usage without @views when indexing strings.

function solve_a(filename)
    coord = 0.0 + 0im
    dir = 1.0 + 0im
    for line in eachline(filename)
        inst, arg = Symbol(line[1]), parse(Int, line[2:end])

        inst == :N && (coord += arg * im)
        inst == :S && (coord -= arg * im)
        inst == :E && (coord += arg)
        inst == :W && (coord -= arg)
        inst == :L && (dir *= im^div(arg, 90))
        inst == :R && (dir *= (-im)^div(arg, 90))
        inst == :F && (coord += dir * arg)
    end

    sum(abs, reim(coord)) |> Int
end

function solve_b(filename)
    coord = 0.0 + 0im
    waypoint = 10.0 + 1im
    for line in eachline(filename)
        inst, arg = Symbol(line[1]), parse(Int, line[2:end])

        inst == :N && (waypoint += arg * im)
        inst == :S && (waypoint -= arg * im)
        inst == :E && (waypoint += arg)
        inst == :W && (waypoint -= arg)
        inst == :L && (waypoint *= im^div(arg, 90))
        inst == :R && (waypoint *= (-im)^div(arg, 90))
        inst == :F && (coord += waypoint * arg)
    end

    sum(abs, reim(coord)) |> Int
end

solve(filename = save_input(2020, 12)) = solve_a(filename), solve_b(filename)

end


function nlp_mi_007_020(
    optimizer,
    objective_tol,
    primal_tol,
    dual_tol,
    termination_target = TERMINATION_TARGET_LOCAL,
    primal_target = PRIMAL_TARGET_LOCAL,
)
    # Test Goals:
    # - infeasible model, due to integrality

    model = Model(optimizer)

    @variable(model, -2 <= x <= 3, Int)
    @variable(model, y, Bin)

    @NLconstraint(model, (x - 0.5)^2 + (4 * y - 2)^2 <= 3)

    optimize!(model)

    return check_status(
        model,
        INFEASIBLE_PROBLEM,
        termination_target,
        primal_target,
    )
end


######### Stan program example  ###########

using StanSample, Test

eightschools ="
data {
  int<lower=0> J; // number of schools
  real y[J]; // estimated treatment effects
  real<lower=0> sigma[J]; // s.e. of effect estimates
}
parameters {
  real mu;
  real<lower=0> tau;
  real eta[J];
}
transformed parameters {
  real theta[J];
  for (j in 1:J)
    theta[j] <- mu + tau * eta[j];
}
model {
  eta ~ normal(0, 1);
  y ~ normal(theta, sigma);
}
"

schools8data = Dict("J" => 8,
  "y" => [28,  8, -3,  7, -1,  1, 18, 12],
  "sigma" => [15, 10, 16, 11,  9, 11, 10, 18],
  "tau" => 25
)

sm = SampleModel("schools8", eightschools)

rc = stan_sample(sm, data=schools8data)

if success(rc)
  samples = read_samples(sm)

  df = read_summary(sm)
  @test df[df.parameters .== :mu, :mean][1] ≈ 7.64 rtol=0.5
  @test df[df.parameters .== :tau, :mean][1] ≈ 6.50 rtol=0.5
  @test df[df.parameters .== Symbol("theta[1]"), :mean][1] ≈ 11.1 rtol=0.5
  @test df[df.parameters .== Symbol("theta[8]"), :mean][1] ≈ 8.3 rtol=0.5
  @test df[df.parameters .== Symbol("eta[1]"), :mean][1] ≈ 0.4 rtol=0.5
  @test df[df.parameters .== Symbol("eta[8]"), :mean][1] ≈ 0.066 rtol=0.5

end


"""
    MattssonAlmquistCarpenter2014Extended()

Coefficients of the extended SBP operators given in
- Mattsson, Almquist, Carpenter (2014)
  Optimal diagonal-norm SBP operators.
  Journal of Computational Physics 264, pp. 91-111.
"""
struct MattssonAlmquistCarpenter2014Extended <: SourceOfCoefficients end

function Base.show(io::IO, source::MattssonAlmquistCarpenter2014Extended)
    if get(io, :compact, false)
        summary(io, source)
    else
        print(io,
            "Mattsson, Almquist, Carpenter (2014) \n",
            "  Optimal diagonal-norm SBP operators. \n",
            "  Journal of Computational Physics 264, pp. 91-111.")
    end
end

function first_derivative_coefficients(source::MattssonAlmquistCarpenter2014Extended,
                                       order::Int, T=Float64, mode=FastMode())
    if order == 2
        left_boundary = (
            # d1
            DerivativeCoefficientRow{T,1,3}(SVector(T(-6//5),
                                                    T(7//5),
                                                    T(-1//5) )),
            # d2
            DerivativeCoefficientRow{T,1,3}(SVector(T(-1//2),
                                                    T(0),
                                                    T(1//2) )),
            # d3
            DerivativeCoefficientRow{T,1,4}(SVector(T(1//11),
                                                    T(-7//11),
                                                    T(0),
                                                    T(6//11) )),
        )
        right_boundary = .- left_boundary
        upper_coef = SVector(T(1//2))
        central_coef = zero(T)
        lower_coef = -upper_coef
        left_weights = SVector( T(5//12),
                                T(7//6),
                                T(11//12) )
        right_weights = left_weights
        left_boundary_derivatives = Tuple{}()
        right_boundary_derivatives = left_boundary_derivatives

        DerivativeCoefficients(left_boundary, right_boundary,
                                left_boundary_derivatives, right_boundary_derivatives,
                                lower_coef, central_coef, upper_coef,
                                left_weights, right_weights, mode, 1, order, source)
    elseif order == 4
        left_boundary = (
            # d1
            DerivativeCoefficientRow{T,1,6}(SVector(T(-800//511),
                                                    T(1744244//869211),
                                                    T(-312391//1738422),
                                                    T(-100109//289737),
                                                    T(54107//1738422),
                                                    T(6575//124173) )),
            # d2
            DerivativeCoefficientRow{T,1,6}(SVector(T(-1744244//3752595),
                                                    T(0),
                                                    T(238831//750519),
                                                    T(290357//1501038),
                                                    T(-625//35739),
                                                    T(-220357//7505190) )),
            # d3
            DerivativeCoefficientRow{T,1,6}(SVector(T(312391//3511620),
                                                    T(-238831//351162),
                                                    T(0),
                                                    T(17335//25083),
                                                    T(-74827//702324),
                                                    T(3859//585270) )),
            # d4
            DerivativeCoefficientRow{T,1,6}(SVector(T(100109//1109430),
                                                    T(-290357//1331316),
                                                    T(-17335//47547),
                                                    T(0),
                                                    T(342799//665658),
                                                    T(-149959//6656580) )),
            # d5
            DerivativeCoefficientRow{T,1,7}(SVector(T(-54107//5025510),
                                                    T(625//23931),
                                                    T(74827//1005102),
                                                    T(-342799//502551),
                                                    T(0),
                                                    T(1715156//2512755),
                                                    T(-240//2659) )),
            # d6
            DerivativeCoefficientRow{T,1,8}(SVector(T(-6575//392877),
                                                    T(220357//5500278),
                                                    T(-3859//916713),
                                                    T(149959//5500278),
                                                    T(-1715156//2750139),
                                                    T(0),
                                                    T(9600//14551),
                                                    T(-1200//14551) )),
        )
        right_boundary = .- left_boundary
        upper_coef = SVector(T(2//3), T(-1//12))
        central_coef = zero(T)
        lower_coef = -upper_coef
        left_weights = SVector( T(511//1600),
                                T(3971//2880),
                                T(929//1440),
                                T(587//480),
                                T(2659//2880),
                                T(14551//14400) )
        right_weights = left_weights
        left_boundary_derivatives = Tuple{}()
        right_boundary_derivatives = left_boundary_derivatives

        DerivativeCoefficients(left_boundary, right_boundary,
                                left_boundary_derivatives, right_boundary_derivatives,
                                lower_coef, central_coef, upper_coef,
                                left_weights, right_weights, mode, 1, order, source)
    elseif order == 6
        left_boundary = (
            # d1
            DerivativeCoefficientRow{T,1,8}(SVector(T(-12700800//7497391),
                                                    T(84512094//37486955),
                                                    T(-12926907//149947820),
                                                    T(-1378838//2044743),
                                                    T(-2874//40093),
                                                    T(2857680//7497391),
                                                    T(-39279943//449843460),
                                                    T(-825552//37486955) )),
            # d2
            DerivativeCoefficientRow{T,1,8}(SVector(T(-84512094//193589725),
                                                    T(0),
                                                    T(56626691//464615340),
                                                    T(97579603//232307670),
                                                    T(3642717//77435890),
                                                    T(-246914711//1161538350),
                                                    T(23380423//464615340),
                                                    T(54432//5531135) )),
            # d3
            DerivativeCoefficientRow{T,1,8}(SVector(T(205189//2106340),
                                                    T(-56626691//79619652),
                                                    T(0),
                                                    T(969659//1153908),
                                                    T(-30244385//79619652),
                                                    T(9737351//44233140),
                                                    T(-508309//6634971),
                                                    T(780937//79619652) )),
            # d4
            DerivativeCoefficientRow{T,1,8}(SVector(T(15167218//136593975),
                                                    T(-97579603//273187950),
                                                    T(-22302157//182125300),
                                                    T(0),
                                                    T(8120941//54637590),
                                                    T(13871884//45531325),
                                                    T(-42123859//546375900),
                                                    T(-2075677//273187950) )),
            # d5
            DerivativeCoefficientRow{T,1,8}(SVector(T(537438//10627085),
                                                    T(-3642717//21254170),
                                                    T(6048877//25505004),
                                                    T(-8120941//12752502),
                                                    T(0),
                                                    T(29205107//63762510),
                                                    T(15263287//127525020),
                                                    T(-243381//4250834) )),
            # d6
            DerivativeCoefficientRow{T,1,9}(SVector(T(-45360//513973),
                                                    T(246914711//971408970),
                                                    T(-9737351//215868660),
                                                    T(-13871884//32380299),
                                                    T(-29205107//194281794),
                                                    T(0),
                                                    T(321139631//647605980),
                                                    T(-49594423//971408970),
                                                    T(6720//513973) )),
            # d7
            DerivativeCoefficientRow{T,1,10}(SVector(T(39279943//1409459100),
                                                    T(-23380423//281891820),
                                                    T(508309//23490985),
                                                    T(42123859//281891820),
                                                    T(-15263287//281891820),
                                                    T(-321139631//469819700),
                                                    T(0),
                                                    T(30841499//40270260),
                                                    T(-108864//671171),
                                                    T(12096//671171) )),
            # d8
            DerivativeCoefficientRow{T,1,11}(SVector(T(825552//128159995),
                                                    T(-381024//25631999),
                                                    T(-780937//307583988),
                                                    T(2075677//153791994),
                                                    T(1216905//51263998),
                                                    T(49594423//768959970),
                                                    T(-215890493//307583988),
                                                    T(0),
                                                    T(19051200//25631999),
                                                    T(-3810240//25631999),
                                                    T(423360//25631999) )),
        )
        right_boundary = .- left_boundary
        upper_coef = SVector(T(3//4), T(-3//20), T(1//60))
        central_coef = zero(T)
        lower_coef = -upper_coef
        left_weights = SVector( T(7497391//25401600),
                                T(1106227//725760),
                                T(105317//403200),
                                T(260179//145152),
                                T(303631//725760),
                                T(513973//403200),
                                T(671171//725760),
                                T(25631999//25401600) )
        right_weights = left_weights
        left_boundary_derivatives = Tuple{}()
        right_boundary_derivatives = left_boundary_derivatives

        DerivativeCoefficients(left_boundary, right_boundary,
                                left_boundary_derivatives, right_boundary_derivatives,
                                lower_coef, central_coef, upper_coef,
                                left_weights, right_weights, mode, 1, order, source)
    else
        throw(ArgumentError("Order $order not implemented/derived."))
    end
end


#=
Copyright (c) 2018-2022 Chris Coey, Lea Kapelevich, and contributors

This Julia package Hypatia.jl is released under the MIT license; see LICENSE
file in the root directory or at https://github.com/chriscoey/Hypatia.jl

find maximum volume hypercube with edges parallel to the axes inside a polyhedron
or an ellipsoid defined with l_1, l_infty, or l_2 ball constraints
=#

struct MaxVolumeJuMP{T <: Real} <: ExampleInstanceJuMP{T}
    n::Int
    epinormeucl_constr::Bool # add an L2 ball constraint
    epinorminf_constrs::Bool # add L1 and Linfty ball constraints
end

function build(inst::MaxVolumeJuMP{T}) where {T <: Float64}
    @assert xor(inst.epinormeucl_constr, inst.epinorminf_constrs)
    n = inst.n

    A = randn(n, n)
    # ensure there will be a feasible solution
    x = randn(n)
    A = A * A' + 10I
    gamma = norm(A * x) / sqrt(n)

    model = JuMP.Model()
    JuMP.@variable(model, t)
    JuMP.@variable(model, x[1:n])
    JuMP.@objective(model, Max, t)
    JuMP.@constraint(model, vcat(t, x) in MOI.GeometricMeanCone(n + 1))

    if inst.epinormeucl_constr
        JuMP.@constraint(model, vcat(gamma, A * x) in JuMP.SecondOrderCone())
    end
    if inst.epinorminf_constrs
        JuMP.@constraint(model, vcat(gamma, A * x) in MOI.NormInfinityCone(n + 1))
        JuMP.@constraint(model, vcat(sqrt(n) * gamma, A * x) in
            MOI.NormOneCone(n + 1))
    end

    return model
end


n_points = 10
tau = 1

@testset "Refining triangulation" begin
    embedding = embedding_example(n_points, 3, tau)

    triang_points, simplex_indices = triangulate(embedding)

    # How many simplices are there in the triangulation?
    n_orig = size(simplex_indices, 1)
    simplices = [triang_points[simplex_indices[i, :], :] for i in 1:n_orig]

    @testset "Triangulation" begin
        # Create an 3D embedding to triangulate.
        @test isa(triang_points, Array{Float64, 2})
        @test isa(simplex_indices, Array{Int, 2})
    end

    # Rules for splitting simplices in 3D with size reducing factor 2
    splitting_rules = simplicial_subdivision(2, 3)

    @testset "Simplex division rules" begin
        @test isa(splitting_rules[1], Array{Int64, 2})
        @test isa(splitting_rules[2], Array{Int64, 2})
    end

    @testset "Refine triangulation" begin
        @testset "E = $E" for E in 2:4
            @testset "k = $k" for k in 1:3
                splitting_rules = simplicial_subdivision(k, E)
                embedding = embedding_example(n_points, E, 1)
                triang_points, simplex_indices = triangulate(embedding)

                # How many simplices did we get?
                n_orig = size(simplex_indices, 1)

                simplices = [triang_points[simplex_indices[i, :], :] for i in 1:n_orig]
                refined_triangulation = refine_triangulation(triang_points, simplex_indices, collect(1:n_orig), k)

                # How many new simplices did we get?
                n_refined = size(refined_triangulation[2], 1)
                refined_simplices = [refined_triangulation[1][refined_triangulation[2][i, :], :]
                                        for i in 1:n_refined]

                refined_vertices = refined_triangulation[1]
                refined_simplex_indices = refined_triangulation[2]

                @testset "Arrays are of right dimensions and types" begin
                    @test isa(refined_vertices, Array{Float64, 2})
                    @test isa(refined_simplex_indices, Array{Int, 2})
                end
                # Compare volume of original triangulation with the refined triangulation

                @testset "Volume is preserved" begin
                    triang_vols = [abs(det(hcat(simplices[i], ones(E + 1)))) for i in 1:n_orig]
                    refined_triang_vols = [abs(det(hcat(refined_simplices[i], ones(E + 1)))) for i in 1:n_refined]
                    @test sum(triang_vols) ≈ sum(refined_triang_vols) atol = 1/10^7
                end
            end
        end
    end

    @testset "Simplex volumes are split by factor of k" begin

        @testset "k = $k" for k in 1:3
            @testset "E = $E" for E in 2:7
                canonical_simplex_vertices = zeros(E + 1, E)
                canonical_simplex_vertices[2:(E + 1), :] = eye(E)

                # Refine the canonical simplex
                simplex_indices = zeros(Int, 1, E + 1)
                simplex_indices[1, :] = collect(1:E+1)

                # Generic splitting rules for this combination of dimension and splitting
                # factor.
                splitting_rules = simplicial_subdivision(k, E)

                refined = refine_triangulation(canonical_simplex_vertices, simplex_indices, [1], k)

                # How many new simplices did we get?
                n = size(refined[2], 1)
                refined_simplices = [refined[1][refined[2][i, :], :] for i in 1:n]

                canonical_simplex_vol = abs(det(
                    hcat(canonical_simplex_vertices, ones(E + 1))
                    ))

                volumes_subsimplices = [abs(det(hcat(refined_simplices[i], ones(E + 1))))
                                                                            for i in 1:n]

                @test canonical_simplex_vol ≈ sum(volumes_subsimplices) atol = 1/10^7
            end
        end
    end
end

@testset "Centroids and radii of triangulation" begin
    @testset "E = $E" for E in 2:4
        @testset "k = $k" for k in 1:4
            splitting_rules = simplicial_subdivision(k, E)
            embedding = embedding_example(n_points, E, 1)
            triang_points, simplex_indices = triangulate(embedding)

            centroids, radii = centroids_radii2(triang_points, simplex_indices)
            @test isa(centroids, Array{Float64, 2})
            @test isa(radii, Array{Float64, 1})
        end
    end
end


using DynamicPolynomials
using MultivariateMoments
using Distributions
using PDMats

const MM=MultivariateMoments

include("../src/BeurlingLasso.jl")
using .BeurlingLasso

params = [[-0.8], [0.7]]
weights = [0.3, 0.7]

domain = reshape([-1, 1], (1, 2))

natoms = length(params)
ndims = length(params[1])

@polyvar x[1:ndims]
μ0 = AtomicMeasure(x, [WeightedDiracMeasure(params[i], weights[i]) for i=1:natoms])

gmm = MixtureModel(Normal[Normal(param[1], 0.5) for param in params],
                   weights)

λ = 1e-3
σ = 0.5

freq_distrib = Normal(0, 2.0)
nfreqs = 20
freqs = rand(freq_distrib, nfreqs)

function φ(x, n)
   μ = x[1]
   ω = freqs[n]
   exp(im*ω*μ - σ^2*ω^2/2)
end

y = [BeurlingLasso.expectation(μ0, x -> φ(x, n)) for n=1:nfreqs]

# function θ(t, n)
#    ω = freqs[n]
#    exp(im*ω*t)
# end

# nsamples = 1000
# samples = rand(gmm, nsamples)
# y = [sum(θ(t, n) for t in samples) / nsamples for n=1:nfreqs]

gridsize = 1000
niter = 10

prob = blasso(y, φ, λ, domain)
solver = sfwsolver(niter, gridsize)

solve!(prob, solver)


using QuantumCumulants
using SymbolicUtils
using Test

@testset "parameters" begin

hf = FockSpace(:cavity)
ha = NLevelSpace(:atom,(:g,:e))
h = hf⊗ha

a = Destroy(h,:a)

@cnumbers p q
# @test p*q isa NumberTerm
@test p*q*a isa QuantumCumulants.QTerm

σ = Transition(h,:σ,:g,:e)
σee = Transition(h,:σ,:e,:e)

# JC
@cnumbers ωc ωa g
H = ωc*a'*a + ωa*σ'*σ + g*(a'*σ + σ'*a)

da = commutator(1im*H,a)
@test iszero(simplify(da - ((-1im*g)*σ + (-1im*ωc)*a)))
ds = commutator(1im*H,σ)
@test iszero(simplify(ds - ((-1im*g)*a + (-1im*ωa)*σ + (2im*g)*a*σee)))
dn = commutator(1im*H,a'*a)
@test iszero(simplify(dn - ((-1im*g)*a'*σ + (1im*g)*a*σ')))

he = meanfield([a,σ,a'*a],H)
@test iszero(simplify(he.operator_equations[1].rhs - da))
@test iszero(simplify(he.operator_equations[2].rhs - ds))
@test iszero(simplify(he.operator_equations[3].rhs - dn))

# Lossy JC
@cnumbers κ γ
J = [a,σ]
he_diss = meanfield([a,σ,σ'*σ],H,J;rates=[κ,γ])

@test iszero(simplify(he_diss.operator_equations[1].rhs - ((-1im*ωc - 0.5κ)*a + (-1im*g)*σ); expand=true))
@test iszero(simplify(he_diss.operator_equations[2].rhs - ((-1im*g)*a + (-1im*ωa - 0.5γ)*σ + (2im*g)*a*σee); expand=true))
@test iszero(simplify(he_diss.operator_equations[3].rhs - ((-γ)*σee + (1im*g)*a'*σ + (-1im*g)*a*σ')))

# Single-atom laser
@cnumbers ν
J = [a,σ,σ']
he_laser = meanfield([a'*a,σ'*σ,a'*σ],H,J;rates=[κ,γ,ν])

@test iszero(simplify(he_laser.operator_equations[1].rhs - ((-κ)*a'*a + (-1im*g)*a'*σ + (1im*g)*a*σ')))
@test iszero(simplify(he_laser.operator_equations[2].rhs - (ν + (-ν - γ)*σee + (1im*g)*a'*σ + (-1im*g)*a*σ'); expand=true))
# @test iszero(simplify(he_laser.operator_equations[3].rhs - ((1im*g)*σee + (-1im*g)*a'*a + (1im*ωc + -1im*ωa - 0.5*(κ + γ + ν))*a'*σ + (2im*g)*a'*a*σee); expand=true))

# Test sorting of longer term
@cnumbers λ Γ N
b = Destroy(h, :b)
yy = 1im*σ*b*(N-1)*λ*Γ
# @test isequal(simplify(yy), QuantumCumulants.QMul(im*Γ*λ*(N-1), [b, σ]))

# Test numbers in commutator
@cnumbers Δ
h = NLevelSpace(:a, 2)
s(i,j) = Transition(h, :s, i, j)
H = Δ*s(2,2) - Δ*s(1,1)
H = simplify(H)
@test iszero(simplify(meanfield(s(1,2), H).operator_equations[1].rhs  + 2im*Δ*s(1,2)))

end # testset


using Interpolations,Plots

t = 0:.1:1
x = sin.(2π*t)
y = cos.(2π*t)
A = hcat(x,y)

itp = scale(interpolate(A, (BSpline(Cubic(Natural())), NoInterp()), OnGrid()), t, 1:2)

tfine = 0:.01:1
xs, ys = [itp(t,1) for t in tfine], [itp(t,2) for t in tfine]

scatter(x, y, label="knots")
plot!(xs, ys, label="spline")


using ConvDiffMIPDECO
using jInv.Mesh
using jInv.ForwardShare
using jInv.InverseSolve
using jInv.LinearSolvers
using MUMPSjInv
using LinearAlgebra
using MAT

# read results from l-curve
dataset    = "Sources3D"
reg        = "wTVReg"
m          = [96 48 48]
noiseLevel = 0.1

filename= joinpath(dirname(pathof(ConvDiffMIPDECO)),"..","examples","$(dataset).mat")
data = matread(filename)
domain = data["domain"]
v      = data["v"]
sig    = data["sig"]
dtrue  = data["dtrue"]
rec    = data["rec"];
mf     = round.(Int64,data["m"])

M = getRegularMesh(domain,m)

resFile = joinpath(dirname(pathof(ConvDiffMIPDECO)),"..","examples","$(dataset)-$(reg)-noise-$(noiseLevel)-$(m[1])x$(m[2])x$(m[3]).mat")
res = matread(resFile)
dobs = res["dobs"]

idx = 12;
alph = res["alphas"][idx]

println("solve using alpha = $(alph)")

noiseLevel = norm(dobs-dtrue)/norm(dtrue)
println("estimated noise level: $noiseLevel")
if noiseLevel > 0.5
    error("noise level too large, double check that data was loaded correctly. ")
end

# build inverse problem
Mfine = getRegularMesh(domain,vec(data["m"]));
x1c,x2c,x3c = getCellCenteredAxes(Mfine)
rec3D = [kron(ones(length(x3c)),rec) kron(x3c,ones(size(rec,1)))]
# build linear interpolation matrix from nodes to receiver locations
x1,x2,x3 = getNodalAxes(M)
P = interpmat(x1,x2,x3, rec3D);

pFor = getConvDiffFEMParam(M,v=v,sig=sig,P=P,Ainv=getMUMPSsolver());

## configure misfit
Wt         = ones(size(dobs))/sqrt(mf[3])
sigback    = 0.0
pMis       = getMisfitParam(pFor,Wt,dobs,SSDFun)

## Configure regularization
mref       = zeros(M.nc)
reg        = (m,mr,M,I=1.0) -> wTVReg(m,mr,M,eps=1e-8)
# reg        = (m,mr,M,I=1.0) -> wTVReg(m,mr,M,eps=1e-8)

## Configure optimization
maxIter    = 50
minUpdate  = 1e-3
HesPrec    = getSSORRegularizationPreconditioner(1.0,1e-15,50)
cgit       = 5
pcgTol     = 1e-1
modFun     = identityMod
boundsLow  = 0*ones(M.nc)
boundsHigh = 1*ones(M.nc)
maxStep	   = 0.1*maximum(boundsHigh)

## store the configuration
pInv       = getInverseParam(M,modFun,reg,alph,mref,
                             boundsLow,boundsHigh,maxStep=maxStep,
                            pcgMaxIter=cgit,pcgTol=pcgTol,minUpdate=minUpdate,maxIter=maxIter,
                            HesPrec=HesPrec);

mc = mref.+0.1

println("alpha:\t\t\t\t$(alph)")
println("no. cells:\t\t\t$(length(mc))")
println("no. nodes:\t\t\t$(size(P,2))")
println("no. measurements:\t$(size(P,1))")
runtimeRelaxed = @elapsed begin
	mc,Dc,flag,HisRelaxed = projGNCG(mc,pInv,pMis)
	uc = pFor.Fields;          # u variables
	wr = pInv.modelfun(mc)[1]; # w variables
end
println("runtime:\t$runtimeRelaxed")

# get number of PDE solves
nCGIter = cgit * length(HisRelaxed.hisLinSol)
nGNIter = count(HisRelaxed.Jc.>0)
println("no. PDE solves:\t$(pFor.Ainv.nSolve)")
println("time PDE factor:\t$(pFor.Ainv.facTime)")
println("time PDE solves:\t$(pFor.Ainv.solveTime)")

FieldsRelaxed        = uc
SourcesRelaxed       = wr
PredictedDataRelaxed = Dc

println("err(Fields) = $((norm(FieldsRelaxed - res["Fields"][:,idx]))/norm(res["Fields"][:,idx]))")
println("err(Sources) = $((norm(SourcesRelaxed - res["Sources"][:,idx]))/norm(res["Sources"][:,idx]))")
println("err(PredictedData) = $((norm(PredictedDataRelaxed - res["PredictedData"][:,idx]))/norm(res["PredictedData"][:,idx]))")

res["FieldsRelaxed"] = FieldsRelaxed
res["SourcesRelaxed"] = SourcesRelaxed
res["PredictedDataRelaxed"] = PredictedDataRelaxed
res["runtimeRelaxed"] = runtimeRelaxed
res["alphaRelaxed"] = alph
res["HisRelaxed"] = HisRelaxed

matwrite(resFile,res)


function train_layer_1_sigmoid_fast(neurons,vars,obs,n_per_part,inds_all,x,y)

    a_all=Vector{Vector{Float64}}()
    ooops=0
    for i=1:neurons
        # if i in 1:10:neurons println(Dates.format(now(), "HH:MM:SS")," Calculating weights w for ",i," neuron, of ",neurons," Total.") end
        try
            atmp=[ones(n_per_part[i+1]-n_per_part[i]) x[inds_all[n_per_part[i]+1:n_per_part[i+1]],:]]\isigm1.(y[inds_all[n_per_part[i]+1:n_per_part[i+1]]])
            obs1=length(inds_all[n_per_part[i]+1:n_per_part[i+1]])
            if sum(isnan.(atmp))==0 && sum(isinf.(atmp))==0 && sum(abs.(atmp[2:end]))>0
                push!(a_all,atmp)
            else
                ooops+=1
            end
        catch ex
            ooops+=1
            println("problem neuron=",i," total ooops=",ooops," ex=",ex)
            push!(a_all,zeros(vars+1))
        end
    end
    layer1=sigm1.([ones(obs) x]*reduce(hcat, a_all))
    mat1=inv([layer1 ones(obs)]'*[layer1 ones(obs)])
    # println(Dates.format(now(), "HH:MM:SS")," Solving system (",obs," by ",size(layer1,2)+1,") for weights v in the output Layer")
    a_layer1=mat1*[layer1 ones(obs)]'*y
    # println(Dates.format(now(), "HH:MM:SS")," All weights computed.")
    return a_all,a_layer1,layer1,mat1

end


using ApproxFun
using EffectiveWaves

T = Float64
θin = 0.13
k=1.; ho = 2
medium = Medium(1.0,1.0+0.0im)
ω = real(k*medium.c)
specie = Specie(ρ=0.1,r=0.1, c=0.5, volfrac=0.1)

# Guess a course mesh
k_eff0 = wavenumber_low_volfrac(ω, medium, [specie]; tol = 1e-12)
max_x = 10 .* k/imag(k_eff0)
x = 0.0:0.1:max_x

wave0 = EffectiveWave(ω, k_eff0, medium, [specie]; θin = θin, hankel_order = ho, tol=1e-8)
wave_avg0 = AverageWave(x, wave0)

R_eff = reflection_coefficient(ω, wave0, medium, [specie]; θin = θin, hankel_order = ho)
R = reflection_coefficient(ω, wave_avg0, medium, specie; θin = θin)

amps = wave_avg0;
# k = ω/medium.c
M = amps.hankel_order
σ = trap_scheme(amps.x);

R_ms = T(2)*specie.num_density/(cos(θin)*k^2)*
[
    sum(
        im^T(m)*exp(-im*θin*m)*amps.amplitudes[j,m+M+1,1]*exp(im*amps.x[j]*cos(θin))*σ[j]
    for j in eachindex(amps.x))
for m = -M:M];
sum(R_ms) - R

x = 0.:0.02:10

# function space
S = Chebyshev(minimum(x)..maximum(x));
n1 = length(x); m1 = Int(round(n*0.4));
V = Array{Float64}(n1,m1); # Vandermonde matrix
for k = 1:m1
    V[:,k] = Fun(S,[zeros(k-1);1]).(x)
end

v = exp(x.*(-1.0 + 1.0im))
σ = trap_scheme(x)
sum(σ[i]*v[i] for i in eachindex(x))

f = Fun(S,V\v)
sum(Fun(S,V\v))
1/(1.0+1.0im)

R_funs = map(-M:M) do m
    v = [
        im^T(m)*exp(-im*θin*m)*amps.amplitudes[j,m+M+1,1]*exp(im*amps.x[j]*cos(θin))
    for j in eachindex(amps.x)]
    sum(Fun(S,V\v))
end

R_fun = sum(R_funs)*T(2)*specie.num_density/(cos(θin)*k^2)

    R_m = sum(Fun(S,V\v));
sum(R_m)


module mna

using Reduce
@force using Reduce.Algebra

export nodes, volts, analyze, transferFunction

"""
    nodes(netlist::Dict)::Array{Int64,1}

Takes a netlist dictionary and returns a list of all
nodes in the netlist.
"""
function nodes(netlist::Dict)::Array{Int64,1}
    return ( values(netlist)
                 |> x -> reduce((n,p) ->  [n...,p[2:(end-1)]...], x, init=[])
                 |> unique
                 |> x -> filter((n) -> n != 0, x))
end

"""
    volts(netlist::Dict)::Array{Any,1}

Takes a netlist dictionary and returns all the voltages
in the netlist.
"""
function volts(netlist::Dict)::Array{Any,1}
    return ( keys(netlist)
           |> x -> filter( c -> in( first(netlist[c])
                                  , [:V :VCVS :VCCS :CCVS :CCCS] )
                         , x)
           |> x -> identity.(x) )
end

"""
    systemMatrix(netlist::Dict, ω::Any)::Tuple

Takes a netlist and a angle freuqency and returns the
system matrix, as well as the y vector.
"""
function systemMatrix(netlist::Dict, ω::Any)::Tuple
    m = length(nodes(netlist));
    n = length(volts(netlist));
    M = RExpr(zeros(m+n , m+n)) |> parse |> Reduce.mat;
    y = RExpr(zeros(m+n , 1)) |> parse |> Reduce.mat;

    v = volts(netlist);

    expr = function(type, value)
        if type in [:R :Z :VCCS]
            return (1 / value)
        elseif type == :C
            return (im * (ω == nothing ? :s : ω) * value)
        elseif type == :L
            return (1 / (im * (ω == nothing ? :s : ω) * value))
        else
            return value
        end
    end

    for (id, component) in netlist
        type = first(component);
        nodes = component[2:(end-1)];
        value = expr(first(component), last(component));

        if in(type, [:R :G :L :C :Z :Y])
            idx = ( repeat([nodes], 2)
                  |> x -> Iterators.product(x...)
                  |> collect
                  |> x -> reshape(x, (sum(size(x)),1))
                  |> x -> filter(y -> !in(0,y), x) );
            for (i,j) in idx
                M[i,j] = M[i,j] + (i == j ? value : -value);
            end
        elseif type == :I
            iv = ( identity.(zip(nodes, [-value,value]))
                  |> x -> filter(y -> y[1] != 0, x) );
            for i in iv
                y[i[1]] = y[i[1]] + i[2];
            end
        elseif type == :V
            i_idx = m + findfirst(x -> x == id, v);
            y[i_idx] = y[i_idx] + value;
            m_idx = ( identity.(zip(nodes, [1,-1]))
                    |> x -> filter(y -> y[1] != 0, x) );
            for m in m_idx
                M[m[1], i_idx] = M[m[1], i_idx] + m[2];
                M[i_idx, m[1]] = M[i_idx, m[1]] + m[2];
            end
        elseif type == :VCCS
            i_idx = m + findfirst(x -> x == id, v);
            b_idx = ( identity.(zip(nodes[1:2], [-1 1]))
                    |> x -> filter(y -> y[1] != 0, x) );
            c_idx = ( identity.(zip(nodes[3:4], [1 -1]))
                    |> x -> filter(y -> y[1] != 0, x) );

            M[i_idx, i_idx] = M[i_idx, i_idx] + value;
            for b in b_idx
                M[b[1], i_idx] = M[b[1], i_idx] + b[2];
            end
            for c in c_idx
                M[i_idx, c[1]] = M[i_idx, c[1]] + c[2];
            end
        elseif type == :VCVS
            i_idx = m + findfirst(x -> x == id, v);
            b_idx = ( identity.(zip(nodes[3:4], [1 -1]))
                    |> x -> filter(y -> y[1] != 0, x) );
            c_idx = ( identity.(zip(nodes, [-value value 1 -1]))
                    |> x -> filter(y -> y[1] != 0, x) );
            for b in b_idx
                M[b[1], i_idx] = m[b[1], i_idx] + b[2];
            end
            for c in c_idx
                M[i_idx, c[1]] = m[i_idx, c[1]] + c[2];
            end
        elseif type == :CCVS
            nothing
        elseif type == :CCCS
            nothing
        else
            nothing
        end
    end

    return (M, y)
end

"""
    analyze(netlist::Dict)::Dict

Performs modified nodal analysis (mna) on the given
`netlist` and returns the vector containing node voltages
and branch currents, as well as the transfer function H(ω).
"""
function analyze(netlist::Dict, ω = nothing)

    Reduce.rounded(true);
    Algebra.scientific_notation(2,2);
    M, y = systemMatrix(netlist, ω);

    if ω == nothing
        x = RExpr(M \ y);
    else
        x = convert(Array{Complex, 2}, M) \ convert(Array{Complex, 1}, y);
    end

    return x
end

"""
    transferFunciton(::RExpr) => ::Function

Takes an RExpr with only one symbolic variable `s` and
returns a function that evaluates the small signal model
at the given angle frequency ω.
"""
function transferFunction(x::RExpr)
    return (ω) -> Algebra.sub(:(s = $ω), x) |> parse |> eval
end

end # module


# monom of bool func
struct Monom
    v :: Int
end

# algebraic normal form of bool fun
# up to 63 variables
struct ANF
    monoms :: Vector{Monom}
end

Monom(vs :: Vector{Int}) = Monom(set_bits(vs))
function Monom(v :: BoolVec)
    value(v) == 0 && return Monom(-1)
    return collect(Bool, v) |> reverse |> itr2bin |> Monom
end

value(m :: Monom) = m.v
iszero(m :: Monom) = (value(m) == 0)
isone(m :: Monom) = (value(m) == -1)
(m :: Monom)(vs...) = m(tuple(vs...))

function (m :: Monom)(vs)
    isone(m) && return true
    iszero(m) && return false
    val = itr2bin(reverse(vs))
    return (value(m) & val) == value(m)
end

monoms(anf :: ANF) = anf.monoms

function iszero(anf :: ANF)
    check1 = length(monoms(anf)) == 1
    check2 = iszero(monoms(anf)[1])
    return check1 && check2
end

function isone(anf :: ANF)
    check1 = length(monoms(anf)) == 1
    check2 = isone(monoms(anf)[1])
    return check1 && check2
end

(anf :: ANF)(vs) = reduce(xor, [m(vs) for m in monoms(anf)])
(anf :: ANF)(vs...) = anf(tuple(vs...))

function ANF(ms :: Vector{T}) where T
    mons = [Monom(set_bits(m)) for m in ms]
    return ANF(mons)
end

prev(x, dom) = filter(y -> all(y .<= x), dom)

# given bool fun
# construct ANF
function ANF(f :: BoolFun)
    dom = collect(domain(f))
    mons = Vector{Monom}([])
    for x in dom
        ys = filter(t -> (t <= x), dom)
        ha = sum(x -> value(f(x)), ys) % 2
        if ha == 1
            push!(mons, Monom(x))
        end
    end
    if isempty(mons)
        push!(mons, Monom(0))
    end
    return ANF(mons)
end

# given ANF
# construct bool fun
function BoolFun(anf :: ANF)
    dom = collect(domain(f))
    mons = Vector{Monom}([])
    for x in dom
        ys = filter(t -> (t <= x), dom)
        ha = sum(f, ys) % 2
        if ha == 1
            push!(mons, Monom(x))
        end
    end
    if isempty(mons)
        push!(mons, Monom(0))
    end
    return ANF(mons)
end

# given bool map
# construct ANF
function ANF(fs :: BoolMap)
    anfs = [ANF(proj(fs, i)) for i in 1 : nfuns(fs)]
    return anfs
end

# show utilities
function Base.show(io :: IO, m :: Monom)
    res = ""
    if iszero(m)
        res *= "0"
    elseif isone(m)
        res *= "1"
    else
        v = value(m)
        i = 0
        while v > 0
            i += 1
            ind = v & 1
            if ind != 0
                res *= "x$i"
            end
            v >>= 1
        end
    end
    print(io, res)
end

function Base.show(io :: IO, anf :: ANF)
    res = reduce((x, y) -> x * " ⊕ " * y, repr.(monoms(anf)))
    print(io, res)
end


using LinearAlgebra

⊗ = kron

function encode(s::Tuple{Array{Float64,1},Int64})
  n = sqrt(s[1][1]^2 + s[1][2]^2)
  # return (s[1]/n, s[2])
  return (s[1]/n ⊗ s[1]/n, s[2])
end

function encode(ar::Array{Tuple{Array{Float64,1},Int64}})
  return encode.(ar)
end


# p27.jl - Solve KdV eq. u_t + uu_x + u_xxx = 0 on [-pi,pi] by
#         FFT with integrating factor v = exp(-ik^3t)*u-hat.

# Set up grid and two-soliton initial data:
N = 256; dt = .4/N^2; x = (2*pi/N)*(-N/2:N/2-1);
A = 25; B = 16; clf();
u = @. 3*A^2*sech(.5*(A*(x+2)))^2 + 3*B^2*sech(.5*(B*(x+1)))^2;
v = fft(u); k = [0:N/2-1;0;-N/2+1:-1]; ik3 = 1im*k.^3;

# Solve PDE and plot results:
tmax = 0.006; nplt = floor(Int,(tmax/25)/dt); nmax = round(Int,tmax/dt);
udata = u; tdata = [0.0];
for n = 1:nmax
    t = n*dt; g = -.5im*dt*k;
    E = exp.(dt*ik3/2); E2 = E.^2;
    a = g.*fft(real( ifft(     v    ) ).^2);
    b = g.*fft(real( ifft(E.*(v+a/2)) ).^2);     # 4th-order
    c = g.*fft(real( ifft(E.*v + b/2) ).^2);     # Runge-Kutta
    d = g.*fft(real( ifft(E2.*v+E.*c) ).^2);
    v = E2.*v + (E2.*a + 2*E.*(b+c) + d)/6;
    if mod(n,nplt) == 0
        u = real(ifft(v));
        udata = [udata u]; tdata = [tdata;t];
    end
end
mesh(x,tdata,udata',ccount=0,rcount=N); view(-20,25);
xlabel("x"); ylabel("y"); grid(true);
xlim(-pi,pi); ylim(0,tmax); zlim(0,12000);|
zticks([0,2000]);


module TestRanges

using Test
using RLEVectors

@testset begin

# disjoin_length
@test disjoin_length([],[]) == 0
@test disjoin_length([4,5],[]) == 2
@test disjoin_length([],[9,10,12]) == 3
@test disjoin_length([1,2,3],[4,5,6]) == 6
@test disjoin_length([1,2,3],[3,5,6]) == 5
@test disjoin_length([1,3],[2,5,6]) == 5
@test disjoin_length(RLEVector([1,2], [1,3]), RLEVector([3,2,1], [2,5,6])) == 5

# disjoin
@test disjoin( [1,3,8,10],[3,4,9,10] ) == [1,3,4,8,9,10]
@test disjoin( [3,4,9,10], [1,3,8,10] ) == [1,3,4,8,9,10]

@test disjoin( RLEVector([1,2,3,4], [1,3,8,10]), RLEVector([4,5,6,7],[3,4,9,10])) == ([1,3,4,8,9,10], [1,2,3,3,4,4], [4,4,5,6,6,7])
@test disjoin( RLEVector([4,5,6,7],[3,4,9,10]), RLEVector([1,2,3,4], [1,3,8,10])) == ([1,3,4,8,9,10], [4,4,5,6,6,7], [1,2,3,3,4,4])

# rangeMeans

x = RLEVector( collect(2:2:20) )
y = RLEVector( collect(1:1:10) )

ranges = [ 1:1, 1:2, 2:3, 4:8, 1:10, 9:10, 10:10 ]
@test [ mean(x[ r ]) for r in ranges ] == rangeMeans( ranges, x )

end # testset

end # module


# ---
# title: 518. Coin Change 2
# id: problem518
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories:
# link: <https://leetcode.com/problems/coin-change-2/description/>
# hidden: true
# ---
#
# You are given coins of different denominations and a total amount of money.
# Write a function to compute the number of combinations that make up that
# amount. You may assume that you have infinite number of each kind of coin.
#
#
#
# **Example 1:**
#
#
#
#     Input: amount = 5, coins = [1, 2, 5]
#     Output: 4
#     Explanation: there are four ways to make up the amount:
#     5=5
#     5=2+2+1
#     5=2+1+1+1
#     5=1+1+1+1+1
#
#
# **Example 2:**
#
#
#
#     Input: amount = 3, coins = [2]
#     Output: 0
#     Explanation: the amount of 3 cannot be made up just with coins of 2.
#
#
# **Example 3:**
#
#
#
#     Input: amount = 10, coins = [10]
#     Output: 1
#
#
#
#
# **Note:**
#
# You can assume that
#
#   * 0 <= amount <= 5000
#   * 1 <= coin <= 5000
#   * the number of coins is less than 500
#   * the answer is guaranteed to fit into signed 32-bit integer
#
#
## @lc code=start
using LeetCode

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


using Infinity
using StatsDiscretizations

ℝ_discr = RealLineDiscretizer{:open,:closed}(-5:0.1:5)
@test ℝ_discr(2) == Interval{:open,:closed}(1.9,2)
@test ℝ_discr(2.0001) == Interval{:open,:closed}(2,2.1)

@test_throws Exception ℝ_discr[0]
@test_throws Exception ℝ_discr[1000]

𝕀_discr = BoundedIntervalDiscretizer{:open, :closed}(-5:0.1:5)

@test collect(𝕀_discr) == collect(ℝ_discr[2:(end-1)])

xs = rand(1000)
discr_𝕀_xs = 𝕀_discr.(xs)
@test all( in.(xs, discr_𝕀_xs))
@test discr_𝕀_xs == 𝕀_discr.(discr_𝕀_xs)
@test 𝕀_discr(Interval{:open,:open}(0.05,0.06))  == Interval{:open,:closed}(0.0,0.1)
@test 𝕀_discr(Interval{:open,:closed}(0.099,0.1))  == Interval{:open,:closed}(0.0,0.1)

@test_throws ArgumentError 𝕀_discr(Interval{:open,:closed}(0.05,0.15))
@test_throws ArgumentError 𝕀_discr(Interval{:open,:closed}(0.05,0.15))

@test_throws Exception 𝕀_discr(-10)
@test_throws ArgumentError 𝕀_discr(+10)

discr_ℝ_xs = ℝ_discr.(xs)
@test all( in.(xs, discr_ℝ_xs))
@test discr_ℝ_xs == 𝕀_discr.(discr_ℝ_xs)
@test ℝ_discr(Interval{:open,:open}(0.05,0.06))  == Interval{:open,:closed}(0.0,0.1)
@test ℝ_discr(Interval{:open,:closed}(0.099,0.1))  == Interval{:open,:closed}(0.0,0.1)

@test_throws ArgumentError ℝ_discr(Interval{:open,:closed}(0.05,0.15))

@test ℝ_discr(-10) == Interval{:open,:closed}(-∞,-5.0)
@test ℝ_discr(10) == Interval{:open,:closed}(5.0, ∞)

finite_discretizer = FiniteSupportDiscretizer(-4:1:4)

@inferred finite_discretizer(2)
@test_throws String finite_discretizer(10)

double_fun = DiscretizedFunction(finite_discretizer, x->2x)

@test double_fun(4) == 8
@test double_fun(3) == 6

findfirst( ==(2), finite_discretizer)::Int

searchsorted(finite_discretizer, 2)


### A Pluto.jl notebook ###
# v0.12.14

using Markdown
using InteractiveUtils

# ╔═╡ c9762a40-3119-11eb-021f-3526881bee53
begin
   using MLJ, MLJFlux, Flux
   import Random.seed!; seed!(123)
   using Plots
end

# ╔═╡ ffc72f20-3184-11eb-0f84-f57d92c16b65
md"### Aqui construímos nosso próprio modelo usando MLJ+Flux.

É necessário a criação de uma estrutura que herda de um builder. Adicionalmente, devemos sobrecarregar a função build, pois esta será chamada internamente."

# ╔═╡ 563a4730-311b-11eb-0b9f-45f44f995aa6
begin

	mutable struct MNISTBuilder <: MLJFlux.Builder
		n_hidden::Int
	end

	function MLJFlux.build(builder::MNISTBuilder, (n1, n2), m, n_channels)
		return Chain(
			flatten,
			Dense(n1 * n2, builder.n_hidden, relu),
			Dense(builder.n_hidden, m),
		)
	end

end

# ╔═╡ c7f7cf90-3185-11eb-0f94-f5e182753280
md"### Instanciando Classificador"

# ╔═╡ 5c511db0-311b-11eb-3eec-2fb8f22a5a53
begin
	@load ImageClassifier
	clf = ImageClassifier(;
		builder=MNISTBuilder(128),
		optimiser=ADAM(0.001),
		loss=Flux.crossentropy,
		epochs=6,
		batch_size=128,
	)
end

# ╔═╡ aa9142ae-3185-11eb-22a3-f39504976138
md"### O problema

url: http://yann.lecun.com/exdb/mnist/

The MNIST database of handwritten digits, available from this page, has a training set of 60,000 examples, and a test set of 10,000 examples. It is a subset of a larger set available from NIST. The digits have been size-normalized and centered in a fixed-size image.
It is a good database for people who want to try learning techniques and pattern recognition methods on real-world data while spending minimal efforts on preprocessing and formatting."

# ╔═╡ 5b631430-311b-11eb-1388-6f434e9cc354
begin
	X = Flux.Data.MNIST.images()
	y = Flux.Data.MNIST.labels()
	y = coerce(y, Multiclass)
end

# ╔═╡ 130f6240-311d-11eb-0efd-7b4822376ef8
X[100]

# ╔═╡ e40bc4c0-3185-11eb-0794-63633f2c5269
md"### Particionando os dados

Realizamos aqui o que conhecemos como hold-out. Particionamos em treino e teste, que é a forma mais simples de se fazer uma validação. Sabemos que existem outras no curso. No caso, deixei 70% para treino e 30% para teste. Como podem notar, a função gera índices qu correspondem às linhas dos exemplos. Ele não tira cópia dos dados."

# ╔═╡ fa3a71d0-3124-11eb-3b83-014b67169868
train, test = partition(eachindex(y), 0.01, shuffle=true, rng=1234)

# ╔═╡ d6fd7bc0-3185-11eb-13c0-7381eb7a588a
md"### Treinando a Rede

Um conceito interessante do MLJ é o machine. Esta função faz uma mapeamento do preditor ao dado. Quando aplicamos machine, estamos gerando um nó num grafo de computação e um estado associado. Existem algmas vantagens neste paradigma como: associar um mesmo preditor a vários dados distintos que podem ser processados em diferentes recursos; evitar re execução de um mesmo preditor e um mesmo dado."

# ╔═╡ 5d2b5110-311b-11eb-3dc3-0b7fdfd060fd
mach = machine(clf, X, y)

# ╔═╡ 177cb6c2-3186-11eb-2d73-39f8a0ac039b
fit!(mach,rows=train)

# ╔═╡ 3d71d0e0-3186-11eb-3a9c-d17db573ba54
md"### Predição

Aqui usamos o predict_mode para pegar as previsões discretas sem as probabilidades. às vezes é preciso usar apenas o predict para pegar ambas as classes e probabilidades, pois certas métricas necessitam"

# ╔═╡ c5fd5630-3124-11eb-2844-7fc187c8a99b
ŷ = predict_mode(mach, rows=test)

# ╔═╡ d0212970-3124-11eb-2041-91a20d4349a2
cm = confusion_matrix(ŷ,y[test])

# ╔═╡ d1e7b8a0-3124-11eb-254e-83e4e57c4982
accuracy(cm)

# ╔═╡ dfab9600-3124-11eb-2459-6f59178a5d84
balanced_accuracy(ŷ,y[test])

# ╔═╡ 9d2bb6ae-31ed-11eb-2878-074b2adae35e
md"### Encontrando o melhor modelo"

# ╔═╡ 2e03a3a2-31ee-11eb-03eb-35d112cba7a6
clf.builder.n_hidden

# ╔═╡ a894b420-31ed-11eb-2de1-6b5a80d19ea9
begin

	# defining hyperparams for tunning
	r1 = range(clf, :lambda, lower=0.001, upper=1.0)
	r2 = range(clf, :epochs, lower=10, upper=300)
	r3 = range(clf, :(builder.n_hidden), lower=64, upper=256)

	# attaching tune
	self_tuning_ann_model = TunedModel(model        =  clf,
									   resampling   = CV(nfolds = 3),
									   tuning       = RandomSearch(),
									   range        = [r1,r2,r3],
									   measure      = accuracy,
									   operation    = predict_mode,
									   n=8)

end

# ╔═╡ bd1883d0-31ee-11eb-0929-9f9e106252e9
begin
    # putting into the machine
	self_tuning_ann = machine(self_tuning_ann_model, X, y)

	# fitting with tunning
	fit!(self_tuning_ann,rows=train)
end

# ╔═╡ 2629c250-329c-11eb-332d-7debeb62dabe
fitted_params(self_tuning_ann)

# ╔═╡ 7f796040-329c-11eb-0cba-2f3c6648a92e
report(self_tuning_ann)

# ╔═╡ 7b4d6320-3186-11eb-3228-616d0e73b93c
md"### Aplicando o melhor modelo

Depois que testamos combinações com variações de parâmetros, vamos aplicar o modelo aprendido no conjunto de teste."

# ╔═╡ 5e5eef12-311b-11eb-23a9-6b715d078655
ŷb = predict_mode(self_tuning_ann,rows=test)

# ╔═╡ 8238a550-32b2-11eb-1d18-ed7f4b004b27
cmb = confusion_matrix(ŷb,y[test])

# ╔═╡ b35e47c0-32b2-11eb-1fce-cf420134aed0
accuracy(cmb)

# ╔═╡ b9377900-32b2-11eb-3478-e11f07450ec5
balanced_accuracy(ŷb,y[test])

# ╔═╡ Cell order:
# ╠═c9762a40-3119-11eb-021f-3526881bee53
# ╟─ffc72f20-3184-11eb-0f84-f57d92c16b65
# ╠═563a4730-311b-11eb-0b9f-45f44f995aa6
# ╟─c7f7cf90-3185-11eb-0f94-f5e182753280
# ╠═5c511db0-311b-11eb-3eec-2fb8f22a5a53
# ╟─aa9142ae-3185-11eb-22a3-f39504976138
# ╠═5b631430-311b-11eb-1388-6f434e9cc354
# ╠═130f6240-311d-11eb-0efd-7b4822376ef8
# ╟─e40bc4c0-3185-11eb-0794-63633f2c5269
# ╠═fa3a71d0-3124-11eb-3b83-014b67169868
# ╟─d6fd7bc0-3185-11eb-13c0-7381eb7a588a
# ╠═5d2b5110-311b-11eb-3dc3-0b7fdfd060fd
# ╠═177cb6c2-3186-11eb-2d73-39f8a0ac039b
# ╟─3d71d0e0-3186-11eb-3a9c-d17db573ba54
# ╠═c5fd5630-3124-11eb-2844-7fc187c8a99b
# ╠═d0212970-3124-11eb-2041-91a20d4349a2
# ╠═d1e7b8a0-3124-11eb-254e-83e4e57c4982
# ╠═dfab9600-3124-11eb-2459-6f59178a5d84
# ╟─9d2bb6ae-31ed-11eb-2878-074b2adae35e
# ╠═2e03a3a2-31ee-11eb-03eb-35d112cba7a6
# ╠═a894b420-31ed-11eb-2de1-6b5a80d19ea9
# ╠═bd1883d0-31ee-11eb-0929-9f9e106252e9
# ╠═2629c250-329c-11eb-332d-7debeb62dabe
# ╠═7f796040-329c-11eb-0cba-2f3c6648a92e
# ╟─7b4d6320-3186-11eb-3228-616d0e73b93c
# ╠═5e5eef12-311b-11eb-23a9-6b715d078655
# ╠═8238a550-32b2-11eb-1d18-ed7f4b004b27
# ╠═b35e47c0-32b2-11eb-1fce-cf420134aed0
# ╠═b9377900-32b2-11eb-3478-e11f07450ec5


#=
Form factor + edge velocity calculation
=#

##################################################################
"""
$(SIGNATURES)

Find the circumcenter of a triangle.

Created from C source of Jonathan R Shewchuk <jrs@cs.cmu.edu>

Modified to return absolute coordinates.
"""
function tricircumcenter!(circumcenter,a,b,c)

    # Use coordinates relative to point `a' of the triangle.
    xba = b[1] - a[1]
    yba = b[2] - a[2]
    xca = c[1] - a[1]
    yca = c[2] - a[2]

    # Squares of lengths of the edges incident to `a'.
    balength = xba * xba + yba * yba
    calength = xca * xca + yca * yca

    # Calculate the denominator of the formulae.
    # if EXACT
    #    Use orient2d() from http://www.cs.cmu.edu/~quake/robust.html
    #    to ensure a correctly signed (and reasonably accurate) result,
    #    avoiding any possibility of division by zero.
    #  denominator = 0.5 / orient2d((double*) b, (double*) c, (double*) a)

    # Take your chances with floating-point roundoff
    denominator = 0.5 / (xba * yca - yba * xca)

    # Calculate offset (from `a') of circumcenter.
    xcirca = (yca * balength - yba * calength) * denominator
    ycirca = (xba * calength - xca * balength) * denominator

# The result is returned both in terms of x-y coordinates and xi-eta
# coordinates, relative to the triangle's point `a' (that is, `a' is
# the origin of both coordinate systems).	 Hence, the x-y coordinates
# returned are NOT absolute; one must add the coordinates of `a' to
# find the absolute coordinates of the circumcircle.  However, this means
# that the result is frequently more accurate than would be possible if
# absolute coordinates were returned, due to limited floating-point
# precision.  In general, the circumradius can be computed much more
# accurately.

    circumcenter[1] = xcirca+a[1]
    circumcenter[2] = ycirca+a[2]

    return circumcenter
end

################################################
"""
$(SIGNATURES)

Calculate node volume  and voronoi surface contributions for cell.
"""

function cellfactors!(::Type{Edge1D},::Type{Cartesian1D},coord,cellnodes,icell,nodefac,edgefac)
    K=cellnodes[1,icell]
    L=cellnodes[2,icell]
    xK=coord[1,K]
    xL=coord[1,L]
    d=abs(xL[1]-xK[1])
    nodefac[1]=d/2
    nodefac[2]=d/2
    edgefac[1]=1/d
    nothing
end

function cellfactors!(::Type{Edge1D},::Type{<:Polar1D}, coord,cellnodes,icell,nodefac,edgefac)
    K=cellnodes[1,icell]
    L=cellnodes[2,icell]
    xK=coord[1,K]
    xL=coord[1,L]
    r0=xK[1]
    r1=xL[1]
    if r1<r0
        r0=xL[1]
        r1=xK[1]
    end
    rhalf=0.5*(r1+r0);
    # cpar[1]= π*(r1*r1-r0*r0);         # circular volume
    nodefac[1]= π*(rhalf*rhalf-r0*r0);   # circular volume between midline and boundary
    nodefac[2]= π*(r1*r1-rhalf*rhalf);   # circular volume between midline and boundary
    edgefac[1]= 2.0*π*rhalf/(r1-r0);     # circular surface / width
    nothing
end

function cellfactors!(::Type{Triangle2D},::Type{Cartesian2D},coord,cellnodes,icell,npar,epar)
    i1=cellnodes[1,icell]
    i2=cellnodes[2,icell]
    i3=cellnodes[3,icell]

    # Fill matrix of edge vectors
    V11= coord[1,i2]- coord[1,i1]
    V21= coord[2,i2]- coord[2,i1]

    V12= coord[1,i3]- coord[1,i1]
    V22= coord[2,i3]- coord[2,i1]

    V13= coord[1,i3]- coord[1,i2]
    V23= coord[2,i3]- coord[2,i2]

    # Compute determinant
    det=V11*V22 - V12*V21
    vol=0.5*det

    ivol = 1.0/vol

    # squares of edge lengths
    dd1=V13*V13+V23*V23 # l32
    dd2=V12*V12+V22*V22 # l31
    dd3=V11*V11+V21*V21 # l21

    # contributions to \sigma_kl/h_kl
    epar[1]= (dd2+dd3-dd1)*0.125*ivol
    epar[2]= (dd3+dd1-dd2)*0.125*ivol
    epar[3]= (dd1+dd2-dd3)*0.125*ivol

    # contributions to \omega_k
    npar[1]= (epar[3]*dd3+epar[2]*dd2)*0.25
    npar[2]= (epar[1]*dd1+epar[3]*dd3)*0.25
    npar[3]= (epar[2]*dd2+epar[1]*dd1)*0.25
    nothing
end

function cellfactors!(::Type{Tetrahedron3D},::Type{Cartesian3D},coord,cellnodes,icell,npar,epar)
    # Transferred from WIAS/pdelib, (c) J. Fuhrmann, H. Langmach, I. Schmelzer
    es=@MVector zeros(6)
    dd=@MVector zeros(6)
    i1=cellnodes[1,icell]
    i2=cellnodes[2,icell]
    i3=cellnodes[3,icell]
    i4=cellnodes[4,icell]

    #-> celledgenodes
    pp1=(1,1,1,2,2,3)
    pp2=(2,3,4,3,4,4)

    pi1=(5,6,5,1)
    pi2=(6,3,1,4)
    pi3=(4,2,3,2)

    po1=(2,1,2,6)
    po2=(1,4,6,3)
    po3=(3,5,4,5)
    pedge=(6,5,4,3,2,1)

    for i=1:6
        p1=cellnodes[pp1[i],icell]
        p2=cellnodes[pp2[i],icell]
        dx=coord[1,p1]-coord[1,p2]
        dy=coord[2,p1]-coord[2,p2]
        dz=coord[3,p1]-coord[3,p2]
        dd[i]=dx*dx+dy*dy+dz*dz
        es[i]=0.0
        epar[i]=0.0
    end

    x1=coord[1,i2]-coord[1,i1]
    y1=coord[2,i2]-coord[2,i1]
    z1=coord[3,i2]-coord[3,i1]

    x2=coord[1,i3]-coord[1,i1]
    y2=coord[2,i3]-coord[2,i1]
    z2=coord[3,i3]-coord[3,i1]

    x3=coord[1,i4]-coord[1,i1]
    y3=coord[2,i4]-coord[2,i1]
    z3=coord[3,i4]-coord[3,i1]

    det= (x1*(y2*z3-y3*z2) + x2*(y3*z1-y1*z3) + x3*(y1*z2-y2*z1))

    if det <0
        det=-det
    end
    vol=det/6
    vv= 96*6*vol

    for  i=1:4
        npar[i]=0.0
        i1=pi1[i]
        i2=pi2[i]
        i3=pi3[i]

        h1=dd[i1]*(dd[i2]+dd[i3]-dd[i1])
        h2=dd[i2]*(dd[i3]+dd[i1]-dd[i2])
        h3=dd[i3]*(dd[i1]+dd[i2]-dd[i3])

        df=h1+h2+h3
        vf=(h1*dd[po1[i]] + h2*dd[po2[i]] + h3*dd[po3[i]]-2*dd[i1]*dd[i2]*dd[i3]) / (vv*df)
        es[i1] += h1*vf
        es[i2] += h2*vf
        es[i3] += h3*vf
    end

    for i=1:6
        epar[pedge[i]] = 6*es[i]/dd[i]
        npar[pp1[i]] += es[i]
        npar[pp2[i]] += es[i]
    end
    nothing
end

function cellfactors!(::Type{Triangle2D},::Type{<:Cylindrical2D},coord,cellnodes,icell,npar,epar)
    function area2d(coord1, coord2, coord3)
        V11= coord2[1]- coord1[1]
        V21= coord2[2]- coord1[2]

        V12= coord3[1]- coord1[1]
        V22= coord3[2]- coord1[2]

        V13= coord3[1]- coord2[1]
        V23= coord3[2]- coord2[2]

        # Compute determinant
        det=V11*V22 - V12*V21
        area=abs(0.5*det)
    end

    i1=cellnodes[1,icell]
    i2=cellnodes[2,icell]
    i3=cellnodes[3,icell]

    # Fill matrix of edge vectors
    V11= coord[1,i2]- coord[1,i1]
    V21= coord[2,i2]- coord[2,i1]

    V12= coord[1,i3]- coord[1,i1]
    V22= coord[2,i3]- coord[2,i1]

    V13= coord[1,i3]- coord[1,i2]
    V23= coord[2,i3]- coord[2,i2]

    # Compute determinant
    det=V11*V22 - V12*V21
    area=0.5*det

    # Integrate R over triangle (via quadrature rule)
    vol=2.0*π*area*(coord[1,i1]+coord[1,i2]+coord[1,i3])/3.0

    # squares of edge lengths
    dd1=V13*V13+V23*V23 # l32
    dd2=V12*V12+V22*V22 # l31
    dd3=V11*V11+V21*V21 # l21

    emid23=[0.5*(coord[1,i3]+coord[1,i2]),
            0.5*(coord[2,i3]+coord[2,i2])]

    emid13=[0.5*(coord[1,i1]+coord[1,i3]),
            0.5*(coord[2,i1]+coord[2,i3])]

    emid12=[0.5*(coord[1,i1]+coord[1,i2]),
            0.5*(coord[2,i1]+coord[2,i2])]

    cc=@MVector zeros(2)
    tricircumcenter!(cc,coord[:,i1],coord[:,i2],coord[:,i3])

    r(p)=p[1]
    z(p)=p[2]
    sq(x)=x*x
    epar[1]= π*(r(cc)+r(emid23))*sqrt(sq(r(cc)-r(emid23))+sq(z(cc)-z(emid23)))/sqrt(dd1);
    epar[2]= π*(r(cc)+r(emid13))*sqrt(sq(r(cc)-r(emid13))+sq(z(cc)-z(emid13)))/sqrt(dd2);
    epar[3]= π*(r(cc)+r(emid12))*sqrt(sq(r(cc)-r(emid12))+sq(z(cc)-z(emid12)))/sqrt(dd3);

    rintegrate(coord1, coord2, coord3)=2.0*π*area2d(coord1,coord2,coord3)*(coord1[1]+coord2[1]+coord3[1])/3.0
    npar[1]=rintegrate(coord[:,i1],cc,emid13)+rintegrate(coord[:,i1],cc,emid12)
    npar[2]=rintegrate(coord[:,i2],cc,emid23)+rintegrate(coord[:,i2],cc,emid12)
    npar[3]=rintegrate(coord[:,i3],cc,emid13)+rintegrate(coord[:,i3],cc,emid23)
    nothing
end

################################################
"""
$(SIGNATURES)

Calculate node volume  contributions for boundary face.
"""

function bfacefactors!(::Type{Vertex0D},::Type{Cartesian1D},coord,bfacenodes,ibface,nodefac)
    nodefac[1]=1.0
    nothing
end

function bfacefactors!(::Type{Vertex0D},::Type{<:Polar1D},coord,bfacenodes,ibface,nodefac)
    inode=bfacenodes[1,ibface]
    r=coord[1,inode]
    nodefac[1]=2*π*r
    nothing
end

function bfacefactors!(::Type{Edge1D},::Type{<:Cartesian2D},coord,bfacenodes,ibface,nodefac)
    i1=bfacenodes[1,ibface]
    i2=bfacenodes[2,ibface]
    dx=coord[1,i1]-coord[1,i2]
    dy=coord[2,i1]-coord[2,i2]
    d=0.5*sqrt(dx*dx+dy*dy)
    nodefac[1]=d
    nodefac[2]=d
    nothing
end

function bfacefactors!(::Type{Edge1D},::Type{<:Cylindrical2D},coord,bfacenodes,ibface,nodefac)
    i1=bfacenodes[1,ibface]
    i2=bfacenodes[2,ibface]
    r1=coord[1,i1]
    r2=coord[1,i2]
    z1=coord[2,i1]
    z2=coord[2,i2]
    dr=r1-r2
    rmid=(r1+r2)/2
    dz=z1-z2
    l=sqrt(dr*dr+dz*dz)
    nodefac[1]=π*(r1+rmid)*l/2
    nodefac[2]=π*(r2+rmid)*l/2
    nothing
end

function bfacefactors!(::Type{Triangle2D},::Type{<:Cartesian3D},coord,bfacenodes,ibface,npar)

    # Transferred from WIAS/pdelib, (c) J. Fuhrmann, H. Langmach, I. Schmelzer
    i1=bfacenodes[1,ibface]
    i2=bfacenodes[2,ibface]
    i3=bfacenodes[3,ibface]
    dd=@MVector zeros(3)
    epar=@MVector zeros(3)
    for j=1:3
        d= coord[j,i1] - coord[j,i3];  dd[2]+= d*d;
        d= coord[j,i2] - coord[j,i1];  dd[3]+= d*d;
        d= coord[j,i3] - coord[j,i2];  dd[1]+= d*d;
    end

    # Kanten-Flaechenanteile (ohne Abschneiden); epar als Hilfsfeld benutzt
    epar[1]= (dd[2]+dd[3]-dd[1])*dd[1];
    epar[2]= (dd[3]+dd[1]-dd[2])*dd[2];
    epar[3]= (dd[1]+dd[2]-dd[3])*dd[3];
    vol= sqrt(epar[1]+epar[2]+epar[3])*0.25;

    d = 1.0/(8*vol);

    # Knoten-Flaechenanteile (ohne Abschneiden)
    npar[1]= (epar[3]+epar[2])*d*0.25;
    npar[2]= (epar[1]+epar[3])*d*0.25;
    npar[3]= (epar[2]+epar[1])*d*0.25;

    # Kantengewichte
    # epar[1]= epar[1]*d/dd[1];
    # epar[2]= epar[2]*d/dd[2];
    # epar[3]= epar[3]*d/dd[3];
    nothing
end

##################################################################

#
# TODO: this should be generalized for more quadrules
#
function integrate(coordl,coordr,hnormal,velofunc)
    wl=1.0/6.0
    wm=2.0/3.0
    wr=1.0/6.0
    coordm=0.5*(coordl+coordr)
    (vxl,vyl)=velofunc(coordl[1],coordl[2])
    (vxm,vym)=velofunc(coordm[1],coordm[2])
    (vxr,vyr)=velofunc(coordr[1],coordr[2])
    return (wl*vxl +wm*vxm+wr*vxr)*hnormal[1] + (wl*vyl +wm*vym+wr*vyr)*hnormal[2]
end

"""
$(SIGNATURES)

Project velocity onto grid edges,
"""
function edgevelocities(grid,velofunc)

    @assert dim_space(grid)<3

    cn=grid[CellNodes]
    ec=grid[EdgeCells]
    en=grid[EdgeNodes]
    coord=grid[Coordinates]

    velovec=zeros(Float64,num_edges(grid))
    if dim_space(grid)==1
        for iedge=1:num_edges(grid)
            K=en[1,iedge]
            L=en[2,iedge]
            elen=coord[1,L]-coord[1,K]
            vx,vy=velofunc((coord[1,K]+coord[1,L])/2,0)
            velovec[iedge]=-elen*vx
        end
    else
        for iedge=1:num_edges(grid)
            K=en[1,iedge]
            L=en[2,iedge]
            p1=@MVector zeros(2)
            p2=@MVector zeros(2)
            tricircumcenter!(p1,
                             coord[:,cn[1,ec[1,iedge]]],
                             coord[:,cn[2,ec[1,iedge]]],
                             coord[:,cn[3,ec[1,iedge]]])
            if ec[2,iedge]>0
                tricircumcenter!(p2,
                                 coord[:,cn[1,ec[2,iedge]]],
                                 coord[:,cn[2,ec[2,iedge]]],
                                 coord[:,cn[3,ec[2,iedge]]])
            else
                p2.=0.5*(coord[:,K]+coord[:,L])
            end
            hnormal=coord[:,K]-coord[:,L]
            velovec[iedge]=integrate(p1,p2,hnormal,velofunc)
        end
    end
    return velovec
end

function bfacevelocities(grid,velofunc)
    @assert dim_space(grid)<3
    bfacenodes=grid[BFaceNodes]
    coord=grid[Coordinates]
    bfacecells=grid[BFaceCells]
    bfacenormals=grid[BFaceNormals]
    bfr=grid[BFaceRegions]
    velovec=zeros(Float64,2,num_bfaces(grid))
    if dim_space(grid)==1
        for ibface=1:num_bfaces(grid)
            vx,vy=velofunc(coord[1,bfacenodes[1,ibface]])
            velovec[ibface]=vx*bfacenormals[1,ibface]
        end
    else
        for ibface=1:num_bfaces(grid)
            p1=coord[:,bfacenodes[1,ibface]]
            p2=coord[:,bfacenodes[2,ibface]]
            pm=0.5*(p1+p2)
            velovec[1,ibface]=integrate(p1,pm,bfacenormals[:,ibface],velofunc)
            velovec[2,ibface]=integrate(pm,p2,bfacenormals[:,ibface],velofunc)
        end
    end
    return velovec
end