#This opf model is borrowed from JuMP JuMPSupplement
#   reference: https://github.com/mlubin/JuMPSupplement/tree/master/acpower
#   modified to benchmark the Edge Pushing implementation

using JuMP
using Ipopt
using Compat

if parse(Int, ARGS[2]) == 1
    #use JuMP
elseif parse(Int, ARGS[2]) == 2
    include("interface_tape.jl")
    using TapeInterface
elseif parse(Int, ARGS[2]) == 3
    include("interface_check.jl")
    using TapeInterface
else
    @assert false
end

type Bus
    bustype::Int
    name::ASCIIString
    voltage0::Float64
    angle0::Float64
    p_gen::Float64
    q_gen::Float64
    q_min::Float64
    q_max::Float64
    p_load::Float64
    q_load::Float64
    g_shunt::Float64
    b_shunt0::Float64
    b_shunt_min::Float64
    b_shunt_max::Float64
    b_dispatch::Float64
    area::Float64
end

type Branch
    from::Int
    to::Int
    branchtype::Int
    r::Float64
    x::Float64
    c::Float64
    tap0::Float64
    tap_min0::Float64
    tap_max0::Float64
    def0::Float64
    def_min::Float64
    def_max::Float64
    g::Float64
    b::Float64
end

function opf(bus, branch,
    bus_voltage_min, bus_voltage_max,
    branch_tap_min, branch_tap_max,
    p_gen_upper, p_gen_lower)

    simplify = true
    @show simplify
    if parse(Int, ARGS[2]) == 1
        mod = Model(solver=IpoptSolver(max_iter=1), simplify_nonlinear_expressions=simplify)
    elseif parse(Int, ARGS[2]) == 2 || parse(Int, ARGS[2]) == 3
        mod = Model(solver=TapeSolver(IpoptSolver(max_iter=1)), simplify_nonlinear_expressions=simplify)
    else
        @assert false
    end

    nbus = length(bus)
    nbranch = length(branch)
    @variable(mod, bus_voltage_min[bus[i].bustype] <= bus_voltage[i=1:nbus] <= bus_voltage_max[bus[i].bustype], start=1)
    @variable(mod, bus[i].b_shunt_min <= bus_b_shunt[i=1:nbus] <= bus[i].b_shunt_max, start=bus[i].b_shunt0)
    @variable(mod, bus_angle[1:nbus], start=0)

    @variable(mod, branch_tap_min <= branch_tap[1:nbranch] <= branch_tap_max, start=1)
    @variable(mod, branch[i].def_min <= branch_def[i=1:nbranch] <= branch[i].def_max, start = branch[i].def0)

    in_lines = [Int[] for i in 1:nbus] # indices of incoming branches
    out_lines = [Int[] for i in 1:nbus] # indices of outgoing branches
    @show nbranch
    @show nbus
    for i in 1:nbranch
        b = branch[i]
        push!(out_lines[b.from],i)
        push!(in_lines[b.to],i)
        @assert 1 <= b.to <= nbus
    end

    @NLexpression(mod, Gself[k=1:nbus],
        bus[k].g_shunt +
        sum{branch[i].g*branch_tap[i]^2, i=out_lines[k]} +
        sum{ branch[i].g, i=in_lines[k]})

    @NLexpression(mod, Gout[i=1:nbranch],
        (0-branch[i].g*cos(branch_def[i])+branch[i].b*sin(branch_def[i]))*branch_tap[i])
    @NLexpression(mod, Gin[i=1:nbranch],
        (0-branch[i].g*cos(branch_def[i])-branch[i].b*sin(branch_def[i]))*branch_tap[i])

    @NLexpression(mod, Bself[k=1:nbus],
        bus_b_shunt[k] +
        sum{branch[i].b*branch_tap[i]^2 + branch[i].c/2, i=out_lines[k]} +
        sum{ branch[i].b + branch[i].c/2, i=in_lines[k]})

    @NLexpression(mod, Bin[i=1:nbranch],
        (branch[i].g*sin(branch_def[i])-branch[i].b*cos(branch_def[i]))*branch_tap[i])
    @NLexpression(mod, Bout[i=1:nbranch],
        (0-branch[i].g*sin(branch_def[i])-branch[i].b*cos(branch_def[i]))*branch_tap[i])

    # Minimize active power

    @NLobjective(mod, Min, sum{ (bus[k].p_load +
        sum{ bus_voltage[k] * bus_voltage[branch[i].from] *
            (Gin[i] * cos(bus_angle[k] - bus_angle[branch[i].from]) +
             Bin[i] * sin(bus_angle[k] - bus_angle[branch[i].from])), i=in_lines[k] } +
        sum{ bus_voltage[k] * bus_voltage[branch[i].to] *
            (Gout[i] * cos(bus_angle[k] - bus_angle[branch[i].to]) +
             Bout[i] * sin(bus_angle[k] - bus_angle[branch[i].to])), i=out_lines[k] }
        + bus_voltage[k]^2*Gself[k] )^2,
        k in 1:nbus; bus[k].bustype == 2 || bus[k].bustype == 3})
    for k in 1:nbus
        bus[k].bustype == 0 || continue
        # p_load
        @NLconstraint(mod, bus[k].p_gen - bus[k].p_load -
            sum{ bus_voltage[k] * bus_voltage[branch[i].from] *
                (Gin[i] * cos(bus_angle[k] - bus_angle[branch[i].from]) +
                 Bin[i] * sin(bus_angle[k] - bus_angle[branch[i].from])), i=in_lines[k] } -
            sum{ bus_voltage[k] * bus_voltage[branch[i].to] *
                (Gout[i] * cos(bus_angle[k] - bus_angle[branch[i].to]) +
                 Bout[i] * sin(bus_angle[k] - bus_angle[branch[i].to])), i=out_lines[k] }
            - bus_voltage[k]^2*Gself[k] == 0)
        # q_load
        @NLconstraint(mod, bus[k].q_gen - bus[k].q_load -
            sum{ bus_voltage[k] * bus_voltage[branch[i].from] *
                (Gin[i] * sin(bus_angle[k] - bus_angle[branch[i].from]) -
                 Bin[i] * cos(bus_angle[k] - bus_angle[branch[i].from])), i=in_lines[k] } -
            sum{ bus_voltage[k] * bus_voltage[branch[i].to] *
                (Gout[i] * sin(bus_angle[k] - bus_angle[branch[i].to]) -
                 Bout[i] * cos(bus_angle[k] - bus_angle[branch[i].to])), i=out_lines[k] }
             + bus_voltage[k]^2*Bself[k] == 0)
    end
    for k in 1:nbus
        (bus[k].bustype == 2 || bus[k].bustype == 3) || continue
        # q_inj
        @NLconstraint(mod, bus[k].q_min <= bus[k].q_load +
            sum{ bus_voltage[k] * bus_voltage[branch[i].from] *
                (Gin[i] * sin(bus_angle[k] - bus_angle[branch[i].from]) -
                 Bin[i] * cos(bus_angle[k] - bus_angle[branch[i].from])), i=in_lines[k] } +
            sum{ bus_voltage[k] * bus_voltage[branch[i].to] *
                (Gout[i] * sin(bus_angle[k] - bus_angle[branch[i].to]) -
                 Bout[i] * cos(bus_angle[k] - bus_angle[branch[i].to])), i=out_lines[k] } +
            - bus_voltage[k]^2*Bself[k] <= bus[k].q_max)
        # p_inj
        @NLconstraint(mod, 0 <= bus[k].p_load +
            sum{ bus_voltage[k] * bus_voltage[branch[i].from] *
                (Gin[i] * cos(bus_angle[k] - bus_angle[branch[i].from]) +
                 Bin[i] * sin(bus_angle[k] - bus_angle[branch[i].from])), i=in_lines[k] } +
            sum{ bus_voltage[k] * bus_voltage[branch[i].to] *
                (Gout[i] * cos(bus_angle[k] - bus_angle[branch[i].to]) +
                 Bout[i] * sin(bus_angle[k] - bus_angle[branch[i].to])), i=out_lines[k] } +
            + bus_voltage[k]^2*Gself[k] <= p_gen_upper*bus[k].p_gen)
    end

    for i in 1:nbus
        #---- FREEZE THE REFERENCE BUS ANGLE TO ZERO.
        if bus[i].bustype == 3
            setlowerbound(bus_angle[i], 0.0)
            setupperbound(bus_angle[i], 0.0)
        end
        #---- FREEZE ANY DISPATCHABLE SHUNTS.
        if bus[i].b_dispatch == 0
            setlowerbound(bus_b_shunt[i],bus[i].b_shunt0)
            setupperbound(bus_b_shunt[i],bus[i].b_shunt0)
        end
    end

    for i in 1:nbranch
        #---- FREEZE ANY BRANCH TAPS.
        if branch[i].branchtype == 0 || branch[i].branchtype == 3
            setlowerbound(branch_tap[i],1)
            setupperbound(branch_tap[i],1)
        end
        #---- FREEZE CERTAIN PHASE SHIFTERS.
        if branch[i].branchtype != 4
            setlowerbound(branch_def[i],getvalue(branch_def[i]))
            setupperbound(branch_def[i],getvalue(branch_def[i]))
        end
    end

    println("solving")
    solve(mod)

end

function prepdata(bus, branch)

    buses = Array(Bus,size(bus,1))
    refbus = 0
    shunt = 0
    busmap = Dict()
    for i in 1:size(bus,1)
        @assert !haskey(busmap, bus[i,1])
        busmap[bus[i,1]] = i
        buses[i] = Bus(bus[i,2:end]...)
        # rescale
        buses[i].p_gen /= 100
        buses[i].q_gen /= 100
        buses[i].q_min /= 100
        buses[i].q_max /= 100
        buses[i].p_load /= 100
        buses[i].q_load /= 100
        if buses[i].bustype == 3
            refbus += 1
        end
        if buses[i].b_dispatch == 0
            shunt += 1
        end
    end
    @show refbus
    @show shunt

    branches = Array(Branch,size(branch,1))
    branchtap = 0
    phaseshift = 0
    for i in 1:size(branch,1)
        branches[i] = Branch(branch[i,2:end]...,0,0)
        @assert branch[i,1] == i
        branches[i].to = busmap[branches[i].to]
        branches[i].from = busmap[branches[i].from]
        branches[i].g = branches[i].r /(branches[i].r^2 + branches[i].x^2)
        branches[i].b = -branches[i].x /(branches[i].r^2 + branches[i].x^2)
        # rescale
        branches[i].def_min *= 3.14159/180
        branches[i].def_max *= 3.14159/180
        branches[i].def0 *= -3.14159/180
        if branches[i].branchtype == 0 || branches[i].branchtype == 3
            branchtap += 1
        end
        if branches[i].branchtype != 4
            phaseshift += 1
        end
    end
    @show branchtap
    @show phaseshift
    return branches, buses
end

numbuses = ARGS[1]
branches, buses = prepdata(readdlm("IEEE$numbuses.bus"),readdlm("IEEE$numbuses.branch"))

bus_voltage_min = @compat Dict(0 => 0.85, 1 => 0.85, 2 => 0.92, 3 => 0.99)
bus_voltage_max = @compat Dict(0 => 1.15, 1 => 1.15, 2 => 1.08, 3 => 1.01)
branch_tap_min = 0.85
branch_tap_max = 1.15

p_gen_upper = 1.10
p_gen_lower = 0.90
opf(buses, branches,
    bus_voltage_min, bus_voltage_max,
    branch_tap_min, branch_tap_max,
    p_gen_upper, p_gen_lower)


module SparseRegressionTest
using SparseRegression, Test
include("datagenerator.jl")

#-----------------------------------------------------------------------# Sanity Check and show
@testset "Sanity Check" begin
    n, p = 1000, 5
    x, y, β = SparseRegression.fakedata(L2DistLoss(), n, p)
    show(SModel(x, y))
    println("\n")
end

name(o) = replace(string(typeof(o)), r"([a-zA-Z]*\.)", "")

function _test(l::Loss, p::Penalty, alg)
    x, y, β = SparseRegression.fakedata(l, 100, 5)
    o = @inferred SModel(x, y, l, p)
    a = @inferred alg(o)
    @inferred learn!(o, strategy(a, MaxIter(3)))
    @test coef(o) == o.β
    @test length(predict(o, x)) == length(y)

    w = rand(100)
    o = @inferred SModel(x, y, l, Weights(w), p)
    a = @inferred alg(o)
    learn!(o, strategy(a, MaxIter(3)))
    coef(o)
    predict(o, x)
end

@testset "Sanity Check" begin
    for a in [ProxGrad, Fista, AdaptiveProxGrad, GradientDescent]
        @info(a)
        for l in [L2DistLoss(), .5*L2DistLoss(), HuberLoss(2.0), QuantileLoss(.7),
            LogitMarginLoss(), DWDMarginLoss(2.0), L2HingeLoss(), ExpLoss()]
            println("  > $l")
            for p in [NoPenalty(), L2Penalty(), L1Penalty(), ElasticNetPenalty()]
                _test(l, p, a)
            end
        end
        println()
    end
    for a in [Sweep, LinRegCholesky]
        for l in [L2DistLoss(), .5L2DistLoss()]
            for p in [NoPenalty(), L2Penalty()]
                _test(l ,p, a)
            end
        end
    end
end

