# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module SelfConvolutive

using Nemo

export ModuleSelfConvolutive, SelfConvRec
export L000698, L001710, L003319, L005411, L005412, L006012, L006318
export L047891, L062980, L082298, L082301, L082302, L105523, L107716
export L111529, L111530, L111531, L111532, L111533, L146559, L167872

"""
Some exactly solvable self-convolutive recurrences.

* SelfConvRec, L000698, L001710, L003319, L005411, L005412, L006012, L006318
L047891, L062980, L082298, L082301, L082302, L105523, L107716
L111529, L111530, L111531, L111532, L111533, L146559, L167872
"""
const ModuleSelfConvolutive = ""

# R. J. Martin and M. J. Kearney,
# An exactly solvable self-convolutive recurrence
# http://arXiv.org/abs/1103.4936
# Integral representation of certain combinatorial recurrences
# see also http://oeis.org/A172455

Convolution(A, n) = sum(A[j+1] * A[n-j+1] for j in 0:n)

"""
An exactly solvable self-convolutive recurrence studied by R. J. Martin and M. J. Kearney.
"""
function SelfConvRec(len, a, b, c)
    A = fill(ZZ(0), len)
    len == 0 && return A
    A[1] = 1

    for n in 1:len - 1
        A[n+1] = (a * (n + 1) + b) * A[n] + c * Convolution(A, n-1)
    end
    A
end

# """
# Return the powers of 2.
# """
# L000079(len::Int) = SelfConvRec(len, 1, 1, -1)
# """
# Return the Catalan numbers.
# """
# L000108(len::Int) = SelfConvRec(len, 0, 0, 1)
# """
# Return the factorial numbers.
# """
# L000142(len::Int) = SelfConvRec(len, 1, -1, 0)
# """
# Return the powers of 3.
# """
# L000244(len::Int) = SelfConvRec(len, 2, 1, -2)
# """
# Return the powers of 5.
# """
# L000351(len::Int) = SelfConvRec(len, 4, 1, -4)
# """
# Return the powers of 6.
# """
# L000400(len::Int) = SelfConvRec(len, 5, 1, -5)
# """
# Return the powers of 7.
# """
# L000420(len::Int) = SelfConvRec(len, 6, 1, -6)

"""
Return the number of indecomposable perfect matchings on ``[2n]``.
"""
L000698(len::Int) = SelfConvRec(len, 2, -3, 1)
"""
Return the order of alternating group ``A_n``, or number of even permutations of ``n`` letters.
"""
L001710(len::Int) = SelfConvRec(len, 1, 1, 0)

# """
# Return n!/3!.
# """
# L001715(len::Int) = SelfConvRec(len, 1, 2, 0)
# """
# Return n!/4!.
# """
# L001720(len::Int) = SelfConvRec(len, 1, 3, 0)
# """
# Return n!/5!.
# """
# L001725(len::Int) = SelfConvRec(len, 1, 4, 0)
# """
# Return n!/6!.
# """
# L001730(len::Int) = SelfConvRec(len, 1, 5, 0)

"""
Return the number of connected permutations of ``[1..n]``. Also called indecomposable permutations.
"""
L003319(len::Int) = SelfConvRec(len, 1, -2, 1)
"""
Return the number of non-vanishing Feynman diagrams of order ``2n`` for the electron or the photon propagators in quantum electrodynamics.
"""
L005411(len::Int) = SelfConvRec(len, 2, -4, 1)
"""
Return the number of non-vanishing Feynman diagrams of order ``2n`` for the vacuum polarization in quantum electrodynamics.
"""
L005412(len::Int) = SelfConvRec(len, 2, -2, 1)
"""
Counting some sets of permutations.
"""
L006012(len::Int) = SelfConvRec(len, -1, 2, 2)
"""
Return the large Schröder numbers.
"""
L006318(len::Int) = SelfConvRec(len, 0, 1, 1)
"""
Return the number of planar rooted trees with ``n`` nodes and tricolored end nodes.
"""
L047891(len::Int) = SelfConvRec(len, 0, 2, 1)
# """
# Return (n+7)!/7!.
# """
# L049388(len::Int) = SelfConvRec(len, 1, 6, 0)
# """
# Return (3*n+4)!!!/4!!!.
# """
# L051604(len::Int) = SelfConvRec(len, 3, 1, 0)
# """
# Return (3*n+5)!!!/5!!!.
# """
# L051605(len::Int) = SelfConvRec(len, 3, 2, 0)
# """
# Return (3*n+6)!!!/6!!!.
# """
# L051606(len::Int) = SelfConvRec(len, 3, 3, 0)
# """
# Return (3*n+7)!!!/7!!!.
# """
# L051607(len::Int) = SelfConvRec(len, 3, 4, 0)
# """
# Return (3*n+8)!!!/8!!!.
# """
# L051608(len::Int) = SelfConvRec(len, 3, 5, 0)
# """
# Return (3*n+9)!!!/9!!!.
# """
# L051609(len::Int) = SelfConvRec(len, 3, 6, 0)
# """
# Return (4*n+5)(!^4)/5(!^4).
# """
# L051617(len::Int) = SelfConvRec(len, 4, 1, 0)
# """
# Return  (4*n+6)(!^4)/6(!^4).
# """
# L051618(len::Int) = SelfConvRec(len, 4, 2, 0)
# """
# Return (4*n+7)(!^4)/7(!^4).
# """
# L051619(len::Int) = SelfConvRec(len, 4, 3, 0)
# """
# Return (4*n+8)(!^4)/8(!^4).
# """
# L051620(len::Int) = SelfConvRec(len, 4, 4, 0)
# """
# Return (4*n+9)(!^4)/9(!^4).
# """
# L051621(len::Int) = SelfConvRec(len, 4, 5, 0)
# """
# Return (4*n+10)(!^4)/10(!^4).
# """
# L051622(len::Int) = SelfConvRec(len, 4, 6, 0)
# """
# Return (5*n+6)(!^5)/6.
# """
# L051687(len::Int) = SelfConvRec(len, 5, 1, 0)
# """
# Return (5*n+7)(!^5)/7(!^5).
# """
# L051688(len::Int) = SelfConvRec(len, 5, 2, 0)
# """
# Return (5*n+8)(!^5)/8(!^5).
# """
# L051689(len::Int) = SelfConvRec(len, 5, 3, 0)
# """
# Return (5*n+9)(!^5)/9(!^5).
# """
# L051690(len::Int) = SelfConvRec(len, 5, 4, 0)
# """
# Return (5*n+10)(!^5)/10(!^5).
# """
# L051691(len::Int) = SelfConvRec(len, 5, 5, 0)
# """
# Return ((6*n+7)(!^6))/7.
# """
# L053100(len::Int) = SelfConvRec(len, 6, 1, 0)
# """
# Return ((6*n+8)(!^6))/8(!^6).
# """
# L053101(len::Int) = SelfConvRec(len, 6, 2, 0)
# """
# Return ((6*n+9)(!^6))/9(!^6).
# """
# L053102(len::Int) = SelfConvRec(len, 6, 3, 0)
# """
# Return ((6*n+10)(!^6))/10(!^6).
# """
# L053103(len::Int) = SelfConvRec(len, 6, 4, 0)
# """
# Return ((7*n+8)(!^7))/8.
# """
# L053104(len::Int) = SelfConvRec(len, 7, 1, 0)
# """
# Return ((7*n+9)(!^7))/9(!^7).
# """
# L053105(len::Int) = SelfConvRec(len, 7, 2, 0)
# """
# Return ((7*n+10)(!^7))/10(1^7).
# """
# L053106(len::Int) = SelfConvRec(len, 7, 3, 0)

"""
Return the number of rooted unlabeled connected triangular maps on a compact closed oriented surface with ``2n`` faces.
"""
L062980(len::Int) = SelfConvRec(len, 6, -8, 1)
"""
Return the number of lattice paths from ``(0,0)`` to ``(n+1,n+1)`` that consist of steps ``(i,0)`` and ``(0,j)`` with ``i,j≥1`` and stay strictly below the diagonal line ``y=x`` except at the endpoints.
"""
L082298(len::Int) = SelfConvRec(len, 0, 3, 1)
"""
Return the number of Schröder paths of semilength n in which the (2,0)-steps come in 4 colors.
"""
L082301(len::Int) = SelfConvRec(len, 0, 4, 1)
"""
Return the coefficients in the expansion of ``(1-5x-√(25x^2-14x+1))/(2x)``.
"""
L082302(len::Int) = SelfConvRec(len, 0, 5, 1)

# """
# Return the coefficients in the expansion of ``(1-6x-√(36x^2-16x+1))/(2x)``.
# """
# L082305(len::Int) = SelfConvRec(len, 0, 6, 1)
# """
# Return the coefficients in the expansion of ``(1-7x-√(49x^2-18x+1))/(2x)``.
# """
# L082366(len::Int) = SelfConvRec(len, 0, 7, 1)
# """
# Return the coefficients in the expansion of ``(1-8x-√(64x^2-20x+1))/(2*x)``.
# """
# L082367(len::Int) = SelfConvRec(len, 0, 8, 1)

"""
Return the coefficients in the expansion of ``(1+2x-√(1+4x^2))/(2x)``.
"""
L105523(len::Int) = SelfConvRec(len, 0, -2, 1)
"""
A transform the of triple factorial numbers.
"""
L107716(len::Int) = SelfConvRec(len, 3, -4, 1)
"""
A convolutory inverse of the factorial sequence.
"""
L111529(len::Int) = SelfConvRec(len, 1, -3, 2)
"""
A convolutory inverse of the factorial sequence.
"""
L111530(len::Int) = SelfConvRec(len, 1, -4, 3)
"""
A convolutory inverse of the factorial sequence.
"""
L111531(len::Int) = SelfConvRec(len, 1, -5, 4)
"""
A convolutory inverse of the factorial sequence.
"""
L111532(len::Int) = SelfConvRec(len, 1, -6, 5)
"""
A convolutory inverse of the factorial sequence.
"""
L111533(len::Int) = SelfConvRec(len, 1, -7, 6)

#"""
#"""
#L111546(len::Int) = SelfConvRec(len, 1, 0, 1)
#"""
#"""
#L111556(len::Int) = SelfConvRec(len, 1, 1, 1)

"""
Return the expansion of ``(1-x)/(1 - 2x + 2x^2)``.
"""
L146559(len::Int) = SelfConvRec(len, 1, 1, -2)
"""
Return half the number of Feynman diagrams of order ``2(n+1)``, for the electron self-energy in quantum electrodynamics.
"""
L167872(len::Int) = SelfConvRec(len, 2, -3, 2)

# """
# """
# L172450(len::Int) = SelfConvRec(len, 2, 0, -1)
# """
# """
# L172455(len::Int) = SelfConvRec(len, 6, -4, -1)
# """
# """
# L172485(len::Int) = SelfConvRec(len, -1, -2, 3)
# """
# """
# L177354(len::Int) = SelfConvRec(len, 1, 2, 1)

#START-TEST-########################################################
using Test, SeqTests

function test()
    @testset "SelfConvRec" begin
        if is_oeis_installed()

            L = [L005411, L005412, L006012, L006318, L047891, L062980, L082298,
                L082301, L082302, L105523, L107716, L111529, L111530, L111531,
                L111532, L111533, L146559, L167872]
            SeqTest(L, 'L')
        end
    end
end

function demo()

    for n in 0:6 println(n, " ↦ ", L005411(n)) end

    f(len) = SelfConvRec(len, 2, -1, -1)
    println(f(11))
end

"""
L005411(500) :: 0.238653 seconds (252.99 k allocations: 3.887 MB)
"""
function perf()
    GC.gc()
    @time L005411(500)
end

function main()
    test()
    demo()
    perf()
end

main()

end # module