@testset "Linear Regression" begin
    n, p = 1000, 10
    x, y, β = DataGenerator.linregdata(n, p)
    s = SModel(x, y, .5 * L2DistLoss(), NoPenalty())

    for a in [ProxGrad(s, .1), Fista(s, .1), GradientDescent(s, .1), Sweep(s), LinRegCholesky(s)]
        learn!(s, strategy(a, MaxIter(25)))
        @test coef(s) ≈ x\y atol = .5
    end
end

@testset "LineSearch" begin
    n, p = 1000, 10
    x, y, β = SparseRegression.fakedata(L2DistLoss(), n, p)
    s = SModel(x, y, L1Penalty())
    strat = strategy(MaxIter(5), LineSearch(GradientDescent(s)))
    learn!(s, strat)
    @test issorted(coef(s))
    strat = strategy(MaxIter(5), LineSearch(ProxGrad(s)))
    learn!(s, strat)
    @test issorted(coef(s))
    strat = strategy(MaxIter(5), LineSearch(Fista(s)))
    learn!(s, strat)
    @test issorted(coef(s))
end

@testset "SModel" begin
    n, p = 100, 5
    x, y, β = DataGenerator.linregdata(n, p)
    @testset "Constructor type stability" begin
        @inferred SModel(x, y)

        @inferred SModel(x, y, L2DistLoss())
        @inferred SModel(x, y, L2Penalty())
        @inferred SModel(x, y, rand(5))

        @inferred SModel(x, y, L2DistLoss(), L2Penalty())
        @inferred SModel(x, y, L2DistLoss(), rand(5))
        @inferred SModel(x, y, L2Penalty(), L2DistLoss())
        @inferred SModel(x, y, L2Penalty(), rand(5))
        @inferred SModel(x, y, rand(5), L2DistLoss())
        @inferred SModel(x, y, rand(5), L2Penalty())

        @inferred SModel(x, y, L2DistLoss(), L2Penalty(), rand(5))
        @inferred SModel(x, y, L2DistLoss(), rand(5), L2Penalty())
        @inferred SModel(x, y, L2Penalty(), L2DistLoss(), rand(5))
        @inferred SModel(x, y, L2Penalty(), rand(5), L2DistLoss())
        @inferred SModel(x, y, rand(5), L2DistLoss(), L2Penalty())
        @inferred SModel(x, y, rand(5), L2Penalty(), L2DistLoss())
    end
    @testset "predict" begin
        o = SModel(x, y)
        @test predict(o, randn(5)) == 0.0
        @test predict(o, randn(10, 5)) == zeros(10)
    end
end

end


module AtBashM

export atbash

using Main.AffineM: affine
using Main.UtilsM: removeExtras

"""
    atbash(text::String, alphabet::String)

This is secretly an affine cipher with `a` and `b` set to the length of the alphabet - 1, so that's how this function treats it.
"""
function atbash(text::String, alphabet::String)

    return affine(
        removeExtras(text, alphabet),
        alphabet,
        length(alphabet) - 1,
        length(alphabet) - 1,
    )

end # function

end # module


include("invHessianEstimationASym.jl")
include("invHessianEstimationSymReduced.jl")

########################
#
# util functions
#
########################
function _mul(S::Symmetric, θ::SparseIterate)
    p = size(S, 1)

    out = zeros(p, p)
    for ci=1:p
        for ri=1:p
            for k=1:p
                @inbounds out[ri, ci] += S[ri, k] * θ[(ci-1)*p + k]
            end
        end
    end
    out
end

function _mul(θ::SparseIterate, S::Symmetric)
    p = size(S, 1)

    out = zeros(p, p)
    for ci=1:p
        for ri=1:p
            for k=1:p
                @inbounds out[ri, ci] += θ[(k-1)*p + ri] * S[k, ci]
            end
        end
    end
    out
end


(s::Symbol)(args...) = WExpr(buildexpr(s), buildexpr.(args))

function buildwith(head, a, b)
    if isa(a, WExpr) && a.head == WSymbol(head)
        WExpr(WSymbol(head), (a.args..., buildexpr(b)))
    else
        WExpr(WSymbol(head), buildexpr.((a, b)))
    end
end

const WTypes = Union{WExpr, WSymbol, MathLink.WInteger, MathLink.WReal, Symbol}

#
# Arithematic operators
#

Base.:+(a::Union{WTypes, T}, b::Union{WTypes, T}) where T <: Number =
    buildwith("Plus", a, b)

Base.:-(a::WTypes) =
    WSymbol("Times")(-1, buildexpr(a))

Base.:-(a::Union{WTypes, T}, b::Union{WTypes, T}) where T <: Number =
    a + (-b)

Base.:*(a::Union{WTypes, T}, b::Union{WTypes, T}) where T <: Number =
    buildwith("Times", a, b)

Base.:/(a::Union{WTypes, T}, b::Union{WTypes, T}) where T <: Number =
    a * WSymbol("Power")(buildexpr(b), -1)

Base.:^(a::Union{WTypes, T}, b::Union{WTypes, T}) where T <: Number =
    WSymbol("Power")(buildexpr(a), buildexpr(b))

#
# Functions
#

Base.:|>(x, f::Symbol) = buildexpr(f)(buildexpr(x))
Base.inv(x::WTypes) = WSymbol("Inverse")(buildexpr(x))