#=
L --> A000079
Nemo.fmpz[1, 2, 4, 8, 16, 32]
Nemo.fmpz[1, 2, 4, 8, 16, 32]
L --> A000108
Nemo.fmpz[1, 1, 2, 5, 14, 42]
Nemo.fmpz[1, 1, 2, 5, 14, 42]
L --> A000142
Nemo.fmpz[1, 1, 2, 6, 24, 120]
Nemo.fmpz[1, 1, 2, 6, 24, 120]
L --> A000244
Nemo.fmpz[1, 3, 9, 27, 81, 243]
Nemo.fmpz[1, 3, 9, 27, 81, 243]
L --> A000351
Nemo.fmpz[1, 5, 25, 125, 625, 3125]
Nemo.fmpz[1, 5, 25, 125, 625, 3125]
L --> A000400
Nemo.fmpz[1, 6, 36, 216, 1296, 7776]
Nemo.fmpz[1, 6, 36, 216, 1296, 7776]
L --> A000420
Nemo.fmpz[1, 7, 49, 343, 2401, 16807]
Nemo.fmpz[1, 7, 49, 343, 2401, 16807]
#############################
L --> A000698
Nemo.fmpz[1, 1, 2, 10, 74, 706]
Nemo.fmpz[1, 2, 10, 74, 706, 8162]
#####################
seq((n+2)!/2, n=0..9);
L --> A001710
Nemo.fmpz[1, 1, 1, 3, 12, 60]
Nemo.fmpz[1, 3, 12, 60, 360, 2520]
######################
L --> A001715
Nemo.fmpz[1, 4, 20, 120, 840, 6720]
Nemo.fmpz[1, 4, 20, 120, 840, 6720]
L --> A001720
Nemo.fmpz[1, 5, 30, 210, 1680, 15120]
Nemo.fmpz[1, 5, 30, 210, 1680, 15120]
L --> A001725
Nemo.fmpz[1, 6, 42, 336, 3024, 30240]
Nemo.fmpz[1, 6, 42, 336, 3024, 30240]
L --> A001730
Nemo.fmpz[1, 7, 56, 504, 5040, 55440]
Nemo.fmpz[1, 7, 56, 504, 5040, 55440]
##################
L --> A003319
Nemo.fmpz[0, 1, 1, 3, 13, 71]
Nemo.fmpz[1, 1, 3, 13, 71, 461]
##################
L --> A005411
Nemo.fmpz[1, 1, 4, 25, 208, 2146]
Nemo.fmpz[1, 1, 4, 25, 208, 2146]
L --> A005412
Nemo.fmpz[1, 3, 18, 153, 1638, 20898]
Nemo.fmpz[1, 3, 18, 153, 1638, 20898]
L --> A006012
Nemo.fmpz[1, 2, 6, 20, 68, 232]
Nemo.fmpz[1, 2, 6, 20, 68, 232]
L --> A006318
Nemo.fmpz[1, 2, 6, 22, 90, 394]
Nemo.fmpz[1, 2, 6, 22, 90, 394]
L --> A047891
Nemo.fmpz[1, 3, 12, 57, 300, 1686]
Nemo.fmpz[1, 3, 12, 57, 300, 1686]
L --> A049388
Nemo.fmpz[1, 8, 72, 720, 7920, 95040]
Nemo.fmpz[1, 8, 72, 720, 7920, 95040]
L --> A051604
Nemo.fmpz[1, 7, 70, 910, 14560, 276640]
Nemo.fmpz[1, 7, 70, 910, 14560, 276640]
L --> A051605
Nemo.fmpz[1, 8, 88, 1232, 20944, 418880]
Nemo.fmpz[1, 8, 88, 1232, 20944, 418880]
L --> A051606
Nemo.fmpz[1, 9, 108, 1620, 29160, 612360]
Nemo.fmpz[1, 9, 108, 1620, 29160, 612360]
L --> A051607
Nemo.fmpz[1, 10, 130, 2080, 39520, 869440]
Nemo.fmpz[1, 10, 130, 2080, 39520, 869440]
L --> A051608
Nemo.fmpz[1, 11, 154, 2618, 52360, 1204280]
Nemo.fmpz[1, 11, 154, 2618, 52360, 1204280]
L --> A051609
Nemo.fmpz[1, 12, 180, 3240, 68040, 1632960]
Nemo.fmpz[1, 12, 180, 3240, 68040, 1632960]
L --> A051617
Nemo.fmpz[1, 9, 117, 1989, 41769, 1044225]
Nemo.fmpz[1, 9, 117, 1989, 41769, 1044225]
L --> A051618
Nemo.fmpz[1, 10, 140, 2520, 55440, 1441440]
Nemo.fmpz[1, 10, 140, 2520, 55440, 1441440]
L --> A051619
Nemo.fmpz[1, 11, 165, 3135, 72105, 1946835]
Nemo.fmpz[1, 11, 165, 3135, 72105, 1946835]
L --> A051620
Nemo.fmpz[1, 12, 192, 3840, 92160, 2580480]
Nemo.fmpz[1, 12, 192, 3840, 92160, 2580480]
L --> A051621
Nemo.fmpz[1, 13, 221, 4641, 116025, 3364725]
Nemo.fmpz[1, 13, 221, 4641, 116025, 3364725]
L --> A051622
Nemo.fmpz[1, 14, 252, 5544, 144144, 4324320]
Nemo.fmpz[1, 14, 252, 5544, 144144, 4324320]
L --> A051687
Nemo.fmpz[1, 11, 176, 3696, 96096, 2978976]
Nemo.fmpz[1, 11, 176, 3696, 96096, 2978976]
L --> A051688
Nemo.fmpz[1, 12, 204, 4488, 121176, 3877632]
Nemo.fmpz[1, 12, 204, 4488, 121176, 3877632]
L --> A051689
Nemo.fmpz[1, 13, 234, 5382, 150696, 4972968]
Nemo.fmpz[1, 13, 234, 5382, 150696, 4972968]
L --> A051690
Nemo.fmpz[1, 14, 266, 6384, 185136, 6294624]
Nemo.fmpz[1, 14, 266, 6384, 185136, 6294624]
L --> A051691
Nemo.fmpz[1, 15, 300, 7500, 225000, 7875000]
Nemo.fmpz[1, 15, 300, 7500, 225000, 7875000]
L --> A053100
Nemo.fmpz[1, 13, 247, 6175, 191425, 7082725]
Nemo.fmpz[1, 13, 247, 6175, 191425, 7082725]
L --> A053101
Nemo.fmpz[1, 14, 280, 7280, 232960, 8852480]
Nemo.fmpz[1, 14, 280, 7280, 232960, 8852480]
L --> A053102
Nemo.fmpz[1, 15, 315, 8505, 280665, 10945935]
Nemo.fmpz[1, 15, 315, 8505, 280665, 10945935]
L --> A053103
Nemo.fmpz[1, 16, 352, 9856, 335104, 13404160]
Nemo.fmpz[1, 16, 352, 9856, 335104, 13404160]
L --> A053104
Nemo.fmpz[1, 15, 330, 9570, 344520, 14814360]
Nemo.fmpz[1, 15, 330, 9570, 344520, 14814360]
L --> A053105
Nemo.fmpz[1, 16, 368, 11040, 408480, 17973120]
Nemo.fmpz[1, 16, 368, 11040, 408480, 17973120]
L --> A053106
Nemo.fmpz[1, 17, 408, 12648, 480624, 21628080]
Nemo.fmpz[1, 17, 408, 12648, 480624, 21628080]
L --> A062980
Nemo.fmpz[1, 5, 60, 1105, 27120, 828250]
Nemo.fmpz[1, 5, 60, 1105, 27120, 828250]
L --> A082298
Nemo.fmpz[1, 4, 20, 116, 740, 5028]
Nemo.fmpz[1, 4, 20, 116, 740, 5028]
L --> A082301
Nemo.fmpz[1, 5, 30, 205, 1530, 12130]
Nemo.fmpz[1, 5, 30, 205, 1530, 12130]
L --> A082302
Nemo.fmpz[1, 6, 42, 330, 2814, 25422]
Nemo.fmpz[1, 6, 42, 330, 2814, 25422]
L --> A082305
Nemo.fmpz[1, 7, 56, 497, 4760, 48174]
Nemo.fmpz[1, 7, 56, 497, 4760, 48174]
L --> A082366
Nemo.fmpz[1, 8, 72, 712, 7560, 84616]
Nemo.fmpz[1, 8, 72, 712, 7560, 84616]
L --> A082367
Nemo.fmpz[1, 9, 90, 981, 11430, 140058]
Nemo.fmpz[1, 9, 90, 981, 11430, 140058]
L --> A105523
Nemo.fmpz[1, -1, 0, 1, 0, -2]
Nemo.fmpz[1, -1, 0, 1, 0, -2]
L --> A107716
Nemo.fmpz[1, 3, 21, 219, 2973, 49323]
Nemo.fmpz[1, 3, 21, 219, 2973, 49323]
L --> A111529
Nemo.fmpz[1, 1, 4, 22, 148, 1156]
Nemo.fmpz[1, 1, 4, 22, 148, 1156]
L --> A111530
Nemo.fmpz[1, 1, 5, 33, 261, 2361]
Nemo.fmpz[1, 1, 5, 33, 261, 2361]
L --> A111531
Nemo.fmpz[1, 1, 6, 46, 416, 4256]
Nemo.fmpz[1, 1, 6, 46, 416, 4256]
L --> A111532
Nemo.fmpz[1, 1, 7, 61, 619, 7045]
Nemo.fmpz[1, 1, 7, 61, 619, 7045]
L --> A111533
Nemo.fmpz[1, 1, 8, 78, 876, 10956]
Nemo.fmpz[1, 1, 8, 78, 876, 10956]
L --> A111546
Nemo.fmpz[1, 3, 15, 99, 783, 7083]
Nemo.fmpz[1, 3, 15, 99, 783, 7083]
L --> A111556
Nemo.fmpz[1, 4, 24, 184, 1664, 17024]
Nemo.fmpz[1, 4, 24, 184, 1664, 17024]
L --> A146559
Nemo.fmpz[1, 1, 0, -2, -4, -4]
Nemo.fmpz[1, 1, 0, -2, -4, -4]
L --> A167872
Nemo.fmpz[1, 3, 21, 207, 2529, 36243]
Nemo.fmpz[1, 3, 21, 207, 2529, 36243]
L --> A172450
Nemo.fmpz[1, 3, 12, 63, 432, 3798]
Nemo.fmpz[1, 3, 12, 63, 432, 3798]
L --> A172485
Nemo.fmpz[1, -1, -1, 3, 3, -21]
Nemo.fmpz[1, -1, -1, 3, 3, -21]
#################################
L --> A177354
Nemo.fmpz[5, 35, 305, 3095, 35225, 439775]
Nemo.fmpz[1, 5, 35, 305, 3095, 35225]
###########################

L = [ L000079, L000108, L000142, L000244, L000351, L000400,
 L000420, L001715, L001720, L001725, L001730,
 L005411, L005412, L006012, L006318, L047891, L049388, L051604,
 L051605, L051606, L051607, L051608, L051609, L051617, L051618,
 L051619, L051620, L051621, L051622, L051687, L051688, L051689,
 L051690, L051691, L053100, L053101, L053102, L053103, L053104,
 L053105, L053106, L062980, L082298, L082301, L082302, L082305,
 L082366, L082367, L105523, L107716, L111529, L111530, L111531,
 L111532, L111533, L111546, L111556, L146559, L167872,
 L172450, L172485 ]
# L000698, L001710, L003319, L177354
SeqTest(L, 'L')

=#


struct SSHBinner
    thetas::Vector{Float64}
    phi_divisions::Vector{Int64}

    # bin_many helpers
    zs::Vector{Float64}
    phi_N_over_2pi::Vector{Float64}
    cumsum::Vector{Int64}
    N_thetas::Float64

    bins::Vector{Int64}
end

################################################################################
### Constructor
################################################################################

function SSHBinner(thetas::Vector{Float64}, phi_divisions::Vector{Int64})
    # Precalculate N/2pi for bin_many!
    # use slightly more than 2pi to guarantee phi * phi_N_over_pi ∈ [0, 1)
    PI_PLUS = 2.0pi + 1e-14
    phi_N_over_2pi = phi_divisions ./ PI_PLUS

    # Precalculate z(θ) for bin_many!
    # move slightly to negative values to guarantee that for an input value
    # value[3] >= minimum(zs)
    zs = cos.(thetas) #.- 1e-14 # 1...-1
    zs[end] -= 1e-14

    # Also precalculate cumulative number of bins for each theta (from 0..pi)
    _cumsum = cumsum(phi_divisions)

    # to avoid conversions to Float64
    N_thetas = Float64(length(thetas)-1)

    N_bins = sum(phi_divisions)
    bins = zeros(Int64, N_bins)
    SSHBinner(thetas, phi_divisions, zs, phi_N_over_2pi, _cumsum, N_thetas, bins)
end

"""
    SSHBinner(N_bins)

Generate a Sphere Surface Histogram Binner with approximately N_bins.
"""
SSHBinner(N_bins::Real; kwargs...) = SSHBinner(round(Int64, N_bins); kwargs...)
function SSHBinner(N_bins::Int64; method=partition_sphere2)
    thetas, phi_divisions = partition_sphere_optim(4pi/N_bins, method=method)
    SSHBinner(thetas, phi_divisions)
end

"""
    SSHBinner(bins)

Generate a Sphere Surface Histogram Binner using the `bins` of another
SSHBinner. This effectively creates a copy of the other source `SSHBinner`.
"""
function SSHBinner(bins::Vector; method=partition_sphere2)
    N_bins = length(bins)
    thetas, phi_divisions = method(4pi/N_bins)

    PI_PLUS = 2.0pi + 1e-14
    phi_N_over_2pi = phi_divisions ./ PI_PLUS
    zs = cos.(thetas)
    zs[end] -= 1e-14
    _cumsum = cumsum(phi_divisions)
    N_thetas = Float64(length(thetas)-1)

    SSHBinner(thetas, phi_divisions, zs, phi_N_over_2pi, _cumsum, N_thetas, bins)
end

"""
    partition_sphere2(dA)

Partitions a sphere into bins of size `dA` where number of divisions in ϕ
(0..2π) is restricted to a power of 2.

Note that for a random `dA` this will not produce equally sized bins.
(Specifically bins at z ≈ 0 will be wrong.) The algorithm relies on
`partition_sphere_optim` to optimize `dA` to a value fitting on the sphere
surface which happens during the creation of an `SSHBinner`.
"""
function partition_sphere2(dA)
    # Use symmetry - only 0..pi/2 needed (± some delta if center of dA @ pi/2)

    # Starting at some point theta1 with width dphi,
    # computes theta2 for an area dA
    next_theta(dA, theta1, dphi) = acos(max(min(cos(theta1) - dA / dphi, 1.0), -1.0))

    # First area
    # This will now be our goal theta-width
    dtheta_goal = next_theta(dA, 0.0, 2pi)

    # Devide 2pi by phi_div, such that phi = 2pi/n with n integer
    phi_divs = Int64[1]
    thetas = Float64[0.0, dtheta_goal]

    # Currently radius of circle, should be sidelength of square
    # pi r^2 = x^2 => sqrt(pi)r = x
    dtheta_goal *= sqrt(pi)

    while true
        phi_div = last(phi_divs)
        theta1 = last(thetas)

        # Find best dtheta
        dtheta = next_theta(dA, theta1, 2pi / phi_div) - theta1
        best_dtheta = dtheta
        while true
            phi_div *= 2
            dtheta = next_theta(dA, theta1, 2pi / phi_div) - theta1
            if abs(dtheta - dtheta_goal) < abs(best_dtheta - dtheta_goal)
                # New dtheta better? Keep it and keep trying
                best_dtheta = dtheta
            else
                # new dtheta worse? undo last phi_div increment and stop
                phi_div = div(phi_div, 2)
                break
            end
        end

        if (theta1 + best_dtheta / 2.0) > pi/2
            break
        else
            push!(thetas, theta1 + best_dtheta)
            push!(phi_divs, phi_div)
        end
    end

    dtheta = thetas[end] - thetas[end-1]
    theta_fourth = thetas[end-1] + 3dtheta/4
    if theta_fourth > pi/2
        # center should be flush
        thetas[end] = pi - thetas[end-1]
        for i in length(thetas)-2:-1:1
            push!(thetas, pi - thetas[i])
            push!(phi_divs, phi_divs[i])
        end
    else
        # last(thetas) should be flush
        thetas[end] = pi/2
        for i in length(thetas)-1:-1:1
            push!(thetas, pi - thetas[i])
            push!(phi_divs, phi_divs[i])
        end
    end

    return thetas, phi_divs
end

"""
    partition_sphere1(dA)

Partitions a sphere into bins of size `dA` where number of divisions in ϕ
(0..2π) is not restricted, i.e. it can be any integer.

Note that for a random `dA` this will not produce equally sized bins.
(Specifically bins at z ≈ 0 will be wrong.) The algorithm relies on
`partition_sphere_optim` to optimize `dA` to a value fitting on the sphere
surface which happens during the creation of an `SSHBinner`.
"""
function partition_sphere1(dA,)
    next_theta(dA, theta1, dphi) = acos(clamp(cos(theta1) - dA / dphi, -1.0, 1.0))

    # First area
    # This will now be our goal theta-width
    dtheta_goal = next_theta(dA, 0.0, 2pi)

    # Devide 2pi by phi_div, such that phi = 2pi/n with n integer
    phi_divs = Int64[1]
    thetas = Float64[0.0, dtheta_goal]

    # Currently radius of circle, should be sidelength of square
    # pi r^2 = x^2 => sqrt(pi)r = x
    dtheta_goal *= sqrt(pi)

    while true
        phi_div = last(phi_divs)
        theta1 = last(thetas)

        # Find best dtheta
        dtheta = next_theta(dA, theta1, 2pi / phi_div) - theta1
        best_dtheta = dtheta
        while true
            phi_div += 1
            dtheta = next_theta(dA, theta1, 2pi / phi_div) - theta1
            if abs(dtheta - dtheta_goal) < abs(best_dtheta - dtheta_goal)
                # New dtheta better? Keep it and keep trying
                best_dtheta = dtheta
            else
                # new dtheta worse? undo last phi_div increment and stop
                phi_div -= 1
                break
            end
        end

        if (theta1 + best_dtheta / 2.0) > pi/2
            break
        else
            push!(thetas, theta1 + best_dtheta)
            push!(phi_divs, phi_div)
        end
    end

    dtheta = thetas[end] - thetas[end-1]
    theta_fourth = thetas[end-1] + 3dtheta/4
    if theta_fourth > pi/2
        # center should be flush
        thetas[end] = pi - thetas[end-1]
        for i in length(thetas)-2:-1:1
            push!(thetas, pi - thetas[i])
            push!(phi_divs, phi_divs[i])
        end
    else
        # last(thetas) should be flush
        thetas[end] = pi/2
        for i in length(thetas)-1:-1:1
            push!(thetas, pi - thetas[i])
            push!(phi_divs, phi_divs[i])
        end
    end

    return thetas, phi_divs
end

"""
    partition_sphere_optim(dA[; max_iter = 10, method = partition_sphere2])

Optimizes the sphere partitioning by modifying dA (~ the number of bins). After
a few iterations, dA becomes consistent for all bins.
"""
function partition_sphere_optim(dA; max_iter = 10, method=partition_sphere2)
    # NOTE this converges really fast

    new_dA = dA
    N = round(Int64, 4pi/dA)
    _iterations = 0
    for i in 1:max_iter
        _iterations = i
        thetas, phi_divs = method(new_dA)
        next_N = sum(phi_divs)
        if N == next_N
            break
        else
            N = next_N
            new_dA = 4pi / N
        end
    end

    method(new_dA)
end

################################################################################
### binning (push, append)
################################################################################

@fastmath @inline function fast_theta_index_approximation(x, l::Float64)
    # trunc faster than floor
    # Brackets cause llvm code to be `fmul` rather than `afoldl`, much
    # faster (~100x according to BenchmarkTools)
    # equivalent to
    # trunc(Int64, l*(0.5 - (7x + x^5 + x^9 + x^17 + x^31) / 22)
    # which is ≈ trunc(Int64, l*acos(x))
    trunc(Int64,
        l * (0.5 - x*(
            0.3181818181818182 + ((((x*x)*x)*x)*0.045454545454545456)*(
                1.0 + (((x*x)*x)*x) * (
                    1.0 + (((((((x*x)*x)*x)*x)*x)*x)*x)*(
                        1.0 + (((((((((((((x*x)*x)*x)*x)*x)*x)*x)*x)*x)*x)*x)*x)*x)
                    )
                )
            )
        ))
    ) + 1
end

# Tested for 10, 1000, 10_000_000 bins, each with 1_000_000 random spins pushed
@inline function fast_theta_index_search(B::SSHBinner, value)
    index = fast_theta_index_approximation(value, B.N_thetas)
    if value > B.zs[index]
        while true
            index -= 1
            value <= B.zs[index] && (return index)
        end
    elseif B.zs[index] >= value
        while true
            index += 1
            B.zs[index] < value && (return index-1)
        end
    end
    return index
end

"""
    push!(B::SSHBinner, value)

Bins a single value (three dimensional unit vector).
"""
function Base.push!(B::SSHBinner, value)
    theta_index = fast_theta_index_search(B, value[3])
    phi_index = if B.phi_divisions[theta_index] == 1
        1
    else
        # Normalize xy-component
        # y >= 0 for 0..pi, else y < 0
        # acos(x) well defined for 0..pi
        R = sqrt(value[1]^2 + value[2]^2)
        phi = if value[2] >= 0.0
            acos(value[1]/R)
        else
            2.0pi - acos(value[1]/R)
        end

        trunc(Int64, phi * B.phi_N_over_2pi[theta_index]) + 1
    end

    # Calculate position in bins
    l = theta_index > 1 ? B.cumsum[theta_index-1] : 0
    B.bins[l+phi_index] += 1
    nothing
end

"""
    append!(B::SSHBinner, values)

Bins an array of values (three dimensional unit vectors).
"""
function Base.append!(B::SSHBinner, values)
    for v in values
        push!(B, v)
    end
    nothing
end

"""
    unsafe_append!(B::SSHBinner, values)

Bins an arra of values (three dimensional unit vector) without boundschecks.
"""
function unsafe_append!(B::SSHBinner, values)
    for v in values
        @inbounds push!(B, v)
    end
    nothing
end


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------------------------
"""
    mes = meshmeasures(p, t)

compute areas of every element
"""
function meshareas(p, t)
    d12 = p[t[:, 2], :] - p[t[:, 1], :]
    d13 = p[t[:, 3], :] - p[t[:, 1], :]
    mes = (d12[:, 1] .* d13[:, 2] .- d12[:, 2] .* d13[:, 1]) / 2.
    return reshape(abs.(mes), length(mes))
end
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------------------------
"""
   I, J = indKM_sparse(t)

non zero indices in stiffness and mass P1 sparse matrices
"""
function indKM_sparse(t)
    it1, it2, it3 = t[:, 1], t[:, 2], t[:, 3]
    I = vcat(it1, it2, it3, it2, it3, it1, it1, it2, it3)
    J = vcat(it2, it3, it1, it1, it2, it3, it1, it2, it3)
    return I, J
end
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------------------------
"""
     SK, SM = assembKM_P12D(p, t)

assemble the P1 stifness and mass matrices coefficients.
"""
function assembKM_P12D(p, t)
    np, dim, nt = size(p, 1), size(p, 2), size(t, 1)
    # compute ar add define arK and arM (not used ?)
    ar = meshareas(p, t)
    arK, arM = ar, ar
    it1, it2, it3 = t[:, 1], t[:, 2], t[:, 3]
    g1x, g1y, g2x, g2y, g3x, g3y = pdetrg(p, t, ar)
    c3 = (g1x .* g2x .+ g1y .* g2y) .* arK
    c1 = (g2x .* g3x .+ g2y .* g3y) .* arK
    c2 = (g3x .* g1x .+ g3y .* g1y) .* arK
    SK = vcat(c3, c1, c2, c3, c1, c2, -c2 - c3, -c3 - c1, -c1 - c2)
    # mass matrix
    aod = arM / 12. # off diagonal element
    ad = 2 * aod    # diagonal element
    SM = vcat(aod, aod, aod, aod, aod, aod, ad, ad, ad)
    return SK, SM
end
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-------------------------
"""
     nothing = assembK_P12D_inplace!(p, t, S)

assemble the P1 stifness matrice coefficients in place. Useful for SparseDiffTools.
"""
function assembK_P12D_inplace!(pin, t, S)
    p = if typeof(pin) <: Vector
        reshape(pin, length(pin) ÷ 2, 2)
    else
        pin
    end
    np, dim, nt = size(p, 1), size(p, 2), size(t, 1)
    # compute ar add define arK and arM (not used ?)
    ar = meshareas(p, t)
    arK, arM = ar, ar
    it1, it2, it3 = t[:, 1], t[:, 2], t[:, 3]
    g1x, g1y, g2x, g2y, g3x, g3y = pdetrg(p, t, ar)
    c3 = (g1x .* g2x .+ g1y .* g2y) .* arK
    c1 = (g2x .* g3x .+ g2y .* g3y) .* arK
    c2 = (g3x .* g1x .+ g3y .* g1y) .* arK
    SK = vcat(c3, c1, c2, c3, c1, c2, -c2 - c3, -c3 - c1, -c1 - c2)
    copy!(S, SK)
    return nothing
end
"""
     nothing = assembM_P12D_inplace!(p, t, S)

assemble the P1 mass matrice coefficients in place. Useful for SparseDiffTools.
"""
function assembM_P12D_inplace!(pin, t, S)
    p = if typeof(pin) <: Vector
        reshape(pin, length(pin) ÷ 2, 2)
    else
        pin
    end
    np, dim, nt = size(p, 1), size(p, 2), size(t, 1)
    # compute ar add define arK and arM (not used ?)
    arM = meshareas(p, t)
    aod = arM / 12. # off diagonal element
    ad = 2 * aod    # diagonal element
    SM = vcat(aod, aod, aod, aod, aod, aod, ad, ad, ad)
    copy!(S, SM)
    return nothing
end
#-------------------------------------------------------------------------------
"""
   g1x, g1y, g2x, g2y, g3x, g3y = pdetrg(p, t, ar)

returns the  the gradient components of the triangle base functions
"""
function pdetrg(p, t, ar)
    # corner point indices
    a1, a2, a3 =  t[:, 1], t[:, 2], t[:, 3]
    # triangle sides
    r23x =  p[a3, 1] .- p[a2, 1]
    r23y =  p[a3, 2] .- p[a2, 2]
    r31x =  p[a1, 1] .- p[a3, 1]
    r31y =  p[a1, 2] .- p[a3, 2]
    r12x =  p[a2, 1] .- p[a1, 1]
    r12y =  p[a2, 2] .- p[a1, 2]
    g1x =  - 0.5 .* r23y ./ ar
    g1y =    0.5 .* r23x ./ ar
    g2x =  - 0.5 .* r31y ./ ar
    g2y =    0.5 .* r31x ./ ar
    g3x =  - 0.5 .* r12y ./ ar
    g3y =    0.5 .* r12x ./ ar
    return g1x, g1y, g2x, g2y, g3x, g3y
end
#-------------------------------------------------------------------------------
"""
    be = btri(T::Array{Int64, 2})

finds boundary edges
"""
function btri(T::Array{Int64, 2})
    # form all faces, non - duplicates are surface triangles
    faces = vcat(T[:, [1, 2]], T[:, [1, 3]], T[:, [2, 3]])
    faces = sort(faces, dims = 2)
    _, ix, jx = uniquerows_int(faces)
    ee = collect(1:(maximum(jx) + 1)) .- 1e-8
    result = fit(Histogram, jx, (1:(maximum(jx) + 1)) .- 1e-8,
                 closed = :right)
    vec = result.weights
    qx = findall(x -> x != 0, vec .- 2)
    return faces[ix[qx], :]
end
#-------------------------------------------------------------------------------
function uniquerows_int(x::Matrix{Int64})
    Vx = [x[k, :] for k = 1:size(x, 1)]
    jj = groupslices(Vx)
    ix, jx = firstinds(jj), similar(jj)
    for k = 1:length(ix)
        jx[jj .== ix[k]] .= k
    end
    Vy = Vx[ix]
    y = [Vy[k][l] for k = 1:length(Vy), l = 1:length(Vy[1])]
    return y, ix, jx
end