"""
`module JAC.AngularMomentum`
    ... a submodel of JAC that contains various methods to calculate (standard) symbols, coefficients and functions from
        the theory of angular momentum and Racah's algebra.
"""
module AngularMomentum

    using SpecialFunctions, ..Basics
    using GSL: sf_coupling_3j, sf_coupling_6j, sf_coupling_9j

    """
    `AngularMomentum.allowedKappaSymmetries(syma::LevelSymmetry, symb::LevelSymmetry)`
        ... to determine all allowed single-electron symmetries/partial waves kappa (l,j) that can be coupled to the given
            level symmetries. A list::Array{Int64,1} of kappa-values is returned.
    """
    function  allowedKappaSymmetries(syma::LevelSymmetry, symb::LevelSymmetry)
        kappaList = Int64[];    JList = Basics.oplus(syma.J, symb.J);    kappaMax = syma.J.num + symb.J.num + 2
        for  kappa = -kappaMax:kappaMax
             kappa == 0  &&  continue
             if  syma.parity == Basics.plus   la = 0               else   la = 1       end
             if  symb.parity == Basics.plus   lb = 0               else   lb = 1       end
             if  kappa < 0                 l  = abs(kappa) -1   else   l  = kappa   end
             isodd( la + lb + l )          && continue
             for  j in JList
                 if  j == AngularJ64(abs(kappa) - 1//2)   push!( kappaList, kappa)  end
             end
        end
        return( kappaList )
    end

   """
    `AngularMomentum.allowedMultipoleSymmetries(syma::LevelSymmetry, multipole::EmMultipole)`
        ... to determine all allowed level symmetries for which the given multipole can give rise to a non-zero (transition)
            amplitude; a symList::Array{LevelSymmetry,1} is returned.
    """
    function  allowedMultipoleSymmetries(syma::LevelSymmetry, multipole::EmMultipole)
        symList = LevelSymmetry[];    JList = Basics.oplus(syma.J, AngularJ64(multipole.L) )
        for  J in JList
            if      parityEmMultipolePi(syma.parity, multipole, Basics.plus)     push!( symList, LevelSymmetry(J, Basics.plus) )
            elseif  parityEmMultipolePi(syma.parity, multipole, Basics.minus)    push!( symList, LevelSymmetry(J, Basics.minus) )
            else    error("stop a")
            end
        end
        return( symList )
    end

    """
    `AngularMomentum.allowedTotalSymmetries(syma::LevelSymmetry, kappa::Int64)`
        ... to determine all allowed total symmetries J^P that can be constructed by coupling a partial wave kappa (l,j)
            to the given level symmetry syma. A list::Array{LevelSymmetry,1} of total symmetries is returned.
    """
    function allowedTotalSymmetries(syma::LevelSymmetry, kappa::Int64)
        symtList = LevelSymmetry[]
        if  kappa < 0    l  = abs(kappa) -1   else   l  = kappa   end
        j = AngularJ64( abs(kappa) - 1//2 )
        #
        JList = Basics.oplus(syma.J, j)
        for J in JList
            if    iseven(l)   push!( symtList, LevelSymmetry(J, syma.parity) )
            else              push!( symtList, LevelSymmetry(J, Basics.invertParity(syma.parity)) )
            end
        end
        return( symtList )
    end

    """
    `AngularMomentum.bracket(jList::Array{AngularJ64,1})`
        ... to compute the bracket [a, b, c, ... ] = (2a+1) * (2b+1) * (2b+1) * ... of the given angular momenta.
            A value::Int64 is returned.
    """
    function bracket(jList::Array{AngularJ64,1})
        value = 1;    for  j in jList    value = value * (AngularMomentum.twoJ(j) + 1)    end
        return( value )
    end

    """
    `AngularMomentum.ChengI

    + (kapa::Int64, ma::AngularM64, kapb::Int64, mb::AngularM64, L::AngularJ64, M::AngularM64)`
        ... evaluates the angular I (kappa m, kappa' m', LM) integral as defined by Cheng, NATO summerschool (198x),
            Eq. (A4.5), and including the full magnetic (orientational) dependence. A value::Float64 is returned.
    """
    function ChengI(kapa::Int64, ma::AngularM64, kapb::Int64, mb::AngularM64, L::AngularJ64, M::AngularM64)
        ja = Basics.subshell_j( Subshell(9, kapa) );    jb = Basics.subshell_j( Subshell(9, kapb) )   # Use principal QN n=9 arbitrarely here
        la = Basics.subshell_l( Subshell(9, kapa) );    lb = Basics.subshell_l( Subshell(9, kapb) )
        #
        # Test for parity
        if  L.den != 1   error("stop a")                end
        if  isodd( la + lb + L.num )    return( 0. )    end
        #
        wa = AngularMomentum.phaseFactor([jb, +1, L, -1, ja]) * sqrt( (twoJ(jb)+1)*(twoJ(JL)+1) / (4*pi*(twoJ(ja)+1) ) ) *
             AngularMomentum.ClebschGordan(jb, AngularM64(1//2), L, AngularM64(0), ja, AngularM64(1//2)) *
             AngularMomentum.ClebschGordan(jb, mb, L, M, ja, ma)
        return( wa )
    end

    """
    + (kapa::Int64, kapb::Int64, L::AngularJ64)`
        ... evaluates the same angular I (kappa m, kappa' m', LM) integral but without the magnetic (orientational) dependence.
            A value::Float64 is returned.
    """
    function ChengI(kapa::Int64, kapb::Int64, L::AngularJ64)
        ja = Basics.subshell_j( Subshell(9, kapa) );    jb = Basics.subshell_j( Subshell(9, kapb) )   # Use principal QN n=9 arbitrarely here
        la = Basics.subshell_l( Subshell(9, kapa) );    lb = Basics.subshell_l( Subshell(9, kapb) )
        #
        # Test for parity
        if  L.den != 1   error("stop a")                end
        if  isodd( la + lb + L.num )    return( 0. )    end
        #
        # There occurs here two changes with regard to Cheng formulas:
        # i)  We now divide by sqrt(twoJ(jb)+1) instead of sqrt(twoJ(ja)+1) ... which is likely related to change emission - absorption
        # ii) The phase (-1)^(ja + L - jb) is replaced by (-1)^L  to get a proper phase between 1/2 --> 3/2 ME (compared to 3/2 --> 3/2) ...
        #     but which already comes from the Clebsch-Gordan
        ## wa = AngularMomentum.phaseFactor([ja, +1, L, -1, jb]) *
        wa = (-1)^L.num  * sqrt( (twoJ(jb)+1)*(twoJ(L)+1) / (4*pi) ) / sqrt(twoJ(jb)+1) *
             AngularMomentum.ClebschGordan(jb, AngularM64(1//2), L, AngularM64(0), ja, AngularM64(1//2))
        return( wa )
    end

    """
    `AngularMomentum.ClebschGordan(ja, ma, jb, mb, Jab, Mab)`
        ... calculates the Clebsch-Gordan coefficient  <ja, ma, jb, mb; Jab, Mab> for given quantum numbers by
            a proper call to a Wigner 3-j symbol. A value::Float64 is returned.
    """
    function ClebschGordan(ja, ma, jb, mb, Jab, Mab)
        mab = - Basics.twice(Mab) / 2
        pp  = (Basics.twice(ja) - Basics.twice(jb) + Basics.twice(Jab))/2
        cg  = (-1)^pp * sqrt(Basics.twice(Jab) + 1) * sf_coupling_3j(Basics.twice(ja), Basics.twice(jb), Basics.twice(Jab),
                                                                     Basics.twice(ma), Basics.twice(mb), Basics.twice(mab))
        return( cg )
    end

    """
    `AngularMomentum.ClebschGordan_old(ja::AngularJ64, ma::AngularM64, jb::AngularJ64, mb::AngularM64, Jab::AngularJ64, Mab::AngularM64)`
        ... calculates the Clebsch-Gordan coefficient  <ja, ma, jb, mb; Jab, Mab> for given quantum numbers by
            a proper call to a Wigner 3-j symbol; a value::Float64 is returned.
    """
    function ClebschGordan_old(ja::AngularJ64, ma::AngularM64, jb::AngularJ64, mb::AngularM64, Jab::AngularJ64, Mab::AngularM64)
        cg = 0.   ## ja-jb+Mab must be integer
        mab = AngularM64(-Mab.num, Mab.den)
        cg = AngularMomentum.phaseFactor([ja, -1, jb, +1, Mab]) * sqrt( (twoJ(Jab)+1) ) *
             AngularMomentum.Wigner_3j(ja, jb, Jab, ma, mb, mab)
    end

    """
    `AngularMomentum.CL_reduced_me(suba::Subshell, L::Int64, subb::Subshell)`
        ... calculates the reduced matrix element of the C^L spherical tensor <suba || C^(L) || subb>; a value::Float64
            is returned.
    """
    function  CL_reduced_me(suba::Subshell, L::Int64, subb::Subshell)
        la = Basics.subshell_l(suba);    ja2 = Basics.subshell_2j(suba);
        lb = Basics.subshell_l(subb);    jb2 = Basics.subshell_2j(subb)
        rem(ja2+1, 2) != 0    &&    error("stop a")

        redme = ((-1)^((ja2+1)/2)) * sqrt( (ja2+1)*(jb2+1) ) *
                Wigner_3j(AngularJ64(ja2//2), AngularJ64(L), AngularJ64(jb2//2),  AngularM64(1//2), AngularM64(0), AngularM64(-1//2) )

        return( redme )
    end

    """
    `AngularMomentum.CL_reduced_me_rb(suba::Subshell, L::Int64, subb::Subshell)`
        ... calculates the reduced matrix element of the C^L spherical tensor <suba || C^(L) || subb>; a value::Float64 is returned.
    """
    function  CL_reduced_me_rb(suba::Subshell, L::Int64, subb::Subshell)
        la = Basics.subshell_l(suba);    ja2 = Basics.subshell_2j(suba);
        lb = Basics.subshell_l(subb);    jb2 = Basics.subshell_2j(subb)
        if rem(la + lb + L, 2) != 0   return 0.     end

        redme = ((-1)^((jb2-2L-1)/2)) * sqrt( (jb2+1) ) *
                Wigner_3j(AngularJ64(ja2//2), AngularJ64(jb2//2), AngularJ64(L), AngularM64(1//2), AngularM64(-1//2), AngularM64(0) )

        return( redme )
    end

    """
    `AngularMomentum.isAllowedMultipole(syma::LevelSymmetry, multipole::EmMultipole, symb::LevelSymmetry)`
        ... evaluates to true if the given multipole may connect the two level symmetries, and false otherwise.
    """
    function  isAllowedMultipole(syma::LevelSymmetry, multipole::EmMultipole, symb::LevelSymmetry)
        if  isTriangle(syma.J, AngularJ64(multipole.L), symb.J)  &&
            parityEmMultipolePi(syma.parity, multipole, symb.parity)    return( true )
        else                                                            return( false )
        end
    end

    """
    `AngularMomentum.isTriangle(ja::AngularJ64, jb::Int64, jc::AngularJ64)`
        ... evaluates to true if Delta(ja,jb,jc) = 1, ie. if the angular momenta ja, jb and jc can couple to each other,
            and false otherwise.
    """
    function isTriangle(ja::AngularJ64, jb::AngularJ64, jc::AngularJ64)
        if  ja.den == 1   ja2 = 2ja.num   else   ja2 = ja.num   end
        if  jb.den == 1   jb2 = 2jb.num   else   jb2 = jb.num   end
        if  jc.den == 1   jc2 = 2jc.num   else   jc2 = jc.num   end
        isodd(ja2 + jb2 + jc2)   &&    error("Angular momenta do no fullfill proper coupling rules; 2ja = $ja2, 2jb = $jb2, 2jc = $jc2")
        if  ja2 + jb2 >= jc2     &&    jc2 + ja2 >= jb2   &&   jb2 + jc2 >= ja2    return( true )
        else                                                                       return( false )
        end
    end

    """
    `AngularMomentum.kappa_j(kappa::Int64)`  ... calculates the j::AngularJ64 value of a given kappa.
    """
    function  kappa_j(kappa::Int64)
        j = AngularJ64( abs(kappa) - 1//2 )
        return( j )
    end

    """
    `AngularMomentum.kappa_l(kappa::Int64)`  ... calculates the l::AngularJ64 value of a given kappa.
    """
    function  kappa_l(kappa::Int64)
        if  kappa < 0    l  = abs(kappa) -1   else   l  = kappa   end
        return( AngularJ64(l) )
    end

    """
    `AngularMomentum.oneJ(ja::AngularJ64)`  ... calculates ja; a (positive) value::Float64 is returned.
    """
    function  oneJ(ja::AngularJ64)
        if  ja.den  == 1    ja1 = 1.0 * ja.num   else   ja1 = ja.num / 2.   end
        return( ja1 )
    end

    """
    `AngularMomentum.parityEmMultipolePi(pa::Parity, multipole::EmMultipole, pb::Parity)`
        ... evaluates to true if the given multipole fullfills the parity selection rule pi(a, multipole, b) = 1,
            and false otherwise. This includes a proper test for both, electric and magnetic multipoles, based on
            multipole.electric.
    """
    function  parityEmMultipolePi(pa::Parity, multipole::EmMultipole, pb::Parity)
        # The the multipolarity into account to 'interprete' electric multipoles
        if       isodd(multipole.L)    Le = multipole.electric    else   Le = !(multipole.electric)   end

        if        pa == Basics.plus    &&   Le   &&   pb == Basics.minus        return( true )
        elseif    pa == Basics.minus   &&   Le   &&   pb == Basics.plus         return( true )
        elseif    pa == pb          &&   !(Le)                                          return( true )
        else                                                                            return( false )
        end
    end

    """
    `AngularMomentum.phaseFactor(list::Array{Any,1})`
        ... checks and calculates the phase factor (-1)^(ja + mb -jc ...) that occur frequently in angular momentum theory;
            a value +1. or -1. is returned. Use phaseFactor([ja::Union{AngularJ64,AngularM64), -1, mb::Union{AngularJ64,AngularM64),
            ..., 1, jc::Union{AngularJ64,AngularM64)]) to specify the phase.
    """
    function phaseFactor(list::Array{Any,1})
        if   iseven( length(list) )                                                   error("Wrong number of arguments.")   end
        for  i = 1:2:length(list)
             if  !(typeof(list[i]) == AngularJ64  || typeof(list[i]) == AngularM64)   error("Wrong type of argument $i")    end
             if  i == length(list)                                                    break                                 end
             if  list[i+1] != 1   &&  list[i+1] != -1                error("Wrong type of argument $(i+1); must be +-1")    end
        end
        #
        ja = list[1];        if  ja.den  == 1    jm2 = 2ja.num                  else   jm2 = ja.num                  end
        for  i = 2:2:length(list)
           ja = list[i+1];   if  ja.den  == 1    jm2 = jm2 + list[i]*2ja.num    else   jm2 = jm2 + list[i]*ja.num    end
        end
        #
        if rem(jm2,2) != 0    error("Improper combination of angular momenta")   end

        return( (-1.)^(jm2/2) )
    end

    """
    `AngularMomentum.sigma_reduced_me(suba::Subshell, subb::Subshell)`
        ... calculates the reduced matrix element of the sigma^(1) spherical tensor <suba || sigma^(1) || subb>;
            a value::Float64 is returned.
    """
    function  sigma_reduced_me(suba::Subshell, subb::Subshell)
        la = Basics.subshell_l(suba);    ja2 = Basics.subshell_2j(suba);
        lb = Basics.subshell_l(subb);    jb2 = Basics.subshell_2j(subb)
        if rem(la + lb, 2) != 0   return 0.     end

        ## redme = ((-1)^((jb2-2L-1)/2)) * sqrt( (jb2+1) ) *
        ##         Wigner_3j(AngularJ64(ja2//2), AngularJ64(jb2//2), AngularJ64(L), AngularM64(1//2), AngularM64(-1//2), AngularM64(0) )
        redme = 0.

        return( redme )
    end

    """
    `AngularMomentum.triangularDelta(ia2::Int64, ib2::Int64, ic2::Int64)`
        ... calculates the tringular Delta(ja,jb,jc). The arguments in this integer function are i2a = 2*ja+1, ...
            The result is 0 if the triangular condition failes and 1 otherwise.
    """
    function triangularDelta(ia2::Int64, ib2::Int64, ic2::Int64)
        i = ib2 - ic2
        if  ia2 >= abs(i) + 1   &&   ia2 <= ib2 + ic2 - 1    return( 1 )   else    return( 0 )    end
    end

    """
    `AngularMomentum.triangularDelta(ja::AngularJ64, jb::AngularJ64, jc::AngularJ64)`
        ... calculates the tringular Delta(ja,jb,jc). The result is 0 if the triangular condition failes and 1 otherwise.
    """
    function triangularDelta(ja::AngularJ64, jb::AngularJ64, jc::AngularJ64)
        if  abs(ja.num//ja.den - jb.num//jb.den) <= jc.num//jc.den <= ja.num//ja.den + jb.num//jb.den  return( 1 )   else    return( 0 )    end
    end

    """
    `AngularMomentum.twoJ(ja::AngularJ64)`  ... calculates 2*ja; an (positive) value::Int64 is returned.
    """
    function  twoJ(ja::AngularJ64)
        if  ja.den  == 1    ja2 = 2ja.num   else   ja2 = ja.num   end
        return( ja2 )
    end

    """
    `AngularMomentum.twoM(ma::AngularM64)`  ... calculates 2*ma; a value::Int64 is returned.
    """
    function  twoM(ma::AngularM64)
        if  ma.den  == 1    ma2 = 2ma.num   else   ma2 = ma.num   end
        return( ma2 )
    end

    """
    `AngularMomentum.Wigner_3j(a, b, c, m_a, m_b, m_c)`
        ... calculates the value of a Wigner 3-j symbol for given quantum numbers as displayed in many texts on the theory of
            angular momentum (see R. D. Cowan, The Theory of Atomic Structure and Spectra; University of California Press, 1981, p. 142);
            it calls the corresponding function from the GNU Scientific Library. A value::Float64 is returned.
    """
    function Wigner_3j(a, b, c, m_a, m_b, m_c)
        sf_coupling_3j(Basics.twice(a),   Basics.twice(b),   Basics.twice(c),
                       Basics.twice(m_a), Basics.twice(m_b), Basics.twice(m_c))
    end

    """
    `AngularMomentum.Wigner_3j_old(a::AngularJ64, b::AngularJ64, c::AngularJ64, m_a::AngularM64, m_b::AngularM64, m_c::AngularM64)`
         ... calculates the value of a Wigner 3-j symbol  for given quantum numbers by its algebraic formulae as displayed in many texts on the
             theory of angular momentum (see R. D. Cowan, The Theory of Atomic Structure and Spectra; University of California Press, 1981,
             p. 142); a value::Float64 is returned.
    """
    function Wigner_3j_old(a::AngularJ64, b::AngularJ64, c::AngularJ64, m_a::AngularM64, m_b::AngularM64, m_c::AngularM64)
        # Use twice the given arguments and integer arithmetik throughout.
        if  a.den == 1      ja = a.num + a.num    else    ja = a.num      end
        if  b.den == 1      jb = b.num + b.num    else    jb = b.num      end
        if  c.den == 1      jc = c.num + c.num    else    jc = c.num      end
        if  m_a.den == 1    ma = 2m_a.num         else    ma = m_a.num    end
        if  m_b.den == 1    mb = 2m_b.num         else    mb = m_b.num    end
        if  m_c.den == 1    mc = 2m_c.num         else    mc = m_c.num    end

        # Test the triangular condition and that for magnetic quantum numbers
        if      ma+mb+mc != 0  ||   triangularDelta(ja+1,jb+1,jc+1) == 0      return( 0. )
        elseif  abs(ma) > ja   ||   abs(mb) > jb   ||  abs(mc) > jc           return( 0. )
        elseif  rem(ma+ja+ja,2) != rem(ja,2)   ||   rem(mb+jb+jb,2) != rem(jb,2)   ||   rem(mc+jc+jc,2) != rem(jc,2)    error("stop a")
        end

        ik = [ ja + jb - jc,    ja - jb + jc,    -ja + jb + jc,    ja + jb + jc + 2,    ja - ma,         ja + ma,         jb - mb,
               jb + mb,         jc - mc,          jc + mc,         jb - jc - ma,        ja - jc + mb,    jc - jb + ma,    ja - jb - mc ]
        for  i  in  1:14
            rem(ik[i],2) == 1   &&   error("stop b")
            ik[i] = ik[i] / 2
        end
        # Calculate the 3-j delta factor
        delta = (factorial( ik[1] ) * factorial( ik[2] ) * factorial( ik[3]) * factorial( ik[5]) * factorial( ik[6] ) * factorial( ik[7] ) *
                 factorial( ik[8] ) * factorial( ik[9] ) * factorial(ik[10] )  /  factorial( ik[4] ) )
        # Find out the intervall of summation  k  and sum up
        kmin = max(0, ik[11], ik[12])
        kmax = min(ik[1], ik[5], ik[8] )
        qsum = 0.
        for  k in kmin:kmax
            sumk = (factorial(k) * factorial( ik[1]-k )  * factorial( ik[5]-k ) * factorial( ik[8]-k ) *
                    factorial( ik[13]+k ) * factorial( k-ik[12]) )
            sumk = 1.0 / sumk
            if  rem(k,2) == 0   qsum = qsum + sumk    else   qsum = qsum - sumk    end
        end

        if  rem(ik[14],2) != 0    w3j = - sqrt(delta) * qsum    else    w3j = sqrt(delta) * qsum    end
        return( w3j )
    end

    """
    `AngularMomentum.Wigner_6j(a, b, c, d, e, f)`
        ... calculates the value of a Wigner 6-j symbol for given quantum numbers as displayed in many texts on the theory of
            angular momentum (see R. D. Cowan, The Theory of Atomic Structure and Spectra; University of California Press, 1981, p. 142);
            it calls the corresponding function from the GNU Scientific Library. A value::Float64 is returned.
    """
    function Wigner_6j(a, b, c, d, e, f)
        sf_coupling_6j(Basics.twice(a), Basics.twice(b), Basics.twice(c),
                       Basics.twice(d), Basics.twice(e), Basics.twice(f))
    end

    """
    `AngularMomentum.Wigner_6j_old(a::AngularJ64, b::AngularJ64, c::AngularJ64, d::AngularJ64, e::AngularJ64, f::AngularJ64)`
         ... calculates the value of a Wigner 6-j symbol  for given quantum numbers by its algebraic formulae as displayed in many texts on the
             theory of angular momentum (see R. D. Cowan, The Theory of Atomic Structure and Spectra; University of California Press, 1981,
             p. 142); a value::Float64 is returned.
    """
    function Wigner_6j_old(a::AngularJ64, b::AngularJ64, c::AngularJ64, d::AngularJ64, ee::AngularJ64, f::AngularJ64)
        # Use twice the given arguments and integer arithmetik throughout.
        if  a.den == 1       ja = a.num + a.num    else    ja = a.num      end
        if  b.den == 1       jb = b.num + b.num    else    jb = b.num      end
        if  c.den == 1       jc = c.num + c.num    else    jc = c.num      end
        if  d.den == 1       jd = d.num + d.num    else    jd = d.num      end
        if  ee.den == 1      je = ee.num + ee.num  else    je = ee.num     end
        if  f.den == 1       jf = f.num + f.num    else    jf = f.num      end
        #
        wa = AngularMomentum.triangularDelta(ja+1, jb+1, jc+1) * AngularMomentum.triangularDelta(ja+1, je+1, jf+1) *
             AngularMomentum.triangularDelta(jd+1, jb+1, jf+1) * AngularMomentum.triangularDelta(jd+1, je+1, jc+1)
        if  wa == 0    return( 0. )    end
        #
        # Evaluate the algebraic formulae
        n1 = Int64( (ja + jb + jc)//2 );    n2 = Int64( (je + jd + jc)//2 );       n3 = Int64( (ja + je + jf)//2 )
        n4 = Int64( (jb + jd + jf)//2 );    n5 = Int64( (ja + jb + je + jd)//2 );  n6 = Int64( (ja + jd + jc + jf)//2 )
        n7 = Int64( (jb + je + jc + jf)//2 )
        kmin = max(n1,n2,n3,n4) + 1;   kmax = min(n5,n6,n7) + 1
        #
        w6j = 1.0;   icount = 0
        if  kmin != kmax
            for k = kmin+1:kmax
                ki = kmax - icount
	            w6j = 1.0 - (w6j * ki*(n5 - ki + 2.) * (n6 - ki + 2.) * (n7 - ki + 2.)) /
                            ((ki - 1. - n1) * (ki - 1. - n2) * (ki - 1. - n3) * (ki - 1. - n4))
                icount = icount + 1
            end
        end
        #
        w6j = w6j * exp( (lgamma(kmin+1) - lgamma(kmin-n1) - lgamma(kmin-n2) - lgamma(kmin-n3) - lgamma(kmin-n4) - lgamma(n5+2-kmin) -
	                      lgamma(n6+2-kmin) - lgamma(n7+2-kmin))  +
	                   ( (lgamma(n1+1-ja) + lgamma(n1+1-jb) + lgamma(n1+1-jc) - lgamma(n1+2) + lgamma(n2+1-je) + lgamma(n2+1-jd) +
	                      lgamma(n2+1-jc) - lgamma(n2+2) + lgamma(n3+1-ja) + lgamma(n3+1-je) + lgamma(n3+1-jf) - lgamma(n3+2) +
                          lgamma(n4+1-jb) + lgamma(n4+1-jd) + lgamma(n4+1-jf) - lgamma(n4+2)) / 2. ) )
        #
        if   rem(n5+kmin,2) == 0       w6j = -w6j   end
        if   rem(ja+jb+je+jd,4) != 0   w6j = -w6j   end

        return( w6j )
    end

    """
    `AngularMomentum.Wigner_9j(a, b, c, d, e, f, g, h, i)`
        ... calculates the value of a Wigner 3-j symbol for given quantum numbers as displayed in many texts on the theory of
            angular momentum (see R. D. Cowan, The Theory of Atomic Structure and Spectra; University of California Press, 1981, p. 142);
            it calls the corresponding function from the GNU Scientific Library. A value::Float64 is returned.
    """
    function Wigner_9j(a, b, c, d, e, f, g, h, i)
        sf_coupling_9j(Basics.twice(a), Basics.twice(b), Basics.twice(c),
                       Basics.twice(d), Basics.twice(e), Basics.twice(f),
                       Basics.twice(g), Basics.twice(h), Basics.twice(i))
    end

    """
    `AngularMomentum.Wigner_9j_old(a::AngularJ64, b::AngularJ64, c::AngularJ64, d::AngularJ64, e::AngularJ64, f::AngularJ64,
                                   g::AngularJ64, h::AngularJ64, i::AngularJ64)`
         ... calculates the value of a Wigner 6-j symbol  for given quantum numbers by its algebraic formulae as displayed in many texts on the
             theory of angular momentum (see R. D. Cowan, The Theory of Atomic Structure and Spectra; University of California Press, 1981,
             p. 142); a value::Float64 is returned.
    """
    function Wigner_9j_old(a::AngularJ64, b::AngularJ64, c::AngularJ64, d::AngularJ64, ee::AngularJ64, f::AngularJ64,
                       g::AngularJ64, h::AngularJ64, i::AngularJ64)
        Defaults.warn(AddWarning, "AngularMomentum.Wigner_9j(): Here, we wish to call an external Julia package/interface; now set to 2.0")
        w9j = 2.0
        return( w9j )
    end

    #========================  Currently not in use

    """
        clebschgordan(j₁, m₁, j₂, m₂, J, M)

    Calculate the Clebsch–Gordan coefficient ``\\langle j_1 m_1 j_2 m_2 | J M \\rangle``. The
    calculation uses the Wigner 3-j symbol from the GNU Scientific Library (cf.
    [`wigner3j`](@ref)). The Condon–Shortley phase convention is employed.
    """
    function clebschgordan(j₁, m₁, j₂, m₂, J, M)
        J₁ = HalfInt(j₁) # explicit conversion because AngularJ64 does not support arithmetic
        J₂ = HalfInt(j₂) # explicit conversion because AngularJ64 does not support arithmetic
        mM = -HalfInt(M) # explicit conversion because AngularM64 does not support arithmetic
        exp = mM-J₁+J₂
        isinteger(exp) || return 0.0
        phase = ifelse(iseven(Integer(exp)), 1, -1)
        phase * sqrt(Basics.twice(J)+1) * wigner3j(J₁, J₂, J, m₁, m₂, mM)
    end

    """
        wigner3j(j₁, j₂, j₃, m₁, m₂, m₃)

    Calculate the Wigner 3-j symbol
    ```math
    \\begin{pmatrix}
        j_1 & j_2 & j_3 \\\\
        m_1 & m_2 & m_3
    \\end{pmatrix}
    ```
    by calling the corresponding function from the GNU Scientific Library.
    """
    function wigner3j(j₁, j₂, j₃, m₁, m₂, m₃)
        sf_coupling_3j(Basics.twice(j₁), Basics.twice(j₂), Basics.twice(j₃),
                       Basics.twice(m₁), Basics.twice(m₂), Basics.twice(m₃))
    end

    """
        wigner6j(j₁, j₂, j₃, j₄, j₅, j₆)

    Calculate the Wigner 6-j symbol
    ```math
    \\begin{Bmatrix}
        j_1 & j_2 & j_3 \\\\
        j_4 & j_5 & j_6
    \\end{Bmatrix}
    ```
    by calling the corresponding function from the GNU Scientific Library.
    """
    function wigner6j(j₁, j₂, j₃, j₄, j₅, j₆)
        sf_coupling_6j(Basics.twice(j₁), Basics.twice(j₂), Basics.twice(j₃),
                       Basics.twice(j₄), Basics.twice(j₅), Basics.twice(j₆))
    end

    """
        wigner9j(j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)

    Calculate the Wigner 9-j symbol
    ```math
    \\begin{Bmatrix}
        j_1 & j_2 & j_3 \\\\
        j_4 & j_5 & j_6 \\\\
        j_7 & j_8 & j_9
    \\end{Bmatrix}
    ```
    by calling the corresponding function from the GNU Scientific Library.
    """
    function wigner9j(j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)
        sf_coupling_9j(Basics.twice(j₁), Basics.twice(j₂), Basics.twice(j₃),
                       Basics.twice(j₄), Basics.twice(j₅), Basics.twice(j₆),
                       Basics.twice(j₇), Basics.twice(j₈), Basics.twice(j₉))
    end
    ===========================#

end # module


module sssudoko
using Base.Dates

row1 = [0,0,0,0,0,0,0,7,0]
row2 = [0,4,0,2,9,1,8,6,5]
row3 = [8,5,0,0,0,0,0,0,9]
row4 = [4,0,8,6,0,0,2,0,0]
row5 = [3,2,0,0,4,0,0,8,6]
row6 = [0,0,1,0,0,9,4,0,3]
row7 = [1,0,0,0,0,0,0,9,8]
row8 = [5,8,4,9,6,3,0,2,0]
row9 = [0,6,0,0,0,0,0,0,0]
p1=[0,7,0,9,6,4,0,0,0]
p2=[0,3,0,1,0,0,0,4,6]
p3=[0,0,0,8,0,0,0,0,5]
p4=[0,2,0,0,0,0,0,0,3]
p5=[6,0,0,0,0,0,0,0,4]
p6=[4,0,0,0,0,0,0,9,0]
p7=[1,0,0,0,0,3,0,0,0]
p8=[3,9,0,0,0,5,0,1,0]
p9=[0,0,0,7,1,6,0,5,0]
#array = hvcat (9,row1,row2,row3,row4,row5,row6,row7,row8,row9)
array =hvcat(9,p1,p2,p3,p4,p5,p6,p7,p8,p9)
result = []
type CandidateSet{T}
    rx::Int
    ry::Int
    candidates::Array{T}
end

function howManySquares(matrix::Array)
    count = 0
    for thing in matrix
        if thing==0 count+=1
        end
    end
    return count
end

"Returns an instance of CandidateSet"
function generateCandidates (rx::Int,ry::Int, matrix::Array)
    starter=CandidateSet(rx,ry,Int[0])
    if matrix [rx,ry]!=0
        return starter #because we don't need a candidate
	#not good code - need to think of a way to just not have this value
    else
    allMembers = rowColumnBlock(rx,ry,matrix)
   # print (allMembers)
    elements=Int[]
    for i in 1:9
        if !in(i,allMembers)
            push!(elements,i)
        end
    end
    if (elements==[])
       # print ("rx=",rx,"ry=",ry)
        throw (UndefRefError()) # because this is an matrix with a contradiction not yet discovered
        end
          candidate=CandidateSet(rx,ry,elements)
    return candidate
    end
end

function createCandidateMatrix (things::Array)
    matrix = CandidateSet[]
    for n in 1:9
        for m in 1:9
            candidates = generateCandidates (m,n,things)
                push!(matrix,candidates)
            end
    end
    matrix2 = reshape (matrix,9,9)
   #@show(matrix2)
    return (matrix2)
end

"extract relevant elements for considering a square in sudoko"
"the row the item is in, the column it is in and the 3*3 local that its in"
function rowColumnBlock (rx::Int, ry::Int, matrix::Array)
   # matrix[rx,ry] = 0
  row = matrix[rx,:]
    column = matrix[:, ry]
    x1 = div(rx-1,3)*3
    y1 = div(ry-1,3)*3
    x2=x1+3
    y2=y1+3
    x1+=1
    y1+=1
   # @show x1,x2,y1,y2
    block = matrix[x1:x2,y1:y2]
    #=we've got the row, column and block so now
    get membership and see if there's a missing one =#
    allMembers = [reshape(block,1,9);reshape(column,1,9);reshape(row,1,9)]
    return allMembers
end
#=
function exclusiveRowColumnBlock(rx::Int, ry::Int, matrix::Array)
    value = matrix[rx,ry]
    matrix[rx,ry] = 0
    retval = rowColumnBlock(rx,ry,matrix)
    matrix[rx,ry] = value
    return retval
end
=#

function copytx (matrix::Array{Int})
    retval = Array (Int,length(matrix))
    for i in 1:length (matrix)
        retval[i] = matrix[i]
        end
        return reshape (retval,9,9)
        end

function notConsistent (matrix::Array)
    copyt = copytx(matrix)
    for n in 1:9
        for m in 1:9
        if (matrix[n,m]!=0)
          copyt[n,m]=10
          row = copyt[n,:]
          if (in(matrix[n,m],row))
            #print ("not consistent")
            return (true)
            end
          column = copyt[:, m]
          if (in(matrix[n,m],column))
            #print ("not consistent")
            return (true)
          end
         x1 = div(n-1,3)*3
        y1 = div(m-1,3)*3
        x2=x1+3
        y2=y1+3
        x1+=1
        y1+=1
   # @show x1,x2,y1,y2
        block = copyt[x1:x2,y1:y2]
        if (in(matrix[n,m],block))
            #print ("not consistent")
            return (true)
        end #if
        copyt [n,m] = matrix[n,m]
        end #for
        end #for
    end #if
    return false
    end

function solutionSearch( matrix::Array, depth::Int)
    # for every element in matrix which is a 0 (undefined) look
    # at all the other 0s to find any that contain exclusions
    # which can solve this square
    # check singles and pairs only for now !
    try
    touched = false
    newMatrix = Int[]
    candidateSet = createCandidateMatrix(matrix)
    if (notConsistent(matrix))
    return false
    else
    for i in 1:81
        if (matrix[i]==0 ) #something to do
           if (length(candidateSet[i].candidates)==1)
              #simple write in
        #      println("write in=",candidateSet[i].candidates[1]," at ",i)
              matrix[i]=candidateSet[i].candidates[1]
            if (notConsistent(matrix))
         #         println("found contradiction")
                  return false
              end #unwind as this is a dead attempt
              touched = true
            end #end if
        end #end if
    end #end for
    candidateSet=createCandidateMatrix(matrix) #in case a repair was needed
    tries = zeros(9,9)
    marktries(tries,matrix)
    while (!complete (tries))
    index = shortest(candidateSet,tries)
    println("depth=",depth," index=",index)
    for guess in candidateSet[index].candidates
              #= for every option try writing it in and then solving the matrix with that assumption
              if a contradiction arises then stop and unwind and try a different option =#
              newMatrix = copytx(matrix)
              newMatrix[index] =guess
              if(!notConsistent (newMatrix))
                  if(solutionSearch (newMatrix,depth+1))
                    touched = true
                    matrix = newMatrix
                   end
              else
               matrix[index] = 0
              end#if
        end #for
    return false
    end #else
    end
     if(rem(datetime2unix(now()),10)==0.0)
       println(matrix)
       end
      if (howManySquares(matrix)==0)
       throw (InterruptException())
       end

 catch (e)
        if (isa(e,InterruptException))
            global result = matrix
            return true
         end
  return false
  end
 end

function marktries(tries::Array,tester::Array)
    for i in 1:length(tester)
        if (tester[i]!=0)
            tries[i]=1
        end#if
    end#for
end

 function complete (tries::Array)
    if (in(0,tries))
        return false
    else
    return true
    end
end

 function shortest (candidates::Array{CandidateSet}, tries::Array)
    theOne =0
    for i in 1:length(candidates)
       if (tries[i]==0.0)
       if candidates[i].candidates[1]!=0 # there is a list
       if (theOne==0)
        theOne=i else
        if (length(candidates[i].candidates) < length(candidates[theOne].candidates))
            theOne = i
        end #if
        end #else
        end #if
    end #if
    end #for
    return theOne
end

cache= []
cache2 = []

 function cleanArray(thing::Int,target::Array)
    retVal =Int[]
    if (length(target)==1) return [0] end
  #  print ("starting>>", target)
    for i = 1:length(target)
        if target[i]!=thing
            push!(retVal,target[i])
        end
    end
   #         print ("done >> ",retVal)
    return retVal
    end

function hypothesis (guess::Int, tester::Array{CandidateSet})
  #print (tester)
  for i in tester
    if in (guess,i.candidates)
    return (false)
    end
    end
    return (true)
end

@time(solutionSearch(array,0))

println("result is ",result)

end #module


using Optim, StatsBase

"""
    ols_cdf(parameters, experimental, experimental_ecdf, ν0, I)

Compute ordinary least squares comparing the experimental ecdf with the
theoretical cdf, calculated with the nmr parameters, the spin (I), and the
Larmor frequency (ν0) at each x-value in the experimental data

"""
function ols_cdf(
    parameters::Quadrupolar,
    exp::Array{Float64, 2},
    exp_ecdf::Array{Float64, 1},
    ν0::Float64;
    I::Int64 = 3,
    samples::Int64 = 1_000_000,
    transitions::UnitRange{Int64} = 1:(2*I),
    range::Tuple{Float64,Float64} = (exp[1, 1], exp[end, 1]),
)
    #range = (findfirst(x -> range[1] < x, exp[:, 1]),
    #    findlast(x -> range[2] > x, exp[:, 1]) - 1)
    th_ecdf = ecdf(estimate_powder_pattern(parameters, samples, ν0, I,
        transitions = transitions)).(exp[:, 1])
    th_ecdf .-= th_ecdf[1]
    th_ecdf ./= th_ecdf[end]
    return sum((exp_ecdf .- th_ecdf) .^ 2)
end

function ols_cdf(
    parameters::Missing,
    exp::Array{Float64, 2},
    exp_ecdf::Array{Float64, 1},
    ν0::Float64;
    I::Int64 = 3,
    samples::Int64 = 1_000_000,
    transitions::UnitRange{Int64} = 1:(2*I),
)
    return size(exp)[1] / 10
end

function ols_cdf(
    parameters::ChemicalShift,
    exp::Array{Float64, 2},
    exp_ecdf::Array{Float64, 1};
    samples::Int64 = 1_000_000,
)
    th_ecdf = ecdf(estimate_powder_pattern(parameters, samples)).(exp[:, 1])
    th_ecdf .-= th_ecdf[1]
    th_ecdf ./= th_ecdf[end]
    return sum((exp_ecdf .- th_ecdf) .^ 2)
end

function ols_cdf(
    parameters::Missing,
    exp::Array{Float64, 2},
    exp_ecdf::Array{Float64, 1};
    samples::Int64 = 1_000_000,
)
    return size(exp)[1] / 10
end

function transform_params(x::Array{Float64}, T::DataType)
    if T == Quadrupolar
        x = x .^ 2
    elseif T == ChemicalShift
        for i = 1:length(x) ÷ 7
            x[7 * (i - 1) + 2] ^= 2
            x[7 * (i - 1) + 4] ^= 2
            x[7 * (i - 1) + 5] ^= 2
            x[7 * (i - 1) + 6] ^= 2
            x[7 * (i - 1) + 7] ^= 2
        end
    end
    return x
end

"""
    fit_quadrupolar(experimental; sites, iters, options, method)

Fit the NMR parameters, assuming a normal distribution and using the specified
optimization method (currently implementer are Nelder-Mead and Simulated
Annealing) for with Optim options (options) for iterations, timeout, etc, with
the loss function of ordinary least squares of the CDFs, assuming a specified
number of sites (sites, defaults to 1)

"""
function fit_quadrupolar(
    experimental::Array{Float64, 2};
    sites::Int64 = 1,
    iters::Int64 = 1000,
    options = Optim.Options(iterations = iters),
    method::String = "NelderMead",
    I::Int64 = 3,
    samples::Int64 = 1_000_000,
    starting_values = get_quadrupolar_starting_values(sites),
    transitions::UnitRange{Int64} = 1:(2*I),
    range::Tuple{Float64,Float64} = (experimental[1, 1], experimental[end, 1]),
)
    experimental_ecdf = get_experimental_ecdf(experimental)
    ν0 =  get_ν0(experimental, experimental_ecdf)

    if method == "SimulatedAnnealing"
        upper_bounds, lower_bounds = zeros(5 * sites), zeros(5 * sites)
        upper_bounds[1:5:end] .= 9  # Qcc
        upper_bounds[2:5:end] .= 1  # σQcc
        upper_bounds[3:5:end] .= 1  # η
        upper_bounds[4:5:end] .= 1  # ση
        upper_bounds[5:5:end] .= 1  # weights
        result = optimize(
            x -> ols_cdf(  # objective function
                Quadrupolar(x),
                experimental,
                experimental_ecdf,
                ν0,
                I = I,
                samples = samples,
                transitions = transitions,
                range = range,
            ),
            lower_bounds,
            upper_bounds,
            starting_values,
            SAMIN(),
            options,
        )
    elseif method == "ParticleSwarm"
        upper_bounds, lower_bounds = zeros(5 * sites), zeros(5 * sites)
        upper_bounds[1:5:end] .= 9  # Qcc
        upper_bounds[2:5:end] .= 1  # σQcc
        upper_bounds[3:5:end] .= 1  # η
        upper_bounds[4:5:end] .= 1  # ση
        upper_bounds[5:5:end] .= 1  # weights
        result = optimize(
            x -> ols_cdf(  # objective function
                Quadrupolar(x),
                experimental,
                experimental_ecdf,
                ν0,
                I = I,
                samples = samples,
                transitions = transitions,
            ),
            lower_bounds,
            upper_bounds,
            starting_values,
            ParticleSwarm(),
            options,
        )
    elseif method == "NelderMead"
        result = optimize(
            x -> ols_cdf(
                Quadrupolar(transform_params(x, Quadrupolar)),
                experimental,
                experimental_ecdf,
                ν0,
                I = I,
                samples = samples,
                transitions = transitions,
                range = range,
            ),
            starting_values,
            options,
        )
    else
        println("Unknown method $(method). Try NelderMead, SimulatedAnnealing, or ParticleSwarm")
    end
    return result
end

function fit_chemical_shift(
    experimental::Array{Float64, 2};
    sites::Int64 = 1,
    iters::Int64 = 1000,
    options = Optim.Options(iterations = iters),
    method::String = "NelderMead",
    samples::Int64 = 1_000_000,
    starting_values = get_chemical_shift_starting_values(sites),
    range::Tuple{Float64,Float64} = (experimental[1, 1], experimental[end, 1]),
)
    experimental_ecdf = get_experimental_ecdf(experimental)

    if method == "SimulatedAnnealing"
        upper_bounds, lower_bounds = zeros(7 * sites), zeros(7 * sites)
        upper_bounds[1:7:end] .= 4000
        upper_bounds[2:7:end] .= 800
        upper_bounds[3:7:end] .= 4000
        upper_bounds[4:7:end] .= 400
        upper_bounds[5:7:end] .= 1
        upper_bounds[6:7:end] .= 1
        upper_bounds[7:7:end] .= 1
        lower_bounds[1:7:end] .= -4000
        lower_bounds[2:7:end] .= 0.0000001
        lower_bounds[3:7:end] .= -4000
        lower_bounds[4:7:end] .= 0.0000001
        lower_bounds[5:7:end] .= 0.0000001
        lower_bounds[6:7:end] .= 0.0000001
        lower_bounds[7:7:end] .= 0.0000001
        result = optimize(
            x -> ols_cdf(  # objective function
                ChemicalShift(x),
                experimental,
                experimental_ecdf,
                samples = samples,
            ),
            lower_bounds,
            upper_bounds,
            starting_values,
            SAMIN(),
            options,
        )
    elseif method == "ParticleSwarm"
        upper_bounds, lower_bounds = zeros(7 * sites), zeros(7 * sites)
        upper_bounds[1:7:end] .= 4000
        upper_bounds[2:7:end] .= 800
        upper_bounds[3:7:end] .= 4000
        upper_bounds[4:7:end] .= 400
        upper_bounds[5:7:end] .= 1
        upper_bounds[6:7:end] .= 1
        upper_bounds[7:7:end] .= 1
        lower_bounds[1:7:end] .= -4000
        lower_bounds[2:7:end] .= 0.0000001
        lower_bounds[3:7:end] .= -4000
        lower_bounds[4:7:end] .= 0.0000001
        lower_bounds[5:7:end] .= 0.0000001
        lower_bounds[6:7:end] .= 0.0000001
        lower_bounds[7:7:end] .= 0.0000001
        result = optimize(
            x -> ols_cdf(  # objective function
                ChemicalShift(x),
                experimental,
                experimental_ecdf,
                samples = samples,
            ),
            lower_bounds,
            upper_bounds,
            starting_values,
            ParticleSwarm(),
            options,
        )
    elseif method == "NelderMead"
        result = optimize(
            x -> ols_cdf(
                ChemicalShift([Normal(x[1], x[2] ^ 2)], [Normal(x[3], x[4] ^ 2)],
                    [truncated(Normal(x[5] ^ 2, x[6] ^ 2), 0, 1)], [1.0]),
                experimental,
                experimental_ecdf,
                samples = samples,
            ),
            starting_values,
            options,
        )
    end
    return result
end


module Utils
# Module containing the necessary structs and functions for LIIC execution

using LinearAlgebra
using Statistics
using Formatting

export import_molecule, write_file

struct ZMatrix
    Atoms::Vector{String}
    IntVars::Vector{Float64}
    VarNames::Vector{String}
    Number::Int64
end

struct Molecule
    Atoms::Vector{String}
    Coord::Array{Float64,2}
    Number::Int64
end

function translate_to_centroid(coord_matrix)
    # Normalises the molecular coordinates by centering them.
    center = mean(coord_matrix,dims=1)
    translated_geom = coord_matrix .- center
    return translated_geom
end

function optimal_rotation_matrix(CCmatrix)
    # Returns 3x3 matrix that can be applied to P to get Q
    # Previous implementation, not general (for cases of non-invertible matrices)
    # ORmatrix = sqrt(transpose(CCmatrix)*CCmatrix)*inv(CCmatrix)
    # Using SVD instead
    u,sing,vt = svd(CCmatrix)
    if sign(det(transpose(vt)*transpose(u))) == 1.0
        ORmatrix = transpose(vt)*transpose(u)
    elseif sign(det(transpose(vt)*transpose(u))) == -1.0
        mat = [1. 0. 0.;0. 1. 0.; 0. 0. -1.]
        ORmatrix = transpose(vt)*mat*transpose(u)
    else
        println("Error: Issue with SVD routine in finding optimal rotation matrix")
    end
    return ORmatrix
end

function kabsch_rotate(Pgeom,Qgeom)
    # Returns the two geometries in xyz format, one of which has been translated, the
    # other translated and rotated
    # Pgeom = import_molecule(Pxyz)
    # Qgeom = import_molecule(Qxyz)

    normalisedP = (translate_to_centroid(Pgeom.Coord))
    normalisedQ = (translate_to_centroid(Qgeom.Coord))

    # Calculate cross covariance matrix
    xcov = transpose(normalisedP)*normalisedQ
    orot = optimal_rotation_matrix(xcov)

    num_atoms = size(normalisedP)[1]
    rotated = zeros(Float64,num_atoms,3)

    for i=1:num_atoms
        rotated[i,:] = orot*normalisedP[i,:]
    end

    RMSD_value = norm(rotated-normalisedQ)

    println("The RMSD between these two structures is ",RMSD_value)

    RotatedP = Molecule(Pgeom.Atoms,rotated,num_atoms)
    NormalisedQ = Molecule(Qgeom.Atoms,normalisedQ,num_atoms)

    return RotatedP, NormalisedQ
end

function import_molecule(xyz_file::String)
    io = open(xyz_file)
    natoms = parse(Int,readline(io))
    readline(io);
    reg=r"([a-zA-Z]+)\s+(-?\d+.?\d*)\s+(-?\d+.?\d*)\s+(-?\d+.?\d*)"
    atoms=Vector{String}()
    atom_coords=Array{Float64,2}(undef,natoms,3)
    for num in 1:natoms
        m=match(reg,readline(io))
        push!(atoms,string(m.captures[1]::SubString))
        atom_coords[num,:]=[parse(Float64,m.captures[2]),parse(Float64,m.captures[3]),parse(Float64,m.captures[4])]
    end
    close(io)
    return Molecule(atoms,atom_coords,natoms)
end

function bond(a1::Vector{Float64},a2::Vector{Float64})
    return norm(a1-a2)::Float64
end

function angle(a1::Vector{Float64},a2::Vector{Float64},a3::Vector{Float64})
    bond1 = a3 - a2
    bond2 = a2 - a1
    sintheta = norm(cross(bond1,bond2))
    costheta = dot(bond1,bond2)
    theta = atan(sintheta,costheta)
    angle = 180.0 * theta/pi
    # angle = acosd(dot(bond1,bond2)/(norm(bond1)*norm(bond2)))
    return angle::Float64
end

function dihedral(a1::Vector{Float64},a2::Vector{Float64},a3::Vector{Float64},a4::Vector{Float64})
    bond1 = a4-a3
    bond2 = a3-a2
    bond3 = a2-a1
    plane1 = cross(bond1,bond2)
    plane2 = cross(bond2,bond3)
    dihedral = acosd(dot(plane1,plane2)/(norm(plane1)*norm(plane2)))
    if dot(cross(plane1,plane2),bond2) < 0
        dihedral = -dihedral
    end
    return dihedral::Float64
end

function write_zmat(zmat::ZMatrix,withvars::Bool=true)
    if withvars==true
        natoms = zmat.Number
        for atom in 1:natoms
            if atom < 2
                printfmtln("{:3s}",zmat.Atoms[atom])
            elseif atom < 3
                printfmtln("{:3s} {:>4d}  {:11s}",zmat.Atoms[atom],(atom-1),zmat.VarNames[atom-1])
            elseif atom < 4
                printfmtln("{:3s} {:>4d}  {:11s} {:>4d}  {:11s}",zmat.Atoms[atom],(atom-1),
                zmat.VarNames[atom-1],(atom-2),zmat.VarNames[atom])
            else
                printfmtln("{:3s} {:>4d}  {:11s} {:>4d}  {:11s} {:>4d}  {:11s}",zmat.Atoms[atom],
                (atom-1),zmat.VarNames[3*atom-8],(atom-2),zmat.VarNames[3*atom-7],(atom-3),zmat.VarNames[3*atom-6])
            end
        end
        println("  Variables:")
        for var in 1:length(zmat.IntVars)
            printfmtln("{:3s} {:>11.8f}",zmat.VarNames[var],zmat.IntVars[var])
        end
    else
        natoms = length(zmat.Atoms)
        for atom in 1:natoms
            if atom < 2
                printfmtln("{:3s}",zmat.Atoms[atom])
            elseif atom < 3
                printfmtln("{:3s} {:>4d}  {:>11.8f}",zmat.Atoms[atom],(atom-1),zmat.IntVars[atom-1])
            elseif atom < 4
                printfmtln("{:3s} {:>4d}  {:>11.8f} {:>4d}  {:>11.8f}",zmat.Atoms[atom],(atom-1),
                zmat.IntVars[atom-1],(atom-2),zmat.IntVars[atom])
            else
                printfmtln("{:3s} {:>4d}  {:>11.8f} {:>4d}  {:>11.8f} {:>4d}  {:>11.8f}",zmat.Atoms[atom],
                (atom-1),zmat.IntVars[3*atom-8],(atom-2),zmat.IntVars[3*atom-7],(atom-3),zmat.IntVars[3*atom-6])
            end
        end
    end
end

function write_xyz(mol::Molecule,io=nothing)
    natoms = mol.Number
    if io !== nothing
        printfmtln(io,"{:>4d}",natoms)
        println(io)
        for atom in 1:natoms
            printfmtln(io,"{:3s}   {:>11.8f}   {:>11.8f}    {:>11.8f}",mol.Atoms[atom],mol.Coord[atom,1],mol.Coord[atom,2],mol.Coord[atom,3])
        end
    elseif io === nothing
        printfmtln("{:>4d}",natoms)
        println()
        for atom in 1:natoms
            printfmtln("{:3s}   {:>11.8f}   {:>11.8f}    {:>11.8f}",mol.Atoms[atom],mol.Coord[atom,1],mol.Coord[atom,2],mol.Coord[atom,3])
        end
    else
        println("Error in write_xyz")
    end
end

function write_liic(outArray,filename::String)
    io = open(filename,"w")
    for i in 1:length(outArray)
        write_xyz(outArray[i],io)
    end
    close(io)
end

function xyz_to_zmat(molecule)
    natoms     = molecule.Number
    atoms      = molecule.Atoms
    intvars      = Vector{Float64}()
    varnames   = Vector{String}()
    for atom in 1:natoms
        if atom > 1
            bl     = bond(molecule.Coord[atom-1,:],molecule.Coord[atom,:])
            push!(intvars,bl)
            push!(varnames,"R$atom")
        end
        if atom > 2
            agl  = angle(molecule.Coord[atom-2,:],molecule.Coord[atom-1,:],molecule.Coord[atom,:])
            push!(intvars,agl)
            push!(varnames,"A$atom")
        end
        if atom > 3
            dhl  = dihedral(molecule.Coord[atom-3,:],molecule.Coord[atom-2,:],molecule.Coord[atom-1,:],molecule.Coord[atom,:])
            push!(intvars,dhl)
            push!(varnames,"D$atom")
        end
    end
    zmatrix    = ZMatrix(atoms,intvars,varnames,natoms)
    return zmatrix
end

function rotation_matrix(axis,angle)
    # Rotation about the origin in 3 dimensions using the Euler-Rodrigues formula
    axis = -axis / norm(axis)
    a = cosd(angle*0.5)
    (b,c,d) = axis * sind(angle*0.5)
    rotM = [a*a+b*b-c*c-d*d 2*(b*c-a*d) 2*(b*d+a*c);
    2*(b*c+a*d) a*a+c*c-b*b-d*d 2*(c*d-a*b);
    2*(b*d-a*c) 2*(c*d+a*b) a*a+d*d-b*b-c*c]
    return rotM::Matrix{Float64}
end

function zmat_to_xyz(zmat::ZMatrix)
    natoms=zmat.Number
    atom_coords = Array{Float64,2}(undef,natoms,3)

    if natoms > 0
        atom_coords[1,:]=[0.0,0.0,0.0]

        if natoms > 1
            r2 = zmat.IntVars[1]
            atom_coords[2,:]=[r2,0.0,0.0]

            if natoms > 2
                vec = atom_coords[2,:] - atom_coords[1,:]
                r3 = zmat.IntVars[2]
                a3 = zmat.IntVars[3]
                vec = r3 * vec / norm(vec)
                atom_coords[3,:] = atom_coords[2,:] + (rotation_matrix([0, 0, 1], a3) * vec)

                if natoms > 3
                    for atom in 4:natoms
                        rn = zmat.IntVars[3*atom-8]
                        an = zmat.IntVars[3*atom-7]
                        dn = zmat.IntVars[3*atom-6]

                        # ox = rn * cosd(an)
                        # oy = rn * cosd(dn) * sind(an)
                        # oz = rn * sind(an) * sind(dn)

                        # Coordinates of the last three atoms
                        am1 = atom_coords[atom-1,:]
                        am2 = atom_coords[atom-2,:]
                        am3 = atom_coords[atom-3,:]

                        # Vectors between them
                        vec1 = am1 - am2
                        vec2 = am2 - am3
                        # Plane normal defined by these vectors
                        nm = cross(vec1,vec2)

                        # Vector of length rn pointing along vec1
                        vecrn = rn * vec1 / norm(vec1)

                        # Rotate vecrn by angle an around the normal
                        vecan = rotation_matrix(nm,an)*vecrn

                        # Rotate vecan around dihedral angle
                        vecdn = rotation_matrix(vec1,dn)*vecan

                        # Add this vector to previous coordinate
                        atom_coords[atom,:] = am1 + vecdn
                    end
                end
            end
        end
    end

    mol=Molecule(zmat.Atoms,atom_coords,natoms)

    return mol::Molecule
end

function cartesian(mol1::Molecule,mol2::Molecule,steps::Int64)
	"""
    This function returns an "steps" length array of Molecule objects
	"""
    # difference = (mol2.Coord - mol1.Coord) / (steps - 1)
    natoms = mol1.Number
	liic = Array{Float64,3}(undef,natoms,3,steps)
    arrayOfMolecules = Array{Molecule, 1}(undef, steps)
    for step in 1:steps
		fracA = (steps-step)/(steps-1)
		fracB = (step-1)/(steps-1)
		liic[:,:,step] = mol1.Coord*fracA + mol2.Coord*fracB
        arrayOfMolecules[step] = Molecule(mol1.Atoms,liic[:,:,step],natoms)
    end
    return arrayOfMolecules
end

function internal_babel(int_arr_1,int_arr_2,steps,header)
	"""
	Takes array of Z-matrix internal coordinates and interpolates between them
	to return an array of xyzs.
	"""
	numInternalCrd = size(int_arr_1)[1]
	natoms = Int((numInternalCrd + 6)/3)
	diffVec = Vector(undef,numInternalCrd)
	for i in 1:numInternalCrd
		if ( -180. <= (int_arr_2[i,2]-int_arr_1[i,2]) <= 180. )
			diffVec[i] = (int_arr_2[i,2]-int_arr_1[i,2])/steps
		elseif (int_arr_2[i,2] - int_arr_1[i,2]) < -180.
			diffVec[i] = (int_arr_2[i,2] - int_arr_1[i,2] + 360.)/steps
		elseif (int_arr_2[i,2]-int_arr_1[i,2]) > 180.
			diffVec[i] = (int_arr_2[i,2] - int_arr_1[i,2] - 360.)/steps
		else
			println("Something has gone terribly wrong")
		end
	end
	zmatArray = Vector(undef,steps)
	interXYZ = Array{Float32,3}(undef,natoms,3,steps)
	atom_names = Array{String,1}(undef,natoms)
	for j in 1:steps
		zmatArray[j] = header * "Variables:\n" * intlVec(int_arr_1,diffVec,j)
		open("tmp","w") do io
			write(io,zmatArray[j])
		end
		convXyz = read(`obabel -igzmat tmp -oxyz`,String)
		run(`rm tmp`)
		just_coords = Array{Float32}(readdlm(IOBuffer(convXyz),skipstart=2)[:,2:4])
		atom_names = Array{String}(readdlm(IOBuffer(convXyz),skipstart=2)[:,1])
		interXYZ[:,:,j] = just_coords
	end
	return interXYZ, atom_names
end

function internal(zmat_f::ZMatrix,zmat_l::ZMatrix,steps::Int64)
	int_f = zmat_f.IntVars
	int_l = zmat_l.IntVars
	natoms = zmat_l.Number
	nvars = length(zmat_l.IntVars)
	difference = Vector(undef,nvars)
	for i in 1:nvars
		toAngle = (zmat_l.IntVars[i]+180.)
		fromAngle = (zmat_f.IntVars[i]+180.)
		diff = abs(toAngle - fromAngle)
		if diff < 180.
			difference[i] = toAngle - fromAngle
		else
			if fromAngle > toAngle
				fromAngle = fromAngle - 360.
				difference[i] = toAngle - fromAngle
			elseif toAngle > fromAngle
				toAngle = toAngle - 360.
				difference[i] = toAngle - fromAngle
			end
		end
	end
	difference = difference / (steps-1)
	liic = Array{Float64,2}(undef,nvars,steps)
	arrayOfMolecules = Array{Molecule, 1}(undef, steps)
	for step in 1:steps
		liic[:,step] = int_f + (step-1)*difference
		arrayOfMolecules[step] = zmat_to_xyz(ZMatrix(zmat_f.Atoms,liic[:,step],zmat_f.VarNames,natoms))
	end
	# @assert isapprox(int_l,liic[:,steps],atol=1e-5)
	return arrayOfMolecules
end

end


mutable struct HopfieldQAP <: HopfieldNet
    # s::Array{Float64, N} where {N}
    s::BitArray{N} where {N}
    W::Array{Float64, N} where {N}
    λ::Float64
    # D::Array{Float64, N} where {N}
    # F::Array{Float64, N} where {N}
    # A::Float64
    # B::Float64
    # q::Float64
    ϵ::Float64
end

function δ(ind...)
    all(y -> y==first(ind), ind)
end

function HopfieldQAP(D::Array{Float64, Dim}, F::Array{Float64, Dim}
                    # , A=0.9, B=0.9, q=140
                    , ϵ=0.02
                    , λ = 1.1
    ) where {Dim}
    # A = B = 2

    # D & F are square matrices always
    N = (size(D)[1],size(F)[1]) #dimensions of the Hopfield Network

    tnet = DiscreteHopfieldNet(N)

    iters = Base.Iterators.product([collect(1:i) for i in N]...)
    for i in iters
        for j in iters
            # tnet.W[i..., j...] = -2(A * (1-δ(i[1],j[1]) * δ(i[2],j[2])) + B * δ(i[1], j[1]) * (1 - δ(i[2], j[2])) + (D[i[1], j[1]] * F[i[2], j[2]])/q)
            tnet.W[i..., j...] = D[i[1], j[1]] * F[i[2], j[2]]
        end
    end

    return HopfieldQAP(tnet.s , tnet.W
                        # , D, F
                        # , A, B, q
                        , ϵ
                        , λ
                        )
end

# This is a test function to return variable wieghts at each call
function weights(net::HopfieldQAP, i::Tuple)
    N = size(net.s)
    w = Array{Float64}(undef, N...)
    A = net.A
    B = net.B
    q = net.q
    D = net.D
    F = net.F
    iters = Base.Iterators.product([collect(1:i) for i in N]...)
    for j in iters
        w[j...] = -2(A * (1-δ(i[1],j[1]) * δ(i[2],j[2])) + B * δ(i[1], j[1]) * (1 - δ(i[2], j[2])) + (D[i[1], j[1]] * F[i[2], j[2]])/q)
    end

    return w

end

function energy(net::HopfieldQAP)
    N = size(net.s)
    N_2 = size(net.W)
    iters = Base.Iterators.product([collect(1:i) for i in N]...)
    e = 0.0
    for ind in iters
        a = []
        for i in 1:length(N_2)
            if i <= length(N)
                push!(a, ind[i])
            else
                push!(a, :)
            end
        endMMo
        # e += (dot(net.W[a...], net.s) + net.A + net.B) *net.s[ind...]
        e += dot(net.W[a...], net.s) * net.s[ind...] + net.λ * ((sum(net.s[:,ind[2]]) - 1)^2 + (sum(net.s[ind[1],:]) - 1)^2)
        # e += (dot(weights(net, ind), net.s) + net.A + net.B) * net.s[ind...]
    end
    return e

end

function update!(net::HopfieldQAP, ind::Tuple
                 , θ = 50
                 )
    N = size(net.s)
    N_2 = size(net.W)
    a = []
    for i in 1:length(N_2)
        if i <= length(N)
            push!(a, ind[i])
        else
            push!(a, :)
        end
    end
    # dotp = dot(net.W[a...], net.s) * net.s[ind...] - net.A - net.B
    # dotp = dot(weights(net, ind), net.s) * net.s[ind...] - net.A - net.B
    # net.s[ind...] = (1/ (1 + exp(dotp/net.ϵ)) )#> 0.5
    # net.s[ind...] = dot(net.W[a...], net.s) > θ #? +1 : -1
    fin = dot(net.W[a...], net.s) + net.λ * 2 * (sum(net.s[ind[1], : ]) + sum(net.s[:, ind[2]])) - 4 * net.λ
    net.s[ind...] = round(1 / (1 + exp(fin/net.ϵ)))
    return nothing
end

# Perform an update cycle on the whole network
function cycle!(net::HopfieldQAP
                , θ::Number = 50
                )
    N = size(net.s)
    # N_2 = size(net.W)
    randInd = [shuffle(1:i) for i in N]
    for i in Base.Iterators.product(randInd...)
        update!(net, i
                , θ
                )
    end
    return nothing
end

function isFeasible(net::HopfieldQAP)
    N = size(net.s)
    for i in 1:N[1]
        if sum(net.s[i,:]) != 1
            return false
        end
    end
    for j in i:N[2]
        if sum(net.s[:,j]) != 1
            return false
        end
    end
    return true
end

function lowEFeasible(net::HopfieldQAP, cyc::Int)
    e = 0.0
    f = isFeasible(net)

    for i in 1:cyc
        e = energy(net)
        cycle!(hnqap1)
        f = isFeasible(hnqap1)
end


const featurenames = [
    :DN_HistogramMode_5 # These shouldn't change between versions of catch22
    :DN_HistogramMode_10
    :CO_Embed2_Dist_tau_d_expfit_meandiff
    :CO_f1ecac
    :CO_FirstMin_ac
    :CO_HistogramAMI_even_2_5
    :CO_trev_1_num
    :DN_OutlierInclude_p_001_mdrmd
    :DN_OutlierInclude_n_001_mdrmd
    :FC_LocalSimple_mean1_tauresrat
    :FC_LocalSimple_mean3_stderr
    :IN_AutoMutualInfoStats_40_gaussian_fmmi
    :MD_hrv_classic_pnn40
    :SB_BinaryStats_diff_longstretch0
    :SB_BinaryStats_mean_longstretch1
    :SB_MotifThree_quantile_hh
    :SC_FluctAnal_2_rsrangefit_50_1_logi_prop_r1
    :SC_FluctAnal_2_dfa_50_1_2_logi_prop_r1
    :SP_Summaries_welch_rect_area_5_1
    :SP_Summaries_welch_rect_centroid
    :SB_TransitionMatrix_3ac_sumdiagcov
    :PD_PeriodicityWang_th0_01]

const featuretypes = Dict(featurenames .=> [
                                    #DN_HistogramMode_5
                                    Cdouble
                                    #DN_HistogramMode_10
                                    Cdouble
                                    #CO_Embed2_Dist_tau_d_expfit_meandiff
                                    Cdouble
                                    #CO_f1ecac
                                    Cint
                                    #CO_FirstMin_ac
                                    Cint
                                    #CO_HistogramAMI_even_2_5
                                    Cdouble
                                    #CO_trev_1_num
                                    Cdouble
                                    #DN_OutlierInclude_p_001_mdrmd
                                    Cdouble
                                    #DN_OutlierInclude_n_001_mdrmd
                                    Cdouble
                                    #FC_LocalSimple_mean1_tauresrat
                                    Cdouble
                                    #FC_LocalSimple_mean3_stderr
                                    Cdouble
                                    #IN_AutoMutualInfoStats_40_gaussian_fmmi
                                    Cdouble
                                    #MD_hrv_classic_pnn40
                                    Cdouble
                                    #SB_BinaryStats_diff_longstretch0
                                    Cdouble
                                    #SB_BinaryStats_mean_longstretch1
                                    Cdouble
                                    #SB_MotifThree_quantile_hh
                                    Cdouble
                                    #SC_FluctAnal_2_rsrangefit_50_1_logi_prop_r1
                                    Cdouble
                                    #SC_FluctAnal_2_dfa_50_1_2_logi_prop_r1
                                    Cdouble
                                    #SP_Summaries_welch_rect_area_5_1
                                    Cdouble
                                    #SP_Summaries_welch_rect_centroid
                                    Cdouble
                                    #SB_TransitionMatrix_3ac_sumdiagcov
                                    Cdouble
                                    #PD_PeriodicityWang_th0_01
                                    Cint])

"""
    Catch22.featurekeywords
A vector listing keywords of features as vectors of strings.
"""
const featurekeywords = [ # See hctsa
                                    #DN_HistogramMode_5
                                    ["distribution", "location"],
                                    #DN_HistogramMode_10
                                    ["distribution", "location"],
                                    #CO_Embed2_Dist_tau_d_expfit_meandiff
                                    ["correlation", "embedding"],
                                    #CO_f1ecac
                                    ["correlation", "timescale"],
                                    #CO_FirstMin_ac
                                    ["correlation", "timescale"],
                                    #CO_HistogramAMI_even_2_5
                                    ["information", "correlation", "AMI"],
                                    #CO_trev_1_num
                                    ["correlation", "nonlinear"],
                                    #DN_OutlierInclude_p_001_mdrmd
                                    ["distribution", "outliers"],
                                    #DN_OutlierInclude_n_001_mdrmd
                                    ["distribution", "outliers"],
                                    #FC_LocalSimple_mean1_tauresrat
                                    ["forecasting"],
                                    #FC_LocalSimple_mean3_stderr
                                    ["forecasting"],
                                    #IN_AutoMutualInfoStats_40_gaussian_fmmi
                                    ["information", "correlation", "AMI"],
                                    #MD_hrv_classic_pnn40
                                    ["medical"],
                                    #SB_BinaryStats_diff_longstretch0
                                    ["distribution", "stationarity"],
                                    #SB_BinaryStats_mean_longstretch1
                                    ["distribution", "stationarity"],
                                    #SB_MotifThree_quantile_hh
                                    ["symbolic", "motifs"],
                                    #SC_FluctAnal_2_rsrangefit_50_1_logi_prop_r1
                                    ["scaling"],
                                    #SC_FluctAnal_2_dfa_50_1_2_logi_prop_r1
                                    ["scaling"],
                                    #SP_Summaries_welch_rect_area_5_1
                                    ["FourierSpectrum"],
                                    #SP_Summaries_welch_rect_centroid
                                    ["FourierSpectrum"],
                                    #SB_TransitionMatrix_3ac_sumdiagcov
                                    ["symbolic", "transitionmat"],
                                    #PD_PeriodicityWang_th0_01
                                    ["periodicity", "spline"]]

"""
    Catch22.featuredescriptions
A vector listing short descriptions of each feature, as strings.
"""
const featuredescriptions = [  # See catch22 paper
                                    #DN_HistogramMode_5
                                    "Mode of z-scored distribution (5-bin histogram)"
                                    #DN_HistogramMode_10
                                    "Mode of z-scored distribution (10-bin histogram)"
                                    #CO_Embed2_Dist_tau_d_expfit_meandiff
                                    "Exponential fit to successive distances in 2-d embedding space"
                                    #CO_f1ecac
                                    "First 1/𝑒 crossing of autocorrelation function"
                                    #CO_FirstMin_ac
                                    "First minimum of autocorrelation function"
                                    #CO_HistogramAMI_even_2_5
                                    "Automutual information, 𝑚=2,𝜏=5"
                                    #CO_trev_1_num
                                    "Time-reversibility statistic, ⟨(𝑥ₜ₊₁−𝑥ₜ)³⟩ₜ"
                                    #DN_OutlierInclude_p_001_mdrmd
                                    "Time intervals between successive extreme events above the mean"
                                    #DN_OutlierInclude_n_001_mdrmd
                                    "Time intervals between successive extreme events below the mean"
                                    #FC_LocalSimple_mean1_tauresrat
                                    "Change in correlation length after iterative differencing"
                                    #FC_LocalSimple_mean3_stderr
                                    "Mean error from a rolling 3-sample mean forecasting"
                                    #IN_AutoMutualInfoStats_40_gaussian_fmmi
                                    "First minimum of the automutual information function"
                                    #MD_hrv_classic_pnn40
                                    "Proportion of successive differences exceeding 0.04𝜎 (Mietus 2002)"
                                    #SB_BinaryStats_diff_longstretch0
                                    "Longest period of successive incremental decreases"
                                    #SB_BinaryStats_mean_longstretch1
                                    "Longest period of consecutive values above the mean"
                                    #SB_MotifThree_quantile_hh
                                    "Shannon entropy of two successive letters in equiprobable 3-letter symbolization"
                                    #SC_FluctAnal_2_rsrangefit_50_1_logi_prop_r1
                                    "Proportion of slower timescale fluctuations that scale with linearly rescaled range fits"
                                    #SC_FluctAnal_2_dfa_50_1_2_logi_prop_r1
                                    "Proportion of slower timescale fluctuations that scale with DFA (50% sampling)"
                                    #SP_Summaries_welch_rect_area_5_1
                                    "Total power in lowest fifth of frequencies in the Fourier power spectrum"
                                    #SP_Summaries_welch_rect_centroid
                                    "Centroid of the Fourier power spectrum"
                                    #SB_TransitionMatrix_3ac_sumdiagcov
                                    "Trace of covariance of transition matrix between symbols in 3-letter alphabet"
                                    #PD_PeriodicityWang_th0_01
                                    "Periodicity measure of (Wang et al. 2007)"]


function drgenes(d::AbstractVector{T}) where T<:Real
    d²=d.^2
    fc=d²./mean(d²)
    χ² = Chisq(1)
    pVals = ccdf.(χ², fc)
    pAdjusted = MultipleTesting.adjust(pVals, BenjaminiHochberg())
    return fc,pVals,pAdjusted
end


function complement(f::CSGNode, coords)
    ev = evaluate(f, coords)
    ev.signint = -1*ev.signint
    ev.value = -1*ev.value
    return ev
end

function intersection(f::CSGNode, g::CSGNode, coords)
    return max(evaluate(f, coords), evaluate(g, coords))
end

function subtraction(f::CSGNode, g::CSGNode, coords)
    return max(evaluate(f, coords), complement(g, coords))
end

function union(f::CSGNode, g::CSGNode, coords)
    return min(evaluate(f, coords), evaluate(g, coords))
end

const CSGOperations = [complement, intersection, subtraction, union]
const CSGopsyms = [:complement, :intersection, :subtraction, :union]
const opDict = Dict(:complement=>complement,
                :intersection=>intersection,
                :subtraction=>subtraction,
                :union=>union)

function complement(f::CachedCSGNode, coords, ind)
    return flipsign(evaluate(f, coords, ind))
end

function intersection(f::CachedCSGNode, g::CachedCSGNode, coords, ind)
    return max(evaluate(f, coords, ind), evaluate(g, coords, ind))
end

function subtraction(f::CachedCSGNode, g::CachedCSGNode, coords, ind)
    return max(evaluate(f, coords, ind), complement(g, coords, ind))
end

function union(f::CachedCSGNode, g::CachedCSGNode, coords, ind)
    return min(evaluate(f, coords, ind), evaluate(g, coords, ind))
end


using Revise
using ADCME
using NNFEM
using JLD2
using PyCall
using LinearAlgebra
reset_default_graph()

stress_scale = 1e5
strain_scale = 1.0
force_scale = 1.0
fiber_size = 2
porder = 2

nntype = "stiffmat"

include("nnutil.jl")

H0 = [1.04167e6  2.08333e5  0.0
      2.08333e5  1.04167e6  0.0
      0.0        0.0        4.16667e5]/stress_scale

n_data = [100, 101, 102, 103, 104, 200, 201, 202, 203, 204]

loss = constant(0.0)
for tid in n_data
    global loss
    strain, stress = read_strain_stress("Data/order$porder/$(tid)_$(force_scale)_$(fiber_size).dat")
    X, Y = prepare_strain_stress_data2D(strain, stress)
    x = constant(X)
    y = nn(x[:,1:3], x[:,4:6], x[:,7:9])
    @show "tid is ", tid
    loss += mean((y-Y)^2) #/stress_scale^2
end

sess = Session(); init(sess)
@show run(sess, loss)
# ADCME.load(sess, "Data/NNLearn.mat")
for i = 1:50
    BFGS!(sess, loss, 1000)
    ADCME.save(sess, "Data/$nntype/NNLearn_$(idx)_$(H_function)_ite$(i).mat")
end


using Glob;
using DelimitedFiles;
using Quadmath;
using DecFP;

if length(ARGS) != 3
  println("usage: julia statistical_analysis_mcmc.jl <outfile> <indir> <pattern>");
  exit(1);
end

outfile = open(ARGS[1], "w");
indir = ARGS[2];
pattern = Glob.GlobMatch(ARGS[3]);

data_series = Dict();
for infile in readdir(pattern, indir)
  for line in readlines(infile)
    var, val = map(strip, split(line, "="));
    val = eval(Meta.parse(val));
    if haskey(data_series, var)
      push!(data_series[var], val);
    else
      data_series[var] = [val];
    end
  end
end

avgs = Dict();
vars = Dict();
std_norms = Dict();
for (k, v) in data_series
  avgs[k] = sum(v) / length(v);
  vars[k] = sum(map(x -> (x - avgs[k]).^2, v)) / length(v);
  std_norms[k] = map(sqrt, vars[k]) ./ avgs[k];
end
for k in keys(avgs)
  @info "$k: μ = $(avgs[k]); σ^2 = $(vars[k]); σ / μ = $(std_norms[k])";
end

close(outfile);


abstract type AbstractSubOptimizer end

"""
sense 0.5 x'Qx + c'x + μ (s1 + s2)
subject to
c_lb <= Ax + b + s1 - s2 + s <= c_ub
v_lb <= x + x_k <= v_ub
-Δ <= x <= Δ
s1 + max(0,s) >= 0
s2 - min(0,s) >= 0
"""
struct QpData{T,Tv<:AbstractArray{T},Tm<:AbstractMatrix{T}}
    sense::MOI.OptimizationSense
    Q::Union{Nothing,Tm}
    c::Tv
    A::Tm
    b::Tv
    c_lb::Tv
    c_ub::Tv
    v_lb::Tv
    v_ub::Tv
    num_linear_constraints::Int
end

SubModel = Union{
    MOI.AbstractOptimizer,
    JuMP.AbstractModel,
}

include("subproblem_MOI.jl")
include("subproblem_JuMP.jl")


# Nothing here yet

module HalfSine

using BigCombinatorics, SimplePolynomials

export semisine

include("data.txt")  # get the b-values

ndata = length(b)
a = [ b[k]//big(2)^(k-1) // Factorial(2k-1) for k=1:ndata ]

"""
`semisine(x,terms=10)` gives the analytic (power series)
value of the half sine.
"""
function semisine(x::Real,terms=10)
    x = _reset(x)
    sum(  a[k]*x^(2k-1) for k=1:terms)
end

include("rufus.jl")

end


"""
    TriComplex{T <: Real} = Complexification{BiComplex{T}}

A tri-complex number is a complexification of bi-complex numbers.
"""
const TriComplex{T <: Real} = Complexification{BiComplex{T}}

function TriComplex(z::BiComplex{T}) where T <: Real
    TriComplex{T}(z, zero(BiComplex{T}))
end

function TriComplex(z::Cmplex{T}) where T <: Real
    TriComplex{T}(
        BiComplex{T}(z, zero(Cmplex{T})),
        zero(BiComplex{T})
    )
end

function TriComplex(a::T, b::T, c::T, d::T, f::T, g::T, h::T, j::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            Cmplex{T}(c, d)
        ),
        BiComplex{T}(
            Cmplex{T}(f, g),
            Cmplex{T}(h, j)
        )
    )
end

function TriComplex(a::T, b::T, c::T, d::T, f::T, g::T, h::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            Cmplex{T}(c, d)
        ),
        BiComplex{T}(
            Cmplex{T}(f, g),
            Cmplex{T}(h, zero(T))
        )
    )
end

function TriComplex(a::T, b::T, c::T, d::T, f::T, g::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            Cmplex{T}(c, d)
        ),
        BiComplex{T}(
            Cmplex{T}(f, g),
            zero(Cmplex{T})
        )
    )
end

function TriComplex(a::T, b::T, c::T, d::T, f::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            Cmplex{T}(c, d)
        ),
        BiComplex{T}(
            Cmplex{T}(f, zero(T)),
            zero(Cmplex{T})
        )
    )
end

function TriComplex(a::T, b::T, c::T, d::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            Cmplex{T}(c, d)
        ),
        zero(BiComplex{T})
    )
end

function TriComplex(a::T, b::T, c::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            Cmplex{T}(c, zero(T))
        ),
        zero(BiComplex{T})
    )
end

function TriComplex(a::T, b::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, b),
            zero(Cmplex{T})
        ),
        zero(BiComplex{T})
    )
end

function TriComplex(a::T) where T <: Real
    TriComplex{T}(
        BiComplex{T}(
            Cmplex{T}(a, zero(T)),
            zero(Cmplex{T})
        ),
        zero(BiComplex{T})
    )
end

function TriComplex(a::Real, b::Real)
    TriComplex(promote(a, b)...)
end

function TriComplex(a::Real, b::Real, c::Real)
    TriComplex(promote(a, b, c)...)
end

function TriComplex(a::Real, b::Real, c::Real, d::Real)
    TriComplex(promote(a, b, c, d)...)
end

function TriComplex(a::Real, b::Real, c::Real, d::Real, f::Real)
    TriComplex(promote(a, b, c, d, f)...)
end

function TriComplex(a::Real, b::Real, c::Real, d::Real, f::Real, g::Real)
    TriComplex(promote(a, b, c, d, f, g)...)
end

function TriComplex(a::Real, b::Real, c::Real, d::Real, f::Real, g::Real, h::Real)
    TriComplex(promote(a, b, c, d, f, g, h)...)
end

function TriComplex(a::Real, b::Real, c::Real, d::Real, f::Real, g::Real, h::Real, j::Real)
    TriComplex(promote(a, b, c, d, f, g, h, j)...)
end

"""
    unreal(z::TriComplex)

The unreal part of a tri-complex number is a 7-vector.
"""
function unreal(z::TriComplex)
    vcat(unreal(z.l), array(z.r))
end

function show(io::IO, z::TriComplex)
    print(io, "[1: ")
    print(io, z.l.l.l)
    print(io, ", H: ")
    print(io, z.l.l.r)
    print(io, ", G: ")
    print(io, z.l.r.l)
    print(io, ", GH: ")
    print(io, z.l.r.r)
    print(io, ", F: ")
    print(io, z.r.l.l)
    print(io, ", FH: ")
    print(io, z.r.l.r)
    print(io, ", FG: ")
    print(io, z.r.r.l)
    print(io, ", FGH: ")
    print(io, z.r.r.r)
    print(io, "]")
end

function random(::Type{TriComplex{T}}) where T <: Real
    TriComplex{T}(random(BiComplex{T}), random(BiComplex{T}))
end

function barH(z::TriComplex{T}) where T <: Real
    TriComplex{T}(barH(z.l), barH(z.r))
end

function barG(z::TriComplex{T}) where T <: Real
    TriComplex{T}(barG(z.l), barG(z.r))
end

function barF(z::TriComplex{T}) where T <: Real
    TriComplex{T}(z.l, -(z.r))
end

function quadrance(z::TriComplex)
    (z * barF(z)).l
end

function norm(z::TriComplex)
    abs2(quadrance(quadrance(z)))
end

function iszerodivisor(z::TriComplex)
    iszerodivisor(quadrance(z))
end

function inv(z::TriComplex)
    if iszerodivisor(z)
        error(ZeroDivisorInverse)
    end

    q = quadrance(z)
    qq = quadrance(q)
    barF(z) * (barG(q) * barH(qq)) / abs2(qq)
end


"""
    LessOrEqualConstant(x::SeaPearl.AbstractIntVar, v::Int)

Inequality constraint, `x <= v`
"""
struct LessOrEqualConstant <: OnePropagationConstraint
    x       ::AbstractIntVar
    v       ::Int
    active  ::StateObject{Bool}
    function LessOrEqualConstant(x, v, trailer)
        constraint = new(x, v, StateObject(true, trailer))
        addOnDomainChange!(x, constraint)
        return constraint
    end
end

# Base.show(io::IO, c::LessOrEqualConstant) = write(io, "LessOrEqualConstant constraint: ", c.x, " <= ", c.v)

"""
    propagate!(constraint::LessOrEqualConstant, toPropagate::Set{Constraint}, prunedDomains::CPModification)

`LessOrEqualConstant` propagation function. Basically remove the values above `v`.
"""
function propagate!(constraint::LessOrEqualConstant, toPropagate::Set{Constraint}, prunedDomains::CPModification)
    setValue!(constraint.active, false)

    addToPrunedDomains!(prunedDomains, constraint.x, removeAbove!(constraint.x.domain, constraint.v))
    triggerDomainChange!(toPropagate, constraint.x)
    if constraint in toPropagate
        pop!(toPropagate, constraint)
    end
    return !isempty(constraint.x.domain)
end

variablesArray(constraint::LessOrEqualConstant) = [constraint.x]

function Base.show(io::IO, ::MIME"text/plain", con::LessOrEqualConstant)
    println(io, typeof(con), ": ", con.x.id, " <= ", con.v, ", active = ", con.active)
    println(io, "   ", con.x)
end

function Base.show(io::IO, con::LessOrEqualConstant)
    print(io, typeof(con), ": ", con.x.id, " <= ", con.v)
end

struct LessOrEqual <: Constraint
    x       ::AbstractIntVar
    y       ::AbstractIntVar
    active  ::StateObject{Bool}

    """
        LessOrEqual(x::AbstractIntVar, y::AbstractIntVar, trailer::Trailer)

    Inequality between variables constraint, states that `x <= y`
    """
    function LessOrEqual(x, y, trailer)
        constraint = new(x, y, StateObject(true, trailer))
        addOnDomainChange!(x, constraint)
        addOnDomainChange!(y, constraint)
        return constraint
    end
end

"""
    propagate!(constraint::LessOrEqual, toPropagate::Set{Constraint}, prunedDomains::CPModification)

`LessOrEqual` propagation function.
"""
function propagate!(constraint::LessOrEqual, toPropagate::Set{Constraint}, prunedDomains::CPModification)

    if maximum(constraint.x.domain) <= minimum(constraint.y.domain)
        setValue!(constraint.active, false)
    end

    prunedX = removeAbove!(constraint.x.domain, maximum(constraint.y.domain))
    if !isempty(prunedX)
        addToPrunedDomains!(prunedDomains, constraint.x, prunedX)
        triggerDomainChange!(toPropagate, constraint.x)
    end

    prunedY = removeBelow!(constraint.y.domain, minimum(constraint.x.domain))
    if !isempty(prunedY)
        addToPrunedDomains!(prunedDomains, constraint.y, prunedY)
        triggerDomainChange!(toPropagate, constraint.y)
    end
    return !isempty(constraint.x.domain) && !isempty(constraint.y.domain)
end

variablesArray(constraint::LessOrEqual) = [constraint.x, constraint.y]

function Base.show(io::IO, ::MIME"text/plain", con::LessOrEqual)
    println(io, typeof(con), ": ", con.x.id, " ≤ ", con.y.id, ", active = ", con.active)
    println(io, "   ", con.x)
    println(io, "   ", con.y)
end

function Base.show(io::IO, con::LessOrEqual)
    print(io, typeof(con), ": ", con.x.id, " ≤ ", con.y.id)
end