function rosenbrock2d(x)
  return (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
end

facts("bboptimize smoketest") do

 context("test each method option in short runs, just to ensure they work") do

   for(m in keys(BlackBoxOptim.ValidMethods))
     b, f = bboptimize(rosenbrock2d; method = m,
      search_space = [(-5.0, 5.0), (-2.0, 2.0)], max_time = 0.3,
      parameters = {:ShowTrace => false})

     # println("Fitness for $m: $f")

     @fact size(b) => (1, 2)
     @fact typeof(f) => Float64
     @fact f < 100.0 => true # this can't be very tight since we give very little time for optimization...
   end

 end

end


module Shannon

using StatsBase

include("distribution.jl")
include("entropy.jl")
include("binning.jl")
include("mi.jl")
include("te.jl")
include("mc.jl")

export KL, PI, MI, TE, MC

export entropy

export bin_vector, bin_matrix, bin_value
export unbin_value, unbin_matrix, unbin_vector
export combine_binned_matrix, combine_binned_vector
export combine_and_relabel_binned_matrix
export unary_of_matrix
export relabel

function KL(p::Vector{Float64}, q::Vector{Float64}; base=2)
  @assert (length(p) == length(q)) "Size mismatch"
  sum([ (p[i] != 0 && q[i] != 0)? p[i] * log(base, p[i]/q[i]) : 0 for i=1:length(p)])
end

end # module


using LinearAlgebra
using Combinatorics
using QuantumInformation
using SparseArrays

function superoperator(N, atol=1e-3)
    """
        Returns the superoperator of the channel:
            ϕ(Y) = ∫_U dU (U* ⊗ U^{⊗N}) Y (U* ⊗ U^{⊗N})^†
    """

    P = zeros(2^(2N+2), 2^(2N+2))
    idx = CartesianIndices(Tuple(fill(2, N+1)))

    for perm in permutations(1:N+1)
        x = zeros(fill(2, 2N+2)...)
        for k1 in idx
            k0 = CartesianIndex(Tuple(k1)[perm])
            x[k0, k1] = 1
        end
        x = vec(x)
        P += x * x'
    end

    F = eigen(P)
    Λ = Diagonal([x > atol ? 1 : 0 for x in F.values])
    P = F.vectors * Λ * F.vectors'

    sparse(permutesystems(P, [2, 2^N, 2, 2^N], [3, 2, 1, 4]))
end

function diff_q_op(N, atol=1e-03)
    """
        Returns the dynamical matrix of the difference of channels:
            ϕ_0(X) = ∫_U dU (U* ⊗ U^{⊗N}) (|0><0| ⊗ X) (U* ⊗ U^{⊗N})^†
            ϕ_1(X) = ∫_U dU (U* ⊗ U^{⊗N}) (|1><1| ⊗ X) (U* ⊗ U^{⊗N})^†
    """

    S = superoperator(N, atol)
    Φ_0 = S * (ket(1,2) ⊗ I(2^N) ⊗ ket(1,2) ⊗ I(2^N))
    Φ_1 = S * (ket(2,2) ⊗ I(2^N) ⊗ ket(2,2) ⊗ I(2^N))
    return DynamicalMatrix(reshuffle(Φ_0 - Φ_1), 2^N, 2^(N+1))
end

function calculate_p_opt(N, atol=1e-03)
    """
        Calulate the maximal value of probability (p_opt) of discrimination of
        channels:
            ϕ_0(X) = ∫_U dU (U* ⊗ U^{⊗N}) (|0><0| ⊗ X) (U* ⊗ U^{⊗N})^†
            ϕ_1(X) = ∫_U dU (U* ⊗ U^{⊗N}) (|1><1| ⊗ X) (U* ⊗ U^{⊗N})^†

        p_opt = 0.5 + 0.25 * |||ϕ_0 - ϕ_1|||,
        where ||| ⋅ ||| is the diamond norm.
    """

    1/2 + 1/4 * norm_diamond(diff_q_op(N), Val(:primal), atol)
end

function calculate_p_opt_dual(N, atol=1e-03)
    """
        Calulate the dual value for calculate_p_opt(N)
    """

    1/2 + 1/4 * norm_diamond(diff_q_op(N), Val(:dual), atol)
end

N = 1
print(calculate_p_opt_dual(N), " ≤ p_opt ≤ ", calculate_p_opt(N))


using neutrinos
using Test

@testset "neutrinos.jl" begin

    #----- Basic tests for computing the matter mixing angle

    # Test basic vacuum angles. Start with with theta = 0, s.t. tan(2*theta)
    @test 0.0 == neutrinos.getMatterMixingAngle(0.0,0.0);

    ## If the coupling strength is zero, the denominator is 1.0. At 2theta = pi/4.0, the numerator is also 1 such that the result is theta_m = pi/8, which correctly matches the vacuum angle.
    @test isapprox(neutrinos.getMatterMixingAngle(pi/8.0,0.0), pi/8.0; atol=1.0e-5);

    # There is an analytical result nearly as simple as the above, but with coupling strength equal to 1.0. In this case, the denominator is 1 - sqrt(2.0).
    @test isapprox(neutrinos.getMatterMixingAngle(pi/8.0,1.0), 0.5*atan(1.0/(1.0-sqrt(2.0))); atol=1.0e-5);

    ## This version exploits the sqrt(3.0) produced by cos(pi/6.0) (which is just cos(2theta) for theta = pi/12).

    #Really not sure where this one came from...

    @test isapprox(neutrinos.getMatterMixingAngle(pi/12.0,sqrt(3.0)), 0.5*atan(-0.5); atol=1.0e-5);

    ## Problem 12.6 in Guidry's stars book presents an alternative formulation for the mixing angle based on sin that can be used to make the comparsion here.
    twoTheta = 1.0;
    chi = 0.5;
    thetaM_fromSin = 0.5*asin(sin(twoTheta)/(sqrt(1.0-2.0*chi*cos(twoTheta)+chi*chi)));
    @test isapprox(thetaM_fromSin,neutrinos.getMatterMixingAngle(twoTheta/2.0,chi); atol=1.0e-10);

    #----- Basic tests for computing the coupling strength

    # Test zero case. Yeah, this means that the neutrinos have no energy and that there are no electrons in the universe! :-P
    @test 0.0 == neutrinos.getCouplingStrength(0.0,0.0);

    # Test the case where either the neutrino energy is zero or the electron number density is zero. Both cases result in no coupling strength.
    @test 0.0 == neutrinos.getCouplingStrength(0.0,5.0);
    @test 0.0 == neutrinos.getCouplingStrength(5.0,0.0);

    # If the energy is equal to deltaMSquared/(2sqrt(2)*fermiConst), then the coupling strength is equal to the electron number density.
    @test isapprox( neutrinos.getCouplingStrength((neutrinos.deltaMSquared/(2.0*sqrt(2.0)*neutrinos.fermiConst)),1.0),1.0; atol=1.0e-5);

    # If the energy and the electron density are both one, the coupling strength is just 2sqrt(2)fermiConst/deltaMSquared.
    @test isapprox( neutrinos.getCouplingStrength(1.0,1.0), (2.0*sqrt(2.0)*neutrinos.fermiConst/neutrinos.deltaMSquared); atol=1.0e-5);

    # I made this one up using the defaults and r/R = 0.0 for the number density. It uses chi = L/l_m directly.
    l_m = sqrt(2.0)*pi/(neutrinos.fermiConst*1.475e26);
    L = 4.0*pi*neutrinos.energy/neutrinos.deltaMSquared;
    chi = L/l_m;
    println("test: $L, $l_m, $chi");
    @test isapprox(chi,neutrinos.getCouplingStrength(neutrinos.energy,1.475e26); atol=1.0e-5);

    #----- Tests for the scaling factor f(theta,chi)
    # For chi = theta = 0.0, f = 1.
    @test isapprox(1.0,neutrinos.getScalingFactor(0.0,0.0);atol=1.0e-16)
    # For chi = 1.0, theta = 5.0pi/6.0, f = 1.0
    @test isapprox(1.0,neutrinos.getScalingFactor(5.0*pi/6.0,1.0);atol=1.0e-8)
    # For chi = 1.0, theta = 2.0pi/6.0, f = sqrt(3.0)
    @test isapprox(sqrt(3.0),neutrinos.getScalingFactor(2.0*pi/6.0,1.0);atol=1.0e-8)
    # For chi = 1.0, theta = 4.0pi/6.0, f = sqrt(3.0)
    @test isapprox(sqrt(3.0),neutrinos.getScalingFactor(4.0*pi/6.0,1.0);atol=1.0e-8)

    #----- Basic tests for the electron number density
    # This simple test checks behavior when R/R_sun = 0.0, which sets the exponent to zero and the result is the coefficient, 1.475E26.
    @test isapprox(1.475E26, neutrinos.getElectronNumberDensity(0.0),atol=1.0e-5);
    # Similar to the above, but in the case where the radius is equal to the solar radius and the fraction is 1.0, then the result is 1.475E26*exp(-10.54).
    @test isapprox(1.475E26*exp(-10.54), neutrinos.getElectronNumberDensity(1.0), atol=1.0e-5);

    #----- Basic tests for the electron neutrino probability
    # For r/R = theta = 0.0, P = 1.0.
    @test 1.0 == neutrinos.getElectronNeutrinoProbability(0.0,0.0);
    #@test 0.5 == #neutrinos.getElectronNeutrinoProbability();
    # This is the example from Guidry's stars book (ADD DEETS!)
end


import Composites

# Step 1: Identify Material Properties
e1 = [181e9,203e9,38.6e9,76e9]
e2 = [10.3e9,11.2e9,8.27e9,5.5e9]
g12 = [7.17e9,8.4e9,4.14e9,2.3e9]
nu12 = [0.28,0.32,0.26,0.34]
rho = zeros(Float64,4)
xt = [1500.0,3500.0,1062.0,1400.0]*1e6
xc = [1500.0,1540.0,610.0,235.0]*1e6
yt = [40.0,56.0,31.0,12.0]*1e6
yc = [246.0,150.0,118.0,53.0]*1e6
s = [68.0,98.0,72.0,34.0]*1e6
t = fill(0.0001,4)
mat = Composites.Material.(e1,e2,g12,nu12,rho,xt,xc,yt,yc,s,t)

# Step 2: Input Ply Information
matid = [4,4,4,4,4,4,4]
nply = [5,5,5,10,5,5,5]
tply = [0.0001,0.0001,0.0001,0.0001,0.0001,0.0001,0.0001]
theta = [0.0,45.0,-45.0,90.0,-45.0,45.0,0.0]
lam = Composites.Laminate(matid,nply,tply,theta)

# Step 3: Get Material Stiffness Matrix (Q)
q = Composites.getQ.(mat)

# Step 4: Determine Laminate Stiffness Matrix (A,B, and D)
A,B,D = Composites.getABD(lam,q)

# Step 5: Get Resultant Forces and Moments
force = [25000.0,0.0,0.0]
moment = [1000.0,0.0,0.0]
resultantstress = vcat(force,moment)

# Step 6: Get Resultant Strain
S = vcat(hcat(A,B),hcat(B',D))
resultantstrain = S\resultantstress

# Step 7: Solve for local ply strains and stresses in material coordinate system
lowerplystrain,upperplystrain = Composites.getplystrain(lam,resultantstrain)
lowerplystress = Composites.getplystress(lowerplystrain,q,lam)
upperplystress = Composites.getplystress(upperplystrain,q,lam)

# Step 8: Apply appropriate material failure criteria
lowermaxstress,lsfmaxstress = Composites.getmatfail(lowerplystress,mat,lam,"maxstress")
uppermaxstress,usfmaxstress = Composites.getmatfail(upperplystress,mat,lam,"maxstress")
lowertsai,lsftsai = Composites.getmatfail(lowerplystress,mat,lam,"tsaiwu")
uppertsai,usftsai = Composites.getmatfail(upperplystress,mat,lam,"tsaiwu")
lowerhashinrotem,lsfhashinrotem = Composites.getmatfail(lowerplystress,mat,lam,"hashinrotem")
upperhashinrotem,usfhashinrotem = Composites.getmatfail(upperplystress,mat,lam,"hashinrotem")


function power(maxit::Int64, fidMtx::FidMtx, w::Array{Float64,1}, irz::Array{Int64,1}, irx::Array{Int64,1}, tmax::Float64)
    nz = fidMtx.nz
    nx = fidMtx.nx
    x = randn(nz, nx)
    lambda = 0.0
    for k = 1 : maxit
        b = MultiStepForward(irz, irx, w, x, fidMtx, tmax=tmax)
        y = MultiStepAdjoint(w, b, fidMtx)
        n = norm(vec(x))
        x = y / n
        lambda = n
        println("iteration: $k, maximum eig: $lambda")
    end
    return lambda
end

function fista(shot::Shot, w::Array{Float64,1}, mu::Float64, lambda::Float64, fidMtx::FidMtx, irz::Array{Int64,1}, irx::Array{Int64,1}, tmax::Float64, maxit::Int64)
    nz = fidMtx.nz
    nx = fidMtx.nx
    x = zeros(nz, nx)
    T = mu / (2*lambda)
    t = 1
    yk = copy(x)
    for k = 1 : maxit
        tmpx = copy(x)
        bshot = MultiStepForward(irz, irx, w, yk, fidMtx, tmax=tmax)
        bshot.p = bshot.p - shot.p
        x = MultiStepAdjoint(w, bshot, fidMtx)
        x = yk - x/lambda
        x = vec(x)
        x = softThresh(x, T)
        x = reshape(x, nz, nx)
        tmpt = t
        t = (1 + sqrt(1+4*t^2)) / 2
        yk = x + (tmpt-1)/t * (x-tmpx)
        println("iteration $k")
    end
    return x
end

function softThresh(x::Array{Float64,1}, t::Float64)
    tmp = abs(x) - t
    tmp = (tmp + abs(tmp)) / 2
    y   = sign(x) .* tmp
    return y
end


using PyPlot
using Statistics
using PolynomialBasis
using ImplicitDomainQuadrature
using Revise
using CutCellDG
include("../../../../test/useful_routines.jl")
include("../transformation-elasticity-solver.jl")

TES = TransformationElasticitySolver

function perturbation(x, frequency, amplitude)
    return amplitude * sin.(2 * pi * frequency * x)
end

perturbed_distancefunction(x, initialposition, frequency, amplitude) =
    plane_distance_function(x, [1.0, 0.0], [initialposition, 0.0]) +
    perturbation(x[2, :], frequency, amplitude)

function perturbed_normal(y, frequency, amplitude)
    ny = 2pi * amplitude * frequency * cos.(2pi * frequency * y)
    magnitude = sqrt.(1.0 .+ ny .^ 2)
    numpts = length(y)
    nx = 1.0 ./ magnitude
    ny = ny ./ magnitude
    normals = vcat(nx', ny')
    return normals
end

function plot_levelset_normal_and_exact_normal(
    ycoords,
    levelsetnormal,
    exactnormal;
    filepath = "",
)
    fig, ax = PyPlot.subplots(2, 1, sharex = true)
    ax[1].plot(ycoords, levelsetnormal[1, :], label = "levelset")
    ax[1].plot(ycoords, exactnormal[1, :], "--", label = "exact")
    ax[1].set_title("X-component of interface normal")
    ax[1].grid()
    ax[1].legend()

    ax[2].plot(ycoords, levelsetnormal[2, :])
    ax[2].plot(ycoords, exactnormal[2, :], "--")
    ax[2].set_title("Y-component of interface normal")
    ax[2].grid()

    if length(filepath) > 0
        fig.savefig(filepath)
        return fig
    else
        return fig
    end
end

function update_cell_interface_normals_tangents_areas!(
    interfacequads,
    cellid,
    normals,
    invjac,
)
    t = CutCellDG.rotate_90(normals)
    s = CutCellDG.scale_area(t, invjac)

    idx = interfacequads.celltoquad[cellid]
    interfacequads.normals[idx] .= normals
    interfacequads.tangents[idx] .= t
    interfacequads.scaleareas[idx] .= s
end

function update_interface_normals!(interfacequads, cutmesh, exactnormalfunc)
    ncells = CutCellDG.number_of_cells(cutmesh)
    cellsign = [CutCellDG.cell_sign(cutmesh, cellid) for cellid = 1:ncells]
    cellids = findall(cellsign .== 0)
    invjac = CutCellDG.inverse_jacobian(cutmesh)

    for cellid in cellids
        cellmap = CutCellDG.cell_map(cutmesh, cellid)
        qps = cellmap(CutCellDG.points(interfacequads[1, cellid]))
        exactnormals = exactnormalfunc(qps[2, :])
        update_cell_interface_normals_tangents_areas!(
            interfacequads,
            cellid,
            exactnormals,
            invjac,
        )
    end
end

function plot_normal_stress(ycoords, parentsrr, productsrr; filepath = "")

    fig, ax = PyPlot.subplots()
    ax.plot(ycoords, parentsrr, label = "parent")
    ax.plot(ycoords, productsrr, label = "product")
    ax.grid()
    ax.set_ylabel(L"\sigma_{nn}")
    ax.set_xlabel("y")
    ax.legend()

    if length(filepath) > 0
        fig.savefig(filepath)
        return fig
    else
        return fig
    end
end

distancefunction(x) =
    perturbed_distancefunction(x, initialposition, frequency, amplitude)

initialposition = 0.5
frequency = 2.5
amplitude = 1e-3
# amplitude = 1e-10
polyorder = 2
nelmts = 17
penaltyfactor = 1e3
meshwidth = [1.0, 1.0]
numqp = required_quadrature_order(polyorder) + 2

K1, K2 = 247.0, 247.0    # GPa
mu1, mu2 = 126.0, 126.0   # GPa

lambda1 = lame_lambda(K1, mu1)
lambda2 = lame_lambda(K2, mu2)
stiffness = CutCellDG.HookeStiffness(lambda1, mu1, lambda2, mu2)
rho1 = 3.93e3           # Kg/m^3
rho2 = 3.68e3           # Kg/m^3
V01 = 1.0 / rho1
V02 = 1.0 / rho2
ΔG0Jmol = -14351.0
molarmass = 0.147
ΔG0 = ΔG0Jmol / molarmass
theta0 = -0.067

numquerypoints = 1000
querypoints = vcat(
    repeat([initialposition], numquerypoints)',
    range(0, 1, length = numquerypoints)',
)
ycoords = querypoints[2, :]

transfstress =
    CutCellDG.plane_strain_transformation_stress(lambda1, mu1, theta0)

elasticitybasis = LagrangeTensorProductBasis(2, polyorder)
levelsetbasis = HermiteTensorProductBasis(2)
quad = tensor_product_quadrature(2, 4)
dim, numpts = size(interpolation_points(levelsetbasis))
basispts = interpolation_points(elasticitybasis)

cgmesh = CutCellDG.CGMesh([0.0, 0.0], meshwidth, [nelmts, nelmts], numpts)
dgmesh = CutCellDG.DGMesh([0.0, 0.0], meshwidth, [nelmts, nelmts], basispts)

# levelset = CutCellDG.LevelSet(distancefunction, cgmesh, levelsetbasis)
levelset = CutCellDG.LevelSet(x->distancefunction(x)[1], cgmesh, levelsetbasis)

elementsize = CutCellDG.element_size(cgmesh)
minelmtsize = minimum(elementsize)
maxelmtsize = maximum(elementsize)
penalty = penaltyfactor / minelmtsize * 0.5 * (lambda1 + mu1 + lambda2 + mu2)
tol = minelmtsize^(polyorder + 1)
boundingradius = 1.5 * maxelmtsize

cutmesh = CutCellDG.CutMesh(dgmesh, levelset)
refseedpoints, seedcellids = CutCellDG.seed_zero_levelset(2, levelset, cutmesh)
spatialseedpoints =
    CutCellDG.map_to_spatial(refseedpoints, seedcellids, cutmesh)

mesh, cellquads, facequads, interfacequads =
    TES.construct_merged_mesh_and_quadratures(cutmesh, levelset, numqp)
update_interface_normals!(
    interfacequads,
    cutmesh,
    x -> perturbed_normal(x, frequency, amplitude),
)

interfacenormals = CutCellDG.collect_interface_normals(interfacequads, cutmesh)
refinterfaceqps, refcellids =
    CutCellDG.collect_interface_quadrature_points(interfacequads, +1, cutmesh)
interfaceqps = CutCellDG.map_to_spatial_on_merged_mesh(
    refinterfaceqps,
    refcellids,
    +1,
    mesh,
)
exactqpnormals = perturbed_normal(interfaceqps[2, :], frequency, amplitude)

nodaldisplacement = TES.nodal_displacement(
    mesh,
    elasticitybasis,
    cellquads,
    facequads,
    interfacequads,
    stiffness,
    theta0,
    penalty,
)

closestpoints, closestcellids = CutCellDG.closest_points_on_zero_levelset(
    querypoints,
    spatialseedpoints,
    seedcellids,
    levelset,
    tol,
    boundingradius,
)

normals = CutCellDG.collect_normals_at_spatial_points(
    closestpoints,
    closestcellids,
    levelset,
)
exactnormals = perturbed_normal(closestpoints[2, :], frequency, amplitude)

parentclosestrefpoints = CutCellDG.map_to_reference_on_merged_mesh(
    closestpoints,
    closestcellids,
    -1,
    mesh,
)
productclosestrefpoints = CutCellDG.map_to_reference_on_merged_mesh(
    closestpoints,
    closestcellids,
    +1,
    mesh,
)

################################################################################
parentstrain = CutCellDG.parent_strain(
    nodaldisplacement,
    elasticitybasis,
    parentclosestrefpoints,
    closestcellids,
    mesh,
)
parentstress = CutCellDG.parent_stress(parentstrain, stiffness)

parentradialtraction = CutCellDG.traction_force_at_points(parentstress, normals)
parentsrr = CutCellDG.traction_component(parentradialtraction, normals)

parentradialtractionalt =
    CutCellDG.traction_force_at_points(parentstress, exactnormals)
parentsrralt =
    CutCellDG.traction_component(parentradialtractionalt, exactnormals)
################################################################################
#
#
#
#
#
# ################################################################################
productstrain = CutCellDG.product_elastic_strain(
    nodaldisplacement,
    elasticitybasis,
    theta0,
    productclosestrefpoints,
    closestcellids,
    mesh,
)
productstress = CutCellDG.product_stress(productstrain, stiffness, theta0)

productradialtraction =
    CutCellDG.traction_force_at_points(productstress, normals)
productsrr = CutCellDG.traction_component(productradialtraction, normals)

productradialtractionalt =
    CutCellDG.traction_force_at_points(productstress, exactnormals)
productsrralt =
    CutCellDG.traction_component(productradialtractionalt, exactnormals)
# ################################################################################
foldername = "examples\\time-step\\perturbed-plane-interface\\analytical-normal-for-elasticity\\"

plot_normal_stress(ycoords,parentsrr,productsrr)


using Mathieu
using Test, Printf, DelimitedFiles
using Statistics: mean, median
using Base.MathConstants: φ

const refdir = joinpath(@__DIR__,"ref")

## Test vs Mathematica
n1 = 0:24
q1 = 0:2φ:80
z1 = LinRange(0,4φ,10)
cases = ((q->Mathieu.ap(n1,q),           "MathieuCharA_Periodic"),
         (q->Mathieu.aa(n1,q),           "MathieuCharA_Antiperiodic"),
         (q->Mathieu.ba(n1,q),           "MathieuCharB_Antiperiodic"),
         (q->Mathieu.bp(n1,q),           "MathieuCharB_Periodic"),
         (q->Mathieu.cep(n1,q,z1),       "MathieuC_Periodic"),
         (q->Mathieu.cea(n1,q,z1),       "MathieuC_Antiperiodic"),
         (q->Mathieu.sea(n1,q,z1),       "MathieuS_Antiperiodic"),
         (q->Mathieu.sep(n1,q,z1),       "MathieuS_Periodic"),
         (q->Mathieu.cep_prime(n1,q,z1), "MathieuCPrime_Periodic"),
         (q->Mathieu.cea_prime(n1,q,z1), "MathieuCPrime_Antiperiodic"),
         (q->Mathieu.sea_prime(n1,q,z1), "MathieuSPrime_Antiperiodic"),
         (q->Mathieu.sep_prime(n1,q,z1), "MathieuSPrime_Periodic"),
         )
for (fjl,file) in cases
    println(string("Comparing ",file," with Mathematica:"))
    # Read Mathematica reference values
    arr_ref = readdlm(joinpath(refdir,"$file.csv"),',',Float64)
    # Calculate corresponding values with Mathieu.jl
    arr_jul = mapreduce(fjl,hcat,q1)
    # Make sure the values are approximately equal
    @test arr_jul ≈ arr_ref
    a = abs.(arr_jul-arr_ref)./eps.(arr_jul)
    println(@sprintf("  mean error: %.2f eps, median: %d eps, max: %d eps.",mean(a),median(a),maximum(a)))
end

println(string("Comparing ","Int_Per_PerPrime"," with Mathematica:"))
# Read Mathematica reference values
arr_ref = readdlm(joinpath(refdir,"Int_Per_PerPrime.csv"),',',Float64)
# Calculate corresponding values with Mathieu.jl
arr_jul = Mathieu.int_p_pprime(n1,n1,223/100,[0.5,3])
# Make sure the values are approximately equal
@test arr_jul ≈ arr_ref[1:25,1:25]
a = abs.(arr_jul-arr_ref[1:25,1:25])./eps.(arr_jul)
println(@sprintf("  mean error: %.2f eps, median: %d eps, max: %d eps.",mean(a),median(a),maximum(a)))

# Test q change of sign
n2 = [0:20,7:21,10:35,12:46,50:100,100:150,150:200]
q2 = LinRange(0,100,202) #0:2φ:30
z2 = LinRange(0,4φ,10)
for nn in n2
    println("Testing change of sign for n = $nn:")
    N2 = maximum(nn)+12 # Make N different to hit different eigendecompositions
    print("  ap")
    A1 = mapreduce(q->Mathieu.ap(nn,q),hcat,-q2)
    A2 = mapreduce(q->Mathieu.ap(nn,q,N2),hcat,q2)
    @test A1 ≈ A2
    print(", aa, ba")
    A1 = mapreduce(q->Mathieu.aa(nn,q),hcat,-q2)
    A2 = mapreduce(q->Mathieu.ba(nn,q,N2),hcat,q2)
    @test A1 ≈ A2
    print(", bp")
    A1 = mapreduce(q->Mathieu.bp(nn,q),hcat,-q2)
    A2 = mapreduce(q->Mathieu.bp(nn,q,N2),hcat,q2)
    @test A1 ≈ A2
    print(", cep")
    A1 = mapreduce(q->Mathieu.cep(nn,q,z2),vcat,-q2)
    A2 = mapreduce(q->Mathieu.cep(nn,q,π/2 .- z2,N2).*(-1).^nn',vcat,q2)
    @test A1 ≈ A2
    print(", cea")
    A1 = mapreduce(q->Mathieu.cea(nn,q,z2),vcat,-q2)
    A2 = mapreduce(q->Mathieu.sea(nn,q,π/2 .- z2,N2).*(-1).^nn',vcat,q2)
    @test A1 ≈ A2
    print(", sea")
    A1 = mapreduce(q->Mathieu.sea(nn,q,z2),vcat,-q2)
    A2 = mapreduce(q->Mathieu.cea(nn,q,π/2 .- z2,N2).*(-1).^nn',vcat,q2)
    @test A1 ≈ A2
    println(", sep.")
    A1 = mapreduce(q->Mathieu.sep(nn,q,z2),vcat,-q2)
    A2 = mapreduce(q->Mathieu.sep(nn,q,π/2 .- z2,N2).*(-1).^nn',vcat,q2)
    @test A1 ≈ A2
end


#= -------------------------------------------------------------------------
# @file problema1_con_disipador_parametric.jl
#
# @date 12/06/17 16:18:05
# @author Martin Noblia
# @email mnoblia@disroot.org
#
# @brief
#
# @detail
#
#  Licence:
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
---------------------------------------------------------------------------=#
# imports
using DifferentialEquations
using PyPlot
#=------------------------------------------------------------------------------
                  sistem parametric equations
------------------------------------------------------------------------------=#
family = @ode_def heat_simulation begin
   dT_c = (-T_c / C_c)*( 1 / (R_ca) + 1 / (R_jc) + 1 / (R_cs))  + (T_j / (R_jc * C_c)) + (T_s / (R_cs * C_c))+ (T_a / (R_ca * C_c))
   dT_j = (T_c/ (R_jc * C_j)) - (T_j / (C_j * R_jc)) + (step_power(t) / C_j)
   dT_s = (T_c / (R_cs * C_s)) - (T_s / C_s) * (1 / (R_cs) + 1 / (R_sa)) + (T_a / (R_sa * C_s))
end R_jc=>1.0 C_c=>6.8 R_ca=>30.0 C_j=>0.5 T_a=>30.0 C_s=>39.6 R_cs=>0.4 R_sa=>4.5


# this computes the GMM estimator by SA minimization, for
# one of the 1000 data sets. Can be parallelized using
# threads.
using Econometrics, DelimitedFiles, Statistics, LinearAlgebra, Calculus
include("DSGEmoments.jl")  # computes errors
function main()
include("parameters.jl") # load true parameter values
data = readdlm("dsgedata.txt")
n = size(data,1)
# estimate by simulated annealing
lb = lb_param_ub[:,1]
ub = lb_param_ub[:,3]
# define CUE GMM criterion
moments = theta -> DSGEmoments(theta, data)
m = theta -> vec(mean(moments(theta),dims=1)) # 1Xg
weight = theta -> inv(cov(sqrt(n)*moments(theta)))
obj = theta -> m(theta)'*weight(theta)*m(theta)
thetastart = (ub+lb)/2.0 # prior mean as start
# attempt gradient based (doesn't work)
thetahat, objvalue, converged = fmincon(obj, thetastart, [], [], lb, ub; iterlim=10000)
println("fmincon results. objective fn. value: ", objvalue)
println("parameter values:")
prettyprint(thetahat)
# simulated annealing
thetahat, objvalue, converged, details = samin(obj, thetastart, lb, ub; ns = 20, verbosity = 2, rt = 0.5)
# compute the estimated standard errors and CIs
D = (Calculus.jacobian(m, vec(thetahat), :central))
W = weight(thetahat)
V = inv(D'*W*D)/n
se = sqrt.(diag(V))
println("estimates, st. error, and limits of 95% CI")
prettyprint([thetahat se thetahat-1.96*se thetahat+1.96*se],["estimate", "std. err.", "CI lower", "CI upper"])
return nothing
end
main()


"""
Julia script for dynamic analysis of four-bar mechanism.
Includes the kinematic analysis file and computes the reaction
forces and actuating torque required as a function of time.
"""

using Plots
plotlyjs()

include("ex3.jl") #file for kinematic analysis

K=ex3() #kinematic analysis

#extracting the kinematic state vectors
(θ₂,θ₃,θ₄,θ̇₂,θ̇₃,θ̇₄,θ̈₂,θ̈₃,θ̈₄)=K

#fixed parameters
#lengths and angles
AB=5
BC=6
CD=5
DA=6
DP=10
θ₁=π

#masses
m=3000
m₂=AB*0.2*0.2*7950
m₃=BC*0.15*0.15*7950
m₄=DP*0.25*0.25*7950
g=9.81

#tension force due to the capsule
T=m*g/2

#moment of inertia
I₂=(m₂/12)*AB^2
I₃=(m₃/12)*BC^2
I₄=(m₄/12)*DP^2

#time limit
t=0:0.01:45

#poses and accelerations of centres of gravity of the links
#link 2
x_2=(AB/2).*cos.(θ₂)
ẍ₂=-(AB/2).*θ̈₂.*sin.(θ₂).-(AB/2).*(θ̇₂.^2).*cos.(θ₂)
y_2=(AB/2).*sin.(θ₂)
ÿ₂=(AB/2).*(θ̈₂.*cos.(θ₂).-(θ̇₂.^2).*sin.(θ₂))

#link 3
x_3=AB.*cos.(θ₂).+(BC/2)*cos.(θ₃)
ẍ₃=-AB.*(θ̈₂.*sin.(θ₂).+(θ̇₂.^2).*cos.(θ₂)) .- (BC/2).*(θ̈₃.*sin.(θ₃).+(θ̇₃.^2).*cos.(θ₃))
y_3=AB.*sin.(θ₂).+(BC/2)*sin.(θ₃)
ÿ₃=AB.*(θ̈₂.*cos.(θ₂).-(θ̇₂.^2).*sin.(θ₂)) .+ (BC/2).*(θ̈₃.*cos.(θ₃).-(θ̇₃.^2).*sin.(θ₃))

#link 4
x_4=DA.+(DP/2).*cos.(θ₄)
ẍ₄=-(DP/2).*(θ̈₄.*sin.(θ₄).+(θ̇₄.^2).*cos.(θ₄))
y_4=(DP/2).*sin.(θ₄)
ÿ₄=(DP/2).*(θ̈₄.*cos.(θ₄).-(θ̇₄.^2).*sin.(θ₄))

#locations of the joints
x_A=zeros(size(x_2))
y_A=zeros(size(y_2))
x_B=AB.*cos.(θ₂)
y_B=AB.*sin.(θ₂)
x_C=DA.+CD.*cos.(θ₄)
y_C=CD.*sin.(θ₄)
x_D=DA.*ones(size(x_4))
y_D=y_A=zeros(size(y_4))
x_P=DA.+DP.*cos.(θ₄)
y_P=DP.*sin.(θ₄)

#solution matrix
X=zeros(9,length(t))

#solving the equations
for i in 1:length(t)
    R₁=[1 0 -1 0 0 0 0 0 0]
    R₂=[0 1 0 -1 0 0 0 0 0]
    R₃=[-(y_A[i]-y_2[i]) (x_A[i]-x_2[i]) (y_B[i]-y_2[i]) -(x_B[i]-x_2[i]) 0 0 0 0 1]
    R₄=[0 0 1 0 -1 0 0 0 0]
    R₅=[0 0 0 1 0 -1 0 0 0]
    R₆=[0 0 -(y_B[i]-y_3[i]) (x_B[i]-x_3[i]) (y_C[i]-y_3[i]) -(x_C[i]-x_3[i]) 0 0 0]
    R₇=[0 0 0 0 1 0 1 0 0]
    R₈=[0 0 0 0 0 1 0 1 0]
    R₉=[0 0 0 0 -(y_C[i]-y_4[i]) (x_C[i]-x_4[i]) -(y_D[i]-y_4[i]) (x_D[i]-x_4[i]) 0]
    A=vcat(R₁,R₂,R₃,R₄,R₅,R₆,R₇,R₈,R₉)
    B=[m₂*ẍ₂[i],(m₂*ÿ₂[i]+m₂*g),I₂*θ̈₂[i],m₃*ẍ₃[i],(m₃*ÿ₃[i]+m₃*g),I₃*θ̈₃[i],m₄*ẍ₄[i],(m₄*ÿ₄[i]+m₄*g+T),I₄*θ̈₄[i]+T*(x_P[i]-x_4[i])]

    Aᴵ=inv(A)
    @assert size(Aᴵ,2)==size(B,1) "dimension mismatch"
    X[:,i]=Aᴵ*B
end

#extracting the solution vectors
(F₁,F₂,F₃,F₄,F₅,F₆,F₇,F₈,τₐ)=[X[i,:] for i in 1:size(X,1)]

#plotting
display(plot(t,F₁,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₁",color="blue"))
savefig("./plots/ex4-41.png")
display(plot(t,F₂,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₂",color="green"))
savefig("./plots/ex4-42.png")
display(plot(t,F₃,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₃",color="orange"))
savefig("./plots/ex4-43.png")
display(plot(t,F₄,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₄",color="purple"))
savefig("./plots/ex4-44.png")
display(plot(t,F₅,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₅",color="cyan"))
savefig("./plots/ex4-45.png")
display(plot(t,F₆,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₆",color="black"))
savefig("./plots/ex4-46.png")
display(plot(t,F₇,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₇",color="brown"))
savefig("./plots/ex4-47.png")
display(plot(t,F₈,linewidth=2,xaxis="time (s)",yaxis="F (N)",label="F₈",color="grey"))
savefig("./plots/ex4-48.png")
display(plot(t,τₐ,linewidth=2,xaxis="time (s)",yaxis="M (Nm)",label="τₐ",color="red"))
savefig("./plots/ex4-49.png")

Dynamic analysis of the A-frame mechanism/scripts/ex4.jl


import Base: extrema
using Optim

have_calculus = true
try
	Pkg.installed("Calculus")
catch
	have_calculus = false
end

if have_calculus
	import Calculus.derivative
end

create_tol = eps(Float64) # The max error tolerated when using a spline to approximate something else

function set_create_tol(newtol::Real)
	global create_tol = newtol
end

min_knot_dist_factor = 10000 #knots must be separated by at leas 10000*eps(T) to be considered unique.

function forwardDifference{T<:Number}(x::AbstractArray{T, 1})
    length = size(x, 1)
    return [x[i]-x[i-1] for i in 2:length]
end

type Spline{T<:Number}
    knots::Array{T, 1}
    values::Array{T, 1}
    a::Array{T, 1}
    b::Array{T, 1}
    c::Array{T, 1}
    d::Array{T, 1}

    function Spline(knots::AbstractArray{T, 1}, values::AbstractArray{T, 1})
	nKnots = length(knots)
    	if nKnots <= 1
		      throw(ArgumentError("There must be at least two knots"))
	end

	if length(values) != nKnots
		    throw(ArgumentError("Values is of wrong size"))
	end

	if any(isnan(knots))
		throw(ArgumentError("Knots cannot be NaN"))
	end

	if any(isnan(values))
		throw(ArgumentError("Values cannot be NaN"))
	end

        if !issorted(knots)
		throw(ArgumentError("Knots must be sorted"))
	end

	todelete = Array{Int, 1}(0)
	for knot in 2:nKnots
		if knots[knot] == knots[knot-1]
			throw(ArgumentError("Knots must all be unique"))
		end
		if knots[knot]-knots[knot-1] < min_knot_dist_factor*eps(T) #then replace the two knots & values by their averages
			knots[knot] = (knots[knot]+knots[knot-1])/2
			values[knot] = (values[knot]+values[knot-1])/2
			push!(todelete, knot-1)
		end
	end

	reverse!(todelete) #reverse the list of knots to delete so that removing one does not affect subsequent removals
	for i in todelete
	    deleteat!(knots, i)
	    deleteat!(values, i)
	end
	nKnots = length(knots)

	a = values
	h = forwardDifference(knots)
	df = forwardDifference(values) ./ h
	# Note: due the way that the gtsv! solver works, when solving A*X = B, this matrix can be X and then be overwritten By B. Therefore, the name of c is logical
	c = Array{Float64, 1}([0])
	append!(c, 3*forwardDifference(df))
	append!(c, [0])

	lowerDiag::Array{Float64, 1} = h[1:nKnots-2]
	append!(lowerDiag, [0.0]);

	diag = Array{Float64}([1])
	append!(diag, [2*(h[i]+h[i-1]) for i in 2:nKnots-1])
	append!(diag, [1.0])

	upperDiag = Array{Float64, 1}([0.0])
	append!(upperDiag, h[2:nKnots-1])

	#LAPACK.gtsv!([h[1:nKnots-2]; 0], [1; [2*(h[i]+h[i-1]) for i in 2:nKnots-1]; 1], [0; h[2:nKnots-1]], c;)
	LAPACK.gtsv!(lowerDiag, diag, upperDiag, c)
	d = [(c[i+1]-c[i])/(3*h[i]) for i in 1:nKnots-1]
	b = [df[i]-((h[i]/3)*((2*c[i])+c[i+1])) for i in 1:nKnots-1]
	new(knots, values, a[1:nKnots-1], b, c[1:nKnots-1], d)
    end
end

"""Recalculate a spline with new values, assuming that the corresponding knots are already in s.knots"""
function recalculate!{T}(s::Spline{T}, values::Array{T, 1})
  @assert issorted(s.knots) "Knots are not sorted. This is probably a bug in the Splines library"
	nKnots = length(s.knots)
	nvalues = length(values)

	if nvalues != nKnots
		throw(ArgumentError("Values is of wrong size"))
	end

	if any(isnan(values))
		throw(ArgumentError("values cannot contain NaN"))
	end

	h = forwardDifference(s.knots)
	todelete = Array{Int, 1}(0)
	if minimum(h) < min_knot_dist_factor*eps(T)
		for (knot, knotdiff) in enumerate(h)
			if knotdiff == zero(T)
				throw(ArgumentError("s.knots must all be unique"))
			end
			if knotdiff < min_knot_dist_factor*eps(T) #then replace the two s.knots & values by their averages
				s.knots[knot+1] = (s.knots[knot+1]+s.knots[knot])/2
				values[knot+1] = (values[knot+1]+values[knot])/2
				push!(todelete, knot)
			end
		end
	end
	reverse!(todelete) #reverse the list of knots to delete so that removing one does not affect subsequent removals

	for i in todelete
		deleteat!(s.knots, i)
		deleteat!(values, i)
	end
	nKnots = length(s.knots)

	a = values
	if length(todelete) > 0
		h = forwardDifference(s.knots)
	end
	df = forwardDifference(values) ./ h
	# Note: due the way that the gtsv! solver works, when solving A*X = B, this matrix can be X and then be overwritten By B. Therefore, the name of c is logical
	c = Array{Float64, 1}([0])
	append!(c, 3*forwardDifference(df))
	append!(c, [0])

	lowerDiag::Array{Float64, 1} = h[1:nKnots-2]
	append!(lowerDiag, [0.0]);

	diag = Array{Float64}([1])
	append!(diag, [2*(h[i]+h[i-1]) for i in 2:nKnots-1])
	append!(diag, [1.0])

	upperDiag = Array{Float64, 1}([0.0])
	append!(upperDiag, h[2:nKnots-1])

	#LAPACK.gtsv!([h[1:nKnots-2]; 0], [1; [2*(h[i]+h[i-1]) for i in 2:nKnots-1]; 1], [0; h[2:nKnots-1]], c;)
	LAPACK.gtsv!(lowerDiag, diag, upperDiag, c)
	d = [(c[i+1]-c[i])/(3*h[i]) for i in 1:nKnots-1]
	b = [df[i]-((h[i]/3)*((2*c[i])+c[i+1])) for i in 1:nKnots-1]
	s.a = a[1:nKnots-1]
	s.b = b
	s.c = c[1:nKnots-1]
	s.d = d
	s.values = values
#	new(knots, a[1:nKnots-1], b, c[1:nKnots-1], d)
	return nothing
end

function indomain{T}(s::Spline{T}, x::T)
	return (x>=s.knots[1] && x<=s.knots[end])
end

function domain{T}(s::Spline{T})
	return (xmin(s), xmax(s))
end

Spline{T<:Number}(knots::AbstractArray{T, 1}, values::AbstractArray{T, 1}) = Spline{T}(knots, values)

#A helper function to find the max error of s(t)-f(t) for a spline s and a callable object f. If f is not continuous and differentiable, this
#function WILL NOT WORK.
function find_maxerr{T}(s::Spline{T}, f, x_lower::Real=domain(s)[1], x_upper::Real=domain(s)[2])
	min = optimize(t->1/((f(t)-s(t))^2+1), x_lower, x_upper)

	return min.minimum
end

# Tries to construct a spline for anything that has a method of call() defined for it. If f is not smooth and continuous, this will not yield good results
function Spline(f, xlower::Real, xupper::Real, tol=create_tol)
	#Start with a really simple set of knots
	knots = [xlower, xlower+xupper/2, xupper]
	values = Float64[f(i) for i in knots]
	s = Spline(knots, values)

	maxerr = find_maxerr(s, f, xlower, xupper)
	err_at_maxerr = abs(f(maxerr)-s(maxerr))

	loopcount = 0
	while err_at_maxerr > tol
		newknot = maxerr
		newvalue = f(newknot)
		insert!(s, newknot, newvalue)

		maxerr = find_maxerr(s, f, xlower, xupper)
		err_at_maxerr = abs(f(maxerr)-s(maxerr))

		loopcount += 1

		if loopcount > 10000
			warn("Creating a spline from a callable object failed after 10000 iterations")
			break
		end
	end

return s
end

function (s::Spline{T}){T}(x::T)
	if x == domain(s)[2]
		return s.values[end]
	end

	if !indomain(s, x)
		throw(DomainError())
		#error("x out of range")
	end

	bracket = searchsortedlast(s.knots, x)
	if bracket == length(s.knots)
		bracket -= 1
	end

	h = x-s.knots[bracket]

	return @evalpoly(h, s.a[bracket], s.b[bracket], s.c[bracket], s.d[bracket])::T
end

"""calling a spline _f_ with another spline _g_ as argument produces a spline approximation to f(g)"""
function (f::Spline{T}){T}(g::Spline{T})
  gmin, gmax = extrema(g)
  if !indomain(f, gmax) || !indomain(f, gmin)
    warn("f(g) is not defined; Range of g is [$gmin, $gmax], but domain of f is only [$(domain(f)[1]), $(domain(f)[2])]. Throwing DomainError")
    throw(DomainError())
  end

  # approixmating f(g) is best done by first approximating g with first-order polynomials, and then strechting or shrinking intervals of f appropriately
  g_discrete = adaptive_discretize(g)

  #However, it is very necessary that the knots/values in f be preserved in the transformation;
  #otherwise, the transformed spline might bear little resemblance to what it should.
  #The simplest way to do this is, for each knot $f_k$ in f to put a knot $g_x$ into g's list of knots such that g($g_k$) = $f_k$

  newknots = g_discrete[:x]
  newvalues = [f(x) for x in g_discrete[:y]]
  f_warped = Spline(newknots, newvalues)

  #We now have some approximation to f(g(t)), but it probably isn't very good.
  # So we begin going through repeatedly finding the location of the max error
  # and adding a knot there
  tol = .001 #FIXME: This is purely arbitrary and probably a bad value
  maxϵ_pos = find_maxerr(f_warped, t->f(g(t)))
  maxϵ = abs(f_warped(maxϵ_pos)-f(g(maxϵ_pos)))
  while maxϵ > tol
    insert!(f_warped, maxϵ_pos, f(g(maxϵ_pos)))
    maxϵ_pos = find_maxerr(f_warped, t->f(g(t)))
    maxϵ = abs(f_warped(maxϵ_pos)-f(g(maxϵ_pos)))
  end

  return f_warped
end

"""Add a knot to a spline"""
function insert!{T}(s::Spline{T}, knot::T, value::T)
	knots = s.knots
	values = [s(i)::T for i in knots] #FIXME: why allocate a whole new array? all but one of the values are already stored in s.a
	if knot in knots
		values[findin(knots, knot)[1]] == value #FIXME: this should be assignment
		recalculate!(s, values)
		return nothing
	end

	push!(knots, knot)
	push!(values, value)

	perm = sortperm(knots)
	s.knots = knots[perm]
	values = values[perm]

	recalculate!(s, values)
	return nothing
end

#"""Add a bunch of knots to a spline all at once"""
function insert!{T}(s::Spline{T}, knots::AbstractArray{T, 1}, values::AbstractArray{T, 1})
  if length(knots) != length(values)
    throw(ArgumentError("knots and values must be the same size"))
  end
  if unique(knots) != knots
    throw(ArgumentError("knots must all be unique"))
  end

  newvalues = copy(s.values)

  #If there are knots in _knots_ that are already in the spline ,then just change the value and add
  #that index to a list of knots and values to be deleted
  todelete = Array{Int, 1}(0)
  for (i, knot) in enumerate(knots)
    if knot in s.knots
      duplicate_pos = findin(s.knots, knot)[1]
      newvalues[duplicate_pos] = values[i] #change the value
      push!(todelete, i) #and add the index to a list of indecies to delete
    end
  end
  #delete whatever was in the list
  reverse!(todelete) #we have to delete starting at the end so that a deletion at one index does not throw off future deletions
  for i in todelete
    deleteat!(knots, i)
    deleteat!(values, i)
  end

  append!(s.knots, knots)
  append!(newvalues, values)

  #Now the knots must be sorted to be in increasing order and the values must re-arranged in the same manner
  perm = sortperm(s.knots)
  s.knots = s.knots[perm]
  newvalues = newvalues[perm]

  recalculate!(s, newvalues)
  return nothing
end

"""Move the knot at knotIndex to the location newlocation"""
function moveknot!{T}(s::Spline{T}, knotIndex::Int, newlocation::T)
  if knotIndex > length(s.knots) || knotIndex <1
    throw(BoundsError(s, knotIndex))
  end

  if !indomain(s, newlocation)
    throw(DomainError())
  end

  if newlocation in s.knots
    throw(ArgumentError("newlocation cannot be a pre-existing knot"))
  end

  s.knots[knotIndex] = newlocation

  recalculate!(s, s.values)
end

"""Move the knots at _knotIndecies_ to the new locations in _newlocations_"""
function moveknot!{T}(s::Spline{T}, knotIndecies::AbstractArray{Int, 1}, newlocations::AbstractArray{T, 1})
  if length(knotIndecies) != length(newlocations)
    throw(ArgumentError("knotIndecies and newlocations must have the same length, not $(length(knotIndecies)) and $(length(newlocations))"))
  end

  for index in knotIndecies
    if index > length(s.knots) || index < 1
      throw(BoundsError(s, index))
    end
  end

  for newloc in newlocations
    if !indomain(s, newloc)
      throw(DomainError)
    end
  end

  for (i, index) in enumerate(knotIndecies)
    s.knots[index] = newlocations[i]
  end

  recalculate!(s, s.values)
end

"""Delete a knot or knots from a spline"""
function deleteknotat!{T}(s::Spline{T}, knotIndex::Integer...)
	knots = s.knots
	values = Array{T, 1}(length(knots))
	for (i, x) in enumerate(knots)
		values[i] = s(x)
	end

	deleteat!(knots, knotIndex)
	deleteat!(values, knotIndex)

	recalculate!(s, values)
end

"""Trim the beginning (everything less than cutoff) off of a spline"""
function trimstart!{T}(s::Spline{T}, cutoff::T)
	if !indomain(s, cutoff)
		throw(DomainError())
	end

	newknot = cutoff
	newvalue = s(newknot)
	insert!(s, newknot, newvalue)

	indeciesToTrim = find(x->x < newknot, s.knots)
	deleteknotat!(s, indeciesToTrim...)
end

"""Trim the end (everything greater than cutoff) off of a spline"""
function trimend!{T}(s::Spline{T}, cutoff::T)
	if !indomain(s, cutoff)
		throw(DomainError())
	end

	newknot = cutoff
	newvalue = s(newknot)
	insert!(s, newknot, newvalue)

	indeciesToTrim = find(x->x > newknot, s.knots)
	deleteknotat!(s, indeciesToTrim...)
end

"""Trim the start and the end (everything less than startx or greater than endx) off of a spline"""
function trim!{T}(s::Spline{T}, startx::T, endx::T)
	insert!(s, startx, s(startx))
	insert!(s, endx, s(endx))
	indeciesToTrim = find(x->x<startx || x>endx, s.knots)
	deleteknotat!(s, indeciesToTrim...)
end

include("cubic_helpers.jl")

function maximum{T}(s::Spline{T})
	maxS = -Inf

	for i in 1:length(s.knots)-1
		maxS = max(maxS, maxcubic(s.knots[i+1]-s.knots[i], s.a[i], s.b[i], s.c[i], s.d[i])[1])
	end

	return maxS
end

function minimum{T}(s::Spline{T})
	minS = typemax(T)

	for i in 1:length(s.knots)-1
		minS = min(minS, mincubic(s.knots[i+1]-s.knots[i], s.a[i], s.b[i], s.c[i], s.d[i])[1])
	end

	return minS
end

function extrema{T}(s::Spline{T})
	minS = typemax(T)
	maxS = typemin(T)

	for i in 1:length(s.knots)-1
		minS = min(minS, mincubic(s.knots[i+1]-s.knots[i], s.a[i], s.b[i], s.c[i], s.d[i])[1])
		maxS = max(maxS, maxcubic(s.knots[i+1]-s.knots[i], s.a[i], s.b[i], s.c[i], s.d[i])[1])
	end

	return (minS, maxS)
end

"""Similar to indmax for arrays, return the x-position of the maximum of the given spline"""
function xmax{T}(s::Spline{T})
	theMax = maximum(s)
	maxPos = 0
	curMax = (0, 0)
	for i in 1:length(s.knots)-1
		curMax = maxcubic(s.knots[i+1]-s.knots[i], s.a[i], s.b[i], s.c[i], s.d[i])
		if curMax[1] == theMax
			maxPos = s.knots[i] + curMax[2]
			break
		end
	end

	return maxPos
end

function maxabs(s::Spline)
	return max(abs(maximum(s)), abs(minimum(s)))
end

"""Find the appropriate step size for discretizing a spline with a uniform mesh maximum absolute error _tol_ due to linear interpolation. If negative, this will be interpreted as a relative error from the maximum value"""
function find_stepsize{T}(s::Spline{T}, tol::Number)
	if tol < 0
		tol = maximum(s)*abs(tol)
	end
	minValues = 100
	maxa2d = maxabs2deriv(s)
	if maxa2d > 0
		stepSize = .9*min(sqrt(8*tol/maxa2d), (s.knots[end]-s.knots[1])/minValues)
	else
		stepSize = (s.knots[end]-s.knots[1])/minValues
	end

	return stepSize
end

"""
Create an evenly spaced mesh of points to discretize the spline so that the maximum absolute error due to linear interpolation is <= tol.
If this is too small, return a mesh with 100 points. Note, this may use horrendous amounts of memory. Consider using adaptive_discretize_mesh instead
"""
function uniform_discretize_mesh{T}(s::Spline{T}, tol::Number=-1)
	stepSize = find_stepsize(s, tol)
	mesh = s.knots[1]:stepSize:s.knots[end]
	return mesh
end

"""Turn a spline object into a discrete list of values on an automatically created uniform mesh"""
function uniform_discretize{T}(s::Spline{T}; tol::Number=-1) #default .001 absolute error tolerated
	mesh = uniform_discretize_mesh(s, tol)

	return discretize(s, mesh)
end

"""Turn a spline object _s_ into a discrete list of values, seperated on the domain of s by _step_"""
function uniform_discretize{T}(s::Spline{T}, step::T)
	mesh = xmin(s):step:xmax(s)
	return discretize(s, mesh)
end

"""Turn a spline object into a discrete list of values at the mesh points given by _mesh_"""
function discretize{T}(s::Spline{T}, mesh::AbstractArray{T})
	discretized = Array{T, 1}(length(mesh))
	for (i, x) in enumerate(mesh)
		discretized[i] = s(x)
	end

	return discretized
end

"""A helper function to find the maximum absolute error due to linear interpolation of
a cubic spline over a mesh _x_

Parameters:
 - *s* is the spline that is being discretized
 - *x* holds x-values that need to be used between the current knot (which is not easily visible to this function) and the one after it to get a reasonable approximation.
 - *a*, *b*, *c*, and *d* are the polynomial coefficients of the spline at the knot where this is being done.
"""
function find_maxϵ{T}(s::Spline{T}, x::Array{T, 1}, a::T, b::T, c::T, d::T)
  if any(isnan(x))
    throw(ArgumentError("NAN encountered in find_maxϵ"))
  end

  maxϵ = maxϵ_pos = maxϵ_index = typemin(T) #maxcubic(x[1], x[2], 0, s.b[i]-((s(s.knots[i]+x[2])-s(s.knots[i]+x[1]))/(x[2]-x[1])), s.c[i], s.d[i])
	for (j, xj) in take(enumerate(x), length(x)-1)
    lin_approx_slope = (s(x[j+1])-s(xj))/(x[j+1]-xj)
	  maxϵ_new, maxϵ_pos_new = maxabscubic(xj-x[1], x[j+1]-x[1], a-(s(xj)-lin_approx_slope*(xj-x[1])), b-lin_approx_slope, c, d)
    @assert maxϵ_pos != xj-x[1] "$xj $(a-(s(xj)-lin_approx_slope*(xj-x[1])))"

	  if maxϵ_new > maxϵ
	    maxϵ = maxϵ_new
		  maxϵ_pos = maxϵ_pos_new
		  maxϵ_index = j
    end
  end

  return maxϵ, maxϵ_pos, maxϵ_index
end

"""
A helper function for adaptive_discretize_mesh

Parameters:
 - *s* is the spline that is being discretized.
 - *x* holds x-values that need to be used between the current knot (which is not easily visible to this function) and the one after it to get a reasonable approximation.
 - *absTol* is the maximum absolute error that is tolerated
 - *a*, *b*, *c*, and *d* are the polynomial coefficients of the spline at the knot where this is being done.

Return:
 - The new error after adding zero or more values to the mesh *x*

Given _s_ and _x_, this function finds the maximum error associated with using linear
interpolation over the mesh _x_. If this error is too large, then it adds a new value
to _x_ where the max error was and returns the new error.
"""
function densify_mesh!{T}(s::Spline{T}, x::Array{T, 1}, absTol::T, a::T, b::T, c::T, d::T)
  maxϵ, maxϵ_pos, maxϵ_index = find_maxϵ(s, x, a, b, c, d)
  @assert !isnan(maxϵ_pos)
  if maxϵ > absTol
    if maxϵ_pos == x[maxϵ_index+1]
      insert!(x, maxϵ_index+1, (x[maxϵ_index+1]+x[maxϵ_index])/2)
    elseif maxϵ_pos in x
      error("duplicate value $maxϵ_pos x=$x")
    else
      insert!(x, maxϵ_index+1, x[1]+maxϵ_pos)
    end
    #but maxϵ has now changed, so we re-compute it
    maxϵ = find_maxϵ(s, x, a, b, c, d)[1]
  end

  return maxϵ
end

"""
Create an adaptive mesh of points for discretizing a spline _s_.

Parameters:
 - *s* A spline to discretize
 - *tol* The tolerance. If this is negative, then it is assumed to be a relative tolerance (with a few modifications to get around the problem of the spline being 0 somewhere)
 otherwise, it is assumed to be an absolute tolerance.
"""
function adaptive_discretize_mesh{T}(s::Spline{T}, tol::Number=-.01)

	if tol > 0
		absTol = tol
	end
	nKnots = length(s.knots)

	x = Array{Array{T, 1}, 1}(nKnots)

	for i in 1:nKnots-1
		x[i] = [s.knots[i], s.knots[i+1]]
		maxϵ = maxabscubic(0.0, x[i][2]-x[i][1], zero(T), s.b[i]-((s.values[i+1]-s.values[i])/(s.knots[i+1]-s.knots[i])), s.c[i], s.d[i])[1]

		if tol < 0
			absTol = abs(tol)*maxabscubic(zero(T), s.knots[i+1]-s.knots[i], s.a[i], s.b[i], s.c[i], s.d[i])[1]
		end

		while maxϵ > absTol
      maxϵ = densify_mesh!(s, x[i], absTol, s.a[i], s.b[i], s.c[i], s.d[i])
		end
		deleteat!(x[i], length(x[i]))
	end
	x[nKnots] = [s.knots[nKnots]]

	return vcat(x...)::Array{T, 1}
end

function adaptive_discretize{T}(s::Spline{T}, tol::Number=-.01)
	mesh = adaptive_discretize_mesh(s, tol)
	y = [s(x) for x in mesh]

	return Dict(:x => mesh, :y => y)
end

"""Scale a spline so that its maximum absolute value is __normalize_to__"""
function normalize_max!{T}(s::Spline{T}, normalize_to::T=one(T))
	s2 = s/maxabs(s)
	s.knots = s2.knots
	s.values = s2.values
	s.a = s2.a
	s.b = s2.b
	s.c = s2.c
	s.d = s2.d
	return nothing
end

"""Return the smallest value of x for which s(x) is defined"""
function xmin{T}(s::Spline{T})
	return s.knots[1]
end

"""Return the largest value of x for which s(x) is defined"""
function xmax{T}(s::Spline{T})
	return s.knots[end]
end

"""Shift the domain of a spline by amount _Δx_"""
function domain_shift!{T}(s::Spline{T}, Δx::T)
	for i in 1:length(s.knots)
		s.knots[i] += Δx
	end
end

export call
export insert!
export moveknot!
export deleteknotat!
export trimstart!
export trimend!
export trim!
export maximum
export minimum
export extrema
export xmax
export maxabs
export Spline
export discretize
export uniform_discretize
export adaptive_discretize
export indomain
export domain
export normalize_max!
export xmin
export xmax
export domain_shift!


# Random point in the Dim-dimensional unit hypercube
Base.rand(::Type{Point{Dim, T}}) where {Dim, T} = Point{Dim, T}(rand(SVector{Dim, T}))
function Base.rand(::Type{Point{Dim, T}}, num_points::Int64) where {Dim, T}
    return [ Point{Dim, T}(rand(SVector{Dim, T})) for i = 1:num_points ]
end

# Random line in the Dim-dimensional unit hypercube
function Base.rand(::Type{LineSegment{Dim, F}}) where {Dim, F}
    points = rand(Point{Dim, F}, 2)
    return LineSegment{Dim, F}(points[1], points[2])
end

# N random lines in the Dim-dimensional unit hypercube
function Base.rand(::Type{LineSegment{Dim, F}}, N::Int64) where {Dim, F}
    return [ rand(LineSegment{Dim, F}) for i ∈ 1:N ]
end

# Random quadratic segment in the Dim-dimensional unit hypercube
function Base.rand(::Type{QuadraticSegment{Dim, F}}) where {Dim, F}
    points = rand(Point{Dim, F}, 3)
    return QuadraticSegment(points[1], points[2], points[3])
end

# N random quadratic segments in the Dim-dimensional unit hypercube
function Base.rand(::Type{QuadraticSegment{Dim, F}}, N::Int64) where {Dim, F}
    return [ rand(QuadraticSegment{Dim, F}) for i ∈ 1:N ]
end

# A random AABox within the Dim-dimensional unit hypercube
# What does the distribution of AABoxs look like? Is this uniform?
function Base.rand(::Type{AABox{Dim, T}}) where {Dim, T}
    coord₁ = rand(T, Dim)
    coord₂ = rand(T, Dim)
    return AABox{Dim, T}(Point{Dim, T}(min.(coord₁, coord₂)),
                         Point{Dim, T}(max.(coord₁, coord₂)))
end

# N random AABoxs within the Dim-dimensional unit hypercube
function Base.rand(::Type{AABox{Dim, T}}, num_boxes::Int64) where {Dim, T}
    return [ rand(AABox{Dim, T}) for i ∈ 1:num_boxes ]
end


type LSFit{T}
    coef::Vector{T}
    fittedfunc::Function
end

call(t::LSFit, x...) = t.fittedfunc(x...)

function _lsfit(flist::Array{Function}, A::Matrix, y::Vector)
    coef = pinv(A)*y
    # use a generated function to avoid the loop call each time you do a lookup
    @generated function fitter(x...)
        eq = :(($coef[1]*$flist[1](x...)))
        for i = 2:length(coef)
            eq = Expr(:call, :+, eq, :($coef[$i]*$flist[$i](x...)))
        end
        return eq
    end
    return LSFit(coef, fitter)
end

"""
    lsfit(flist, x, y)

"""
function lsfit(flist::Array{Function}, x::AbstractVector, y::AbstractVector)
    @assert size(x, 1) == size(y, 1)
    # TODO: figure out a fast and generic way of doing this
    A = eltype(y)[f(xi) for xi in x, f in flist]
    _lsfit(flist, A, y)
end

function lsfit(flist::Array{Function}, x::AbstractMatrix, y::AbstractVector)
    @assert size(x, 1) == size(y, 1)
    A = eltype(y)[f(x[i, :]...) for i in 1:size(x, 1), f in flist]
    _lsfit(flist, A, y)
end

"""
    @afuns(var, flist)

Generates an array of anonymous functions that take `var` as arguments.

# Examples
```jl
@afuns(x, [1, x, x^2])
```
returns
```jl
[x->1, x->x, x->x^2]
```
"""
macro afuns(var, list)
    if  typeof(list) != Expr || list.head != :vect
        throw(ArgumentError("Only simple arrays are supported"))
    end
    flist = []
    for fi in list.args
        push!(flist, Expr(:->, var, Expr(:block, fi)))
    end
    return Expr(:vect, flist...)
end


#RSDeltaSigmaPort: Implement missing core functionality
#-------------------------------------------------------------------------------

"""Emulates rounding of array indices"""
array_round(i) = round(Int, i) #VERIFYME

function rat(v::Real, tol::Float64)
	r=rationalize(Float64(v), tol=tol)
	return (r.num, r.den)
end

function squeeze(a)
	sz = collect(size(a))
	for i in reverse(keys(sz))
		if sz[i]<2
			a = dropdims(a, dims=i)
		end
	end
	return a
end

#Sort complex poles
function _cplxpair(a)
	epstol = 100

	#Sorts real first, then imaginary:
	function isrealless(a,b)
		Δmin = epstol*eps(abs(a))
		if abs(real(a) - real(b)) < Δmin
			return imag(a) < imag(b)
		else
			return real(a) < real(b)
		end
	end

	a = sort(a, lt=isrealless)
	i = 1
	while i < length(a)
		v = a[i]
		Δmin = epstol*eps(abs(v))
		if abs(imag(v)) < Δmin #Really a real number
			a[i] = real(v)
			i+=1
			break
		elseif i < length(a)
			if abs(v-conj(a[i+1])) < Δmin
				i+=2 #Skip both cplx conj pairs.
				break
			else
				throw(ErrorException("cplxpair failed."))
			end
		else
			throw(ErrorException("cplxpair failed."))
		end

	end
	return a
end

"""`cplxpair(a)`

Sort roots in array a such that complex pairs are in ascending order of their
real parts, then by their imaginary part.  In the output, the real roots
re-positionned after the complex roots.
"""
function cplxpair(a)
	epstol = 100

	#Index of real roots:
	Δmin = epstol*eps.(abs.(a)) #Threshold for numerical error
	ireal = abs.(imag.(a)) .< Δmin

	areal = real.(a[ireal]) #Clean up real roots
	acplx = a[.!ireal] #complex pairs
	return vcat(_cplxpair(acplx), sort(areal)) #Sort roots
end

#Custom function to ensure complex conjugate pairs match exactly.
function cleancplxpair(l)
	epstol = 100

	if length(l) < 2
		return l
	end

	l = cplxpair(l) #Sort to group complex conjugates together
	result = similar(l)
	if length(l)>0
		#Copy last value in case not part of conjugate pair
		#(ignored by following algorithm)
		result[end] = l[end]
	end

	i = 1
	while i < length(l)
		a, b = l[i], l[i+1]
		ΔRmin = epstol*eps(real(a))
		ΔXmin = epstol*eps(imag(a))

		result[i] = a
		if abs(real(a) - real(b)) < ΔRmin && abs(imag(a) + imag(b)) < ΔXmin
			a = (a+conj(b))/2 #Take mean & re-write
			result[i] = a
			result[i+1] = conj(a)
			i+=1
		end
		i+=1
	end

	return result
end

#TODO: Will algorithms work without collect (possibly faster?):
eye(n::Int) = collect(LinearAlgebra.I(n))

#It sounds like this should be implementation of orth:
#orth(A) = LinearAlgebra.qr(A).Q

#But scipy seems to do it this way:
orth(A) = LinearAlgebra.svd(A).U

#Convert array of roots into polynomial:
function poly(rA)
	result = [1]
	for r in rA
		result = conv(result, [1, -r])
	end
	return result
end

eig(a) = eigen(a).values

#Implement interp1 using linear interpolation:
function interp1_lin(xA, yA, xv)
	ILib = Interpolations
	itp = ILib.interpolate((x,), y, ILib.Gridded(ILib.Linear()))
		return itp(xv)
end

#Implement interp1 using cubic interpolation:
function interp1_cubic(xA, yA, xv)
	ILib = Interpolations
   itp = ILib.interpolate(yA, ILib.BSpline(ILib.Cubic(ILib.Natural())), ILib.OnGrid())
		intf = ILib.scale(itp, xA)
	return intf(xv)
end

#FIXME/VERIFYME
#Algoritm assembled together by MALaforge, hoping it will be sufficient.
#Probably a good idea to have an expert implement proper function.
function interp(v, OSR::Int)
	thresh = .01
	Δs = OSR*ceil(Int, 1/(thresh*π))
	x = -Δs:1:Δs
	v_0 = zeros(length(v)*OSR)
	for i in 0:length(v)-1
		v_0[1+OSR*i] = v[1+i]
	end
	_filt = sinc.(x/OSR)
	result = conv(_filt, v_0)
	result = result[Δs .+ (1:length(v_0))]
	return result
end

#Evaluate polynomial p at value x
function polyval(p, x)
	#NOTE: [1, 2, 3] => 1 + 2x¹ + 3x²
	polyfn = Polynomial(p)
	return polyfn.(x) #Evaluates polynomial
end

#_roots so as to not confuse it with Polynomials.roots
#NOTE: Polynomial expects the coefficients in opposite order:
_roots(p) = Polynomials.roots(Polynomial(reverse(p)))

#Last line


"""
Tools for Pauli strings and binary symplectic vectors / matrices.
"""
module PauliTools

# exports
export bsp, pack, unpack, to_bsf, to_pauli, weight

@doc raw"""
    bsp(
        A::AbstractVecOrMat{Bool}, B::AbstractVecOrMat{Bool}
    ) -> Union{Bool,BitVector,BitMatrix}

Return the binary symplectic product of `A` with `B`, given in binary symplectic form.

The binary symplectic product ``⊙`` is defined as ``A ⊙ B ≡ A Λ B \bmod 2`` where
``Λ = \left[\begin{smallmatrix} 0 & I \\ I & 0 \end{smallmatrix}\right]``.

# Examples
```jldoctest
julia> a = BitVector([1, 0, 0, 0]);  # XI

julia> b = BitVector([0, 0, 1, 0]);  # ZI

julia> bsp(a', b)
true
```
```jldoctest
julia> stabilizers = BitMatrix(  # 5-qubit stabilizers
       [1 0 0 1 0 0 1 1 0 0     # XZZXI
        0 1 0 0 1 0 0 1 1 0     # IXZZX
        1 0 1 0 0 0 0 0 1 1     # XIXZZ
        0 1 0 1 0 1 0 0 0 1]);  # ZXIXZ

julia> error = BitVector([0, 0, 1, 1, 0, 0, 1, 0, 1, 0]);  # IZXYI

julia> bsp(stabilizers, error)
4-element BitVector:
 0
 1
 1
 0
```
"""
function bsp(a::AbstractVecOrMat{Bool}, b::AbstractVecOrMat{Bool})
    # circshift b by half its 1st dimension to emulate symplectic product
    return isodd.(a * circshift(b, size(b, 1)/2))  # mod elements to base 2
end

"""
    pack(bsf::AbstractVector{Bool}) -> Tuple{String,Int}

Pack a binary vector into a concise representation, typically for log output. See also
[`unpack`](@ref).

# Examples
```jldoctest
julia> a = BitVector([1, 0, 1, 0, 1, 1]);  # XZY

julia> b = pack(a)  # (hex_value, length)
("2b", 6)
julia> unpack(b) == a
true
```
"""
function pack(bsf::AbstractVector{Bool})
    bit_str = join(d ? '1' : '0' for d in bsf)  # convert to bit-string
    val = parse(BigInt, bit_str; base=2)  # parse to val (BigInt to support long vectors)
    return string(val, base=16), length(bsf)  # hex_str, len
end

"""
    unpack(packed_bsf::Tuple{String,Int}) -> BitVector

Unpack a binary vector from a concise representation, typically from log output. See also
[`pack`](@ref).

# Examples
```jldoctest
julia> a = ("2b", 6);  # (hex_value, length)

julia> b = unpack(a)  # XZY
6-element BitVector:
 1
 0
 1
 0
 1
 1
julia> pack(b) == a
true
```
"""
function unpack(packed_bsf::Tuple{String,Int})
    hex_str, len = packed_bsf
    val = parse(BigInt, hex_str; base=16)  # parse to val (BigInt to support long vectors)
    return reverse!(BitVector(digits(val; base=2, pad=len)))  # binary-digits as BitVector
end

"""
    to_bsf(
        pauli::Union{AbstractString,AbstractVector{<:AbstractString}}
    ) -> Union{BitVector,BitMatrix}

Convert the Pauli string operator(s) to binary symplectic form.

A single Pauli string is converted to a vector. A vector of Pauli strings is converted to a
matrix where each row corresponds to a Pauli.

# Examples
```jldoctest
julia> to_bsf("XIZIY")
10-element BitVector:
 1
 0
 0
 0
 1
 0
 0
 1
 0
 1
```
```jldoctest
julia> to_bsf(["XIZIY", "IXZYI"])
2×10 BitMatrix:
 1  0  0  0  1  0  0  1  0  1
 0  1  0  1  0  0  0  1  1  0
```
"""
function to_bsf(pauli::AbstractString)
    p = collect(pauli)
    ys = p .== 'Y'
    return vcat((p .== 'X') .| ys, (p .== 'Z') .| ys)
end
function to_bsf(paulis::AbstractVector{<:AbstractString})
    # for each pauli string, convert to bsf vector, transpose; then concatenate as rows
    return vcat((transpose(to_bsf(p)) for p in paulis)...)
end

"""
    to_pauli(bsf::AbstractVecOrMat{Bool}) -> Union{String,Vector{String}}

Convert the binary symplectic form to Pauli string operator(s).

A vector is converted to a single Pauli string. A matrix is converted row-by-row to a
collection of Pauli strings.

# Examples
```jldoctest
julia> to_pauli(BitVector([1, 0, 0, 0, 1, 0, 0, 1, 0, 1]))
"XIZIY"
```
```jldoctest
julia> to_pauli(BitMatrix([1 0 0 0 1 0 0 1 0 1; 0 1 0 1 0 0 0 1 1 0]))
2-element Vector{String}:
 "XIZIY"
 "IXZYI"
```
"""
function to_pauli(bsf::AbstractVector{Bool})
    l = length(bsf)  # bsf = (1 0 0 0 1 | 0 0 1 0 1)
    x, z = bsf[1:l÷2], bsf[l÷2+1:end]  # x = (1 0 0 0 1), z = (0 0 1 0 1)
    return String(map(i -> "IXZY"[i+1], x+2z))  # x+2z = (1 0 2 0 3) -> "XIZIY"
end
function to_pauli(bsfs::AbstractMatrix{Bool})
    return String[to_pauli(bsf) for bsf in eachrow(bsfs)]
end

"""
    weight(bsf::AbstractVecOrMat{Bool}) -> Int

Return the weight of the binary symplectic form.

# Examples
```jldoctest
julia> weight(BitVector([1, 0, 0, 0, 1, 0, 0, 1, 0, 1]))
3
```
```jldoctest
julia> weight(BitMatrix([1 0 0 0 1 0 0 1 0 1; 1 1 1 1 1 0 0 0 0 0]))
8
```
"""
function weight(bsf::AbstractVector{Bool})
    l = length(bsf)  # bsf = (1 0 0 | 1 1 0)
    return count(>(0), bsf[1:l÷2] + bsf[l÷2+1:end])  # count(>(0), (2 1 0))
end
function weight(bsfs::AbstractMatrix{Bool})
    l = size(bsfs, 2)  # bsf = (1 0 0 | 1 1 0; 1 1 1 | 0 1 0)
    return count(>(0), bsfs[:,1:l÷2] + bsfs[:,l÷2+1:end])  # count(>(0), (2 1 0; 1 2 1))
end

"""
    weight(pauli::Union{AbstractString,AbstractVector{<:AbstractString}}) -> Int

Return the weight of the Pauli string operator(s).

# Examples
```jldoctest
julia> weight("XIZIY")
3
```
```jldoctest
julia> weight(["XIZIY", "XXXXX"])
8
```
"""
function weight(pauli::AbstractString)
    return count(!=('I'), pauli)  # count(!=('I'), "IXYIZ")
end
function weight(paulis::AbstractVector{<:AbstractString})
    return sum(weight, paulis)
end

end


gc_ctr(lat::Array{Float64,1}, lon::Array{Float64,1}) =  (atan.(tan.(deg2rad.(lat)).*0.9933056), deg2rad.(lon))
gc_unwrap!(t::Array{Float64,1}) = (t[t .< 0] .+= (2.0*Float64(π)); return t)

@doc """
    G = gcdist(src, rec)

  Compute great circle distance, azimuth, and backazimuth from single source `s`
with coordinates `[s_lat, s_lon]` to receivers `r` with coordinates `[r_lat r_lon].`

  For a single source, pass `src` as a Float64 vector of the form `[s_lat, s_lon]`;
gcdist will return an Array{Float64,2} of the form

    [Δ₁   θ₁   β₁
     Δ₂   θ₂   β₂
     ⋮    ⋮    ⋮
     Δn   θn   βn]

for receivers `1:n`.

  For multiple sources, pass `src` as an Array{Float64,2} with each row
containing one (lat, lon) pair. This returns a three-dimensional matrix where
each two-dimensional slice takes the form

      [Δᵢ₁   θᵢ₁   βᵢ₁
       ⋮     ⋮     ⋮
       Δᵢn   θᵢn   βᵢn]

for source `i` at receivers `1:n`.
""" gcdist
function gcdist(src::Array{Float64,1}, rec::Array{Float64,2})
  N = size(rec, 1)
  lat_src = repeat([src[1]], N)
  lon_src = repeat([src[2]], N)
  lat_rec = rec[:,1]
  lon_rec = rec[:,2]

  ϕ1, λ1 = gc_ctr(lat_src, lon_src)
  ϕ2, λ2 = gc_ctr(lat_rec, lon_rec)
  Δϕ = ϕ2 - ϕ1
  Δλ = λ2 - λ1

  a = sin.(Δϕ/2.0) .* sin.(Δϕ/2.0) + cos.(ϕ1) .* cos.(ϕ2) .* sin.(Δλ/2.0) .* sin.(Δλ/2.0)
  Δ = 2.0 .* atan.(sqrt.(a), sqrt.(1.0 .- a))
  A = atan.(sin.(Δλ).*cos.(ϕ2), cos.(ϕ1).*sin.(ϕ2) - sin.(ϕ1).*cos.(ϕ2).*cos.(Δλ))
  B = atan.(-1.0.*sin.(Δλ).*cos.(ϕ1), cos.(ϕ2).*sin.(ϕ1) - sin.(ϕ2).*cos.(ϕ1).*cos.(Δλ))

  # convert to degrees
  return rad2deg.(hcat(Δ, gc_unwrap!(A), gc_unwrap!(B)))
end
gcdist(lat0::Float64, lon0::Float64, rec::Array{Float64,2}) = gcdist([lat0, lon0], rec)
gcdist(lat0::Float64, lon0::Float64, lat1::Float64, lon1::Float64) = gcdist([lat0, lon0], [lat1 lon1])

function gcdist(src::Array{Float64,2}, rec::Array{Float64,2})
  N_src = size(src,1)
  N_rec = size(rec,1)
  G = Array{Float64,3}(undef, N_rec, 3, N_src)
  for i = 1:N_src
    G[:,:,i] = gcdist(src[i,:], rec)
  end
  return G
end
gcdist(src::Array{Float64,2}, rec::Array{Float64,1}) = gcdist(src, [rec[1] rec[2]])


using BayesianLinearRegression

X = randn(100, 80);
β = randn(50);
y = X[:,1:50] * β + randn(100);

@time m = BayesianLinReg(X,y);

@time fit!(m);

logEvidence(m)

effectiveNumParameters(m)

priorScale(m)
noiseScale(m)

posteriorWeights(m)
posteriorVariance(m)

predict(m,X[:,m.active])

function getpath!(m)
    path = Pair{Int, Float64}[]
    while length(m.active) > 1
        bestj = 0
        bestlogEv = -Inf
        activebase = m.active
        for j in activebase
            m.active = setdiff(activebase, j)
            update!(m)
            fit!(m)

            thislogEv = logEvidence(m)
            if thislogEv > bestlogEv
                bestj = j
                bestlogEv = thislogEv
            end
        end

        push!(path, bestj => bestlogEv)
        println("removing ", bestj)
        m.active = setdiff(activebase, bestj)
    end
    return path
end

path = getpath!(m)

logEvs = [getproperty(p,:second) for p in path]

plot(logEvs)

effectiveNumParameters(m)

priorScale(m)
noiseScale(m)

posteriorWeights(m)
posteriorVariance(m)

predict(m,view(m.X, :, m.active))

function f(n)
    m = BayesianLinReg(X[:,1:n],y)
    fit!(m; callback=stopAtIteration(10))
    return m
end;

ms = f.(1:100);

argmax(logEvidence.(ms))
m = ms[49];

effectiveNumParameters(m)

priorScale(m)
noiseScale(m)

posteriorWeights(m)
posteriorVariance(m)

predict(m,m.X)

using Plots
using Statistics

logevs = logEvidence.(ms)

plot(3:100,logevs[3:100], legend=false)
xlabel!("Number of features used")
ylabel!("Log Evidence")

m = BayesianLinReg(X[:,1:50],y)
fit!(m; callback=stopAtIteration(10))


# ## Rose plot

#md # ![FILE_NAME.png](images/FILE_NAME.png)

using Random
Random.seed!(2018)

n = 24
R = rand(n+1)
θ = 0:2pi/n:2pi
plot(θ, R, proj=:polar, line=:steppre, lims=(0:2))


function mm_spec_kj2_dimspec()::MPCCDimSpec
    dimspec = MPCCDimSpec(2, 1, 2, 0, 0, 1, 0)
    return dimspec
end

function mm_spec_kj2_defn(x, pr, ps)

    f = (x[1] - pr[1])^2 + x[2]^3 + x[2]^2

    ce = Vector{Num}()

    ci = Vector{Num}()

    F = Matrix{Num}(undef, 2, 1)
    F[1,1] = x[1]
    F[2,1] = x[2]

    return MPCCDefinition(f, ce, ci, F)
end

function mm_spec_kj2_nzmask()::MPCCModelNZMask
    nz_mask = MPCCModelNZMask(
        # ce
        [],
        # ci
        [   Set{Int64}([1, 2]),
            Set{Int64}([1]) ],
        # F
        [
            # F1
            [   Set{Int64}([1]) ],
            # F2
            [   Set{Int64}([2]) ]
        ] )
    return nz_mask
end

function mm_spec_kj2_testvectors()
    return Vector{MPCCModelTestVector}(undef, 0)
end

function mm_spec_kj2_knownsols()

    function local_kj2_knownsol(pr::Vector{R}, ps::Vector{Int64}) where {R <: Real}
        @assert ( 1 == length(pr) && 0 == length(ps) )
        t = pr[1]

        if ( t < 0 )
            x_sol = [0, 0]
            f_sol = (-t)^2
        else
            x_sol = [t, 0]
            f_sol = 0
        end

        return (x_sol, f_sol)
    end

    return [ local_kj2_knownsol ]
end